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

## 4.5.2

- [626](https://github.com/bvaughn/react-resizable-panels/pull/626): Decrease default hit target size for `Separator` and `Panel` edges; make configurable via a new `Group` prop.

## 4.5.1

- [624](https://github.com/bvaughn/react-resizable-panels/pull/624): **Bugfix**: Fallback to alternate CSS cursor styles for Safari
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,19 @@ For most cases, it is recommended to use the <code>onLayoutChanged</code> callba
<td><p>Called after the Group&#39;s layout has been changed.</p>
<p>ℹ️ For layout changes caused by pointer events, this method is not called until the pointer has been released.
This method is recommended when saving layouts to some storage api.</p>
</td>
</tr>
<tr>
<td>resizeTargetMinimumSize</td>
<td><p>Minimum size of the resizable hit target area (either <code>Separator</code> or <code>Panel</code> edge)
This threshold ensures are large enough to avoid mis-clicks.</p>
<ul>
<li>Coarse inputs (typically a finger on a touchscreen) have reduced accuracy;
to ensure accessibility and ease of use, hit targets should be larger to prevent mis-clicks.</li>
<li>Fine inputs (typically a mouse) can be smaller</li>
</ul>
<p>ℹ️ <a href="https://developer.apple.com/design/human-interface-guidelines/accessibility">Apple interface guidelines</a> suggest <code>20pt</code> (<code>27px</code>) on desktops and <code>28pt</code> (<code>37px</code>) for touch devices
In practice this seems to be much larger than many of their own applications use though.</p>
</td>
</tr>
<tr>
Expand Down
14 changes: 13 additions & 1 deletion lib/components/group/Group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import type { RegisteredPanel } from "../panel/types";
import type { RegisteredSeparator } from "../separator/types";
import { GroupContext } from "./GroupContext";
import { sortByElementOffset } from "./sortByElementOffset";
import type { GroupProps, Layout, RegisteredGroup } from "./types";
import type {
GroupProps,
Layout,
RegisteredGroup,
ResizeTargetMinimumSize
} from "./types";
import { useGroupImperativeHandle } from "./useGroupImperativeHandle";

/**
Expand All @@ -41,6 +46,10 @@ export function Group({
onLayoutChange: onLayoutChangeUnstable,
onLayoutChanged: onLayoutChangedUnstable,
orientation = "horizontal",
resizeTargetMinimumSize = {
coarse: 20,
fine: 10
},
style,
...rest
}: GroupProps) {
Expand Down Expand Up @@ -82,11 +91,13 @@ export function Group({
lastExpandedPanelSizes: { [panelIds: string]: number };
layouts: { [panelIds: string]: Layout };
panels: RegisteredPanel[];
resizeTargetMinimumSize: ResizeTargetMinimumSize;
separators: RegisteredSeparator[];
}>({
lastExpandedPanelSizes: {},
layouts: {},
panels: [],
resizeTargetMinimumSize,
separators: []
});

Expand Down Expand Up @@ -199,6 +210,7 @@ export function Group({
inMemoryLayouts: inMemoryValuesRef.current.layouts,
orientation,
panels: inMemoryValues.panels,
resizeTargetMinimumSize: inMemoryValues.resizeTargetMinimumSize,
separators: inMemoryValues.separators
};

Expand Down
22 changes: 22 additions & 0 deletions lib/components/group/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export type DragState = {
separatorId: string | undefined;
};

export type ResizeTargetMinimumSize = {
coarse: number;
fine: number;
};

export type RegisteredGroup = {
defaultLayout: Layout | undefined;
disableCursor: boolean;
Expand All @@ -38,6 +43,7 @@ export type RegisteredGroup = {
};
orientation: Orientation;
panels: RegisteredPanel[];
resizeTargetMinimumSize: ResizeTargetMinimumSize;
separators: RegisteredSeparator[];
};

Expand Down Expand Up @@ -140,6 +146,22 @@ export type GroupProps = HTMLAttributes<HTMLDivElement> & {
*/
onLayoutChanged?: (layout: Layout) => void | undefined;

/**
* Minimum size of the resizable hit target area (either `Separator` or `Panel` edge)
* This threshold ensures are large enough to avoid mis-clicks.
*
* - Coarse inputs (typically a finger on a touchscreen) have reduced accuracy;
* to ensure accessibility and ease of use, hit targets should be larger to prevent mis-clicks.
* - Fine inputs (typically a mouse) can be smaller
*
* ℹ️ [Apple interface guidelines](https://developer.apple.com/design/human-interface-guidelines/accessibility) suggest `20pt` (`27px`) on desktops and `28pt` (`37px`) for touch devices
* In practice this seems to be much larger than many of their own applications use though.
*/
resizeTargetMinimumSize?: {
coarse: number;
fine: number;
};

/**
* Specifies the resizable orientation ("horizontal" or "vertical"); defaults to "horizontal"
*/
Expand Down
58 changes: 29 additions & 29 deletions lib/global/dom/calculateHitRegions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe("calculateHitRegions", () => {
"group-1-left",
"group-1-right"
],
"rect": "36.5,0 27 x 50"
"rect": "45,0 10 x 50"
}
]"
`);
Expand All @@ -60,14 +60,14 @@ describe("calculateHitRegions", () => {
"group-1-left",
"group-1-center"
],
"rect": "26.5,0 27 x 50"
"rect": "35,0 10 x 50"
},
{
"panels": [
"group-1-center",
"group-1-right"
],
"rect": "66.5,0 27 x 50"
"rect": "75,0 10 x 50"
}
]"
`);
Expand All @@ -88,15 +88,15 @@ describe("calculateHitRegions", () => {
"group-1-left",
"group-1-center"
],
"rect": "31.5,0 27 x 50",
"rect": "40,0 10 x 50",
"separator": "group-1-left"
},
{
"panels": [
"group-1-center",
"group-1-right"
],
"rect": "81.5,0 27 x 50",
"rect": "90,0 10 x 50",
"separator": "group-1-right"
}
]"
Expand All @@ -117,14 +117,14 @@ describe("calculateHitRegions", () => {
"group-1-a",
"group-1-b"
],
"rect": "26.5,0 27 x 50"
"rect": "35,0 10 x 50"
},
{
"panels": [
"group-1-b",
"group-1-c"
],
"rect": "69,0 27 x 50",
"rect": "77.5,0 10 x 50",
"separator": "group-1-separator"
}
]"
Expand All @@ -148,38 +148,38 @@ describe("calculateHitRegions", () => {
"group-1-a",
"group-1-b"
],
"rect": "46.5,0 27 x 50"
"rect": "55,0 10 x 50"
},
{
"panels": [
"group-1-b",
"group-1-c"
],
"rect": "96.5,0 27 x 50"
"rect": "105,0 10 x 50"
},
{
"panels": [
"group-1-b",
"group-1-c"
],
"rect": "106.5,0 27 x 50"
"rect": "115,0 10 x 50"
},
{
"panels": [
"group-1-c",
"group-1-d"
],
"rect": "156.5,0 27 x 50"
"rect": "165,0 10 x 50"
}
]"
`);
});

test("CSS styles (e.g. padding and flex gap)", () => {
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");
const group = mockGroup(new DOMRect(0, 0, 190, 70));
group.addPanel(new DOMRect(10, 10, 50, 40), "left");
group.addPanel(new DOMRect(70, 10, 50, 40), "center");
group.addPanel(new DOMRect(130, 10, 50, 40), "right");

expect(serialize(group)).toMatchInlineSnapshot(`
"[
Expand All @@ -188,14 +188,14 @@ describe("calculateHitRegions", () => {
"group-1-left",
"group-1-center"
],
"rect": "39,5 27 x 40"
"rect": "60,10 10 x 40"
},
{
"panels": [
"group-1-center",
"group-1-right"
],
"rect": "89,5 27 x 40"
"rect": "120,10 10 x 40"
}
]"
`);
Expand All @@ -216,28 +216,28 @@ describe("calculateHitRegions", () => {
"group-1-left",
"group-1-center"
],
"rect": "36.5,0 27 x 50"
"rect": "45,0 10 x 50"
},
{
"panels": [
"group-1-center",
"group-1-right"
],
"rect": "86.5,0 27 x 50"
"rect": "95,0 10 x 50"
}
]"
`);
});

