Skip to content

Commit 1b216f5

Browse files
authored
fix: Skip popover position updates if it happened offscreen (#3568)
1 parent 9f97233 commit 1b216f5

File tree

2 files changed

+33
-5
lines changed

2 files changed

+33
-5
lines changed

src/annotation-context/__integ__/annotation-scroll.test.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,17 @@ import createWrapper from '../../../lib/components/test-utils/selectors';
88
const wrapper = createWrapper();
99
const annotationWrapper = wrapper.findAnnotation();
1010

11+
function setupTest(testFn: (page: BasePageObject) => Promise<void>) {
12+
return useBrowser(async browser => {
13+
const page = new BasePageObject(browser);
14+
await browser.url('#/light/annotation-context/annotation-scroll');
15+
await testFn(page);
16+
});
17+
}
18+
1119
test(
1220
'scrolls annotation in view with fixed elements',
13-
useBrowser(async browser => {
14-
await browser.url('#/light/annotation-context/annotation-scroll');
15-
const page = new BasePageObject(browser);
21+
setupTest(async page => {
1622
const nextButtonSelector = annotationWrapper.findNextButton().toSelector();
1723
await page.waitForVisible(nextButtonSelector);
1824
await page.click(nextButtonSelector);
@@ -37,6 +43,21 @@ test(
3743
})
3844
);
3945

46+
test(
47+
'does not update popover position when annotation is rendered offscreen',
48+
setupTest(async page => {
49+
await expect(page.isDisplayedInViewport(annotationWrapper.toSelector())).resolves.toEqual(true);
50+
// scroll to the page bottom to make annotation offscreen
51+
await page.windowScrollTo({ top: 1200 });
52+
await expect(page.isDisplayedInViewport(annotationWrapper.toSelector())).resolves.toEqual(false);
53+
const positionBefore = await page.getBoundingBox(annotationWrapper.toSelector());
54+
// click somewhere to trigger the regression
55+
await page.click('main');
56+
const positionAfter = await page.getBoundingBox(annotationWrapper.toSelector());
57+
expect(positionBefore).toEqual(positionAfter);
58+
})
59+
);
60+
4061
function overlap(one: ElementRect, two: ElementRect) {
4162
if (one.right < two.left || one.left > two.right) {
4263
return false;

src/popover/container.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React, { useLayoutEffect, useRef } from 'react';
44
import clsx from 'clsx';
55

66
import { nodeContains } from '@cloudscape-design/component-toolkit/dom';
7-
import { useResizeObserver } from '@cloudscape-design/component-toolkit/internal';
7+
import { getLogicalBoundingClientRect, useResizeObserver } from '@cloudscape-design/component-toolkit/internal';
88

99
import { useVisualRefresh } from '../internal/hooks/use-visual-mode';
1010
import { InternalPosition, PopoverProps } from './interfaces';
@@ -18,7 +18,7 @@ interface PopoverContainerProps {
1818
getTrack?: () => null | HTMLElement | SVGElement;
1919
/**
2020
Used to update the container position in case track or track position changes:
21-
21+
2222
const trackRef = useRef<Element>(null)
2323
return (<>
2424
<Track style={getPosition(selectedItemId)} ref={trackRef} />
@@ -131,6 +131,13 @@ export default function PopoverContainer({
131131
return;
132132
}
133133

134+
// Do not update position if popover moved offscreen
135+
const popoverOffset = popoverRef.current && getLogicalBoundingClientRect(popoverRef.current);
136+
// istanbul ignore if - tested via integration tests
137+
if (!popoverOffset || popoverOffset.insetBlockStart < 0 || popoverOffset.insetBlockEnd > window.innerHeight) {
138+
return;
139+
}
140+
134141
// Continuously update the popover position for one second to account for any layout changes
135142
// and animations. On browsers where `requestIdleCallback` is supported,
136143
// this runs only while the CPU is otherwise idle. In other browsers (mainly Safari), we call it

0 commit comments

Comments
 (0)