Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-taxis-resize.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@digdir/designsystemet-web': patch
---

**popover:** Prevent ResizeObserver loop errors when positioning the option list.
37 changes: 36 additions & 1 deletion packages/web/src/popover/popover.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,37 @@ describe('popover floating behavior', () => {
expect(popover).toHaveAttribute('data-floating', 'bottom-end');
});

it('defers dimension changes to avoid resizing during ResizeObserver delivery', async () => {
const animationFrames: FrameRequestCallback[] = [];
vi.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
animationFrames.push(callback);
return animationFrames.length;
});
render(`
<button style="width: 120px" popovertarget="my-popover">Open</button>
<div
id="my-popover"
popover="auto"
style="--_ds-floating: top; --_ds-floating-overscroll: fit"
>Content</div>
`);

document.querySelector('button')?.click();
await tick();

const popover = document.getElementById('my-popover') as HTMLElement;
expect(animationFrames.length).toBeGreaterThan(0);
expect(popover.style.width).toBe('');
expect(popover.style.maxHeight).toBe('');

animationFrames.forEach((callback) => {
callback(performance.now());
});

expect(popover.style.width).toBe('120px');
expect(popover.style.maxHeight).not.toBe('');
});

it('does not position when data-placement is "none"', async () => {
render(`
<button popovertarget="my-popover">Open</button>
Expand Down Expand Up @@ -214,6 +245,7 @@ describe('popover overscroll sizing', () => {
trigger.click();
await tick();

await vi.waitFor(() => expect(popover.style.maxHeight).toMatch(/px$/));
const sizeAtOpen = popover.style.maxHeight;
expect(sizeAtOpen).toMatch(/px$/);

Expand All @@ -235,6 +267,7 @@ describe('popover overscroll sizing', () => {

trigger.click();
await tick();
await vi.waitFor(() => expect(popover.style.maxHeight).toMatch(/px$/));
const sizeAtFirstOpen = popover.style.maxHeight;

trigger.click(); // Close
Expand All @@ -243,8 +276,10 @@ describe('popover overscroll sizing', () => {
trigger.click();
await tick();

await vi.waitFor(() =>
expect(popover.style.maxHeight).not.toBe(sizeAtFirstOpen),
);
expect(popover.style.maxHeight).toMatch(/px$/);
expect(popover.style.maxHeight).not.toBe(sizeAtFirstOpen);
});
});

Expand Down
13 changes: 10 additions & 3 deletions packages/web/src/popover/popover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,16 @@ function toggle(
apply({ availableHeight }) {
if (sized) return;
sized = true;
if (overscroll === 'fit')
el.style.width = `${source.offsetWidth}px`; // Use offsetWidth to include padding, matching the width of the source element
el.style.maxHeight = `${Math.max(50, availableHeight - padding * 2)}px`;
const width = `${source.offsetWidth}px`; // Use offsetWidth to include padding, matching the width of the source element
const maxHeight = `${Math.max(50, availableHeight - padding * 2)}px`;

requestAnimationFrame(() => {
// Avoid changing an observed element during ResizeObserver delivery.
if (overscroll === 'fit' && el.style.width !== width)
el.style.width = width;
if (el.style.maxHeight !== maxHeight)
el.style.maxHeight = maxHeight;
});
},
}),
]
Expand Down
Loading