Skip to content

Commit 656f181

Browse files
authored
fix: uui-popover container scroll event (#713)
* look for scroll parent and listen for scroll * update story * make eventlisteners passive and remove in disconnecedCallback
1 parent f5ccaa4 commit 656f181

File tree

2 files changed

+110
-2
lines changed

2 files changed

+110
-2
lines changed

packages/uui-popover-container/lib/uui-popover-container.element.ts

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export class UUIPopoverContainerElement extends LitElement {
6969
_actualPlacement: PopoverContainerPlacement = this._placement;
7070

7171
#targetElement: HTMLElement | null = null;
72+
#scrollParents: Element[] = [];
7273

7374
connectedCallback(): void {
7475
//TODO: Remove this polyfill when firefox supports the new popover API
@@ -85,6 +86,7 @@ export class UUIPopoverContainerElement extends LitElement {
8586
disconnectedCallback(): void {
8687
super.disconnectedCallback();
8788
this.removeEventListener('beforetoggle', this.#onBeforeToggle);
89+
this.#stopScrollListener();
8890
}
8991

9092
#onBeforeToggle = (event: any) => {
@@ -96,6 +98,8 @@ export class UUIPopoverContainerElement extends LitElement {
9698
this.id
9799
);
98100

101+
this.#getScrollParents();
102+
99103
// Dispatch a custom event that can be listened to by the popover target.
100104
// Mostly used for UUIButton.
101105
this.#targetElement?.dispatchEvent(
@@ -110,11 +114,11 @@ export class UUIPopoverContainerElement extends LitElement {
110114
);
111115

112116
if (!this._open) {
113-
document.removeEventListener('scroll', this.#initUpdate);
117+
this.#stopScrollListener();
114118
return;
115119
}
116120

117-
document.addEventListener('scroll', this.#initUpdate);
121+
this.#startScrollListener();
118122

119123
requestAnimationFrame(() => {
120124
this.#initUpdate();
@@ -305,6 +309,52 @@ export class UUIPopoverContainerElement extends LitElement {
305309
`${oppositeDirection}-${position}` as PopoverContainerPlacement;
306310
}
307311

312+
#startScrollListener() {
313+
this.#scrollParents.forEach(el => {
314+
el.addEventListener('scroll', this.#initUpdate, { passive: true });
315+
});
316+
document.addEventListener('scroll', this.#initUpdate, { passive: true });
317+
}
318+
#stopScrollListener() {
319+
this.#scrollParents.forEach(el => {
320+
el.removeEventListener('scroll', this.#initUpdate);
321+
});
322+
document.removeEventListener('scroll', this.#initUpdate);
323+
}
324+
325+
#getScrollParents(): any {
326+
if (!this.#targetElement) return;
327+
328+
let style = getComputedStyle(this.#targetElement);
329+
if (style.position === 'fixed') {
330+
return;
331+
}
332+
333+
const includeHidden = false;
334+
const excludeStaticParent = style.position === 'absolute';
335+
const overflowRegex = includeHidden
336+
? /(auto|scroll|hidden)/
337+
: /(auto|scroll)/;
338+
339+
let el = this.#targetElement;
340+
while ((el = el.parentElement as HTMLElement)) {
341+
style = getComputedStyle(el);
342+
343+
if (excludeStaticParent && style.position === 'static') {
344+
continue;
345+
}
346+
if (
347+
overflowRegex.test(style.overflow + style.overflowY + style.overflowX)
348+
) {
349+
this.#scrollParents.push(el);
350+
}
351+
if (style.position === 'fixed') {
352+
return;
353+
}
354+
}
355+
this.#scrollParents.push(document.body);
356+
}
357+
308358
render() {
309359
return html`<slot></slot>`;
310360
}

packages/uui-popover-container/lib/uui-popover-container.story.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,61 @@ export const Tooltip: Story = {
158158
</uui-popover-container>
159159
`,
160160
};
161+
162+
export const InsideScrollContainer: Story = {
163+
args: {
164+
placement: 'bottom-start',
165+
margin: 0,
166+
},
167+
argTypes: {
168+
open: {
169+
control: false,
170+
},
171+
placement: {
172+
options: [
173+
'auto',
174+
'top',
175+
'top-start',
176+
'top-end',
177+
'bottom',
178+
'bottom-start',
179+
'bottom-end',
180+
'right',
181+
'right-start',
182+
'right-end',
183+
'left',
184+
'left-start',
185+
'left-end',
186+
],
187+
},
188+
},
189+
render: args => html`
190+
<div style="height: 500px; overflow: auto; outline: 1px solid black">
191+
<div
192+
style="width: 300px; height: 300px; outline: 1px solid black; overflow: auto;">
193+
<div style="height: 150px"></div>
194+
<uui-button
195+
id="popover-button"
196+
popovertarget="popover-container"
197+
look="primary"
198+
label="Open popover">
199+
Open popover
200+
</uui-button>
201+
<div style="height: 150px"></div>
202+
<div style="height: 150px"></div>
203+
</div>
204+
<div style="height: 400px"></div>
205+
</div>
206+
<uui-popover-container
207+
id="popover-container"
208+
popover
209+
placement=${args.placement}
210+
margin=${args.margin}>
211+
<div
212+
style="width: 100%; background-color: var(--uui-color-surface); max-width: 200px; box-shadow: var(--uui-shadow-depth-4); padding: var(--uui-size-space-4); border-radius: var(--uui-border-radius); font-size: 0.9rem;">
213+
<h3>Scroll!</h3>
214+
Scrolling in any of the 2 boxes should trigger an update
215+
</div>
216+
</uui-popover-container>
217+
`,
218+
};

0 commit comments

Comments
 (0)