@@ -3,7 +3,7 @@ import tippy from 'tippy.js';
3
3
export function createTippy ( target , opts = { } ) {
4
4
const instance = tippy ( target , {
5
5
appendTo : document . body ,
6
- placement : target . getAttribute ( 'data-placement' ) || 'top-start' ,
6
+ placement : 'top-start' ,
7
7
animation : false ,
8
8
allowHTML : false ,
9
9
hideOnClick : false ,
@@ -25,38 +25,108 @@ export function createTippy(target, opts = {}) {
25
25
return instance ;
26
26
}
27
27
28
- export function initTooltip ( el , props = { } ) {
29
- const content = el . getAttribute ( 'data-content' ) || props . content ;
28
+ function getTippyTooltipContent ( target ) {
29
+ // prefer to always use the "[data-tooltip-content]" attribute
30
+ // for backward compatibility, we also support the ".tooltip[data-content]" attribute
31
+ let content = target . getAttribute ( 'data-tooltip-content' ) ;
32
+ if ( ! content && target . classList . contains ( 'tooltip' ) ) {
33
+ content = target . getAttribute ( 'data-content' ) ;
34
+ }
35
+ return content ;
36
+ }
37
+
38
+ /**
39
+ * Attach a tippy tooltip to the given target element.
40
+ * If the target element already has a tippy tooltip attached, the tooltip will be updated with the new content.
41
+ * If the target element has no content, then no tooltip will be attached, and it returns null.
42
+ * @param target {HTMLElement}
43
+ * @param content {null|string}
44
+ * @returns {null|tippy }
45
+ */
46
+ function attachTippyTooltip ( target , content = null ) {
47
+ content = content ?? getTippyTooltipContent ( target ) ;
30
48
if ( ! content ) return null ;
31
- if ( ! el . hasAttribute ( 'aria-label' ) ) el . setAttribute ( 'aria-label' , content ) ;
32
- return createTippy ( el , {
49
+
50
+ const props = {
33
51
content,
34
52
delay : 100 ,
35
53
role : 'tooltip' ,
36
- ...( el . getAttribute ( 'data-tooltip-interactive' ) === 'true' ? { interactive : true } : { } ) ,
37
- ...props ,
38
- } ) ;
39
- }
54
+ placement : target . getAttribute ( 'data-tooltip-placement' ) || 'top-start' ,
55
+ ...( target . getAttribute ( 'data-tooltip-interactive' ) === 'true' ? { interactive : true } : { } ) ,
56
+ } ;
40
57
41
- export function showTemporaryTooltip ( target , content ) {
42
- let tippy , oldContent ;
43
- if ( target . _tippy ) {
44
- tippy = target . _tippy ;
45
- oldContent = tippy . props . content ;
58
+ if ( ! target . _tippy ) {
59
+ createTippy ( target , props ) ;
46
60
} else {
47
- tippy = initTooltip ( target , { content} ) ;
61
+ target . _tippy . setProps ( props ) ;
62
+ }
63
+ return target . _tippy ;
64
+ }
65
+
66
+ /**
67
+ * creating tippy instance is expensive, so we only create it when the user hovers over the element
68
+ * @param e {Event}
69
+ */
70
+ function lazyTippyOnMouseEnter ( e ) {
71
+ e . target . removeEventListener ( 'mouseenter' , lazyTippyOnMouseEnter , true ) ;
72
+ attachTippyTooltip ( this ) ;
73
+ }
74
+
75
+ /**
76
+ * Activate the tippy tooltip for all children elements
77
+ * And if the element has no aria-label, use the tooltip content as aria-label
78
+ * @param target {HTMLElement}
79
+ */
80
+ function attachChildrenLazyTippyTooltip ( target ) {
81
+ // the selector must match the logic in getTippyTooltipContent
82
+ for ( const el of target . querySelectorAll ( '[data-tooltip-content], .tooltip[data-content]' ) ) {
83
+ el . addEventListener ( 'mouseenter' , lazyTippyOnMouseEnter , true ) ;
84
+
85
+ // meanwhile, if the element has no aria-label, use the tooltip content as aria-label
86
+ if ( ! el . hasAttribute ( 'aria-label' ) ) {
87
+ const content = getTippyTooltipContent ( el ) ;
88
+ if ( content ) {
89
+ el . setAttribute ( 'aria-label' , content ) ;
90
+ }
91
+ }
48
92
}
93
+ }
49
94
95
+ export function initGlobalTooltips ( ) {
96
+ // use MutationObserver to detect new elements added to the DOM, or attributes changed
97
+ const observer = new MutationObserver ( ( mutationList ) => {
98
+ for ( const mutation of mutationList ) {
99
+ if ( mutation . type === 'childList' ) {
100
+ for ( const el of mutation . addedNodes ) {
101
+ // handle all "tooltip" elements in newly added nodes, skip non-related nodes (eg: "#text")
102
+ if ( el . querySelectorAll ) {
103
+ attachChildrenLazyTippyTooltip ( el ) ;
104
+ }
105
+ }
106
+ } else if ( mutation . type === 'attributes' ) {
107
+ // sync the tooltip content if the attributes change
108
+ attachTippyTooltip ( mutation . target ) ;
109
+ }
110
+ }
111
+ } ) ;
112
+ observer . observe ( document , {
113
+ subtree : true ,
114
+ childList : true ,
115
+ attributeFilter : [ 'data-tooltip-content' , 'data-content' ] ,
116
+ } ) ;
117
+
118
+ attachChildrenLazyTippyTooltip ( document . documentElement ) ;
119
+ }
120
+
121
+ export function showTemporaryTooltip ( target , content ) {
122
+ const tippy = target . _tippy ?? attachTippyTooltip ( target , content ) ;
50
123
tippy . setContent ( content ) ;
51
124
if ( ! tippy . state . isShown ) tippy . show ( ) ;
52
125
tippy . setProps ( {
53
126
onHidden : ( tippy ) => {
54
- if ( oldContent ) {
55
- tippy . setContent ( oldContent ) ;
56
- tippy . setProps ( { onHidden : undefined } ) ;
57
- } else {
127
+ // reset the default tooltip content, if no default, then this temporary tooltip could be destroyed
128
+ if ( ! attachTippyTooltip ( target ) ) {
58
129
tippy . destroy ( ) ;
59
- // after destroy, the `_tippy` is detached, it can't do "setProps (etc...)" anymore
60
130
}
61
131
} ,
62
132
} ) ;
0 commit comments