();
+
+ hitRegions.forEach((current) => {
+ groups.add(current.group);
+ current.panels.forEach((panel) => {
+ panels.add(panel);
+ });
+
+ if (current.separator) {
+ const primaryPanel = current.panels[0];
+ if (primaryPanel.panelConstraints.defaultSize !== undefined) {
+ const api = getImperativePanelMethods({
+ groupId: current.group.id,
+ panelId: primaryPanel.id
+ });
+ if (api) {
+ api.resize(primaryPanel.panelConstraints.defaultSize);
+
+ event.preventDefault();
+ }
+ }
+ }
+ });
+}
diff --git a/lib/global/mountGroup.ts b/lib/global/mountGroup.ts
index a09f6fc12..d16fb8075 100644
--- a/lib/global/mountGroup.ts
+++ b/lib/global/mountGroup.ts
@@ -3,6 +3,7 @@ import { assert } from "../utils/assert";
import { calculateAvailableGroupSize } from "./dom/calculateAvailableGroupSize";
import { calculateHitRegions } from "./dom/calculateHitRegions";
import { calculatePanelConstraints } from "./dom/calculatePanelConstraints";
+import { onDocumentDoubleClick } from "./event-handlers/onDocumentDoubleClick";
import { onDocumentKeyDown } from "./event-handlers/onDocumentKeyDown";
import { onDocumentPointerDown } from "./event-handlers/onDocumentPointerDown";
import { onDocumentPointerLeave } from "./event-handlers/onDocumentPointerLeave";
@@ -170,6 +171,7 @@ export function mountGroup(group: RegisteredGroup) {
// If this is the first group to be mounted, initialize event handlers
if (ownerDocumentReferenceCounts.get(ownerDocument) === 1) {
+ ownerDocument.addEventListener("dblclick", onDocumentDoubleClick, true);
ownerDocument.addEventListener("pointerdown", onDocumentPointerDown, true);
ownerDocument.addEventListener("pointerleave", onDocumentPointerLeave);
ownerDocument.addEventListener("pointermove", onDocumentPointerMove);
@@ -197,6 +199,11 @@ export function mountGroup(group: RegisteredGroup) {
// If this was the last group to be mounted, tear down event handlers
if (!ownerDocumentReferenceCounts.get(ownerDocument)) {
+ ownerDocument.removeEventListener(
+ "dblclick",
+ onDocumentDoubleClick,
+ true
+ );
ownerDocument.removeEventListener(
"pointerdown",
onDocumentPointerDown,
diff --git a/lib/global/utils/findClosetHitRegion.ts b/lib/global/utils/findClosestHitRegion.ts
similarity index 96%
rename from lib/global/utils/findClosetHitRegion.ts
rename to lib/global/utils/findClosestHitRegion.ts
index 720691892..5ec66196b 100644
--- a/lib/global/utils/findClosetHitRegion.ts
+++ b/lib/global/utils/findClosestHitRegion.ts
@@ -3,7 +3,7 @@ import type { Point } from "../../types";
import type { HitRegion } from "../dom/calculateHitRegions";
import { getDistanceBetweenPointAndRect } from "./getDistanceBetweenPointAndRect";
-export function findClosetHitRegion(
+export function findClosestHitRegion(
orientation: Orientation,
hitRegions: HitRegion[],
point: Point
diff --git a/lib/global/utils/findMatchingHitRegions.test.ts b/lib/global/utils/findMatchingHitRegions.test.ts
index 8edaa1a9f..24c46a3ff 100644
--- a/lib/global/utils/findMatchingHitRegions.test.ts
+++ b/lib/global/utils/findMatchingHitRegions.test.ts
@@ -12,7 +12,8 @@ describe("findMatchingHitRegions", () => {
return JSON.stringify(
hitRegions.map((region) => ({
panels: region.panels.map((panel) => panel.id),
- rect: `${region.rect.x},${region.rect.y} ${region.rect.width} x ${region.rect.height}`
+ rect: `${region.rect.x},${region.rect.y} ${region.rect.width} x ${region.rect.height}`,
+ separator: region.separator?.id
})),
null,
2
@@ -27,13 +28,13 @@ describe("findMatchingHitRegions", () => {
).toMatchInlineSnapshot(`"[]"`);
});
- test("group", () => {
- 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");
+ test("group with no separator", () => {
+ 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");
mountGroup(group);
- expect(serialize(mockPointerEvent({ clientX: 10 }), read().mountedGroups))
+ expect(serialize(mockPointerEvent({ clientX: 50 }), read().mountedGroups))
.toMatchInlineSnapshot(`
"[
{
@@ -41,26 +42,48 @@ describe("findMatchingHitRegions", () => {
"group-1-left",
"group-1-right"
],
- "rect": "10,0 0 x 50"
+ "rect": "36.5,0 27 x 50"
+ }
+ ]"
+ `);
+ });
+
+ test("group with separator", () => {
+ const group = mockGroup(new DOMRect(0, 0, 1200, 50));
+ group.addPanel(new DOMRect(0, 0, 50, 50), "left");
+ group.addSeparator(new DOMRect(50, 0, 20, 50), "separator");
+ group.addPanel(new DOMRect(70, 0, 50, 50), "right");
+ mountGroup(group);
+
+ expect(serialize(mockPointerEvent({ clientX: 60 }), read().mountedGroups))
+ .toMatchInlineSnapshot(`
+ "[
+ {
+ "panels": [
+ "group-1-left",
+ "group-1-right"
+ ],
+ "rect": "46.5,0 27 x 50",
+ "separator": "group-1-separator"
}
]"
`);
});
test("nested groups", () => {
- const outerGroup = mockGroup(new DOMRect(0, 0, 20, 50));
- outerGroup.addPanel(new DOMRect(0, 0, 10, 50), "left");
- outerGroup.addPanel(new DOMRect(10, 0, 10, 50), "right");
+ const outerGroup = mockGroup(new DOMRect(0, 0, 100, 50));
+ outerGroup.addPanel(new DOMRect(0, 0, 50, 50), "left");
+ outerGroup.addPanel(new DOMRect(50, 0, 50, 50), "right");
mountGroup(outerGroup);
- const innerGroup = mockGroup(new DOMRect(0, 0, 10, 50), "vertical");
- innerGroup.addPanel(new DOMRect(0, 0, 10, 25), "top");
- innerGroup.addPanel(new DOMRect(0, 25, 10, 25), "bottom");
+ const innerGroup = mockGroup(new DOMRect(0, 0, 50, 50), "vertical");
+ innerGroup.addPanel(new DOMRect(0, 0, 50, 25), "top");
+ innerGroup.addPanel(new DOMRect(0, 25, 50, 25), "bottom");
mountGroup(innerGroup);
expect(
serialize(
- mockPointerEvent({ clientX: 10, clientY: 25 }),
+ mockPointerEvent({ clientX: 50, clientY: 25 }),
read().mountedGroups
)
).toMatchInlineSnapshot(`
@@ -70,27 +93,27 @@ describe("findMatchingHitRegions", () => {
"group-1-left",
"group-1-right"
],
- "rect": "10,0 0 x 50"
+ "rect": "36.5,0 27 x 50"
},
{
"panels": [
"group-2-top",
"group-2-bottom"
],
- "rect": "0,25 10 x 0"
+ "rect": "0,11.5 50 x 27"
}
]"
`);
});
test("should skip disabled groups", () => {
- const group = mockGroup(new DOMRect(0, 0, 20, 50));
+ 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");
group.disabled = true;
- group.addPanel(new DOMRect(0, 0, 10, 50), "left");
- group.addPanel(new DOMRect(10, 0, 10, 50), "right");
mountGroup(group);
- expect(serialize(mockPointerEvent({ clientX: 10 }), read().mountedGroups))
+ expect(serialize(mockPointerEvent({ clientX: 50 }), read().mountedGroups))
.toMatchInlineSnapshot(`
"[]"
`);
diff --git a/lib/global/utils/findMatchingHitRegions.ts b/lib/global/utils/findMatchingHitRegions.ts
index ea10d3b52..159dfcf9e 100644
--- a/lib/global/utils/findMatchingHitRegions.ts
+++ b/lib/global/utils/findMatchingHitRegions.ts
@@ -1,15 +1,17 @@
-import { DEFAULT_POINTER_PRECISION } from "../../constants";
-import type { MountedGroupMap } from "../mutableState";
import {
calculateHitRegions,
type HitRegion
} from "../dom/calculateHitRegions";
-import { findClosetHitRegion } from "./findClosetHitRegion";
-import { isCoarsePointer } from "./isCoarsePointer";
+import type { MountedGroupMap } from "../mutableState";
+import { findClosestHitRegion } from "./findClosestHitRegion";
import { isViableHitTarget } from "./isViableHitTarget";
export function findMatchingHitRegions(
- event: PointerEvent,
+ event: {
+ clientX: number;
+ clientY: number;
+ target: EventTarget | null;
+ },
mountedGroups: MountedGroupMap
): HitRegion[] {
const matchingHitRegions: HitRegion[] = [];
@@ -19,20 +21,15 @@ export function findMatchingHitRegions(
return;
}
- const maxDistance = isCoarsePointer()
- ? DEFAULT_POINTER_PRECISION.coarse
- : DEFAULT_POINTER_PRECISION.precise;
-
const hitRegions = calculateHitRegions(groupData);
- const match = findClosetHitRegion(groupData.orientation, hitRegions, {
+ const match = findClosestHitRegion(groupData.orientation, hitRegions, {
x: event.clientX,
y: event.clientY
});
-
if (
match &&
- match.distance.x <= maxDistance &&
- match.distance.y <= maxDistance &&
+ match.distance.x <= 0 &&
+ match.distance.y <= 0 &&
isViableHitTarget({
groupElement: groupData.element,
hitRegion: match.hitRegion.rect,
diff --git a/lib/global/utils/isCoarsePointer.ts b/lib/global/utils/isCoarsePointer.ts
index a495ab693..5b4bd5191 100644
--- a/lib/global/utils/isCoarsePointer.ts
+++ b/lib/global/utils/isCoarsePointer.ts
@@ -1,5 +1,8 @@
let cached: boolean | undefined = undefined;
+/**
+ * Caches and returns matchMedia()'s computed value for "pointer:coarse"
+ */
export function isCoarsePointer(): boolean {
if (cached === undefined) {
if (typeof matchMedia === "function") {
diff --git a/lib/index.ts b/lib/index.ts
index a50c6f476..7ee713e6c 100644
--- a/lib/index.ts
+++ b/lib/index.ts
@@ -7,6 +7,8 @@ export { usePanelCallbackRef } from "./components/panel/usePanelCallbackRef";
export { usePanelRef } from "./components/panel/usePanelRef";
export { Separator } from "./components/separator/Separator";
+export { isCoarsePointer } from "./global/utils/isCoarsePointer";
+
export type {
GroupImperativeHandle,
GroupProps,
diff --git a/public/generated/examples/LayoutBasicsSeparator.json b/public/generated/examples/LayoutBasicsSeparator.json
index bc497c803..24a645049 100644
--- a/public/generated/examples/LayoutBasicsSeparator.json
+++ b/public/generated/examples/LayoutBasicsSeparator.json
@@ -1,3 +1,3 @@
{
- "html": "<Group>
\n <Panel>left</Panel>
\n <Separator />
\n <Panel>right</Panel>
\n</Group>
"
+ "html": "<Group>
\n <Panel defaultSize=\"50%\">left</Panel>
\n <Separator />
\n <Panel>right</Panel>
\n</Group>
"
}
\ No newline at end of file
diff --git a/src/routes/LayoutBasicsRoute.tsx b/src/routes/LayoutBasicsRoute.tsx
index 18a98c974..c8b0e539f 100644
--- a/src/routes/LayoutBasicsRoute.tsx
+++ b/src/routes/LayoutBasicsRoute.tsx
@@ -42,10 +42,11 @@ export default function LayoutBasicsRoute() {
Panels can be resized by clicking on their borders but explicit
- separators can be rendered to improve UX.
+ separators can be rendered to improve UX. Separators provide another
+ benefit: double-clicking on one resets a panel to its default size.
- left
+ left
right
diff --git a/src/routes/examples/LayoutBasicsSeparator.tsx b/src/routes/examples/LayoutBasicsSeparator.tsx
index 89b1919be..a557c4126 100644
--- a/src/routes/examples/LayoutBasicsSeparator.tsx
+++ b/src/routes/examples/LayoutBasicsSeparator.tsx
@@ -4,7 +4,7 @@ import { Group, Panel, Separator } from "react-resizable-panels";
/* prettier-ignore */
- left
+ left
right
diff --git a/vitest.setup.ts b/vitest.setup.ts
index d726517f7..b16b61ae7 100644
--- a/vitest.setup.ts
+++ b/vitest.setup.ts
@@ -63,7 +63,14 @@ beforeAll(() => {
});
vi.spyOn(console, "warn").mockImplementation(() => {
- throw Error("Unexpectec console warning");
+ throw Error("Unexpected console warning");
+ });
+
+ Object.defineProperty(Document.prototype, "adoptedStyleSheets", {
+ value: []
+ });
+ Object.defineProperty(ShadowRoot.prototype, "adoptedStyleSheets", {
+ value: []
});
});