11import type { ReactiveController } from 'lit' ;
2- import { getElementByIdFromRoot } from '../common/util.js' ;
2+ import { getElementByIdFromRoot , isString } from '../common/util.js' ;
33import service from './tooltip-service.js' ;
44import type IgcTooltipComponent from './tooltip.js' ;
55
6- type TooltipAnchor = Element | null | undefined ;
7-
8- type TooltipCallbacks = {
9- onShow : ( event ?: Event ) => unknown ;
10- onHide : ( event ?: Event ) => unknown ;
11- onEscape : ( event ?: Event ) => unknown ;
12- } ;
13-
146class TooltipController implements ReactiveController {
7+ //#region Internal properties and state
8+
159 private readonly _host : IgcTooltipComponent ;
16- private _anchor : TooltipAnchor ;
17- private _isTransientAnchor = false ;
10+ private readonly _options : TooltipCallbacks ;
11+
12+ private _hostAbortController : AbortController | null = null ;
13+ private _anchorAbortController : AbortController | null = null ;
1814
19- private _options : TooltipCallbacks ;
2015 private _showTriggers = new Set ( [ 'pointerenter' ] ) ;
2116 private _hideTriggers = new Set ( [ 'pointerleave' , 'click' ] ) ;
2217
18+ private _anchor : TooltipAnchor ;
19+ private _isTransientAnchor = false ;
2320 private _open = false ;
2421
22+ //#endregion
23+
24+ //#region Public properties
25+
2526 /** Whether the tooltip is in shown state. */
2627 public get open ( ) : boolean {
2728 return this . _open ;
@@ -52,26 +53,6 @@ class TooltipController implements ReactiveController {
5253 return this . _anchor ;
5354 }
5455
55- /**
56- * Removes all triggers from the previous `anchor` target and rebinds the current
57- * sets back to the new value if it exists.
58- */
59- public setAnchor ( value : TooltipAnchor , transient = false ) : void {
60- if ( this . _anchor === value ) return ;
61-
62- this . _dispose ( ) ;
63- this . _anchor = value ;
64- this . _isTransientAnchor = transient ;
65-
66- for ( const each of this . _showTriggers ) {
67- this . _anchor ?. addEventListener ( each , this ) ;
68- }
69-
70- for ( const each of this . _hideTriggers ) {
71- this . _anchor ?. addEventListener ( each , this ) ;
72- }
73- }
74-
7556 /**
7657 * Returns the current set of hide triggers as a comma-separated string.
7758 */
@@ -87,13 +68,9 @@ class TooltipController implements ReactiveController {
8768 * set of triggers from it and rebind it with the new one.
8869 */
8970 public set hideTriggers ( value : string ) {
90- const triggers = parseTriggers ( value ) ;
91-
92- if ( this . _anchor ) {
93- this . _toggleTriggers ( this . _hideTriggers , triggers ) ;
94- }
95-
96- this . _hideTriggers = triggers ;
71+ this . _hideTriggers = parseTriggers ( value ) ;
72+ this . _removeAnchorListeners ( ) ;
73+ this . _addAnchorListeners ( ) ;
9774 }
9875
9976 /**
@@ -111,66 +88,128 @@ class TooltipController implements ReactiveController {
11188 * set of triggers from it and rebind it with the new one.
11289 */
11390 public set showTriggers ( value : string ) {
114- const triggers = parseTriggers ( value ) ;
115-
116- if ( this . _anchor ) {
117- this . _toggleTriggers ( this . _showTriggers , triggers ) ;
118- }
119-
120- this . _showTriggers = triggers ;
91+ this . _showTriggers = parseTriggers ( value ) ;
92+ this . _removeAnchorListeners ( ) ;
93+ this . _addAnchorListeners ( ) ;
12194 }
12295
96+ //#endregion
97+
12398 constructor ( tooltip : IgcTooltipComponent , options : TooltipCallbacks ) {
12499 this . _host = tooltip ;
125100 this . _options = options ;
126101 this . _host . addController ( this ) ;
127102 }
128103
129- public resolveAnchor ( value : Element | string | undefined ) : void {
130- const resolvedAnchor =
131- typeof value === 'string'
132- ? getElementByIdFromRoot ( this . _host , value )
133- : ( value ?? null ) ;
104+ //#region Internal event listeners state
105+
106+ private _addAnchorListeners ( ) : void {
107+ if ( ! this . _anchor ) return ;
108+
109+ this . _anchorAbortController = new AbortController ( ) ;
110+ const signal = this . _anchorAbortController . signal ;
111+
112+ for ( const each of this . _showTriggers ) {
113+ this . _anchor . addEventListener ( each , this , { passive : true , signal } ) ;
114+ }
134115
135- this . setAnchor ( resolvedAnchor ) ;
116+ for ( const each of this . _hideTriggers ) {
117+ this . _anchor . addEventListener ( each , this , { passive : true , signal } ) ;
118+ }
119+ }
120+
121+ private _removeAnchorListeners ( ) : void {
122+ this . _anchorAbortController ?. abort ( ) ;
123+ this . _anchorAbortController = null ;
136124 }
137125
138126 private _addTooltipListeners ( ) : void {
139- this . _host . addEventListener ( 'pointerenter' , this , { passive : true } ) ;
140- this . _host . addEventListener ( 'pointerleave' , this , { passive : true } ) ;
127+ this . _hostAbortController = new AbortController ( ) ;
128+ const signal = this . _hostAbortController . signal ;
129+
130+ this . _host . addEventListener ( 'pointerenter' , this , {
131+ passive : true ,
132+ signal,
133+ } ) ;
134+ this . _host . addEventListener ( 'pointerleave' , this , {
135+ passive : true ,
136+ signal,
137+ } ) ;
141138 }
142139
143140 private _removeTooltipListeners ( ) : void {
144- this . _host . removeEventListener ( 'pointerenter' , this ) ;
145- this . _host . removeEventListener ( 'pointerleave' , this ) ;
141+ this . _hostAbortController ?. abort ( ) ;
142+ this . _hostAbortController = null ;
146143 }
147144
148- private _toggleTriggers ( previous : Set < string > , current : Set < string > ) : void {
149- for ( const each of previous ) {
150- this . _anchor ?. removeEventListener ( each , this ) ;
151- }
145+ //#endregion
152146
153- for ( const each of current ) {
154- this . _anchor ?. addEventListener ( each , this , { passive : true } ) ;
147+ //#region Event handlers
148+
149+ private _handleTooltipEvent ( event : Event ) : void {
150+ switch ( event . type ) {
151+ case 'pointerenter' :
152+ this . _options . onShow . call ( this . _host ) ;
153+ break ;
154+ case 'pointerleave' :
155+ this . _options . onHide . call ( this . _host ) ;
155156 }
156157 }
157158
158- private _dispose ( ) : void {
159- for ( const each of this . _showTriggers ) {
160- this . _anchor ?. removeEventListener ( each , this ) ;
159+ private _handleAnchorEvent ( event : Event ) : void {
160+ if ( ! this . _open && this . _showTriggers . has ( event . type ) ) {
161+ this . _options . onShow . call ( this . _host ) ;
161162 }
162163
163- for ( const each of this . _hideTriggers ) {
164- this . _anchor ?. removeEventListener ( each , this ) ;
164+ if ( this . _open && this . _hideTriggers . has ( event . type ) ) {
165+ this . _options . onHide . call ( this . _host ) ;
166+ }
167+ }
168+
169+ /** @internal */
170+ public handleEvent ( event : Event ) : void {
171+ if ( event . target === this . _host ) {
172+ this . _handleTooltipEvent ( event ) ;
173+ } else if ( event . target === this . _anchor ) {
174+ this . _handleAnchorEvent ( event ) ;
165175 }
176+ }
177+
178+ //#endregion
166179
180+ private _dispose ( ) : void {
181+ this . _removeAnchorListeners ( ) ;
167182 this . _anchor = null ;
168183 }
169184
185+ //#region Public API
186+
187+ /**
188+ * Removes all triggers from the previous `anchor` target and rebinds the current
189+ * sets back to the new value if it exists.
190+ */
191+ public setAnchor ( value : TooltipAnchor , transient = false ) : void {
192+ if ( this . _anchor === value ) return ;
193+
194+ this . _removeAnchorListeners ( ) ;
195+ this . _anchor = value ;
196+ this . _isTransientAnchor = transient ;
197+ this . _addAnchorListeners ( ) ;
198+ }
199+
200+ public resolveAnchor ( value : Element | string | undefined ) : void {
201+ this . setAnchor (
202+ isString ( value ) ? getElementByIdFromRoot ( this . _host , value ) : value
203+ ) ;
204+ }
205+
206+ //#endregion
207+
208+ //#region ReactiveController interface
209+
170210 /** @internal */
171211 public hostConnected ( ) : void {
172- const anchor = this . _host . anchor ;
173- this . resolveAnchor ( anchor ) ;
212+ this . resolveAnchor ( this . _host . anchor ) ;
174213 }
175214
176215 /** @internal */
@@ -180,33 +219,7 @@ class TooltipController implements ReactiveController {
180219 service . remove ( this . _host ) ;
181220 }
182221
183- /** @internal */
184- public handleEvent ( event : Event ) : void {
185- // Tooltip handlers
186- if ( event . target === this . _host ) {
187- switch ( event . type ) {
188- case 'pointerenter' :
189- this . _options . onShow . call ( this . _host ) ;
190- break ;
191- case 'pointerleave' :
192- this . _options . onHide . call ( this . _host ) ;
193- break ;
194- default :
195- return ;
196- }
197- }
198-
199- // Anchor handlers
200- if ( event . target === this . _anchor ) {
201- if ( this . _showTriggers . has ( event . type ) && ! this . _open ) {
202- this . _options . onShow . call ( this . _host ) ;
203- }
204-
205- if ( this . _hideTriggers . has ( event . type ) && this . _open ) {
206- this . _options . onHide . call ( this . _host ) ;
207- }
208- }
209- }
222+ //#endregion
210223}
211224
212225function parseTriggers ( string : string ) : Set < string > {
@@ -219,3 +232,11 @@ export function addTooltipController(
219232) : TooltipController {
220233 return new TooltipController ( host , options ) ;
221234}
235+
236+ type TooltipAnchor = Element | null | undefined ;
237+
238+ type TooltipCallbacks = {
239+ onShow : ( event ?: Event ) => unknown ;
240+ onHide : ( event ?: Event ) => unknown ;
241+ onEscape : ( event ?: Event ) => unknown ;
242+ } ;
0 commit comments