react-resizable-panels 0.0.14 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -0
- package/README.md +7 -9
- package/dist/react-resizable-panels.d.ts +2 -4
- package/dist/react-resizable-panels.d.ts.map +1 -1
- package/dist/react-resizable-panels.js +67 -71
- package/dist/react-resizable-panels.js.map +1 -1
- package/dist/react-resizable-panels.module.js +67 -71
- package/dist/react-resizable-panels.module.js.map +1 -1
- package/package.json +1 -1
- package/src/Panel.tsx +18 -8
- package/src/PanelGroup.tsx +55 -49
- package/src/constants.ts +1 -1
- package/src/hooks/useWindowSplitterBehavior.ts +8 -5
- package/src/utils/coordinates.ts +6 -3
- package/src/utils/group.ts +22 -42
- package/src/utils/serialization.ts +5 -2
package/src/PanelGroup.tsx
CHANGED
|
@@ -15,9 +15,9 @@ import { loadPanelLayout, savePanelGroupLayout } from "./utils/serialization";
|
|
|
15
15
|
import { getDragOffset, getMovement } from "./utils/coordinates";
|
|
16
16
|
import {
|
|
17
17
|
adjustByDelta,
|
|
18
|
-
|
|
18
|
+
getFlexGrow,
|
|
19
|
+
getPanelGroup,
|
|
19
20
|
getResizeHandlePanelIds,
|
|
20
|
-
getSize,
|
|
21
21
|
panelsMapToSortedArray,
|
|
22
22
|
} from "./utils/group";
|
|
23
23
|
import { useWindowSplitterPanelGroupBehavior } from "./hooks/useWindowSplitterBehavior";
|
|
@@ -25,10 +25,8 @@ import useUniqueId from "./hooks/useUniqueId";
|
|
|
25
25
|
|
|
26
26
|
export type CommittedValues = {
|
|
27
27
|
direction: Direction;
|
|
28
|
-
height: number;
|
|
29
28
|
panels: Map<string, PanelData>;
|
|
30
29
|
sizes: number[];
|
|
31
|
-
width: number;
|
|
32
30
|
};
|
|
33
31
|
|
|
34
32
|
export type PanelDataMap = Map<string, PanelData>;
|
|
@@ -38,9 +36,7 @@ type Props = {
|
|
|
38
36
|
children?: ReactNode;
|
|
39
37
|
className?: string;
|
|
40
38
|
direction: Direction;
|
|
41
|
-
height: number;
|
|
42
39
|
id?: string | null;
|
|
43
|
-
width: number;
|
|
44
40
|
};
|
|
45
41
|
|
|
46
42
|
// TODO [panels]
|
|
@@ -52,9 +48,7 @@ export default function PanelGroup({
|
|
|
52
48
|
children = null,
|
|
53
49
|
className = "",
|
|
54
50
|
direction,
|
|
55
|
-
height,
|
|
56
51
|
id: idFromProps = null,
|
|
57
|
-
width,
|
|
58
52
|
}: Props) {
|
|
59
53
|
const groupId = useUniqueId(idFromProps);
|
|
60
54
|
|
|
@@ -69,18 +63,14 @@ export default function PanelGroup({
|
|
|
69
63
|
// Store committed values to avoid unnecessarily re-running memoization/effects functions.
|
|
70
64
|
const committedValuesRef = useRef<CommittedValues>({
|
|
71
65
|
direction,
|
|
72
|
-
height,
|
|
73
66
|
panels,
|
|
74
67
|
sizes,
|
|
75
|
-
width,
|
|
76
68
|
});
|
|
77
69
|
|
|
78
70
|
useLayoutEffect(() => {
|
|
79
71
|
committedValuesRef.current.direction = direction;
|
|
80
|
-
committedValuesRef.current.height = height;
|
|
81
72
|
committedValuesRef.current.panels = panels;
|
|
82
73
|
committedValuesRef.current.sizes = sizes;
|
|
83
|
-
committedValuesRef.current.width = width;
|
|
84
74
|
});
|
|
85
75
|
|
|
86
76
|
useWindowSplitterPanelGroupBehavior({
|
|
@@ -100,9 +90,6 @@ export default function PanelGroup({
|
|
|
100
90
|
return;
|
|
101
91
|
}
|
|
102
92
|
|
|
103
|
-
// TODO [panels]
|
|
104
|
-
// Validate that the total minSize is <= 1.
|
|
105
|
-
|
|
106
93
|
// If this panel has been configured to persist sizing information,
|
|
107
94
|
// default size should be restored from local storage if possible.
|
|
108
95
|
let defaultSizes: number[] | undefined = undefined;
|
|
@@ -115,11 +102,40 @@ export default function PanelGroup({
|
|
|
115
102
|
setSizes(defaultSizes);
|
|
116
103
|
} else {
|
|
117
104
|
const panelsArray = panelsMapToSortedArray(panels);
|
|
118
|
-
const totalWeight = panelsArray.reduce((weight, panel) => {
|
|
119
|
-
return weight + panel.defaultSize;
|
|
120
|
-
}, 0);
|
|
121
105
|
|
|
122
|
-
|
|
106
|
+
let panelsWithNullDefaultSize = 0;
|
|
107
|
+
let totalDefaultSize = 0;
|
|
108
|
+
let totalMinSize = 0;
|
|
109
|
+
|
|
110
|
+
panelsArray.forEach((panel) => {
|
|
111
|
+
totalMinSize += panel.minSize;
|
|
112
|
+
|
|
113
|
+
if (panel.defaultSize === null) {
|
|
114
|
+
panelsWithNullDefaultSize++;
|
|
115
|
+
} else {
|
|
116
|
+
totalDefaultSize += panel.defaultSize;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (totalDefaultSize > 100) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`The sum of the defaultSize of all panels in a group cannot exceed 100.`
|
|
123
|
+
);
|
|
124
|
+
} else if (totalMinSize > 100) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`The sum of the minSize of all panels in a group cannot exceed 100.`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
setSizes(
|
|
131
|
+
panelsArray.map((panel) => {
|
|
132
|
+
if (panel.defaultSize === null) {
|
|
133
|
+
return (100 - totalDefaultSize) / panelsWithNullDefaultSize;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return panel.defaultSize;
|
|
137
|
+
})
|
|
138
|
+
);
|
|
123
139
|
}
|
|
124
140
|
}, [autoSaveId, panels]);
|
|
125
141
|
|
|
@@ -139,28 +155,11 @@ export default function PanelGroup({
|
|
|
139
155
|
(id: string): CSSProperties => {
|
|
140
156
|
const { panels } = committedValuesRef.current;
|
|
141
157
|
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (direction === "horizontal") {
|
|
146
|
-
return {
|
|
147
|
-
height: "100%",
|
|
148
|
-
position: "absolute",
|
|
149
|
-
left: offset,
|
|
150
|
-
top: 0,
|
|
151
|
-
width: size,
|
|
152
|
-
};
|
|
153
|
-
} else {
|
|
154
|
-
return {
|
|
155
|
-
height: size,
|
|
156
|
-
position: "absolute",
|
|
157
|
-
left: 0,
|
|
158
|
-
top: offset,
|
|
159
|
-
width: "100%",
|
|
160
|
-
};
|
|
161
|
-
}
|
|
158
|
+
const size = getFlexGrow(panels, id, sizes);
|
|
159
|
+
|
|
160
|
+
return { flexGrow: size };
|
|
162
161
|
},
|
|
163
|
-
[direction,
|
|
162
|
+
[direction, sizes]
|
|
164
163
|
);
|
|
165
164
|
|
|
166
165
|
const registerPanel = useCallback((id: string, panel: PanelData) => {
|
|
@@ -183,10 +182,8 @@ export default function PanelGroup({
|
|
|
183
182
|
|
|
184
183
|
const {
|
|
185
184
|
direction,
|
|
186
|
-
height,
|
|
187
185
|
panels,
|
|
188
186
|
sizes: prevSizes,
|
|
189
|
-
width,
|
|
190
187
|
} = committedValuesRef.current;
|
|
191
188
|
|
|
192
189
|
const panelsArray = panelsMapToSortedArray(panels);
|
|
@@ -202,14 +199,20 @@ export default function PanelGroup({
|
|
|
202
199
|
|
|
203
200
|
const movement = getMovement(
|
|
204
201
|
event,
|
|
202
|
+
groupId,
|
|
205
203
|
handleId,
|
|
206
|
-
{ height, width },
|
|
207
204
|
direction,
|
|
208
205
|
dragOffsetRef.current
|
|
209
206
|
);
|
|
207
|
+
if (movement === 0) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
210
|
|
|
211
|
+
const groupElement = getPanelGroup(groupId);
|
|
212
|
+
const rect = groupElement.getBoundingClientRect();
|
|
211
213
|
const isHorizontal = direction === "horizontal";
|
|
212
|
-
const
|
|
214
|
+
const size = isHorizontal ? rect.width : rect.height;
|
|
215
|
+
const delta = (movement / size) * 100;
|
|
213
216
|
|
|
214
217
|
const nextSizes = adjustByDelta(
|
|
215
218
|
panels,
|
|
@@ -275,14 +278,17 @@ export default function PanelGroup({
|
|
|
275
278
|
[activeHandleId]
|
|
276
279
|
);
|
|
277
280
|
|
|
281
|
+
const style: CSSProperties = {
|
|
282
|
+
display: "flex",
|
|
283
|
+
flexDirection: direction === "horizontal" ? "row" : "column",
|
|
284
|
+
height: "100%",
|
|
285
|
+
width: "100%",
|
|
286
|
+
};
|
|
287
|
+
|
|
278
288
|
return (
|
|
279
289
|
<PanelContext.Provider value={panelContext}>
|
|
280
290
|
<PanelGroupContext.Provider value={panelGroupContext}>
|
|
281
|
-
<div
|
|
282
|
-
className={className}
|
|
283
|
-
data-panel-group-id={groupId}
|
|
284
|
-
style={{ height, position: "relative", width }}
|
|
285
|
-
>
|
|
291
|
+
<div className={className} data-panel-group-id={groupId} style={style}>
|
|
286
292
|
{children}
|
|
287
293
|
</div>
|
|
288
294
|
</PanelGroupContext.Provider>
|
package/src/constants.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PRECISION =
|
|
1
|
+
export const PRECISION = 10;
|
|
@@ -6,12 +6,13 @@ import { ResizeHandler } from "../types";
|
|
|
6
6
|
import {
|
|
7
7
|
adjustByDelta,
|
|
8
8
|
getPanel,
|
|
9
|
+
getPanelGroup,
|
|
9
10
|
getResizeHandle,
|
|
10
11
|
getResizeHandleIndex,
|
|
11
12
|
getResizeHandlePanelIds,
|
|
12
13
|
getResizeHandles,
|
|
13
14
|
getResizeHandlesForGroup,
|
|
14
|
-
|
|
15
|
+
getFlexGrow,
|
|
15
16
|
panelsMapToSortedArray,
|
|
16
17
|
} from "../utils/group";
|
|
17
18
|
|
|
@@ -31,7 +32,10 @@ export function useWindowSplitterPanelGroupBehavior({
|
|
|
31
32
|
sizes: number[];
|
|
32
33
|
}): void {
|
|
33
34
|
useEffect(() => {
|
|
34
|
-
const { direction,
|
|
35
|
+
const { direction, panels } = committedValuesRef.current;
|
|
36
|
+
|
|
37
|
+
const groupElement = getPanelGroup(groupId);
|
|
38
|
+
const { height, width } = groupElement.getBoundingClientRect();
|
|
35
39
|
|
|
36
40
|
const handles = getResizeHandlesForGroup(groupId);
|
|
37
41
|
const cleanupFunctions = handles.map((handle) => {
|
|
@@ -57,12 +61,11 @@ export function useWindowSplitterPanelGroupBehavior({
|
|
|
57
61
|
const ariaValueMin =
|
|
58
62
|
panelsArray.find((panel) => panel.id == idBefore)?.minSize ?? 0;
|
|
59
63
|
|
|
60
|
-
const size =
|
|
61
|
-
const ariaValueNow = size / (direction === "horizontal" ? width : height);
|
|
64
|
+
const size = getFlexGrow(panels, idBefore, sizes);
|
|
62
65
|
|
|
63
66
|
handle.setAttribute("aria-valuemax", "" + Math.round(100 * ariaValueMax));
|
|
64
67
|
handle.setAttribute("aria-valuemin", "" + Math.round(100 * ariaValueMin));
|
|
65
|
-
handle.setAttribute("aria-valuenow", "" +
|
|
68
|
+
handle.setAttribute("aria-valuenow", "" + size);
|
|
66
69
|
|
|
67
70
|
const onKeyDown = (event: KeyboardEvent) => {
|
|
68
71
|
switch (event.key) {
|
package/src/utils/coordinates.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Direction, ResizeEvent } from "../types";
|
|
2
|
-
import { getResizeHandle } from "./group";
|
|
2
|
+
import { getPanelGroup, getResizeHandle } from "./group";
|
|
3
3
|
|
|
4
4
|
export type Coordinates = {
|
|
5
5
|
movement: number;
|
|
@@ -39,13 +39,16 @@ export function getDragOffset(
|
|
|
39
39
|
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementX
|
|
40
40
|
export function getMovement(
|
|
41
41
|
event: ResizeEvent,
|
|
42
|
+
groupId: string,
|
|
42
43
|
handleId: string,
|
|
43
|
-
{ height, width }: Size,
|
|
44
44
|
direction: Direction,
|
|
45
45
|
initialOffset: number
|
|
46
46
|
): number {
|
|
47
47
|
const isHorizontal = direction === "horizontal";
|
|
48
|
-
|
|
48
|
+
|
|
49
|
+
const groupElement = getPanelGroup(groupId);
|
|
50
|
+
const rect = groupElement.getBoundingClientRect();
|
|
51
|
+
const size = isHorizontal ? rect.width : rect.height;
|
|
49
52
|
|
|
50
53
|
if (isKeyDown(event)) {
|
|
51
54
|
const denominator = event.shiftKey ? 10 : 100;
|
package/src/utils/group.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PRECISION } from "../constants";
|
|
2
|
-
import {
|
|
2
|
+
import { PanelData } from "../types";
|
|
3
3
|
|
|
4
4
|
export function adjustByDelta(
|
|
5
5
|
panels: Map<string, PanelData>,
|
|
@@ -66,29 +66,26 @@ export function adjustByDelta(
|
|
|
66
66
|
return nextSizes;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
// This method returns a number between 1 and 100 representing
|
|
70
|
+
// the % of the group's overall space this panel should occupy.
|
|
71
|
+
export function getFlexGrow(
|
|
70
72
|
panels: Map<string, PanelData>,
|
|
71
73
|
id: string,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
): number {
|
|
77
|
-
const panelsArray = panelsMapToSortedArray(panels);
|
|
78
|
-
|
|
79
|
-
let index = panelsArray.findIndex((panel) => panel.id === id);
|
|
80
|
-
if (index < 0) {
|
|
81
|
-
return 0;
|
|
74
|
+
sizes: number[]
|
|
75
|
+
): string {
|
|
76
|
+
if (panels.size === 1) {
|
|
77
|
+
return "100";
|
|
82
78
|
}
|
|
83
79
|
|
|
84
|
-
|
|
80
|
+
const panelsArray = panelsMapToSortedArray(panels);
|
|
85
81
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
82
|
+
const index = panelsArray.findIndex((panel) => panel.id === id);
|
|
83
|
+
const size = sizes[index];
|
|
84
|
+
if (size == null) {
|
|
85
|
+
return "0";
|
|
89
86
|
}
|
|
90
87
|
|
|
91
|
-
return
|
|
88
|
+
return size.toPrecision(PRECISION);
|
|
92
89
|
}
|
|
93
90
|
|
|
94
91
|
export function getPanel(id: string): HTMLDivElement | null {
|
|
@@ -99,6 +96,14 @@ export function getPanel(id: string): HTMLDivElement | null {
|
|
|
99
96
|
return null;
|
|
100
97
|
}
|
|
101
98
|
|
|
99
|
+
export function getPanelGroup(id: string): HTMLDivElement | null {
|
|
100
|
+
const element = document.querySelector(`[data-panel-group-id="${id}"]`);
|
|
101
|
+
if (element) {
|
|
102
|
+
return element as HTMLDivElement;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
102
107
|
export function getResizeHandle(id: string): HTMLDivElement | null {
|
|
103
108
|
const element = document.querySelector(
|
|
104
109
|
`[data-panel-resize-handle-id="${id}"]`
|
|
@@ -149,28 +154,3 @@ export function panelsMapToSortedArray(
|
|
|
149
154
|
): PanelData[] {
|
|
150
155
|
return Array.from(panels.values()).sort((a, b) => a.order - b.order);
|
|
151
156
|
}
|
|
152
|
-
|
|
153
|
-
export function getSize(
|
|
154
|
-
panels: Map<string, PanelData>,
|
|
155
|
-
id: string,
|
|
156
|
-
direction: Direction,
|
|
157
|
-
sizes: number[],
|
|
158
|
-
height: number,
|
|
159
|
-
width: number
|
|
160
|
-
): number {
|
|
161
|
-
const totalSize = direction === "horizontal" ? width : height;
|
|
162
|
-
|
|
163
|
-
if (panels.size === 1) {
|
|
164
|
-
return totalSize;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const panelsArray = panelsMapToSortedArray(panels);
|
|
168
|
-
|
|
169
|
-
const index = panelsArray.findIndex((panel) => panel.id === id);
|
|
170
|
-
const size = sizes[index];
|
|
171
|
-
if (size == null) {
|
|
172
|
-
return 0;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return Math.round(size * totalSize);
|
|
176
|
-
}
|
|
@@ -8,8 +8,11 @@ type SerializedPanelGroupState = { [panelIds: string]: number[] };
|
|
|
8
8
|
// Pre-sorting by minSize allows remembering layouts even if panels are re-ordered/dragged.
|
|
9
9
|
function getSerializationKey(panels: PanelData[]): string {
|
|
10
10
|
return panels
|
|
11
|
-
.map((panel) =>
|
|
12
|
-
|
|
11
|
+
.map((panel) => {
|
|
12
|
+
const { minSize, order } = panel;
|
|
13
|
+
return order ? `${order}:${minSize}` : `${minSize}`;
|
|
14
|
+
})
|
|
15
|
+
.sort((a, b) => a.localeCompare(b))
|
|
13
16
|
.join(",");
|
|
14
17
|
}
|
|
15
18
|
|