Skip to content

Commit a6b5a9e

Browse files
committed
refactor(popover): extract rect clamping logic and add tests
1 parent 868ecf7 commit a6b5a9e

File tree

3 files changed

+64
-5
lines changed

3 files changed

+64
-5
lines changed

src/popover/__tests__/positions.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33
import {
44
calculatePosition,
5+
clampRectStart,
56
intersectRectangles,
67
isCenterOutside,
78
PRIORITY_MAPPING,
@@ -280,3 +281,48 @@ describe('isCenterOutside', () => {
280281
).toBe(false);
281282
});
282283
});
284+
285+
describe('clampRectStart', () => {
286+
const parent = { insetInlineStart: 100, insetBlockStart: 100, inlineSize: 400, blockSize: 300 };
287+
288+
test('clamps insetInlineStart to parent start when track is before parent', () => {
289+
const track = { insetInlineStart: 50, insetBlockStart: 200, inlineSize: 20, blockSize: 20 };
290+
expect(clampRectStart(track, parent).insetInlineStart).toBe(100);
291+
});
292+
293+
test('clamps insetBlockStart to parent start when track is above parent', () => {
294+
const track = { insetInlineStart: 200, insetBlockStart: 50, inlineSize: 20, blockSize: 20 };
295+
expect(clampRectStart(track, parent).insetBlockStart).toBe(100);
296+
});
297+
298+
test('clamps insetInlineStart to parent end when track is past parent', () => {
299+
const track = { insetInlineStart: 600, insetBlockStart: 200, inlineSize: 20, blockSize: 20 };
300+
expect(clampRectStart(track, parent).insetInlineStart).toBe(500);
301+
});
302+
303+
test('clamps insetBlockStart to parent end when track is below parent', () => {
304+
const track = { insetInlineStart: 200, insetBlockStart: 500, inlineSize: 20, blockSize: 20 };
305+
expect(clampRectStart(track, parent).insetBlockStart).toBe(400);
306+
});
307+
308+
test('does not clamp when track is within parent bounds', () => {
309+
const track = { insetInlineStart: 200, insetBlockStart: 250, inlineSize: 20, blockSize: 20 };
310+
const result = clampRectStart(track, parent);
311+
expect(result.insetInlineStart).toBe(200);
312+
expect(result.insetBlockStart).toBe(250);
313+
});
314+
315+
test('clamps both axes simultaneously', () => {
316+
const track = { insetInlineStart: 10, insetBlockStart: 500, inlineSize: 20, blockSize: 20 };
317+
const result = clampRectStart(track, parent);
318+
expect(result.insetInlineStart).toBe(100);
319+
expect(result.insetBlockStart).toBe(400);
320+
});
321+
322+
test('preserves inlineSize and blockSize of the track', () => {
323+
const track = { insetInlineStart: 50, insetBlockStart: 50, inlineSize: 20, blockSize: 30 };
324+
const result = clampRectStart(track, parent);
325+
expect(result.inlineSize).toBe(20);
326+
expect(result.blockSize).toBe(30);
327+
});
328+
});

src/popover/use-popover-position.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ import {
1313
scrollRectangleIntoView,
1414
} from '../internal/utils/scrollable-containers';
1515
import { BoundingBox, InternalPosition, Offset, PopoverProps, Rect } from './interfaces';
16-
import { calculatePosition, getDimensions, getOffsetDimensions, isCenterOutside } from './utils/positions';
16+
import {
17+
calculatePosition,
18+
clampRectStart,
19+
getDimensions,
20+
getOffsetDimensions,
21+
isCenterOutside,
22+
} from './utils/positions';
1723

1824
export default function usePopoverPosition({
1925
popoverRef,
@@ -271,8 +277,5 @@ function getClampedTrackRect(track: HTMLElement | SVGElement, parentRef?: HTMLEl
271277
if (!parentRef) {
272278
return trackRect;
273279
}
274-
const parentRect = getLogicalBoundingClientRect(parentRef);
275-
trackRect.insetInlineStart = Math.max(trackRect.insetInlineStart, parentRect.insetInlineStart);
276-
trackRect.insetBlockStart = Math.max(trackRect.insetBlockStart, parentRect.insetBlockStart);
277-
return trackRect;
280+
return clampRectStart(trackRect, getLogicalBoundingClientRect(parentRef));
278281
}

src/popover/utils/positions.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,16 @@ function isTopOrBottom(internalPosition: InternalPosition) {
345345
return ['top', 'bottom'].includes(internalPosition.split('-')[0]);
346346
}
347347

348+
export function clampRectStart<T extends BoundingBox>(rect: T, parentRect: BoundingBox): T {
349+
const parentInlineEnd = parentRect.insetInlineStart + parentRect.inlineSize;
350+
const parentBlockEnd = parentRect.insetBlockStart + parentRect.blockSize;
351+
return {
352+
...rect,
353+
insetInlineStart: Math.max(parentRect.insetInlineStart, Math.min(rect.insetInlineStart, parentInlineEnd)),
354+
insetBlockStart: Math.max(parentRect.insetBlockStart, Math.min(rect.insetBlockStart, parentBlockEnd)),
355+
} as T;
356+
}
357+
348358
export function isCenterOutside(child: Rect, parent: Rect) {
349359
const childCenter = child.insetBlockStart + child.blockSize / 2;
350360
const overflowsBlockStart = childCenter < parent.insetBlockStart;

0 commit comments

Comments
 (0)