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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## Unreleased

- [616](https://github.com/bvaughn/react-resizable-panels/pull/616): Replace `Separator` and `Panel` edge hit-area padding with a minimum size threshold based on [Apple's user interface guidelines](https://developer.apple.com/design/human-interface-guidelines/accessibility). Separators that are large enough will no longer be padded; separators that are too small (or panels without separators) will more or less function like before. This should not have much of a user-facing impact other than an increase in the click target area. (Previously I was not padding enough, as per Apple's guidelines.)
- [615](https://github.com/bvaughn/react-resizable-panels/pull/615): Double-clicking on a `Separator` resets its associated `Panel` to its default-size (see video below); double-click will have no impact on panels without default sizes
- [618](https://github.com/bvaughn/react-resizable-panels/pull/618): Bugfix: Don't override `adoptedStyleSheets`

https://github.com/user-attachments/assets/f19f6c5e-d290-455e-9bad-20e5038c3508

## 4.4.2

- [610](https://github.com/bvaughn/react-resizable-panels/pull/610): Fix calculated cursor style when `"pointermove"` event is has low-precision/rounded `clientX` and `clientY` values
Expand Down
28 changes: 28 additions & 0 deletions integrations/tests/tests/pointer-interactions.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,34 @@ test.describe("pointer interactions", () => {
await expect(mainPage.getByText('"right": 33')).toBeVisible();
});

test("double-clicking a separator resets panel to default size", async ({
page: mainPage
}) => {
const page = await goToUrl(
mainPage,
<Group>
<Panel defaultSize="30%" id="left" minSize={50} />
<Separator id="separator" />
<Panel id="right" minSize={50} />
</Group>,
{ usePopUpWindow }
);

await assertLayoutChangeCounts(mainPage, 1);
await expect(mainPage.getByText('"left": 30')).toBeVisible();

await resizeHelper(page, ["left", "right"], 100, 0);

await assertLayoutChangeCounts(mainPage, 2);
await expect(mainPage.getByText('"left": 40')).toBeVisible();

const separator = page.getByTestId("separator");
await separator.dblclick();

await assertLayoutChangeCounts(mainPage, 3);
await expect(mainPage.getByText('"left": 30')).toBeVisible();
});

test.describe("focus", () => {
test("should update focus to the nearest separator", async ({
page: mainPage
Expand Down
14 changes: 14 additions & 0 deletions lib/components/group/Group.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,20 @@ describe("Group", () => {
});

test("should be called once per layout change", async () => {
setElementBoundsFunction((element) => {
switch (element.id) {
case "a": {
return new DOMRect(0, 0, 50, 50);
}
case "b": {
return new DOMRect(50, 0, 10, 50);
}
case "c": {
return new DOMRect(60, 0, 50, 50);
}
}
});

const onLayoutChange = vi.fn();
const onLayoutChanged = vi.fn();

Expand Down
6 changes: 0 additions & 6 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,3 @@ export const CURSOR_FLAG_VERTICAL_MIN = 0b0100;
export const CURSOR_FLAG_VERTICAL_MAX = 0b1000;
export const CURSOR_FLAGS_HORIZONTAL = 0b0011;
export const CURSOR_FLAGS_VERTICAL = 0b1100;

// Misc. shared values
export const DEFAULT_POINTER_PRECISION = {
coarse: 10,
precise: 5
};
2 changes: 1 addition & 1 deletion lib/global/cursor/updateCursorStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function updateCursorStyle(ownerDocument: Document) {
if (styleSheet === undefined) {
styleSheet = new ownerDocument.defaultView.CSSStyleSheet();

ownerDocument.adoptedStyleSheets = [styleSheet];
ownerDocument.adoptedStyleSheets.push(styleSheet);
}

const { cursorFlags, interactionState } = read();
Expand Down
116 changes: 58 additions & 58 deletions lib/global/dom/calculateHitRegions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ describe("calculateHitRegions", () => {
});

test("two panels", () => {
const group = mockGroup(new DOMRect(0, 0, 20, 50));
group.addPanel(new DOMRect(0, 0, 10, 50), "left");
group.addPanel(new DOMRect(10, 0, 10, 50), "right");
const group = mockGroup(new DOMRect(0, 0, 100, 50));
group.addPanel(new DOMRect(0, 0, 50, 50), "left");
group.addPanel(new DOMRect(50, 0, 50, 50), "right");

expect(serialize(group)).toMatchInlineSnapshot(`
"[
Expand All @@ -41,17 +41,17 @@ describe("calculateHitRegions", () => {
"group-1-left",
"group-1-right"
],
"rect": "10,0 0 x 50"
"rect": "36.5,0 27 x 50"
}
]"
`);
});

test("three panels", () => {
const group = mockGroup(new DOMRect(0, 0, 30, 50));
group.addPanel(new DOMRect(0, 0, 10, 50), "left");
group.addPanel(new DOMRect(10, 0, 10, 50), "center");
group.addPanel(new DOMRect(20, 0, 10, 50), "right");
const group = mockGroup(new DOMRect(0, 0, 120, 50));
group.addPanel(new DOMRect(0, 0, 40, 50), "left");
group.addPanel(new DOMRect(40, 0, 40, 50), "center");
group.addPanel(new DOMRect(80, 0, 40, 50), "right");

expect(serialize(group)).toMatchInlineSnapshot(`
"[
Expand All @@ -60,26 +60,26 @@ describe("calculateHitRegions", () => {
"group-1-left",
"group-1-center"
],
"rect": "10,0 0 x 50"
"rect": "26.5,0 27 x 50"
},
{
"panels": [
"group-1-center",
"group-1-right"
],
"rect": "20,0 0 x 50"
"rect": "66.5,0 27 x 50"
}
]"
`);
});

test("panels and explicit separators", () => {
const group = mockGroup(new DOMRect(0, 0, 50, 50));
group.addPanel(new DOMRect(0, 0, 10, 50), "left");
group.addSeparator(new DOMRect(10, 0, 10, 50), "left");
group.addPanel(new DOMRect(20, 0, 10, 50), "center");
group.addSeparator(new DOMRect(30, 0, 10, 50), "right");
group.addPanel(new DOMRect(40, 0, 10, 50), "right");
const group = mockGroup(new DOMRect(0, 0, 140, 50));
group.addPanel(new DOMRect(0, 0, 40, 50), "left");
group.addSeparator(new DOMRect(40, 0, 10, 50), "left");
group.addPanel(new DOMRect(50, 0, 40, 50), "center");
group.addSeparator(new DOMRect(90, 0, 10, 50), "right");
group.addPanel(new DOMRect(100, 0, 40, 50), "right");

expect(serialize(group)).toMatchInlineSnapshot(`
"[
Expand All @@ -88,27 +88,27 @@ describe("calculateHitRegions", () => {
"group-1-left",
"group-1-center"
],
"rect": "10,0 10 x 50",
"rect": "31.5,0 27 x 50",
"separator": "group-1-left"
},
{
"panels": [
"group-1-center",
"group-1-right"
],
"rect": "30,0 10 x 50",
"rect": "81.5,0 27 x 50",
"separator": "group-1-right"
}
]"
`);
});

test("panels and some explicit separators", () => {
const group = mockGroup(new DOMRect(0, 0, 60, 50));
group.addPanel(new DOMRect(0, 0, 20, 50), "a");
group.addPanel(new DOMRect(20, 0, 20, 50), "b");
group.addSeparator(new DOMRect(40, 0, 5, 50), "separator");
group.addPanel(new DOMRect(40, 0, 40, 50), "c");
const group = mockGroup(new DOMRect(0, 0, 125, 50));
group.addPanel(new DOMRect(0, 0, 40, 50), "a");
group.addPanel(new DOMRect(40, 0, 40, 50), "b");
group.addSeparator(new DOMRect(80, 0, 5, 50), "separator");
group.addPanel(new DOMRect(85, 0, 40, 50), "c");

expect(serialize(group)).toMatchInlineSnapshot(`
"[
Expand All @@ -117,29 +117,29 @@ describe("calculateHitRegions", () => {
"group-1-a",
"group-1-b"
],
"rect": "20,0 0 x 50"
"rect": "26.5,0 27 x 50"
},
{
"panels": [
"group-1-b",
"group-1-c"
],
"rect": "40,0 5 x 50",
"rect": "69,0 27 x 50",
"separator": "group-1-separator"
}
]"
`);
});

test("mixed panels and non-panel children", () => {
const group = mockGroup(new DOMRect(0, 0, 70, 50));
const group = mockGroup(new DOMRect(0, 0, 230, 50));
group.addHTMLElement(new DOMRect(0, 0, 10, 50));
group.addPanel(new DOMRect(10, 0, 10, 50), "a");
group.addPanel(new DOMRect(20, 0, 10, 50), "b");
group.addHTMLElement(new DOMRect(30, 0, 10, 50));
group.addPanel(new DOMRect(40, 0, 10, 50), "c");
group.addPanel(new DOMRect(50, 0, 10, 50), "d");
group.addHTMLElement(new DOMRect(60, 0, 10, 50));
group.addPanel(new DOMRect(10, 0, 50, 50), "a");
group.addPanel(new DOMRect(60, 0, 50, 50), "b");
group.addHTMLElement(new DOMRect(110, 0, 10, 50));
group.addPanel(new DOMRect(120, 0, 50, 50), "c");
group.addPanel(new DOMRect(170, 0, 50, 50), "d");
group.addHTMLElement(new DOMRect(220, 0, 10, 50));

expect(serialize(group)).toMatchInlineSnapshot(`
"[
Expand All @@ -148,38 +148,38 @@ describe("calculateHitRegions", () => {
"group-1-a",
"group-1-b"
],
"rect": "20,0 0 x 50"
"rect": "46.5,0 27 x 50"
},
{
"panels": [
"group-1-b",
"group-1-c"
],
"rect": "30,0 0 x 50"
"rect": "96.5,0 27 x 50"
},
{
"panels": [
"group-1-b",
"group-1-c"
],
"rect": "40,0 0 x 50"
"rect": "106.5,0 27 x 50"
},
{
"panels": [
"group-1-c",
"group-1-d"
],
"rect": "50,0 0 x 50"
"rect": "156.5,0 27 x 50"
}
]"
`);
});

test("CSS styles (e.g. padding and flex gap)", () => {
const group = mockGroup(new DOMRect(0, 0, 50, 50));
group.addPanel(new DOMRect(5, 5, 10, 40), "left");
group.addPanel(new DOMRect(20, 5, 10, 40), "center");
group.addPanel(new DOMRect(35, 5, 10, 40), "right");
const group = mockGroup(new DOMRect(0, 0, 155, 50));
group.addPanel(new DOMRect(5, 5, 45, 40), "left");
group.addPanel(new DOMRect(55, 5, 45, 40), "center");
group.addPanel(new DOMRect(105, 5, 45, 40), "right");

expect(serialize(group)).toMatchInlineSnapshot(`
"[
Expand All @@ -188,26 +188,26 @@ describe("calculateHitRegions", () => {
"group-1-left",
"group-1-center"
],
"rect": "15,5 5 x 40"
"rect": "39,5 27 x 40"
},
{
"panels": [
"group-1-center",
"group-1-right"
],
"rect": "30,5 5 x 40"
"rect": "89,5 27 x 40"
}
]"
`);
});

test("out of order children (e.g. dynamic rendering)", () => {
const group = mockGroup(new DOMRect(0, 0, 30, 50));
group.addPanel(new DOMRect(0, 0, 10, 50), "left");
group.addPanel(new DOMRect(20, 0, 10, 50), "right");
const group = mockGroup(new DOMRect(0, 0, 150, 50));
group.addPanel(new DOMRect(0, 0, 50, 50), "left");
group.addPanel(new DOMRect(100, 0, 50, 50), "right");

// Simulate conditionally rendering a new middle panel
group.addPanel(new DOMRect(10, 0, 10, 50), "center");
group.addPanel(new DOMRect(50, 0, 50, 50), "center");

expect(serialize(group)).toMatchInlineSnapshot(`
"[
Expand All @@ -216,28 +216,28 @@ describe("calculateHitRegions", () => {
"group-1-left",
"group-1-center"
],
"rect": "10,0 0 x 50"
"rect": "36.5,0 27 x 50"
},
{
"panels": [
"group-1-center",
"group-1-right"
],
"rect": "20,0 0 x 50"
"rect": "86.5,0 27 x 50"
}
]"
`);
});

// Test covers conditionally rendered panels and separators
test("should sort elements and separators by offset", () => {
const group = mockGroup(new DOMRect(0, 0, 50, 50));
group.addPanel(new DOMRect(40, 0, 10, 50), "d");
group.addPanel(new DOMRect(15, 0, 10, 50), "b");
group.addPanel(new DOMRect(0, 0, 10, 50), "a");
group.addPanel(new DOMRect(25, 0, 10, 50), "c");
group.addSeparator(new DOMRect(35, 0, 5, 50), "right");
group.addSeparator(new DOMRect(10, 0, 5, 50), "left");
const group = mockGroup(new DOMRect(0, 0, 270, 50));
group.addPanel(new DOMRect(205, 0, 65, 50), "d");
group.addPanel(new DOMRect(70, 0, 65, 50), "b");
group.addPanel(new DOMRect(0, 0, 65, 50), "a");
group.addPanel(new DOMRect(135, 0, 65, 50), "c");
group.addSeparator(new DOMRect(200, 0, 5, 50), "right");
group.addSeparator(new DOMRect(65, 0, 5, 50), "left");

expect(serialize(group)).toMatchInlineSnapshot(`
"[
Expand All @@ -246,22 +246,22 @@ describe("calculateHitRegions", () => {
"group-1-a",
"group-1-b"
],
"rect": "10,0 5 x 50",
"rect": "54,0 27 x 50",
"separator": "group-1-left"
},
{
"panels": [
"group-1-b",
"group-1-c"
],
"rect": "25,0 0 x 50"
"rect": "121.5,0 27 x 50"
},
{
"panels": [
"group-1-c",
"group-1-d"
],
"rect": "35,0 5 x 50",
"rect": "189,0 27 x 50",
"separator": "group-1-right"
}
]"
Expand Down
Loading
Loading