// Test covers conditionally rendered panels and separators
test("should sort elements and separators by offset", () => {
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");
const group = mockGroup(new DOMRect(0, 0, 260, 50));
group.addPanel(new DOMRect(200, 0, 60, 50), "d");
group.addPanel(new DOMRect(70, 0, 60, 50), "b");
group.addPanel(new DOMRect(0, 0, 60, 50), "a");
group.addPanel(new DOMRect(130, 0, 60, 50), "c");
group.addSeparator(new DOMRect(190, 0, 10, 50), "right");
group.addSeparator(new DOMRect(60, 0, 10, 50), "left");

expect(serialize(group)).toMatchInlineSnapshot(`
"[
Expand All @@ -246,22 +246,22 @@ describe("calculateHitRegions", () => {
"group-1-a",
"group-1-b"
],
"rect": "54,0 27 x 50",
"rect": "60,0 10 x 50",
"separator": "group-1-left"
},
{
"panels": [
"group-1-b",
"group-1-c"
],
"rect": "121.5,0 27 x 50"
"rect": "125,0 10 x 50"
},
{
"panels": [
"group-1-c",
"group-1-d"
],
"rect": "189,0 27 x 50",
"rect": "190,0 10 x 50",
"separator": "group-1-right"
}
]"
Expand Down
7 changes: 3 additions & 4 deletions lib/global/dom/calculateHitRegions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,9 @@ export function calculateHitRegions(group: RegisteredGroup) {
? rectOrSeparator
: rectOrSeparator.element.getBoundingClientRect();

// Ensure that Separators or Panel "edges" have large enough hit areas to be interacted with easily
// Apple interface guidelines suggest 20pt (27) on desktops and 28pt (37px) for touch devices
// https://developer.apple.com/design/human-interface-guidelines/accessibility
const minHitTargetSize = isCoarsePointer() ? 37 : 27;
const minHitTargetSize = isCoarsePointer()
? group.resizeTargetMinimumSize.coarse
: group.resizeTargetMinimumSize.fine;
if (rect.width < minHitTargetSize) {
const delta = minHitTargetSize - rect.width;
rect = new DOMRect(
Expand Down
11 changes: 0 additions & 11 deletions lib/global/event-handlers/onDocumentDoubleClick.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { RegisteredGroup } from "../../components/group/types";
import type { RegisteredPanel } from "../../components/panel/types";
import { read } from "../mutableState";
import { findMatchingHitRegions } from "../utils/findMatchingHitRegions";
import { getImperativePanelMethods } from "../utils/getImperativePanelMethods";
Expand All @@ -12,16 +10,7 @@ export function onDocumentDoubleClick(event: MouseEvent) {
const { mountedGroups } = read();

const hitRegions = findMatchingHitRegions(event, mountedGroups);

const groups = new Set<RegisteredGroup>();
const panels = new Set<RegisteredPanel>();

hitRegions.forEach((current) => {
groups.add(current.group);
current.panels.forEach((panel) => {
panels.add(panel);
});

if (current.separator) {
const panelWithDefaultSize = current.panels.find(
(panel) => panel.panelConstraints.defaultSize !== undefined
Expand Down
4 changes: 4 additions & 0 deletions lib/global/test/mockGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ export function mockGroup(
inMemoryLastExpandedPanelSizes: {},
inMemoryLayouts: {},
orientation,
resizeTargetMinimumSize: {
coarse: 20,
fine: 10
},

get panels() {
return Array.from(mockPanels.values());
Expand Down
8 changes: 4 additions & 4 deletions lib/global/utils/findMatchingHitRegions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe("findMatchingHitRegions", () => {
"group-1-left",
"group-1-right"
],
"rect": "36.5,0 27 x 50"
"rect": "45,0 10 x 50"
}
]"
`);
Expand All @@ -63,7 +63,7 @@ describe("findMatchingHitRegions", () => {
"group-1-left",
"group-1-right"
],
"rect": "46.5,0 27 x 50",
"rect": "50,0 20 x 50",
"separator": "group-1-separator"
}
]"
Expand Down Expand Up @@ -93,14 +93,14 @@ describe("findMatchingHitRegions", () => {
"group-1-left",
"group-1-right"
],
"rect": "36.5,0 27 x 50"
"rect": "45,0 10 x 50"
},
{
"panels": [
"group-2-top",
"group-2-bottom"
],
"rect": "0,11.5 50 x 27"
"rect": "0,20 50 x 10"
}
]"
`);
Expand Down
Loading
Loading