@@ -12,17 +12,20 @@ import {
1212 shift ,
1313 size ,
1414} from '@floating-ui/dom' ;
15- import { html , LitElement } from 'lit' ;
16- import { property , query , queryAssignedElements } from 'lit/decorators.js' ;
17-
18- import { watch } from '../common/decorators/watch.js' ;
15+ import { html , LitElement , type PropertyValues } from 'lit' ;
16+ import { property , query } from 'lit/decorators.js' ;
17+ import {
18+ addSlotController ,
19+ type SlotChangeCallbackParameters ,
20+ setSlots ,
21+ } from '../common/controllers/slot.js' ;
1922import { registerComponent } from '../common/definitions/register.js' ;
2023import {
2124 first ,
2225 getElementByIdFromRoot ,
23- isEmpty ,
2426 isString ,
2527 roundByDPR ,
28+ setStyles ,
2629} from '../common/util.js' ;
2730import { styles } from './themes/light/popover.base.css.js' ;
2831
@@ -56,19 +59,36 @@ export default class IgcPopoverComponent extends LitElement {
5659 public static readonly tagName = 'igc-popover' ;
5760 public static override styles = styles ;
5861
62+ private static _oppositeArrowSide = new Map (
63+ Object . entries ( {
64+ top : 'bottom' ,
65+ right : 'left' ,
66+ bottom : 'top' ,
67+ left : 'right' ,
68+ } )
69+ ) ;
70+
5971 /* blazorSuppress */
60- public static register ( ) {
72+ public static register ( ) : void {
6173 registerComponent ( IgcPopoverComponent ) ;
6274 }
6375
64- private dispose ?: ReturnType < typeof autoUpdate > ;
65- private target ?: Element ;
76+ //#region Internal properties and state
6677
67- @ query ( '#container' )
68- private _container ! : HTMLElement ;
78+ private _dispose ?: ReturnType < typeof autoUpdate > ;
79+ private _target ?: Element ;
6980
70- @queryAssignedElements ( { slot : 'anchor' , flatten : true } )
71- private _anchors ! : HTMLElement [ ] ;
81+ private readonly _slots = addSlotController ( this , {
82+ slots : setSlots ( 'anchor' ) ,
83+ onChange : this . _handleSlotChange ,
84+ } ) ;
85+
86+ @query ( '#container' , true )
87+ private readonly _container ! : HTMLElement ;
88+
89+ //#endregion
90+
91+ //#region Public attributes and properties
7292
7393 /**
7494 * Pass an IDREF or an DOM element reference to use as the
@@ -138,85 +158,111 @@ export default class IgcPopoverComponent extends LitElement {
138158 @property ( { type : Number , attribute : 'shift-padding' } )
139159 public shiftPadding = 0 ;
140160
141- @watch ( 'anchor' )
142- protected anchorChange ( ) {
143- const newTarget = isString ( this . anchor )
144- ? getElementByIdFromRoot ( this , this . anchor )
145- : this . anchor ;
161+ //#endregion
162+
163+ //#region Life-cycle hooks
146164
147- if ( newTarget ) {
148- this . target = newTarget ;
149- this . _updateState ( ) ;
165+ protected override update ( properties : PropertyValues < this> ) : void {
166+ if ( properties . has ( 'anchor' ) ) {
167+ const target = isString ( this . anchor )
168+ ? getElementByIdFromRoot ( this , this . anchor )
169+ : this . anchor ;
170+
171+ if ( target ) {
172+ this . _target = target ;
173+ }
150174 }
151- }
152175
153- @watch ( 'open' , { waitUntilFirstUpdate : true } )
154- protected openChange ( ) {
155- this . open ? this . show ( ) : this . hide ( ) ;
156- }
176+ if ( this . hasUpdated && properties . has ( 'open' ) ) {
177+ this . _setOpenState ( this . open ) ;
178+ }
157179
158- @watch ( 'arrow' , { waitUntilFirstUpdate : true } )
159- @watch ( 'arrowOffset' , { waitUntilFirstUpdate : true } )
160- @watch ( 'flip' , { waitUntilFirstUpdate : true } )
161- @watch ( 'inline' , { waitUntilFirstUpdate : true } )
162- @watch ( 'offset' , { waitUntilFirstUpdate : true } )
163- @watch ( 'placement' , { waitUntilFirstUpdate : true } )
164- @watch ( 'sameWidth' , { waitUntilFirstUpdate : true } )
165- @watch ( 'shift' , { waitUntilFirstUpdate : true } )
166- @watch ( 'shiftPadding' , { waitUntilFirstUpdate : true } )
167- protected floatingPropChange ( ) {
168180 this . _updateState ( ) ;
181+ super . update ( properties ) ;
169182 }
170183
171- public override async connectedCallback ( ) {
184+ /** @internal */
185+ public override connectedCallback ( ) : void {
172186 super . connectedCallback ( ) ;
187+ this . updateComplete . then ( ( ) => {
188+ this . _setOpenState ( this . open ) ;
189+ } ) ;
190+ }
173191
174- await this . updateComplete ;
175- if ( this . open ) {
176- this . show ( ) ;
192+ /** @internal */
193+ public override disconnectedCallback ( ) : void {
194+ super . disconnectedCallback ( ) ;
195+ this . _setOpenState ( false ) ;
196+ }
197+
198+ //#endregion
199+
200+ private _handleSlotChange ( {
201+ isDefault,
202+ } : SlotChangeCallbackParameters < unknown > ) : void {
203+ if ( isDefault ) {
204+ return ;
177205 }
206+
207+ const possibleTarget = first (
208+ this . _slots . getAssignedElements ( 'anchor' , { flatten : true } )
209+ ) ;
210+
211+ if ( this . anchor || ! possibleTarget ) {
212+ return ;
213+ }
214+
215+ this . _target = possibleTarget ;
216+ this . _updateState ( ) ;
178217 }
179218
180- public override disconnectedCallback ( ) {
181- this . hide ( ) ;
182- super . disconnectedCallback ( ) ;
219+ //#region Internal open state API
220+ private _setOpenState ( state : boolean ) : void {
221+ state ? this . _setDispose ( ) : this . _clearDispose ( ) ;
222+ this . _setPopoverState ( state ) ;
183223 }
184224
185- protected show ( ) {
186- if ( ! this . target ) {
225+ private _setPopoverState ( open : boolean ) : void {
226+ if ( ! this . _target ) {
187227 return ;
188228 }
229+ open ? this . _container ?. showPopover ( ) : this . _container ?. hidePopover ( ) ;
230+ }
189231
190- this . _showPopover ( ) ;
232+ private _setDispose ( ) : void {
233+ if ( ! this . _target ) {
234+ return ;
235+ }
191236
192- this . dispose = autoUpdate (
193- this . target ,
237+ this . _dispose = autoUpdate (
238+ this . _target ,
194239 this . _container ,
195240 this . _updatePosition . bind ( this )
196241 ) ;
197242 }
198243
199- protected async hide ( ) : Promise < void > {
200- this . _hidePopover ( ) ;
201-
244+ private _clearDispose ( ) : Promise < void > {
202245 return new Promise ( ( resolve ) => {
203- this . dispose ?.( ) ;
204- this . dispose = undefined ;
246+ this . _dispose ?.( ) ;
247+ this . _dispose = undefined ;
205248 resolve ( ) ;
206249 } ) ;
207250 }
208251
209- private _showPopover ( ) {
210- this . _container ?. showPopover ( ) ;
252+ private async _updateState ( ) : Promise < void > {
253+ if ( this . open ) {
254+ await this . _clearDispose ( ) ;
255+ this . _setDispose ( ) ;
256+ }
211257 }
212258
213- private _hidePopover ( ) {
214- this . _container ?. hidePopover ( ) ;
215- }
259+ //#endregion
260+
261+ //#region Internal position API
216262
217263 private _createMiddleware ( ) : Middleware [ ] {
218264 const middleware : Middleware [ ] = [ ] ;
219- const styles = this . _container . style ;
265+ const container = this . _container ;
220266
221267 if ( this . offset ) {
222268 middleware . push ( offset ( this . offset ) ) ;
@@ -246,32 +292,24 @@ export default class IgcPopoverComponent extends LitElement {
246292 if ( this . sameWidth ) {
247293 middleware . push (
248294 size ( {
249- apply : ( { rects } ) => {
250- Object . assign ( styles , { width : `${ rects . reference . width } px` } ) ;
251- } ,
295+ apply : ( { rects } ) =>
296+ setStyles ( container , { width : `${ rects . reference . width } px` } ) ,
252297 } )
253298 ) ;
254299 } else {
255- Object . assign ( styles , { width : '' } ) ;
300+ setStyles ( container , { width : '' } ) ;
256301 }
257302
258303 return middleware ;
259304 }
260305
261- private async _updateState ( ) {
262- if ( this . open ) {
263- await this . hide ( ) ;
264- this . show ( ) ;
265- }
266- }
267-
268- private async _updatePosition ( ) {
269- if ( ! ( this . open && this . target ) ) {
306+ private async _updatePosition ( ) : Promise < void > {
307+ if ( ! ( this . open && this . _target ) ) {
270308 return ;
271309 }
272310
273311 const { x, y, middlewareData, placement } = await computePosition (
274- this . target ,
312+ this . _target ,
275313 this . _container ,
276314 {
277315 placement : this . placement ?? 'bottom-start' ,
@@ -280,54 +318,45 @@ export default class IgcPopoverComponent extends LitElement {
280318 }
281319 ) ;
282320
283- Object . assign ( this . _container . style , {
284- left : 0 ,
285- top : 0 ,
321+ setStyles ( this . _container , {
322+ left : '0' ,
323+ top : '0' ,
286324 transform : `translate(${ roundByDPR ( x ) } px,${ roundByDPR ( y ) } px)` ,
287325 } ) ;
288326
289- this . _positionArrow ( placement , middlewareData ) ;
327+ this . _updateArrowPosition ( placement , middlewareData ) ;
290328 }
291329
292- private _positionArrow ( placement : Placement , data : MiddlewareData ) {
330+ private _updateArrowPosition ( placement : Placement , data : MiddlewareData ) {
293331 if ( ! ( data . arrow && this . arrow ) ) {
294332 return ;
295333 }
296334
297335 const { x, y } = data . arrow ;
336+ const arrow = this . arrow ;
337+ const offset = this . arrowOffset ;
298338
299339 // The current placement of the popover along the x/y axis
300340 const currentPlacement = first ( placement . split ( '-' ) ) ;
301341
302342 // The opposite side where the arrow element should render based on the `currentPlacement`
303- const staticSide = {
304- top : 'bottom' ,
305- right : 'left' ,
306- bottom : 'top' ,
307- left : 'right' ,
308- } [ currentPlacement ] ! ;
343+ const staticSide =
344+ IgcPopoverComponent . _oppositeArrowSide . get ( currentPlacement ) ! ;
309345
310- this . arrow . part = currentPlacement ;
346+ arrow . part = currentPlacement ;
311347
312- Object . assign ( this . arrow . style , {
313- left : x != null ? `${ roundByDPR ( x + this . arrowOffset ) } px` : '' ,
314- top : y != null ? `${ roundByDPR ( y + this . arrowOffset ) } px` : '' ,
348+ setStyles ( arrow , {
349+ left : x != null ? `${ roundByDPR ( x + offset ) } px` : '' ,
350+ top : y != null ? `${ roundByDPR ( y + offset ) } px` : '' ,
315351 [ staticSide ] : '-4px' ,
316352 } ) ;
317353 }
318354
319- private _anchorSlotChange ( ) {
320- if ( this . anchor || isEmpty ( this . _anchors ) ) {
321- return ;
322- }
323-
324- this . target = this . _anchors [ 0 ] ;
325- this . _updateState ( ) ;
326- }
355+ //#endregion
327356
328357 protected override render ( ) {
329358 return html `
330- < slot name ="anchor " @slotchange = ${ this . _anchorSlotChange } > </ slot >
359+ < slot name ="anchor "> </ slot >
331360 < div id ="container " part ="container " popover ="manual ">
332361 < slot > </ slot >
333362 </ div >
0 commit comments