@@ -23,6 +23,7 @@ import { registerComponent } from '../common/definitions/register.js';
2323import {
2424 first ,
2525 getElementByIdFromRoot ,
26+ getRoot ,
2627 isString ,
2728 roundByDPR ,
2829 setStyles ,
@@ -46,6 +47,26 @@ export type PopoverPlacement =
4647 | 'left-start'
4748 | 'left-end' ;
4849
50+ /**
51+ * Walks up the DOM tree of the given element, crossing shadow boundaries, and
52+ * returns whether any ancestor is positioned as `sticky`.
53+ */
54+ function hasStickyAncestor ( element : Element ) : boolean {
55+ let node : Element | null = element ;
56+
57+ while ( node ) {
58+ if ( getComputedStyle ( node ) . position === 'sticky' ) {
59+ return true ;
60+ }
61+
62+ const root = getRoot ( node ) ;
63+ node =
64+ node . parentElement ?? ( root instanceof ShadowRoot ? root . host : null ) ;
65+ }
66+
67+ return false ;
68+ }
69+
4970/* blazorSuppress */
5071/**
5172 * @element igc-popover
@@ -78,6 +99,16 @@ export default class IgcPopoverComponent extends LitElement {
7899 private _dispose ?: ReturnType < typeof autoUpdate > ;
79100 private _target ?: Element ;
80101
102+ /**
103+ * The positioning strategy resolved when the popover is opened. The `fixed`
104+ * strategy is used when the anchor has a `position: sticky` ancestor, otherwise
105+ * the default `absolute` strategy is used. Cached here to avoid repeated DOM
106+ * traversals and style reflows on every scroll/resize reposition.
107+ *
108+ * Also, time to migrate to CSS Anchor positioning!!!
109+ */
110+ private _strategy : 'absolute' | 'fixed' = 'absolute' ;
111+
81112 private readonly _slots = addSlotController ( this , {
82113 slots : setSlots ( 'anchor' ) ,
83114 onChange : this . _handleSlotChange ,
@@ -163,21 +194,25 @@ export default class IgcPopoverComponent extends LitElement {
163194 //#region Life-cycle hooks
164195
165196 protected override update ( properties : PropertyValues < this> ) : void {
197+ let targetChanged = false ;
198+
166199 if ( properties . has ( 'anchor' ) ) {
167200 const target = isString ( this . anchor )
168201 ? getElementByIdFromRoot ( this , this . anchor )
169202 : this . anchor ;
170203
171204 if ( target ) {
172205 this . _target = target ;
206+ targetChanged = true ;
173207 }
174208 }
175209
176210 if ( this . hasUpdated && properties . has ( 'open' ) ) {
177211 this . _setOpenState ( this . open ) ;
212+ } else if ( this . hasUpdated && this . open ) {
213+ targetChanged ? this . _resetAutoUpdate ( ) : this . _updatePosition ( ) ;
178214 }
179215
180- this . _updateState ( ) ;
181216 super . update ( properties ) ;
182217 }
183218
@@ -213,7 +248,10 @@ export default class IgcPopoverComponent extends LitElement {
213248 }
214249
215250 this . _target = possibleTarget ;
216- this . _updateState ( ) ;
251+
252+ if ( this . open ) {
253+ this . _resetAutoUpdate ( ) ;
254+ }
217255 }
218256
219257 //#region Internal open state API
@@ -234,28 +272,24 @@ export default class IgcPopoverComponent extends LitElement {
234272 return ;
235273 }
236274
275+ this . _strategy = hasStickyAncestor ( this . _target ) ? 'fixed' : 'absolute' ;
276+
237277 this . _dispose = autoUpdate (
238278 this . _target ,
239279 this . _container ,
240280 this . _updatePosition . bind ( this )
241281 ) ;
242282 }
243283
244- private _clearDispose ( ) : Promise < void > {
245- return new Promise ( ( resolve ) => {
246- this . _dispose ?.( ) ;
247- this . _dispose = undefined ;
248- resolve ( ) ;
249- } ) ;
284+ private _clearDispose ( ) : void {
285+ this . _dispose ?.( ) ;
286+ this . _dispose = undefined ;
250287 }
251288
252- private async _updateState ( ) : Promise < void > {
253- if ( this . open ) {
254- await this . _clearDispose ( ) ;
255- this . _setDispose ( ) ;
256- }
289+ private _resetAutoUpdate ( ) : void {
290+ this . _clearDispose ( ) ;
291+ this . _setDispose ( ) ;
257292 }
258-
259293 //#endregion
260294
261295 //#region Internal position API
@@ -308,17 +342,20 @@ export default class IgcPopoverComponent extends LitElement {
308342 return ;
309343 }
310344
345+ const strategy = this . _strategy ;
346+
311347 const { x, y, middlewareData, placement } = await computePosition (
312348 this . _target ,
313349 this . _container ,
314350 {
315351 placement : this . placement ?? 'bottom-start' ,
316352 middleware : this . _createMiddleware ( ) ,
317- strategy : 'absolute' ,
353+ strategy,
318354 }
319355 ) ;
320356
321357 setStyles ( this . _container , {
358+ position : strategy ,
322359 left : '0' ,
323360 top : '0' ,
324361 transform : `translate(${ roundByDPR ( x ) } px,${ roundByDPR ( y ) } px)` ,
0 commit comments