react-resizable-panels 0.0.55 → 0.0.56
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/.eslintrc.cjs +26 -0
- package/CHANGELOG.md +234 -90
- package/README.md +55 -49
- package/dist/declarations/src/Panel.d.ts +75 -20
- package/dist/declarations/src/PanelGroup.d.ts +29 -25
- package/dist/declarations/src/PanelResizeHandle.d.ts +1 -1
- package/dist/declarations/src/index.d.ts +5 -6
- package/dist/declarations/src/types.d.ts +3 -26
- package/dist/declarations/src/vendor/react.d.ts +4 -4
- package/dist/react-resizable-panels.browser.cjs.js +1241 -1035
- package/dist/react-resizable-panels.browser.cjs.mjs +1 -2
- package/dist/react-resizable-panels.browser.development.cjs.js +1367 -1081
- package/dist/react-resizable-panels.browser.development.cjs.mjs +1 -2
- package/dist/react-resizable-panels.browser.development.esm.js +1368 -1081
- package/dist/react-resizable-panels.browser.esm.js +1242 -1035
- package/dist/react-resizable-panels.cjs.js +1241 -1035
- package/dist/react-resizable-panels.cjs.js.map +1 -1
- package/dist/react-resizable-panels.cjs.mjs +1 -2
- package/dist/react-resizable-panels.development.cjs.js +1370 -1084
- package/dist/react-resizable-panels.development.cjs.mjs +1 -2
- package/dist/react-resizable-panels.development.esm.js +1371 -1084
- package/dist/react-resizable-panels.development.node.cjs.js +1151 -940
- package/dist/react-resizable-panels.development.node.cjs.mjs +1 -2
- package/dist/react-resizable-panels.development.node.esm.js +1152 -940
- package/dist/react-resizable-panels.esm.js +1242 -1035
- package/dist/react-resizable-panels.esm.js.map +1 -1
- package/dist/react-resizable-panels.node.cjs.js +1049 -912
- package/dist/react-resizable-panels.node.cjs.mjs +1 -2
- package/dist/react-resizable-panels.node.esm.js +1050 -912
- package/jest.config.js +10 -0
- package/package.json +3 -1
- package/src/Panel.test.tsx +308 -0
- package/src/Panel.ts +175 -123
- package/src/PanelGroup.test.tsx +210 -0
- package/src/PanelGroup.ts +730 -669
- package/src/PanelGroupContext.ts +33 -0
- package/src/PanelResizeHandle.ts +13 -8
- package/src/hooks/useUniqueId.ts +1 -1
- package/src/hooks/useWindowSplitterBehavior.ts +9 -164
- package/src/hooks/useWindowSplitterPanelGroupBehavior.ts +185 -0
- package/src/index.ts +19 -14
- package/src/types.ts +3 -30
- package/src/utils/adjustLayoutByDelta.test.ts +1808 -0
- package/src/utils/adjustLayoutByDelta.ts +211 -0
- package/src/utils/calculateAriaValues.test.ts +111 -0
- package/src/utils/calculateAriaValues.ts +67 -0
- package/src/utils/calculateDeltaPercentage.ts +68 -0
- package/src/utils/calculateDragOffsetPercentage.ts +30 -0
- package/src/utils/calculateUnsafeDefaultLayout.test.ts +92 -0
- package/src/utils/calculateUnsafeDefaultLayout.ts +55 -0
- package/src/utils/callPanelCallbacks.ts +81 -0
- package/src/utils/compareLayouts.test.ts +9 -0
- package/src/utils/compareLayouts.ts +12 -0
- package/src/utils/computePanelFlexBoxStyle.ts +44 -0
- package/src/utils/computePercentagePanelConstraints.test.ts +71 -0
- package/src/utils/computePercentagePanelConstraints.ts +56 -0
- package/src/utils/convertPercentageToPixels.test.ts +9 -0
- package/src/utils/convertPercentageToPixels.ts +6 -0
- package/src/utils/convertPixelConstraintsToPercentages.ts +55 -0
- package/src/utils/convertPixelsToPercentage.test.ts +9 -0
- package/src/utils/convertPixelsToPercentage.ts +6 -0
- package/src/utils/determinePivotIndices.ts +10 -0
- package/src/utils/dom/calculateAvailablePanelSizeInPixels.ts +29 -0
- package/src/utils/dom/getAvailableGroupSizePixels.ts +29 -0
- package/src/utils/dom/getPanelElement.ts +7 -0
- package/src/utils/dom/getPanelGroupElement.ts +7 -0
- package/src/utils/dom/getResizeHandleElement.ts +9 -0
- package/src/utils/dom/getResizeHandleElementIndex.ts +12 -0
- package/src/utils/dom/getResizeHandleElementsForGroup.ts +9 -0
- package/src/utils/dom/getResizeHandlePanelIds.ts +18 -0
- package/src/utils/events.ts +13 -0
- package/src/utils/getPercentageSizeFromMixedSizes.test.ts +47 -0
- package/src/utils/getPercentageSizeFromMixedSizes.ts +15 -0
- package/src/utils/getResizeEventCursorPosition.ts +19 -0
- package/src/utils/initializeDefaultStorage.ts +26 -0
- package/src/utils/numbers/fuzzyCompareNumbers.test.ts +16 -0
- package/src/utils/numbers/fuzzyCompareNumbers.ts +17 -0
- package/src/utils/numbers/fuzzyNumbersEqual.ts +9 -0
- package/src/utils/resizePanel.ts +41 -0
- package/src/utils/serialization.ts +9 -4
- package/src/utils/shouldMonitorPixelBasedConstraints.test.ts +23 -0
- package/src/utils/shouldMonitorPixelBasedConstraints.ts +13 -0
- package/src/utils/test-utils.ts +136 -0
- package/src/utils/validatePanelConstraints.test.ts +151 -0
- package/src/utils/validatePanelConstraints.ts +103 -0
- package/src/utils/validatePanelGroupLayout.test.ts +233 -0
- package/src/utils/validatePanelGroupLayout.ts +88 -0
- package/src/vendor/react.ts +4 -0
- package/.eslintrc.json +0 -22
- package/dist/declarations/src/utils/group.d.ts +0 -29
- package/src/PanelContexts.ts +0 -22
- package/src/utils/coordinates.ts +0 -149
- package/src/utils/group.ts +0 -614
|
@@ -7,6 +7,7 @@ import * as React from 'react';
|
|
|
7
7
|
const {
|
|
8
8
|
createElement,
|
|
9
9
|
createContext,
|
|
10
|
+
createRef,
|
|
10
11
|
forwardRef,
|
|
11
12
|
useCallback,
|
|
12
13
|
useContext,
|
|
@@ -21,6 +22,9 @@ const {
|
|
|
21
22
|
// `toString()` prevents bundlers from trying to `import { useId } from 'react'`
|
|
22
23
|
const useId = React["useId".toString()];
|
|
23
24
|
|
|
25
|
+
const PanelGroupContext = createContext(null);
|
|
26
|
+
PanelGroupContext.displayName = "PanelGroupContext";
|
|
27
|
+
|
|
24
28
|
const wrappedUseId = typeof useId === "function" ? useId : () => null;
|
|
25
29
|
let counter = 0;
|
|
26
30
|
function useUniqueId(idFromParams = null) {
|
|
@@ -29,103 +33,106 @@ function useUniqueId(idFromParams = null) {
|
|
|
29
33
|
if (idRef.current === null) {
|
|
30
34
|
idRef.current = "" + counter++;
|
|
31
35
|
}
|
|
32
|
-
return idRef.current;
|
|
36
|
+
return idFromParams ?? idRef.current;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
const PanelGroupContext = createContext(null);
|
|
36
|
-
PanelGroupContext.displayName = "PanelGroupContext";
|
|
37
|
-
|
|
38
39
|
function PanelWithForwardedRef({
|
|
39
|
-
children
|
|
40
|
+
children,
|
|
40
41
|
className: classNameFromProps = "",
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
collapsedSizePercentage,
|
|
43
|
+
collapsedSizePixels,
|
|
44
|
+
collapsible,
|
|
45
|
+
defaultSizePercentage,
|
|
46
|
+
defaultSizePixels,
|
|
44
47
|
forwardedRef,
|
|
45
|
-
id: idFromProps
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
id: idFromProps,
|
|
49
|
+
maxSizePercentage,
|
|
50
|
+
maxSizePixels,
|
|
51
|
+
minSizePercentage,
|
|
52
|
+
minSizePixels,
|
|
53
|
+
onCollapse,
|
|
54
|
+
onExpand,
|
|
55
|
+
onResize,
|
|
56
|
+
order,
|
|
57
|
+
style: styleFromProps,
|
|
52
58
|
tagName: Type = "div"
|
|
53
59
|
}) {
|
|
54
60
|
const context = useContext(PanelGroupContext);
|
|
55
61
|
if (context === null) {
|
|
56
62
|
throw Error(`Panel components must be rendered within a PanelGroup container`);
|
|
57
63
|
}
|
|
58
|
-
const panelId = useUniqueId(idFromProps);
|
|
59
64
|
const {
|
|
60
65
|
collapsePanel,
|
|
61
66
|
expandPanel,
|
|
62
67
|
getPanelSize,
|
|
63
68
|
getPanelStyle,
|
|
69
|
+
isPanelCollapsed,
|
|
64
70
|
registerPanel,
|
|
65
71
|
resizePanel,
|
|
66
|
-
units,
|
|
67
72
|
unregisterPanel
|
|
68
73
|
} = context;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
const style = getPanelStyle(panelId, defaultSize);
|
|
89
|
-
const committedValuesRef = useRef({
|
|
90
|
-
size: parseSizeFromStyle(style)
|
|
91
|
-
});
|
|
92
|
-
useRef({
|
|
93
|
-
callbacksRef,
|
|
94
|
-
collapsedSize,
|
|
95
|
-
collapsible,
|
|
96
|
-
defaultSize,
|
|
74
|
+
const panelId = useUniqueId(idFromProps);
|
|
75
|
+
const panelDataRef = useRef({
|
|
76
|
+
callbacks: {
|
|
77
|
+
onCollapse,
|
|
78
|
+
onExpand,
|
|
79
|
+
onResize
|
|
80
|
+
},
|
|
81
|
+
constraints: {
|
|
82
|
+
collapsedSizePercentage,
|
|
83
|
+
collapsedSizePixels,
|
|
84
|
+
collapsible,
|
|
85
|
+
defaultSizePercentage,
|
|
86
|
+
defaultSizePixels,
|
|
87
|
+
maxSizePercentage,
|
|
88
|
+
maxSizePixels,
|
|
89
|
+
minSizePercentage,
|
|
90
|
+
minSizePixels
|
|
91
|
+
},
|
|
97
92
|
id: panelId,
|
|
98
|
-
|
|
99
|
-
maxSize,
|
|
100
|
-
minSize,
|
|
93
|
+
idIsFromProps: idFromProps !== undefined,
|
|
101
94
|
order
|
|
102
95
|
});
|
|
96
|
+
useRef({
|
|
97
|
+
didLogMissingDefaultSizeWarning: false
|
|
98
|
+
});
|
|
103
99
|
useImperativeHandle(forwardedRef, () => ({
|
|
104
|
-
collapse: () =>
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
collapse: () => {
|
|
101
|
+
collapsePanel(panelDataRef.current);
|
|
102
|
+
},
|
|
103
|
+
expand: () => {
|
|
104
|
+
expandPanel(panelDataRef.current);
|
|
108
105
|
},
|
|
109
106
|
getId() {
|
|
110
107
|
return panelId;
|
|
111
108
|
},
|
|
112
|
-
getSize(
|
|
113
|
-
return getPanelSize(
|
|
109
|
+
getSize() {
|
|
110
|
+
return getPanelSize(panelDataRef.current);
|
|
111
|
+
},
|
|
112
|
+
isCollapsed() {
|
|
113
|
+
return isPanelCollapsed(panelDataRef.current);
|
|
114
114
|
},
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
isExpanded() {
|
|
116
|
+
return !isPanelCollapsed(panelDataRef.current);
|
|
117
|
+
},
|
|
118
|
+
resize: mixedSizes => {
|
|
119
|
+
resizePanel(panelDataRef.current, mixedSizes);
|
|
120
|
+
}
|
|
121
|
+
}), [collapsePanel, expandPanel, getPanelSize, isPanelCollapsed, panelId, resizePanel]);
|
|
122
|
+
const style = getPanelStyle(panelDataRef.current);
|
|
117
123
|
return createElement(Type, {
|
|
118
124
|
children,
|
|
119
125
|
className: classNameFromProps,
|
|
120
|
-
"data-panel": "",
|
|
121
|
-
"data-panel-collapsible": collapsible || undefined,
|
|
122
|
-
"data-panel-id": panelId,
|
|
123
|
-
"data-panel-size": parseFloat("" + style.flexGrow).toFixed(1),
|
|
124
|
-
id: `data-panel-id-${panelId}`,
|
|
125
126
|
style: {
|
|
126
127
|
...style,
|
|
127
128
|
...styleFromProps
|
|
128
|
-
}
|
|
129
|
+
},
|
|
130
|
+
// CSS selectors
|
|
131
|
+
"data-panel": "",
|
|
132
|
+
// e2e test attributes
|
|
133
|
+
"data-panel-collapsible": undefined,
|
|
134
|
+
"data-panel-id": undefined,
|
|
135
|
+
"data-panel-size": undefined
|
|
129
136
|
});
|
|
130
137
|
}
|
|
131
138
|
const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
|
|
@@ -135,36 +142,133 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
|
|
|
135
142
|
PanelWithForwardedRef.displayName = "Panel";
|
|
136
143
|
Panel.displayName = "forwardRef(Panel)";
|
|
137
144
|
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
const PRECISION = 10;
|
|
146
|
+
|
|
147
|
+
function convertPixelsToPercentage(pixels, groupSizePixels) {
|
|
148
|
+
return pixels / groupSizePixels * 100;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function convertPixelConstraintsToPercentages(panelConstraints, groupSizePixels) {
|
|
152
|
+
let {
|
|
153
|
+
collapsedSizePercentage = 0,
|
|
154
|
+
collapsedSizePixels,
|
|
155
|
+
defaultSizePercentage,
|
|
156
|
+
defaultSizePixels,
|
|
157
|
+
maxSizePercentage = 100,
|
|
158
|
+
maxSizePixels,
|
|
159
|
+
minSizePercentage = 0,
|
|
160
|
+
minSizePixels
|
|
161
|
+
} = panelConstraints;
|
|
162
|
+
if (collapsedSizePixels != null) {
|
|
163
|
+
collapsedSizePercentage = convertPixelsToPercentage(collapsedSizePixels, groupSizePixels);
|
|
164
|
+
}
|
|
165
|
+
if (defaultSizePixels != null) {
|
|
166
|
+
defaultSizePercentage = convertPixelsToPercentage(defaultSizePixels, groupSizePixels);
|
|
167
|
+
}
|
|
168
|
+
if (minSizePixels != null) {
|
|
169
|
+
minSizePercentage = convertPixelsToPercentage(minSizePixels, groupSizePixels);
|
|
170
|
+
}
|
|
171
|
+
if (maxSizePixels != null) {
|
|
172
|
+
maxSizePercentage = convertPixelsToPercentage(maxSizePixels, groupSizePixels);
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
collapsedSizePercentage,
|
|
176
|
+
defaultSizePercentage,
|
|
177
|
+
maxSizePercentage,
|
|
178
|
+
minSizePercentage
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, groupSizePixels) {
|
|
183
|
+
// All panel constraints, excluding the current one
|
|
184
|
+
let totalMinConstraints = 0;
|
|
185
|
+
let totalMaxConstraints = 0;
|
|
186
|
+
for (let index = 0; index < panelConstraintsArray.length; index++) {
|
|
187
|
+
if (index !== panelIndex) {
|
|
188
|
+
const {
|
|
189
|
+
collapsible
|
|
190
|
+
} = panelConstraintsArray[index];
|
|
191
|
+
const {
|
|
192
|
+
collapsedSizePercentage,
|
|
193
|
+
maxSizePercentage,
|
|
194
|
+
minSizePercentage
|
|
195
|
+
} = convertPixelConstraintsToPercentages(panelConstraintsArray[index], groupSizePixels);
|
|
196
|
+
totalMaxConstraints += maxSizePercentage;
|
|
197
|
+
totalMinConstraints += collapsible ? collapsedSizePercentage : minSizePercentage;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
140
200
|
const {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
201
|
+
collapsedSizePercentage,
|
|
202
|
+
defaultSizePercentage,
|
|
203
|
+
maxSizePercentage,
|
|
204
|
+
minSizePercentage
|
|
205
|
+
} = convertPixelConstraintsToPercentages(panelConstraintsArray[panelIndex], groupSizePixels);
|
|
206
|
+
return {
|
|
207
|
+
collapsedSizePercentage,
|
|
208
|
+
defaultSizePercentage,
|
|
209
|
+
maxSizePercentage: panelConstraintsArray.length > 1 ? Math.min(maxSizePercentage, 100 - totalMinConstraints) : maxSizePercentage,
|
|
210
|
+
minSizePercentage: panelConstraintsArray.length > 1 ? Math.max(minSizePercentage, 100 - totalMaxConstraints) : minSizePercentage
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
|
|
215
|
+
actual = parseFloat(actual.toFixed(fractionDigits));
|
|
216
|
+
expected = parseFloat(expected.toFixed(fractionDigits));
|
|
217
|
+
const delta = actual - expected;
|
|
218
|
+
if (delta === 0) {
|
|
219
|
+
return 0;
|
|
145
220
|
} else {
|
|
146
|
-
return
|
|
221
|
+
return delta > 0 ? 1 : -1;
|
|
147
222
|
}
|
|
148
223
|
}
|
|
149
224
|
|
|
150
|
-
|
|
225
|
+
function fuzzyNumbersEqual(actual, expected, fractionDigits) {
|
|
226
|
+
return fuzzyCompareNumbers(actual, expected, fractionDigits) === 0;
|
|
227
|
+
}
|
|
151
228
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
229
|
+
// Panel size must be in percentages; pixel values should be pre-converted
|
|
230
|
+
function resizePanel({
|
|
231
|
+
groupSizePixels,
|
|
232
|
+
panelConstraints,
|
|
233
|
+
panelIndex,
|
|
234
|
+
size
|
|
235
|
+
}) {
|
|
236
|
+
let {
|
|
237
|
+
collapsible
|
|
238
|
+
} = panelConstraints[panelIndex];
|
|
159
239
|
const {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
240
|
+
collapsedSizePercentage,
|
|
241
|
+
maxSizePercentage,
|
|
242
|
+
minSizePercentage
|
|
243
|
+
} = computePercentagePanelConstraints(panelConstraints, panelIndex, groupSizePixels);
|
|
244
|
+
if (minSizePercentage != null) {
|
|
245
|
+
if (fuzzyCompareNumbers(size, minSizePercentage) < 0) {
|
|
246
|
+
if (collapsible) {
|
|
247
|
+
size = collapsedSizePercentage;
|
|
248
|
+
} else {
|
|
249
|
+
size = minSizePercentage;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (maxSizePercentage != null) {
|
|
254
|
+
size = Math.min(maxSizePercentage, size);
|
|
255
|
+
}
|
|
256
|
+
return size;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// All units must be in percentages; pixel values should be pre-converted
|
|
260
|
+
function adjustLayoutByDelta({
|
|
261
|
+
delta,
|
|
262
|
+
groupSizePixels,
|
|
263
|
+
layout: prevLayout,
|
|
264
|
+
panelConstraints,
|
|
265
|
+
pivotIndices,
|
|
266
|
+
trigger
|
|
267
|
+
}) {
|
|
268
|
+
if (fuzzyNumbersEqual(delta, 0)) {
|
|
269
|
+
return prevLayout;
|
|
270
|
+
}
|
|
271
|
+
const nextLayout = [...prevLayout];
|
|
168
272
|
let deltaApplied = 0;
|
|
169
273
|
|
|
170
274
|
// A resizing panel affects the panels before or after it.
|
|
@@ -175,119 +279,195 @@ function adjustByDelta(event, committedValues, idBefore, idAfter, deltaPixels, p
|
|
|
175
279
|
// A positive delta means the panel immediately before the resizer should "expand".
|
|
176
280
|
// This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.
|
|
177
281
|
|
|
178
|
-
//
|
|
282
|
+
// First, check the panel we're pivoting around;
|
|
283
|
+
// We should only expand or contract by as much as its constraints allow
|
|
179
284
|
{
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
285
|
+
const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
|
|
286
|
+
const initialSize = nextLayout[pivotIndex];
|
|
287
|
+
const {
|
|
288
|
+
collapsible
|
|
289
|
+
} = panelConstraints[pivotIndex];
|
|
290
|
+
const {
|
|
291
|
+
collapsedSizePercentage,
|
|
292
|
+
maxSizePercentage,
|
|
293
|
+
minSizePercentage
|
|
294
|
+
} = computePercentagePanelConstraints(panelConstraints, pivotIndex, groupSizePixels);
|
|
295
|
+
const isCollapsed = collapsible && fuzzyNumbersEqual(initialSize, collapsedSizePercentage);
|
|
296
|
+
let unsafeSize = initialSize + Math.abs(delta);
|
|
297
|
+
if (isCollapsed) {
|
|
298
|
+
switch (trigger) {
|
|
299
|
+
case "keyboard":
|
|
300
|
+
if (minSizePercentage > unsafeSize) {
|
|
301
|
+
unsafeSize = minSizePercentage;
|
|
302
|
+
}
|
|
191
303
|
}
|
|
192
|
-
deltaPixels = deltaPixels < 0 ? baseSize - nextSize : nextSize - baseSize;
|
|
193
304
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
deltaApplied += baseSize - nextSize;
|
|
207
|
-
nextSizes[index] = nextSize;
|
|
208
|
-
if (deltaApplied.toPrecision(PRECISION).localeCompare(Math.abs(deltaPixels).toPrecision(PRECISION), undefined, {
|
|
209
|
-
numeric: true
|
|
210
|
-
}) >= 0) {
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
305
|
+
const safeSize = resizePanel({
|
|
306
|
+
groupSizePixels,
|
|
307
|
+
panelConstraints,
|
|
308
|
+
panelIndex: pivotIndex,
|
|
309
|
+
size: unsafeSize
|
|
310
|
+
});
|
|
311
|
+
if (fuzzyNumbersEqual(initialSize, safeSize)) {
|
|
312
|
+
// If there's no room for the pivot panel to grow, we should ignore this change
|
|
313
|
+
return nextLayout;
|
|
314
|
+
} else {
|
|
315
|
+
delta = delta < 0 ? initialSize - safeSize : safeSize - initialSize;
|
|
213
316
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Delta added to a panel needs to be subtracted from other panels
|
|
320
|
+
// within the constraints that those panels allow
|
|
321
|
+
{
|
|
322
|
+
const pivotIndex = delta < 0 ? pivotIndices[0] : pivotIndices[1];
|
|
323
|
+
let index = pivotIndex;
|
|
324
|
+
while (index >= 0 && index < panelConstraints.length) {
|
|
325
|
+
const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
|
|
326
|
+
const prevSize = prevLayout[index];
|
|
327
|
+
const unsafeSize = prevSize - deltaRemaining;
|
|
328
|
+
let safeSize = resizePanel({
|
|
329
|
+
groupSizePixels,
|
|
330
|
+
panelConstraints,
|
|
331
|
+
panelIndex: index,
|
|
332
|
+
size: unsafeSize
|
|
333
|
+
});
|
|
334
|
+
if (!fuzzyNumbersEqual(prevSize, safeSize)) {
|
|
335
|
+
deltaApplied += prevSize - safeSize;
|
|
336
|
+
nextLayout[index] = safeSize;
|
|
337
|
+
if (deltaApplied.toPrecision(3).localeCompare(Math.abs(delta).toPrecision(3), undefined, {
|
|
338
|
+
numeric: true
|
|
339
|
+
}) >= 0) {
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
217
342
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
343
|
+
if (delta < 0) {
|
|
344
|
+
index--;
|
|
345
|
+
} else {
|
|
346
|
+
index++;
|
|
221
347
|
}
|
|
222
348
|
}
|
|
223
349
|
}
|
|
224
350
|
|
|
225
351
|
// If we were unable to resize any of the panels panels, return the previous state.
|
|
226
|
-
// This will essentially bailout and ignore
|
|
227
|
-
if (deltaApplied
|
|
228
|
-
return
|
|
352
|
+
// This will essentially bailout and ignore e.g. drags past a panel's boundaries
|
|
353
|
+
if (fuzzyNumbersEqual(deltaApplied, 0)) {
|
|
354
|
+
return prevLayout;
|
|
229
355
|
}
|
|
356
|
+
{
|
|
357
|
+
const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
|
|
358
|
+
const unsafeSize = prevLayout[pivotIndex] + deltaApplied;
|
|
359
|
+
const safeSize = resizePanel({
|
|
360
|
+
groupSizePixels,
|
|
361
|
+
panelConstraints,
|
|
362
|
+
panelIndex: pivotIndex,
|
|
363
|
+
size: unsafeSize
|
|
364
|
+
});
|
|
230
365
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
366
|
+
// Adjust the pivot panel before, but only by the amount that surrounding panels were able to shrink/contract.
|
|
367
|
+
nextLayout[pivotIndex] = safeSize;
|
|
368
|
+
|
|
369
|
+
// Edge case where expanding or contracting one panel caused another one to change collapsed state
|
|
370
|
+
if (!fuzzyNumbersEqual(safeSize, unsafeSize)) {
|
|
371
|
+
let deltaRemaining = unsafeSize - safeSize;
|
|
372
|
+
const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
|
|
373
|
+
let index = pivotIndex;
|
|
374
|
+
while (index >= 0 && index < panelConstraints.length) {
|
|
375
|
+
const prevSize = nextLayout[index];
|
|
376
|
+
const unsafeSize = prevSize + deltaRemaining;
|
|
377
|
+
const safeSize = resizePanel({
|
|
378
|
+
groupSizePixels,
|
|
379
|
+
panelConstraints,
|
|
380
|
+
panelIndex: index,
|
|
381
|
+
size: unsafeSize
|
|
382
|
+
});
|
|
383
|
+
if (!fuzzyNumbersEqual(prevSize, safeSize)) {
|
|
384
|
+
deltaRemaining -= safeSize - prevSize;
|
|
385
|
+
nextLayout[index] = safeSize;
|
|
386
|
+
}
|
|
387
|
+
if (fuzzyNumbersEqual(deltaRemaining, 0)) {
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
if (delta > 0) {
|
|
391
|
+
index--;
|
|
392
|
+
} else {
|
|
393
|
+
index++;
|
|
394
|
+
}
|
|
260
395
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
396
|
+
|
|
397
|
+
// If we can't redistribute, this layout is invalid;
|
|
398
|
+
// There may be an incremental layout that is valid though
|
|
399
|
+
if (!fuzzyNumbersEqual(deltaRemaining, 0)) {
|
|
400
|
+
try {
|
|
401
|
+
return adjustLayoutByDelta({
|
|
402
|
+
delta: delta < 0 ? delta + 1 : delta - 1,
|
|
403
|
+
groupSizePixels,
|
|
404
|
+
layout: prevLayout,
|
|
405
|
+
panelConstraints,
|
|
406
|
+
pivotIndices,
|
|
407
|
+
trigger
|
|
408
|
+
});
|
|
409
|
+
} catch (error) {
|
|
410
|
+
if (error instanceof RangeError) {
|
|
411
|
+
console.error(`Could not apply delta ${delta} to layout`);
|
|
412
|
+
return prevLayout;
|
|
413
|
+
}
|
|
414
|
+
} finally {
|
|
266
415
|
}
|
|
267
416
|
}
|
|
268
417
|
}
|
|
269
|
-
}
|
|
418
|
+
}
|
|
419
|
+
return nextLayout;
|
|
270
420
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
421
|
+
|
|
422
|
+
function assert(expectedCondition, message = "Assertion failed!") {
|
|
423
|
+
if (!expectedCondition) {
|
|
424
|
+
console.error(message);
|
|
425
|
+
throw Error(message);
|
|
274
426
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function getPercentageSizeFromMixedSizes({
|
|
430
|
+
sizePercentage,
|
|
431
|
+
sizePixels
|
|
432
|
+
}, groupSizePixels) {
|
|
433
|
+
if (sizePercentage != null) {
|
|
434
|
+
return sizePercentage;
|
|
435
|
+
} else if (sizePixels != null) {
|
|
436
|
+
return convertPixelsToPercentage(sizePixels, groupSizePixels);
|
|
278
437
|
}
|
|
279
|
-
|
|
280
|
-
const idBefore = isLastPanel ? panelsArray[index - 1].current.id : id;
|
|
281
|
-
const idAfter = isLastPanel ? id : panelsArray[index + 1].current.id;
|
|
282
|
-
return [idBefore, idAfter];
|
|
438
|
+
return undefined;
|
|
283
439
|
}
|
|
284
|
-
|
|
285
|
-
|
|
440
|
+
|
|
441
|
+
function getResizeHandleElementsForGroup(groupId) {
|
|
442
|
+
return Array.from(document.querySelectorAll(`[data-panel-resize-handle-id][data-panel-group-id="${groupId}"]`));
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function getResizeHandleElementIndex(groupId, id) {
|
|
446
|
+
const handles = getResizeHandleElementsForGroup(groupId);
|
|
447
|
+
const index = handles.findIndex(handle => handle.getAttribute("data-panel-resize-handle-id") === id);
|
|
448
|
+
return index ?? null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function determinePivotIndices(groupId, dragHandleId) {
|
|
452
|
+
const index = getResizeHandleElementIndex(groupId, dragHandleId);
|
|
453
|
+
return index != null ? [index, index + 1] : [-1, -1];
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function getPanelGroupElement(id) {
|
|
457
|
+
const element = document.querySelector(`[data-panel-group-id="${id}"]`);
|
|
458
|
+
if (element) {
|
|
459
|
+
return element;
|
|
460
|
+
}
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function calculateAvailablePanelSizeInPixels(groupId) {
|
|
465
|
+
const panelGroupElement = getPanelGroupElement(groupId);
|
|
286
466
|
if (panelGroupElement == null) {
|
|
287
467
|
return NaN;
|
|
288
468
|
}
|
|
289
469
|
const direction = panelGroupElement.getAttribute("data-panel-group-direction");
|
|
290
|
-
const resizeHandles =
|
|
470
|
+
const resizeHandles = getResizeHandleElementsForGroup(groupId);
|
|
291
471
|
if (direction === "horizontal") {
|
|
292
472
|
return panelGroupElement.offsetWidth - resizeHandles.reduce((accumulated, handle) => {
|
|
293
473
|
return accumulated + handle.offsetWidth;
|
|
@@ -299,256 +479,71 @@ function getAvailableGroupSizePixels(groupId) {
|
|
|
299
479
|
}
|
|
300
480
|
}
|
|
301
481
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
return "100";
|
|
307
|
-
}
|
|
308
|
-
const panelsArray = panelsMapToSortedArray(panels);
|
|
309
|
-
const index = panelsArray.findIndex(panel => panel.current.id === id);
|
|
310
|
-
const size = sizes[index];
|
|
311
|
-
if (size == null) {
|
|
312
|
-
return "0";
|
|
313
|
-
}
|
|
314
|
-
return size.toPrecision(PRECISION);
|
|
315
|
-
}
|
|
316
|
-
function getPanel(id) {
|
|
317
|
-
const element = document.querySelector(`[data-panel-id="${id}"]`);
|
|
318
|
-
if (element) {
|
|
319
|
-
return element;
|
|
482
|
+
function getAvailableGroupSizePixels(groupId) {
|
|
483
|
+
const panelGroupElement = getPanelGroupElement(groupId);
|
|
484
|
+
if (panelGroupElement == null) {
|
|
485
|
+
return NaN;
|
|
320
486
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
487
|
+
const direction = panelGroupElement.getAttribute("data-panel-group-direction");
|
|
488
|
+
const resizeHandles = getResizeHandleElementsForGroup(groupId);
|
|
489
|
+
if (direction === "horizontal") {
|
|
490
|
+
return panelGroupElement.offsetWidth - resizeHandles.reduce((accumulated, handle) => {
|
|
491
|
+
return accumulated + handle.offsetWidth;
|
|
492
|
+
}, 0);
|
|
493
|
+
} else {
|
|
494
|
+
return panelGroupElement.offsetHeight - resizeHandles.reduce((accumulated, handle) => {
|
|
495
|
+
return accumulated + handle.offsetHeight;
|
|
496
|
+
}, 0);
|
|
327
497
|
}
|
|
328
|
-
return null;
|
|
329
498
|
}
|
|
330
|
-
|
|
499
|
+
|
|
500
|
+
function getResizeHandleElement(id) {
|
|
331
501
|
const element = document.querySelector(`[data-panel-resize-handle-id="${id}"]`);
|
|
332
502
|
if (element) {
|
|
333
503
|
return element;
|
|
334
504
|
}
|
|
335
505
|
return null;
|
|
336
506
|
}
|
|
337
|
-
|
|
338
|
-
const handles = getResizeHandles();
|
|
339
|
-
const index = handles.findIndex(handle => handle.getAttribute("data-panel-resize-handle-id") === id);
|
|
340
|
-
return index ?? null;
|
|
341
|
-
}
|
|
342
|
-
function getResizeHandles() {
|
|
343
|
-
return Array.from(document.querySelectorAll(`[data-panel-resize-handle-id]`));
|
|
344
|
-
}
|
|
345
|
-
function getResizeHandlesForGroup(groupId) {
|
|
346
|
-
return Array.from(document.querySelectorAll(`[data-panel-resize-handle-id][data-panel-group-id="${groupId}"]`));
|
|
347
|
-
}
|
|
507
|
+
|
|
348
508
|
function getResizeHandlePanelIds(groupId, handleId, panelsArray) {
|
|
349
|
-
const handle =
|
|
350
|
-
const handles =
|
|
509
|
+
const handle = getResizeHandleElement(handleId);
|
|
510
|
+
const handles = getResizeHandleElementsForGroup(groupId);
|
|
351
511
|
const index = handle ? handles.indexOf(handle) : -1;
|
|
352
|
-
const idBefore = panelsArray[index]?.
|
|
353
|
-
const idAfter = panelsArray[index + 1]?.
|
|
512
|
+
const idBefore = panelsArray[index]?.id ?? null;
|
|
513
|
+
const idAfter = panelsArray[index + 1]?.id ?? null;
|
|
354
514
|
return [idBefore, idAfter];
|
|
355
515
|
}
|
|
356
|
-
function panelsMapToSortedArray(panels) {
|
|
357
|
-
return Array.from(panels.values()).sort((panelA, panelB) => {
|
|
358
|
-
const orderA = panelA.current.order;
|
|
359
|
-
const orderB = panelB.current.order;
|
|
360
|
-
if (orderA == null && orderB == null) {
|
|
361
|
-
return 0;
|
|
362
|
-
} else if (orderA == null) {
|
|
363
|
-
return -1;
|
|
364
|
-
} else if (orderB == null) {
|
|
365
|
-
return 1;
|
|
366
|
-
} else {
|
|
367
|
-
return orderA - orderB;
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
function safeResizePanel(units, groupSizePixels, panel, prevSize, nextSize, event = null) {
|
|
372
|
-
let {
|
|
373
|
-
collapsedSize,
|
|
374
|
-
collapsible,
|
|
375
|
-
maxSize,
|
|
376
|
-
minSize
|
|
377
|
-
} = panel.current;
|
|
378
|
-
if (units === "pixels") {
|
|
379
|
-
collapsedSize = collapsedSize / groupSizePixels * 100;
|
|
380
|
-
if (maxSize != null) {
|
|
381
|
-
maxSize = maxSize / groupSizePixels * 100;
|
|
382
|
-
}
|
|
383
|
-
minSize = minSize / groupSizePixels * 100;
|
|
384
|
-
}
|
|
385
|
-
if (collapsible) {
|
|
386
|
-
if (prevSize > collapsedSize) {
|
|
387
|
-
// Mimic VS COde behavior; collapse a panel if it's smaller than half of its min-size
|
|
388
|
-
if (nextSize <= minSize / 2 + collapsedSize) {
|
|
389
|
-
return collapsedSize;
|
|
390
|
-
}
|
|
391
|
-
} else {
|
|
392
|
-
const isKeyboardEvent = event?.type?.startsWith("key");
|
|
393
|
-
if (!isKeyboardEvent) {
|
|
394
|
-
// Keyboard events should expand a collapsed panel to the min size,
|
|
395
|
-
// but mouse events should wait until the panel has reached its min size
|
|
396
|
-
// to avoid a visual flickering when dragging between collapsed and min size.
|
|
397
|
-
if (nextSize < minSize) {
|
|
398
|
-
return collapsedSize;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
return Math.min(maxSize != null ? maxSize : 100, Math.max(minSize, nextSize));
|
|
404
|
-
}
|
|
405
|
-
function validatePanelProps(units, panelData) {
|
|
406
|
-
const {
|
|
407
|
-
collapsible,
|
|
408
|
-
defaultSize,
|
|
409
|
-
maxSize,
|
|
410
|
-
minSize
|
|
411
|
-
} = panelData.current;
|
|
412
|
-
|
|
413
|
-
// Basic props validation
|
|
414
|
-
if (minSize < 0 || units === "percentages" && minSize > 100) {
|
|
415
|
-
panelData.current.minSize = 0;
|
|
416
|
-
}
|
|
417
|
-
if (maxSize != null) {
|
|
418
|
-
if (maxSize < 0 || units === "percentages" && maxSize > 100) {
|
|
419
|
-
panelData.current.maxSize = null;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
if (defaultSize !== null) {
|
|
423
|
-
if (defaultSize < 0 || units === "percentages" && defaultSize > 100) {
|
|
424
|
-
panelData.current.defaultSize = null;
|
|
425
|
-
} else if (defaultSize < minSize && !collapsible) {
|
|
426
|
-
panelData.current.defaultSize = minSize;
|
|
427
|
-
} else if (maxSize != null && defaultSize > maxSize) {
|
|
428
|
-
panelData.current.defaultSize = maxSize;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
function validatePanelGroupLayout({
|
|
433
|
-
groupId,
|
|
434
|
-
panels,
|
|
435
|
-
nextSizes,
|
|
436
|
-
prevSizes,
|
|
437
|
-
units
|
|
438
|
-
}) {
|
|
439
|
-
// Clone because this method modifies
|
|
440
|
-
nextSizes = [...nextSizes];
|
|
441
|
-
const panelsArray = panelsMapToSortedArray(panels);
|
|
442
|
-
const groupSizePixels = units === "pixels" ? getAvailableGroupSizePixels(groupId) : NaN;
|
|
443
|
-
let remainingSize = 0;
|
|
444
|
-
|
|
445
|
-
// First, check all of the proposed sizes against the min/max constraints
|
|
446
|
-
for (let index = 0; index < panelsArray.length; index++) {
|
|
447
|
-
const panel = panelsArray[index];
|
|
448
|
-
const prevSize = prevSizes[index];
|
|
449
|
-
const nextSize = nextSizes[index];
|
|
450
|
-
const safeNextSize = safeResizePanel(units, groupSizePixels, panel, prevSize, nextSize);
|
|
451
|
-
if (nextSize != safeNextSize) {
|
|
452
|
-
remainingSize += nextSize - safeNextSize;
|
|
453
|
-
nextSizes[index] = safeNextSize;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// If there is additional, left over space, assign it to any panel(s) that permits it
|
|
458
|
-
// (It's not worth taking multiple additional passes to evenly distribute)
|
|
459
|
-
if (remainingSize.toFixed(3) !== "0.000") {
|
|
460
|
-
for (let index = 0; index < panelsArray.length; index++) {
|
|
461
|
-
const panel = panelsArray[index];
|
|
462
|
-
let {
|
|
463
|
-
maxSize,
|
|
464
|
-
minSize
|
|
465
|
-
} = panel.current;
|
|
466
|
-
if (units === "pixels") {
|
|
467
|
-
minSize = minSize / groupSizePixels * 100;
|
|
468
|
-
if (maxSize != null) {
|
|
469
|
-
maxSize = maxSize / groupSizePixels * 100;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
const size = Math.min(maxSize != null ? maxSize : 100, Math.max(minSize, nextSizes[index] + remainingSize));
|
|
473
|
-
if (size !== nextSizes[index]) {
|
|
474
|
-
remainingSize -= size - nextSizes[index];
|
|
475
|
-
nextSizes[index] = size;
|
|
476
|
-
|
|
477
|
-
// Fuzzy comparison to account for imprecise floating point math
|
|
478
|
-
if (Math.abs(remainingSize).toFixed(3) === "0.000") {
|
|
479
|
-
break;
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// If we still have remainder, the requested layout wasn't valid and we should warn about it
|
|
486
|
-
if (remainingSize.toFixed(3) !== "0.000") ;
|
|
487
|
-
return nextSizes;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
function assert(expectedCondition, message = "Assertion failed!") {
|
|
491
|
-
if (!expectedCondition) {
|
|
492
|
-
console.error(message);
|
|
493
|
-
throw Error(message);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
516
|
|
|
497
517
|
// https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/
|
|
498
518
|
|
|
499
519
|
function useWindowSplitterPanelGroupBehavior({
|
|
500
520
|
committedValuesRef,
|
|
501
521
|
groupId,
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
panelSizeBeforeCollapse
|
|
522
|
+
layout,
|
|
523
|
+
panelDataArray,
|
|
524
|
+
setLayout
|
|
506
525
|
}) {
|
|
526
|
+
useRef({
|
|
527
|
+
didWarnAboutMissingResizeHandle: false
|
|
528
|
+
});
|
|
507
529
|
useEffect(() => {
|
|
508
530
|
const {
|
|
509
531
|
direction,
|
|
510
|
-
|
|
532
|
+
panelDataArray
|
|
511
533
|
} = committedValuesRef.current;
|
|
512
|
-
const groupElement =
|
|
534
|
+
const groupElement = getPanelGroupElement(groupId);
|
|
513
535
|
assert(groupElement != null, `No group found for id "${groupId}"`);
|
|
514
536
|
const {
|
|
515
537
|
height,
|
|
516
538
|
width
|
|
517
539
|
} = groupElement.getBoundingClientRect();
|
|
518
|
-
const handles =
|
|
540
|
+
const handles = getResizeHandleElementsForGroup(groupId);
|
|
519
541
|
const cleanupFunctions = handles.map(handle => {
|
|
520
542
|
const handleId = handle.getAttribute("data-panel-resize-handle-id");
|
|
521
|
-
const
|
|
522
|
-
const [idBefore, idAfter] = getResizeHandlePanelIds(groupId, handleId, panelsArray);
|
|
543
|
+
const [idBefore, idAfter] = getResizeHandlePanelIds(groupId, handleId, panelDataArray);
|
|
523
544
|
if (idBefore == null || idAfter == null) {
|
|
524
545
|
return () => {};
|
|
525
546
|
}
|
|
526
|
-
let currentMinSize = 0;
|
|
527
|
-
let currentMaxSize = 100;
|
|
528
|
-
let totalMinSize = 0;
|
|
529
|
-
let totalMaxSize = 0;
|
|
530
|
-
|
|
531
|
-
// A panel's effective min/max sizes also need to account for other panel's sizes.
|
|
532
|
-
panelsArray.forEach(panelData => {
|
|
533
|
-
const {
|
|
534
|
-
id,
|
|
535
|
-
maxSize,
|
|
536
|
-
minSize
|
|
537
|
-
} = panelData.current;
|
|
538
|
-
if (id === idBefore) {
|
|
539
|
-
currentMinSize = minSize;
|
|
540
|
-
currentMaxSize = maxSize != null ? maxSize : 100;
|
|
541
|
-
} else {
|
|
542
|
-
totalMinSize += minSize;
|
|
543
|
-
totalMaxSize += maxSize != null ? maxSize : 100;
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
const ariaValueMax = Math.min(currentMaxSize, 100 - totalMinSize);
|
|
547
|
-
const ariaValueMin = Math.max(currentMinSize, (panelsArray.length - 1) * 100 - totalMaxSize);
|
|
548
|
-
const flexGrow = getFlexGrow(panels, idBefore, sizes);
|
|
549
|
-
handle.setAttribute("aria-valuemax", "" + Math.round(ariaValueMax));
|
|
550
|
-
handle.setAttribute("aria-valuemin", "" + Math.round(ariaValueMin));
|
|
551
|
-
handle.setAttribute("aria-valuenow", "" + Math.round(parseInt(flexGrow)));
|
|
552
547
|
const onKeyDown = event => {
|
|
553
548
|
if (event.defaultPrevented) {
|
|
554
549
|
return;
|
|
@@ -557,20 +552,32 @@ function useWindowSplitterPanelGroupBehavior({
|
|
|
557
552
|
case "Enter":
|
|
558
553
|
{
|
|
559
554
|
event.preventDefault();
|
|
560
|
-
const index =
|
|
555
|
+
const index = panelDataArray.findIndex(panelData => panelData.id === idBefore);
|
|
561
556
|
if (index >= 0) {
|
|
562
|
-
const panelData =
|
|
563
|
-
const size =
|
|
557
|
+
const panelData = panelDataArray[index];
|
|
558
|
+
const size = layout[index];
|
|
564
559
|
if (size != null) {
|
|
560
|
+
const groupSizePixels = getAvailableGroupSizePixels(groupId);
|
|
561
|
+
const minSize = getPercentageSizeFromMixedSizes({
|
|
562
|
+
sizePercentage: panelData.constraints.minSizePercentage,
|
|
563
|
+
sizePixels: panelData.constraints.minSizePixels
|
|
564
|
+
}, groupSizePixels) ?? 0;
|
|
565
565
|
let delta = 0;
|
|
566
|
-
if (size.toPrecision(PRECISION) <=
|
|
566
|
+
if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
|
|
567
567
|
delta = direction === "horizontal" ? width : height;
|
|
568
568
|
} else {
|
|
569
569
|
delta = -(direction === "horizontal" ? width : height);
|
|
570
570
|
}
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
571
|
+
const nextLayout = adjustLayoutByDelta({
|
|
572
|
+
delta,
|
|
573
|
+
groupSizePixels,
|
|
574
|
+
layout,
|
|
575
|
+
panelConstraints: panelDataArray.map(panelData => panelData.constraints),
|
|
576
|
+
pivotIndices: determinePivotIndices(groupId, handleId),
|
|
577
|
+
trigger: "keyboard"
|
|
578
|
+
});
|
|
579
|
+
if (layout !== nextLayout) {
|
|
580
|
+
setLayout(nextLayout);
|
|
574
581
|
}
|
|
575
582
|
}
|
|
576
583
|
}
|
|
@@ -579,121 +586,83 @@ function useWindowSplitterPanelGroupBehavior({
|
|
|
579
586
|
}
|
|
580
587
|
};
|
|
581
588
|
handle.addEventListener("keydown", onKeyDown);
|
|
582
|
-
const panelBefore = getPanel(idBefore);
|
|
583
|
-
if (panelBefore != null) {
|
|
584
|
-
handle.setAttribute("aria-controls", panelBefore.id);
|
|
585
|
-
}
|
|
586
589
|
return () => {
|
|
587
|
-
handle.removeAttribute("aria-valuemax");
|
|
588
|
-
handle.removeAttribute("aria-valuemin");
|
|
589
|
-
handle.removeAttribute("aria-valuenow");
|
|
590
590
|
handle.removeEventListener("keydown", onKeyDown);
|
|
591
|
-
if (panelBefore != null) {
|
|
592
|
-
handle.removeAttribute("aria-controls");
|
|
593
|
-
}
|
|
594
591
|
};
|
|
595
592
|
});
|
|
596
593
|
return () => {
|
|
597
594
|
cleanupFunctions.forEach(cleanupFunction => cleanupFunction());
|
|
598
595
|
};
|
|
599
|
-
}, [committedValuesRef, groupId,
|
|
596
|
+
}, [committedValuesRef, groupId, layout, panelDataArray, setLayout]);
|
|
600
597
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
if (
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
const handleElement = getResizeHandle(handleId);
|
|
611
|
-
if (handleElement == null) {
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
const onKeyDown = event => {
|
|
615
|
-
if (event.defaultPrevented) {
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
|
-
switch (event.key) {
|
|
619
|
-
case "ArrowDown":
|
|
620
|
-
case "ArrowLeft":
|
|
621
|
-
case "ArrowRight":
|
|
622
|
-
case "ArrowUp":
|
|
623
|
-
case "End":
|
|
624
|
-
case "Home":
|
|
625
|
-
{
|
|
626
|
-
event.preventDefault();
|
|
627
|
-
resizeHandler(event);
|
|
628
|
-
break;
|
|
629
|
-
}
|
|
630
|
-
case "F6":
|
|
631
|
-
{
|
|
632
|
-
event.preventDefault();
|
|
633
|
-
const handles = getResizeHandles();
|
|
634
|
-
const index = getResizeHandleIndex(handleId);
|
|
635
|
-
assert(index !== null);
|
|
636
|
-
const nextIndex = event.shiftKey ? index > 0 ? index - 1 : handles.length - 1 : index + 1 < handles.length ? index + 1 : 0;
|
|
637
|
-
const nextHandle = handles[nextIndex];
|
|
638
|
-
nextHandle.focus();
|
|
639
|
-
break;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
};
|
|
643
|
-
handleElement.addEventListener("keydown", onKeyDown);
|
|
644
|
-
return () => {
|
|
645
|
-
handleElement.removeEventListener("keydown", onKeyDown);
|
|
646
|
-
};
|
|
647
|
-
}, [disabled, handleId, resizeHandler]);
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
function areEqual(arrayA, arrayB) {
|
|
651
|
-
if (arrayA.length !== arrayB.length) {
|
|
652
|
-
return false;
|
|
653
|
-
}
|
|
654
|
-
for (let index = 0; index < arrayA.length; index++) {
|
|
655
|
-
if (arrayA[index] !== arrayB[index]) {
|
|
656
|
-
return false;
|
|
598
|
+
|
|
599
|
+
function areEqual(arrayA, arrayB) {
|
|
600
|
+
if (arrayA.length !== arrayB.length) {
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
for (let index = 0; index < arrayA.length; index++) {
|
|
604
|
+
if (arrayA[index] !== arrayB[index]) {
|
|
605
|
+
return false;
|
|
657
606
|
}
|
|
658
607
|
}
|
|
659
608
|
return true;
|
|
660
609
|
}
|
|
661
610
|
|
|
662
|
-
function
|
|
611
|
+
function isKeyDown(event) {
|
|
612
|
+
return event.type === "keydown";
|
|
613
|
+
}
|
|
614
|
+
function isMouseEvent(event) {
|
|
615
|
+
return event.type.startsWith("mouse");
|
|
616
|
+
}
|
|
617
|
+
function isTouchEvent(event) {
|
|
618
|
+
return event.type.startsWith("touch");
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function getResizeEventCursorPosition(direction, event) {
|
|
663
622
|
const isHorizontal = direction === "horizontal";
|
|
664
|
-
let pointerOffset = 0;
|
|
665
623
|
if (isMouseEvent(event)) {
|
|
666
|
-
|
|
624
|
+
return isHorizontal ? event.clientX : event.clientY;
|
|
667
625
|
} else if (isTouchEvent(event)) {
|
|
668
626
|
const firstTouch = event.touches[0];
|
|
669
|
-
|
|
627
|
+
return isHorizontal ? firstTouch.screenX : firstTouch.screenY;
|
|
670
628
|
} else {
|
|
671
|
-
|
|
629
|
+
throw Error(`Unsupported event type "${event.type}"`);
|
|
672
630
|
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function calculateDragOffsetPercentage(event, dragHandleId, direction, initialDragState) {
|
|
634
|
+
const isHorizontal = direction === "horizontal";
|
|
635
|
+
const handleElement = getResizeHandleElement(dragHandleId);
|
|
636
|
+
const groupId = handleElement.getAttribute("data-panel-group-id");
|
|
637
|
+
let {
|
|
638
|
+
initialCursorPosition
|
|
639
|
+
} = initialDragState;
|
|
640
|
+
const cursorPosition = getResizeEventCursorPosition(direction, event);
|
|
641
|
+
const groupElement = getPanelGroupElement(groupId);
|
|
642
|
+
const groupRect = groupElement.getBoundingClientRect();
|
|
643
|
+
const groupSizeInPixels = isHorizontal ? groupRect.width : groupRect.height;
|
|
644
|
+
const offsetPixels = cursorPosition - initialCursorPosition;
|
|
645
|
+
const offsetPercentage = offsetPixels / groupSizeInPixels * 100;
|
|
646
|
+
return offsetPercentage;
|
|
677
647
|
}
|
|
678
648
|
|
|
679
649
|
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementX
|
|
680
|
-
function
|
|
681
|
-
const {
|
|
682
|
-
dragOffset = 0,
|
|
683
|
-
dragHandleRect,
|
|
684
|
-
sizes: initialSizes
|
|
685
|
-
} = initialDragState || {};
|
|
686
|
-
|
|
687
|
-
// If we're resizing by mouse or touch, use the initial sizes as a base.
|
|
688
|
-
// This has the benefit of causing force-collapsed panels to spring back open if drag is reversed.
|
|
689
|
-
const baseSizes = initialSizes || prevSizes;
|
|
650
|
+
function calculateDeltaPercentage(event, groupId, dragHandleId, direction, initialDragState, keyboardResizeByOptions) {
|
|
690
651
|
if (isKeyDown(event)) {
|
|
691
652
|
const isHorizontal = direction === "horizontal";
|
|
692
|
-
const groupElement =
|
|
653
|
+
const groupElement = getPanelGroupElement(groupId);
|
|
693
654
|
const rect = groupElement.getBoundingClientRect();
|
|
694
655
|
const groupSizeInPixels = isHorizontal ? rect.width : rect.height;
|
|
695
|
-
|
|
696
|
-
|
|
656
|
+
let delta = 0;
|
|
657
|
+
if (event.shiftKey) {
|
|
658
|
+
delta = 100;
|
|
659
|
+
} else if (keyboardResizeByOptions.percentage != null) {
|
|
660
|
+
delta = keyboardResizeByOptions.percentage;
|
|
661
|
+
} else if (keyboardResizeByOptions.pixels != null) {
|
|
662
|
+
delta = keyboardResizeByOptions.pixels / groupSizeInPixels;
|
|
663
|
+
} else {
|
|
664
|
+
delta = 10;
|
|
665
|
+
}
|
|
697
666
|
let movement = 0;
|
|
698
667
|
switch (event.key) {
|
|
699
668
|
case "ArrowDown":
|
|
@@ -709,40 +678,114 @@ function getMovement(event, groupId, handleId, panelsArray, direction, prevSizes
|
|
|
709
678
|
movement = isHorizontal ? 0 : -delta;
|
|
710
679
|
break;
|
|
711
680
|
case "End":
|
|
712
|
-
movement =
|
|
681
|
+
movement = 100;
|
|
713
682
|
break;
|
|
714
683
|
case "Home":
|
|
715
|
-
movement = -
|
|
684
|
+
movement = -100;
|
|
716
685
|
break;
|
|
717
686
|
}
|
|
718
|
-
|
|
719
|
-
// If the Panel being resized is collapsible,
|
|
720
|
-
// we need to special case resizing around the minSize boundary.
|
|
721
|
-
// If contracting, Panels should shrink to their minSize and then snap to fully collapsed.
|
|
722
|
-
// If expanding from collapsed, they should snap back to their minSize.
|
|
723
|
-
const [idBefore, idAfter] = getResizeHandlePanelIds(groupId, handleId, panelsArray);
|
|
724
|
-
const targetPanelId = movement < 0 ? idBefore : idAfter;
|
|
725
|
-
const targetPanelIndex = panelsArray.findIndex(panel => panel.current.id === targetPanelId);
|
|
726
|
-
const targetPanel = panelsArray[targetPanelIndex];
|
|
727
|
-
if (targetPanel.current.collapsible) {
|
|
728
|
-
const baseSize = baseSizes[targetPanelIndex];
|
|
729
|
-
if (baseSize === 0 || baseSize.toPrecision(PRECISION) === targetPanel.current.minSize.toPrecision(PRECISION)) {
|
|
730
|
-
movement = movement < 0 ? -targetPanel.current.minSize * groupSizeInPixels : targetPanel.current.minSize * groupSizeInPixels;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
687
|
return movement;
|
|
734
688
|
} else {
|
|
735
|
-
return
|
|
689
|
+
return calculateDragOffsetPercentage(event, dragHandleId, direction, initialDragState);
|
|
736
690
|
}
|
|
737
691
|
}
|
|
738
|
-
|
|
739
|
-
|
|
692
|
+
|
|
693
|
+
function convertPercentageToPixels(percentage, groupSizePixels) {
|
|
694
|
+
return percentage / 100 * groupSizePixels;
|
|
740
695
|
}
|
|
741
|
-
|
|
742
|
-
|
|
696
|
+
|
|
697
|
+
// Layout should be pre-converted into percentages
|
|
698
|
+
function callPanelCallbacks(groupId, panelsArray, layout, panelIdToLastNotifiedMixedSizesMap) {
|
|
699
|
+
const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
|
|
700
|
+
layout.forEach((sizePercentage, index) => {
|
|
701
|
+
const panelData = panelsArray[index];
|
|
702
|
+
if (!panelData) {
|
|
703
|
+
// Handle initial mount (when panels are registered too late to be in the panels array)
|
|
704
|
+
// The subsequent render+effects will handle the resize notification
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
const {
|
|
708
|
+
callbacks,
|
|
709
|
+
constraints,
|
|
710
|
+
id: panelId
|
|
711
|
+
} = panelData;
|
|
712
|
+
const {
|
|
713
|
+
collapsible
|
|
714
|
+
} = constraints;
|
|
715
|
+
const mixedSizes = {
|
|
716
|
+
sizePercentage,
|
|
717
|
+
sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
|
|
718
|
+
};
|
|
719
|
+
const lastNotifiedMixedSizes = panelIdToLastNotifiedMixedSizesMap[panelId];
|
|
720
|
+
if (lastNotifiedMixedSizes == null || mixedSizes.sizePercentage !== lastNotifiedMixedSizes.sizePercentage || mixedSizes.sizePixels !== lastNotifiedMixedSizes.sizePixels) {
|
|
721
|
+
panelIdToLastNotifiedMixedSizesMap[panelId] = mixedSizes;
|
|
722
|
+
const {
|
|
723
|
+
onCollapse,
|
|
724
|
+
onExpand,
|
|
725
|
+
onResize
|
|
726
|
+
} = callbacks;
|
|
727
|
+
if (onResize) {
|
|
728
|
+
onResize(mixedSizes, lastNotifiedMixedSizes);
|
|
729
|
+
}
|
|
730
|
+
if (collapsible && (onCollapse || onExpand)) {
|
|
731
|
+
const collapsedSize = getPercentageSizeFromMixedSizes({
|
|
732
|
+
sizePercentage: constraints.collapsedSizePercentage,
|
|
733
|
+
sizePixels: constraints.collapsedSizePixels
|
|
734
|
+
}, groupSizePixels) ?? 0;
|
|
735
|
+
const size = getPercentageSizeFromMixedSizes(mixedSizes, groupSizePixels);
|
|
736
|
+
if (onExpand && (lastNotifiedMixedSizes == null || lastNotifiedMixedSizes.sizePercentage === collapsedSize) && size !== collapsedSize) {
|
|
737
|
+
onExpand();
|
|
738
|
+
}
|
|
739
|
+
if (onCollapse && (lastNotifiedMixedSizes == null || lastNotifiedMixedSizes.sizePercentage !== collapsedSize) && size === collapsedSize) {
|
|
740
|
+
onCollapse();
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
});
|
|
743
745
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
+
|
|
747
|
+
function compareLayouts(a, b) {
|
|
748
|
+
if (a.length !== b.length) {
|
|
749
|
+
return false;
|
|
750
|
+
} else {
|
|
751
|
+
for (let index = 0; index < a.length; index++) {
|
|
752
|
+
if (a[index] != b[index]) {
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return true;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// This method returns a number between 1 and 100 representing
|
|
761
|
+
|
|
762
|
+
// the % of the group's overall space this panel should occupy.
|
|
763
|
+
function computePanelFlexBoxStyle({
|
|
764
|
+
dragState,
|
|
765
|
+
layout,
|
|
766
|
+
panelData,
|
|
767
|
+
panelIndex,
|
|
768
|
+
precision = 3
|
|
769
|
+
}) {
|
|
770
|
+
const size = layout[panelIndex];
|
|
771
|
+
let flexGrow;
|
|
772
|
+
if (panelData.length === 1) {
|
|
773
|
+
flexGrow = "100";
|
|
774
|
+
} else if (size == null) {
|
|
775
|
+
flexGrow = "0";
|
|
776
|
+
} else {
|
|
777
|
+
flexGrow = size.toPrecision(precision);
|
|
778
|
+
}
|
|
779
|
+
return {
|
|
780
|
+
flexBasis: 0,
|
|
781
|
+
flexGrow,
|
|
782
|
+
flexShrink: 1,
|
|
783
|
+
// Without this, Panel sizes may be unintentionally overridden by their content
|
|
784
|
+
overflow: "hidden",
|
|
785
|
+
// Disable pointer events inside of a panel during resize
|
|
786
|
+
// This avoid edge cases like nested iframes
|
|
787
|
+
pointerEvents: dragState !== null ? "none" : undefined
|
|
788
|
+
};
|
|
746
789
|
}
|
|
747
790
|
|
|
748
791
|
let currentState = null;
|
|
@@ -796,17 +839,47 @@ function debounce(callback, durationMs = 10) {
|
|
|
796
839
|
return callable;
|
|
797
840
|
}
|
|
798
841
|
|
|
842
|
+
// PanelGroup might be rendering in a server-side environment where localStorage is not available
|
|
843
|
+
// or on a browser with cookies/storage disabled.
|
|
844
|
+
// In either case, this function avoids accessing localStorage until needed,
|
|
845
|
+
// and avoids throwing user-visible errors.
|
|
846
|
+
function initializeDefaultStorage(storageObject) {
|
|
847
|
+
try {
|
|
848
|
+
if (typeof localStorage !== "undefined") {
|
|
849
|
+
// Bypass this check for future calls
|
|
850
|
+
storageObject.getItem = name => {
|
|
851
|
+
return localStorage.getItem(name);
|
|
852
|
+
};
|
|
853
|
+
storageObject.setItem = (name, value) => {
|
|
854
|
+
localStorage.setItem(name, value);
|
|
855
|
+
};
|
|
856
|
+
} else {
|
|
857
|
+
throw new Error("localStorage not supported in this environment");
|
|
858
|
+
}
|
|
859
|
+
} catch (error) {
|
|
860
|
+
console.error(error);
|
|
861
|
+
storageObject.getItem = () => null;
|
|
862
|
+
storageObject.setItem = () => {};
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
799
866
|
// Note that Panel ids might be user-provided (stable) or useId generated (non-deterministic)
|
|
800
867
|
// so they should not be used as part of the serialization key.
|
|
801
|
-
// Using
|
|
868
|
+
// Using the min/max size attributes should work well enough as a backup.
|
|
802
869
|
// Pre-sorting by minSize allows remembering layouts even if panels are re-ordered/dragged.
|
|
803
870
|
function getSerializationKey(panels) {
|
|
804
871
|
return panels.map(panel => {
|
|
805
872
|
const {
|
|
806
|
-
|
|
873
|
+
constraints,
|
|
874
|
+
id,
|
|
875
|
+
idIsFromProps,
|
|
807
876
|
order
|
|
808
|
-
} = panel
|
|
809
|
-
|
|
877
|
+
} = panel;
|
|
878
|
+
if (idIsFromProps) {
|
|
879
|
+
return id;
|
|
880
|
+
} else {
|
|
881
|
+
return `${order}:${JSON.stringify(constraints)}`;
|
|
882
|
+
}
|
|
810
883
|
}).sort((a, b) => a.localeCompare(b)).join(",");
|
|
811
884
|
}
|
|
812
885
|
function loadSerializedPanelGroupState(autoSaveId, storage) {
|
|
@@ -832,31 +905,62 @@ function savePanelGroupLayout(autoSaveId, panels, sizes, storage) {
|
|
|
832
905
|
}
|
|
833
906
|
}
|
|
834
907
|
|
|
835
|
-
|
|
908
|
+
// All units must be in percentages; pixel values should be pre-converted
|
|
909
|
+
function validatePanelGroupLayout({
|
|
910
|
+
groupSizePixels,
|
|
911
|
+
layout: prevLayout,
|
|
912
|
+
panelConstraints
|
|
913
|
+
}) {
|
|
914
|
+
const nextLayout = [...prevLayout];
|
|
836
915
|
|
|
837
|
-
//
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
}
|
|
852
|
-
|
|
916
|
+
// Validate layout expectations
|
|
917
|
+
if (nextLayout.length !== panelConstraints.length) {
|
|
918
|
+
throw Error(`Invalid ${panelConstraints.length} panel layout: ${nextLayout.map(size => `${size}%`).join(", ")}`);
|
|
919
|
+
} else if (!fuzzyNumbersEqual(nextLayout.reduce((accumulated, current) => accumulated + current, 0), 100)) ;
|
|
920
|
+
let remainingSize = 0;
|
|
921
|
+
|
|
922
|
+
// First pass: Validate the proposed layout given each panel's constraints
|
|
923
|
+
for (let index = 0; index < panelConstraints.length; index++) {
|
|
924
|
+
const unsafeSize = nextLayout[index];
|
|
925
|
+
const safeSize = resizePanel({
|
|
926
|
+
groupSizePixels,
|
|
927
|
+
panelConstraints,
|
|
928
|
+
panelIndex: index,
|
|
929
|
+
size: unsafeSize
|
|
930
|
+
});
|
|
931
|
+
if (unsafeSize != safeSize) {
|
|
932
|
+
remainingSize += unsafeSize - safeSize;
|
|
933
|
+
nextLayout[index] = safeSize;
|
|
853
934
|
}
|
|
854
|
-
} catch (error) {
|
|
855
|
-
console.error(error);
|
|
856
|
-
storageObject.getItem = () => null;
|
|
857
|
-
storageObject.setItem = () => {};
|
|
858
935
|
}
|
|
936
|
+
|
|
937
|
+
// If there is additional, left over space, assign it to any panel(s) that permits it
|
|
938
|
+
// (It's not worth taking multiple additional passes to evenly distribute)
|
|
939
|
+
if (!fuzzyNumbersEqual(remainingSize, 0)) {
|
|
940
|
+
for (let index = 0; index < panelConstraints.length; index++) {
|
|
941
|
+
const prevSize = nextLayout[index];
|
|
942
|
+
const unsafeSize = prevSize + remainingSize;
|
|
943
|
+
const safeSize = resizePanel({
|
|
944
|
+
groupSizePixels,
|
|
945
|
+
panelConstraints,
|
|
946
|
+
panelIndex: index,
|
|
947
|
+
size: unsafeSize
|
|
948
|
+
});
|
|
949
|
+
if (prevSize !== safeSize) {
|
|
950
|
+
remainingSize -= safeSize - prevSize;
|
|
951
|
+
nextLayout[index] = safeSize;
|
|
952
|
+
|
|
953
|
+
// Once we've used up the remainder, bail
|
|
954
|
+
if (fuzzyNumbersEqual(remainingSize, 0)) {
|
|
955
|
+
break;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return nextLayout;
|
|
859
961
|
}
|
|
962
|
+
|
|
963
|
+
const LOCAL_STORAGE_DEBOUNCE_INTERVAL = 100;
|
|
860
964
|
const defaultStorage = {
|
|
861
965
|
getItem: name => {
|
|
862
966
|
initializeDefaultStorage(defaultStorage);
|
|
@@ -867,265 +971,328 @@ const defaultStorage = {
|
|
|
867
971
|
defaultStorage.setItem(name, value);
|
|
868
972
|
}
|
|
869
973
|
};
|
|
870
|
-
|
|
871
|
-
// Initial drag state serves a few purposes:
|
|
872
|
-
// * dragOffset:
|
|
873
|
-
// Resize is calculated by the distance between the current pointer event and the resize handle being "dragged"
|
|
874
|
-
// This value accounts for the initial offset when the touch/click starts, so the handle doesn't appear to "jump"
|
|
875
|
-
// * dragHandleRect, sizes:
|
|
876
|
-
// When resizing is done via mouse/touch event– some initial state is stored
|
|
877
|
-
// so that any panels that contract will also expand if drag direction is reversed.
|
|
974
|
+
const debounceMap = {};
|
|
878
975
|
function PanelGroupWithForwardedRef({
|
|
879
976
|
autoSaveId,
|
|
880
|
-
children
|
|
977
|
+
children,
|
|
881
978
|
className: classNameFromProps = "",
|
|
882
979
|
direction,
|
|
883
|
-
disablePointerEventsDuringResize = false,
|
|
884
980
|
forwardedRef,
|
|
885
|
-
id: idFromProps
|
|
886
|
-
onLayout,
|
|
981
|
+
id: idFromProps,
|
|
982
|
+
onLayout = null,
|
|
983
|
+
keyboardResizeByPercentage = null,
|
|
984
|
+
keyboardResizeByPixels = null,
|
|
887
985
|
storage = defaultStorage,
|
|
888
|
-
style: styleFromProps
|
|
889
|
-
tagName: Type = "div"
|
|
890
|
-
units = "percentages"
|
|
986
|
+
style: styleFromProps,
|
|
987
|
+
tagName: Type = "div"
|
|
891
988
|
}) {
|
|
892
989
|
const groupId = useUniqueId(idFromProps);
|
|
893
|
-
const [
|
|
894
|
-
const [
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
// This has the benefit of causing force-collapsed panels to spring back open if drag is reversed.
|
|
899
|
-
const initialDragStateRef = useRef(null);
|
|
900
|
-
useRef({
|
|
901
|
-
didLogDefaultSizeWarning: false,
|
|
902
|
-
didLogIdAndOrderWarning: false,
|
|
903
|
-
didLogInvalidLayoutWarning: false,
|
|
904
|
-
prevPanelIds: []
|
|
905
|
-
});
|
|
906
|
-
|
|
907
|
-
// Use a ref to guard against users passing inline props
|
|
908
|
-
const callbacksRef = useRef({
|
|
909
|
-
onLayout
|
|
910
|
-
});
|
|
911
|
-
useEffect(() => {
|
|
912
|
-
callbacksRef.current.onLayout = onLayout;
|
|
913
|
-
});
|
|
914
|
-
const panelIdToLastNotifiedSizeMapRef = useRef({});
|
|
915
|
-
|
|
916
|
-
// 0-1 values representing the relative size of each panel.
|
|
917
|
-
const [sizes, setSizes] = useState([]);
|
|
918
|
-
|
|
919
|
-
// Used to support imperative collapse/expand API.
|
|
920
|
-
const panelSizeBeforeCollapse = useRef(new Map());
|
|
990
|
+
const [dragState, setDragState] = useState(null);
|
|
991
|
+
const [layout, setLayout] = useState([]);
|
|
992
|
+
const [panelDataArray, setPanelDataArray] = useState([]);
|
|
993
|
+
const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
|
|
994
|
+
const panelSizeBeforeCollapseRef = useRef(new Map());
|
|
921
995
|
const prevDeltaRef = useRef(0);
|
|
922
|
-
|
|
923
|
-
// Store committed values to avoid unnecessarily re-running memoization/effects functions.
|
|
924
996
|
const committedValuesRef = useRef({
|
|
925
997
|
direction,
|
|
998
|
+
dragState,
|
|
926
999
|
id: groupId,
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1000
|
+
keyboardResizeByPercentage,
|
|
1001
|
+
keyboardResizeByPixels,
|
|
1002
|
+
layout,
|
|
1003
|
+
onLayout,
|
|
1004
|
+
panelDataArray
|
|
1005
|
+
});
|
|
1006
|
+
useRef({
|
|
1007
|
+
didLogIdAndOrderWarning: false,
|
|
1008
|
+
didLogPanelConstraintsWarning: false,
|
|
1009
|
+
prevPanelIds: []
|
|
930
1010
|
});
|
|
931
1011
|
useImperativeHandle(forwardedRef, () => ({
|
|
932
|
-
getId: () =>
|
|
933
|
-
getLayout:
|
|
1012
|
+
getId: () => committedValuesRef.current.id,
|
|
1013
|
+
getLayout: () => {
|
|
934
1014
|
const {
|
|
935
|
-
|
|
936
|
-
|
|
1015
|
+
id: groupId,
|
|
1016
|
+
layout
|
|
937
1017
|
} = committedValuesRef.current;
|
|
938
|
-
const
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
}
|
|
1018
|
+
const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
|
|
1019
|
+
return layout.map(sizePercentage => {
|
|
1020
|
+
return {
|
|
1021
|
+
sizePercentage,
|
|
1022
|
+
sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
|
|
1023
|
+
};
|
|
1024
|
+
});
|
|
945
1025
|
},
|
|
946
|
-
setLayout:
|
|
1026
|
+
setLayout: mixedSizes => {
|
|
947
1027
|
const {
|
|
948
1028
|
id: groupId,
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1029
|
+
layout: prevLayout,
|
|
1030
|
+
onLayout,
|
|
1031
|
+
panelDataArray
|
|
952
1032
|
} = committedValuesRef.current;
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
const nextSizes = validatePanelGroupLayout({
|
|
960
|
-
groupId,
|
|
961
|
-
panels,
|
|
962
|
-
nextSizes: sizes,
|
|
963
|
-
prevSizes,
|
|
964
|
-
units
|
|
1033
|
+
const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
|
|
1034
|
+
const unsafeLayout = mixedSizes.map(mixedSize => getPercentageSizeFromMixedSizes(mixedSize, groupSizePixels));
|
|
1035
|
+
const safeLayout = validatePanelGroupLayout({
|
|
1036
|
+
groupSizePixels,
|
|
1037
|
+
layout: unsafeLayout,
|
|
1038
|
+
panelConstraints: panelDataArray.map(panelData => panelData.constraints)
|
|
965
1039
|
});
|
|
966
|
-
if (!areEqual(
|
|
967
|
-
|
|
968
|
-
|
|
1040
|
+
if (!areEqual(prevLayout, safeLayout)) {
|
|
1041
|
+
setLayout(safeLayout);
|
|
1042
|
+
if (onLayout) {
|
|
1043
|
+
onLayout(safeLayout.map(sizePercentage => ({
|
|
1044
|
+
sizePercentage,
|
|
1045
|
+
sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
|
|
1046
|
+
})));
|
|
1047
|
+
}
|
|
1048
|
+
callPanelCallbacks(groupId, panelDataArray, safeLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
|
|
969
1049
|
}
|
|
970
1050
|
}
|
|
971
|
-
}), [
|
|
1051
|
+
}), []);
|
|
972
1052
|
useWindowSplitterPanelGroupBehavior({
|
|
973
1053
|
committedValuesRef,
|
|
974
1054
|
groupId,
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
panelSizeBeforeCollapse
|
|
1055
|
+
layout,
|
|
1056
|
+
panelDataArray,
|
|
1057
|
+
setLayout
|
|
979
1058
|
});
|
|
980
|
-
|
|
981
|
-
// Notify external code when sizes have changed.
|
|
982
|
-
useEffect(() => {
|
|
983
|
-
const {
|
|
984
|
-
onLayout
|
|
985
|
-
} = callbacksRef.current;
|
|
986
|
-
const {
|
|
987
|
-
panels,
|
|
988
|
-
sizes
|
|
989
|
-
} = committedValuesRef.current;
|
|
990
|
-
|
|
991
|
-
// Don't commit layout until all panels have registered and re-rendered with their actual sizes.
|
|
992
|
-
if (sizes.length > 0) {
|
|
993
|
-
if (onLayout) {
|
|
994
|
-
onLayout(sizes);
|
|
995
|
-
}
|
|
996
|
-
const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
|
|
997
|
-
|
|
998
|
-
// When possible, we notify before the next render so that rendering work can be batched together.
|
|
999
|
-
// Some cases are difficult to detect though,
|
|
1000
|
-
// for example– panels that are conditionally rendered can affect the size of neighboring panels.
|
|
1001
|
-
// In this case, the best we can do is notify on commit.
|
|
1002
|
-
// The callPanelCallbacks() uses its own memoization to avoid notifying panels twice in these cases.
|
|
1003
|
-
const panelsArray = panelsMapToSortedArray(panels);
|
|
1004
|
-
callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap);
|
|
1005
|
-
}
|
|
1006
|
-
}, [sizes]);
|
|
1007
1059
|
useEffect(() => {
|
|
1008
1060
|
// If this panel has been configured to persist sizing information, save sizes to local storage.
|
|
1009
1061
|
if (autoSaveId) {
|
|
1010
|
-
if (
|
|
1062
|
+
if (layout.length === 0 || layout.length !== panelDataArray.length) {
|
|
1011
1063
|
return;
|
|
1012
1064
|
}
|
|
1013
|
-
const panelsArray = panelsMapToSortedArray(panels);
|
|
1014
1065
|
|
|
1015
1066
|
// Limit the frequency of localStorage updates.
|
|
1016
1067
|
if (!debounceMap[autoSaveId]) {
|
|
1017
|
-
debounceMap[autoSaveId] = debounce(savePanelGroupLayout,
|
|
1068
|
+
debounceMap[autoSaveId] = debounce(savePanelGroupLayout, LOCAL_STORAGE_DEBOUNCE_INTERVAL);
|
|
1018
1069
|
}
|
|
1019
|
-
debounceMap[autoSaveId](autoSaveId,
|
|
1070
|
+
debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
|
|
1020
1071
|
}
|
|
1021
|
-
}, [autoSaveId,
|
|
1022
|
-
|
|
1072
|
+
}, [autoSaveId, layout, panelDataArray, storage]);
|
|
1073
|
+
|
|
1074
|
+
// DEV warnings
|
|
1075
|
+
useEffect(() => {
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
// External APIs are safe to memoize via committed values ref
|
|
1079
|
+
const collapsePanel = useCallback(panelData => {
|
|
1023
1080
|
const {
|
|
1024
|
-
|
|
1025
|
-
|
|
1081
|
+
layout: prevLayout,
|
|
1082
|
+
onLayout,
|
|
1083
|
+
panelDataArray
|
|
1026
1084
|
} = committedValuesRef.current;
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1085
|
+
if (panelData.constraints.collapsible) {
|
|
1086
|
+
const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
|
|
1087
|
+
const {
|
|
1088
|
+
collapsedSizePercentage,
|
|
1089
|
+
panelSizePercentage,
|
|
1090
|
+
pivotIndices,
|
|
1091
|
+
groupSizePixels
|
|
1092
|
+
} = panelDataHelper(groupId, panelDataArray, panelData, prevLayout);
|
|
1093
|
+
if (panelSizePercentage !== collapsedSizePercentage) {
|
|
1094
|
+
// Store size before collapse;
|
|
1095
|
+
// This is the size that gets restored if the expand() API is used.
|
|
1096
|
+
panelSizeBeforeCollapseRef.current.set(panelData.id, panelSizePercentage);
|
|
1097
|
+
const isLastPanel = panelDataArray.indexOf(panelData) === panelDataArray.length - 1;
|
|
1098
|
+
const delta = isLastPanel ? panelSizePercentage - collapsedSizePercentage : collapsedSizePercentage - panelSizePercentage;
|
|
1099
|
+
const nextLayout = adjustLayoutByDelta({
|
|
1100
|
+
delta,
|
|
1101
|
+
groupSizePixels,
|
|
1102
|
+
layout: prevLayout,
|
|
1103
|
+
panelConstraints: panelConstraintsArray,
|
|
1104
|
+
pivotIndices,
|
|
1105
|
+
trigger: "imperative-api"
|
|
1106
|
+
});
|
|
1107
|
+
if (!compareLayouts(prevLayout, nextLayout)) {
|
|
1108
|
+
setLayout(nextLayout);
|
|
1109
|
+
if (onLayout) {
|
|
1110
|
+
onLayout(nextLayout.map(sizePercentage => ({
|
|
1111
|
+
sizePercentage,
|
|
1112
|
+
sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
|
|
1113
|
+
})));
|
|
1114
|
+
}
|
|
1115
|
+
callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1036
1118
|
}
|
|
1037
|
-
}, [groupId
|
|
1038
|
-
|
|
1119
|
+
}, [groupId]);
|
|
1120
|
+
|
|
1121
|
+
// External APIs are safe to memoize via committed values ref
|
|
1122
|
+
const expandPanel = useCallback(panelData => {
|
|
1039
1123
|
const {
|
|
1040
|
-
|
|
1124
|
+
layout: prevLayout,
|
|
1125
|
+
onLayout,
|
|
1126
|
+
panelDataArray
|
|
1041
1127
|
} = committedValuesRef.current;
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1128
|
+
if (panelData.constraints.collapsible) {
|
|
1129
|
+
const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
|
|
1130
|
+
const {
|
|
1131
|
+
collapsedSizePercentage,
|
|
1132
|
+
panelSizePercentage,
|
|
1133
|
+
minSizePercentage,
|
|
1134
|
+
pivotIndices,
|
|
1135
|
+
groupSizePixels
|
|
1136
|
+
} = panelDataHelper(groupId, panelDataArray, panelData, prevLayout);
|
|
1137
|
+
if (panelSizePercentage === collapsedSizePercentage) {
|
|
1138
|
+
// Restore this panel to the size it was before it was collapsed, if possible.
|
|
1139
|
+
const prevPanelSizePercentage = panelSizeBeforeCollapseRef.current.get(panelData.id);
|
|
1140
|
+
const baseSizePercentage = prevPanelSizePercentage != null ? prevPanelSizePercentage : minSizePercentage;
|
|
1141
|
+
const isLastPanel = panelDataArray.indexOf(panelData) === panelDataArray.length - 1;
|
|
1142
|
+
const delta = isLastPanel ? panelSizePercentage - baseSizePercentage : baseSizePercentage - panelSizePercentage;
|
|
1143
|
+
const nextLayout = adjustLayoutByDelta({
|
|
1144
|
+
delta,
|
|
1145
|
+
groupSizePixels,
|
|
1146
|
+
layout: prevLayout,
|
|
1147
|
+
panelConstraints: panelConstraintsArray,
|
|
1148
|
+
pivotIndices,
|
|
1149
|
+
trigger: "imperative-api"
|
|
1150
|
+
});
|
|
1151
|
+
if (!compareLayouts(prevLayout, nextLayout)) {
|
|
1152
|
+
setLayout(nextLayout);
|
|
1153
|
+
if (onLayout) {
|
|
1154
|
+
onLayout(nextLayout.map(sizePercentage => ({
|
|
1155
|
+
sizePercentage,
|
|
1156
|
+
sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
|
|
1157
|
+
})));
|
|
1158
|
+
}
|
|
1159
|
+
callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1054
1162
|
}
|
|
1055
|
-
|
|
1163
|
+
}, [groupId]);
|
|
1164
|
+
|
|
1165
|
+
// External APIs are safe to memoize via committed values ref
|
|
1166
|
+
const getPanelSize = useCallback(panelData => {
|
|
1167
|
+
const {
|
|
1168
|
+
layout,
|
|
1169
|
+
panelDataArray
|
|
1170
|
+
} = committedValuesRef.current;
|
|
1171
|
+
const {
|
|
1172
|
+
panelSizePercentage,
|
|
1173
|
+
panelSizePixels
|
|
1174
|
+
} = panelDataHelper(groupId, panelDataArray, panelData, layout);
|
|
1056
1175
|
return {
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
flexShrink: 1,
|
|
1060
|
-
// Without this, Panel sizes may be unintentionally overridden by their content.
|
|
1061
|
-
overflow: "hidden",
|
|
1062
|
-
// Disable pointer events inside of a panel during resize.
|
|
1063
|
-
// This avoid edge cases like nested iframes.
|
|
1064
|
-
pointerEvents: disablePointerEventsDuringResize && activeHandleId !== null ? "none" : undefined
|
|
1176
|
+
sizePercentage: panelSizePercentage,
|
|
1177
|
+
sizePixels: panelSizePixels
|
|
1065
1178
|
};
|
|
1066
|
-
}, [
|
|
1067
|
-
|
|
1179
|
+
}, [groupId]);
|
|
1180
|
+
|
|
1181
|
+
// This API should never read from committedValuesRef
|
|
1182
|
+
const getPanelStyle = useCallback(panelData => {
|
|
1183
|
+
const panelIndex = panelDataArray.indexOf(panelData);
|
|
1184
|
+
return computePanelFlexBoxStyle({
|
|
1185
|
+
dragState,
|
|
1186
|
+
layout,
|
|
1187
|
+
panelData: panelDataArray,
|
|
1188
|
+
panelIndex
|
|
1189
|
+
});
|
|
1190
|
+
}, [dragState, layout, panelDataArray]);
|
|
1191
|
+
|
|
1192
|
+
// External APIs are safe to memoize via committed values ref
|
|
1193
|
+
const isPanelCollapsed = useCallback(panelData => {
|
|
1068
1194
|
const {
|
|
1069
|
-
|
|
1195
|
+
layout,
|
|
1196
|
+
panelDataArray
|
|
1070
1197
|
} = committedValuesRef.current;
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1198
|
+
const {
|
|
1199
|
+
collapsedSizePercentage,
|
|
1200
|
+
collapsible,
|
|
1201
|
+
panelSizePercentage
|
|
1202
|
+
} = panelDataHelper(groupId, panelDataArray, panelData, layout);
|
|
1203
|
+
return collapsible === true && panelSizePercentage === collapsedSizePercentage;
|
|
1204
|
+
}, [groupId]);
|
|
1205
|
+
|
|
1206
|
+
// External APIs are safe to memoize via committed values ref
|
|
1207
|
+
const isPanelExpanded = useCallback(panelData => {
|
|
1208
|
+
const {
|
|
1209
|
+
layout,
|
|
1210
|
+
panelDataArray
|
|
1211
|
+
} = committedValuesRef.current;
|
|
1212
|
+
const {
|
|
1213
|
+
collapsedSizePercentage,
|
|
1214
|
+
collapsible,
|
|
1215
|
+
panelSizePercentage
|
|
1216
|
+
} = panelDataHelper(groupId, panelDataArray, panelData, layout);
|
|
1217
|
+
return !collapsible || panelSizePercentage > collapsedSizePercentage;
|
|
1218
|
+
}, [groupId]);
|
|
1219
|
+
const registerPanel = useCallback(panelData => {
|
|
1220
|
+
setPanelDataArray(prevPanelDataArray => {
|
|
1221
|
+
const nextPanelDataArray = [...prevPanelDataArray, panelData];
|
|
1222
|
+
return nextPanelDataArray.sort((panelA, panelB) => {
|
|
1223
|
+
const orderA = panelA.order;
|
|
1224
|
+
const orderB = panelB.order;
|
|
1225
|
+
if (orderA == null && orderB == null) {
|
|
1226
|
+
return 0;
|
|
1227
|
+
} else if (orderA == null) {
|
|
1228
|
+
return -1;
|
|
1229
|
+
} else if (orderB == null) {
|
|
1230
|
+
return 1;
|
|
1231
|
+
} else {
|
|
1232
|
+
return orderA - orderB;
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1079
1235
|
});
|
|
1080
1236
|
}, []);
|
|
1081
|
-
const registerResizeHandle = useCallback(
|
|
1082
|
-
|
|
1237
|
+
const registerResizeHandle = useCallback(dragHandleId => {
|
|
1238
|
+
return function resizeHandler(event) {
|
|
1083
1239
|
event.preventDefault();
|
|
1084
1240
|
const {
|
|
1085
1241
|
direction,
|
|
1086
|
-
|
|
1087
|
-
|
|
1242
|
+
dragState,
|
|
1243
|
+
id: groupId,
|
|
1244
|
+
keyboardResizeByPercentage,
|
|
1245
|
+
keyboardResizeByPixels,
|
|
1246
|
+
onLayout,
|
|
1247
|
+
panelDataArray,
|
|
1248
|
+
layout: prevLayout
|
|
1088
1249
|
} = committedValuesRef.current;
|
|
1089
|
-
const
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1250
|
+
const {
|
|
1251
|
+
initialLayout
|
|
1252
|
+
} = dragState ?? {};
|
|
1253
|
+
const pivotIndices = determinePivotIndices(groupId, dragHandleId);
|
|
1254
|
+
let delta = calculateDeltaPercentage(event, groupId, dragHandleId, direction, dragState, {
|
|
1255
|
+
percentage: keyboardResizeByPercentage,
|
|
1256
|
+
pixels: keyboardResizeByPixels
|
|
1257
|
+
});
|
|
1258
|
+
if (delta === 0) {
|
|
1096
1259
|
return;
|
|
1097
1260
|
}
|
|
1098
|
-
const groupElement = getPanelGroup(groupId);
|
|
1099
|
-
const rect = groupElement.getBoundingClientRect();
|
|
1100
|
-
const isHorizontal = direction === "horizontal";
|
|
1101
1261
|
|
|
1102
1262
|
// Support RTL layouts
|
|
1263
|
+
const isHorizontal = direction === "horizontal";
|
|
1103
1264
|
if (document.dir === "rtl" && isHorizontal) {
|
|
1104
|
-
|
|
1265
|
+
delta = -delta;
|
|
1105
1266
|
}
|
|
1106
|
-
const
|
|
1107
|
-
const
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1267
|
+
const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
|
|
1268
|
+
const panelConstraints = panelDataArray.map(panelData => panelData.constraints);
|
|
1269
|
+
const nextLayout = adjustLayoutByDelta({
|
|
1270
|
+
delta,
|
|
1271
|
+
groupSizePixels,
|
|
1272
|
+
layout: initialLayout ?? prevLayout,
|
|
1273
|
+
panelConstraints,
|
|
1274
|
+
pivotIndices,
|
|
1275
|
+
trigger: isKeyDown(event) ? "keyboard" : "mouse-or-touch"
|
|
1276
|
+
});
|
|
1277
|
+
const layoutChanged = !compareLayouts(prevLayout, nextLayout);
|
|
1113
1278
|
|
|
1114
|
-
//
|
|
1279
|
+
// Only update the cursor for layout changes triggered by touch/mouse events (not keyboard)
|
|
1280
|
+
// Update the cursor even if the layout hasn't changed (we may need to show an invalid cursor state)
|
|
1115
1281
|
if (isMouseEvent(event) || isTouchEvent(event)) {
|
|
1116
1282
|
// Watch for multiple subsequent deltas; this might occur for tiny cursor movements.
|
|
1117
1283
|
// In this case, Panel sizes might not change–
|
|
1118
1284
|
// but updating cursor in this scenario would cause a flicker.
|
|
1119
1285
|
if (prevDeltaRef.current != delta) {
|
|
1120
|
-
|
|
1286
|
+
prevDeltaRef.current = delta;
|
|
1287
|
+
if (!layoutChanged) {
|
|
1121
1288
|
// If the pointer has moved too far to resize the panel any further,
|
|
1122
1289
|
// update the cursor style for a visual clue.
|
|
1123
1290
|
// This mimics VS Code behavior.
|
|
1124
1291
|
|
|
1125
1292
|
if (isHorizontal) {
|
|
1126
|
-
setGlobalCursorStyle(
|
|
1293
|
+
setGlobalCursorStyle(delta < 0 ? "horizontal-min" : "horizontal-max");
|
|
1127
1294
|
} else {
|
|
1128
|
-
setGlobalCursorStyle(
|
|
1295
|
+
setGlobalCursorStyle(delta < 0 ? "vertical-min" : "vertical-max");
|
|
1129
1296
|
}
|
|
1130
1297
|
} else {
|
|
1131
1298
|
// Reset the cursor style to the the normal resize cursor.
|
|
@@ -1133,202 +1300,100 @@ function PanelGroupWithForwardedRef({
|
|
|
1133
1300
|
}
|
|
1134
1301
|
}
|
|
1135
1302
|
}
|
|
1136
|
-
if (
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
callPanelCallbacks(
|
|
1303
|
+
if (layoutChanged) {
|
|
1304
|
+
setLayout(nextLayout);
|
|
1305
|
+
if (onLayout) {
|
|
1306
|
+
onLayout(nextLayout.map(sizePercentage => ({
|
|
1307
|
+
sizePercentage,
|
|
1308
|
+
sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
|
|
1309
|
+
})));
|
|
1310
|
+
}
|
|
1311
|
+
callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
|
|
1145
1312
|
}
|
|
1146
|
-
prevDeltaRef.current = delta;
|
|
1147
1313
|
};
|
|
1148
|
-
return resizeHandler;
|
|
1149
|
-
}, [groupId]);
|
|
1150
|
-
const unregisterPanel = useCallback(id => {
|
|
1151
|
-
setPanels(prevPanels => {
|
|
1152
|
-
if (!prevPanels.has(id)) {
|
|
1153
|
-
return prevPanels;
|
|
1154
|
-
}
|
|
1155
|
-
const nextPanels = new Map(prevPanels);
|
|
1156
|
-
nextPanels.delete(id);
|
|
1157
|
-
return nextPanels;
|
|
1158
|
-
});
|
|
1159
1314
|
}, []);
|
|
1160
|
-
|
|
1315
|
+
|
|
1316
|
+
// External APIs are safe to memoize via committed values ref
|
|
1317
|
+
const resizePanel = useCallback((panelData, mixedSizes) => {
|
|
1161
1318
|
const {
|
|
1162
|
-
|
|
1163
|
-
|
|
1319
|
+
layout: prevLayout,
|
|
1320
|
+
onLayout,
|
|
1321
|
+
panelDataArray
|
|
1164
1322
|
} = committedValuesRef.current;
|
|
1165
|
-
const
|
|
1166
|
-
if (panel == null) {
|
|
1167
|
-
return;
|
|
1168
|
-
}
|
|
1323
|
+
const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
|
|
1169
1324
|
const {
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
const
|
|
1177
|
-
const
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
if (prevSizes !== nextSizes) {
|
|
1195
|
-
const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
|
|
1196
|
-
setSizes(nextSizes);
|
|
1197
|
-
|
|
1198
|
-
// If resize change handlers have been declared, this is the time to call them.
|
|
1199
|
-
// Trigger user callbacks after updating state, so that user code can override the sizes.
|
|
1200
|
-
callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
|
|
1325
|
+
groupSizePixels,
|
|
1326
|
+
panelSizePercentage,
|
|
1327
|
+
pivotIndices
|
|
1328
|
+
} = panelDataHelper(groupId, panelDataArray, panelData, prevLayout);
|
|
1329
|
+
const sizePercentage = getPercentageSizeFromMixedSizes(mixedSizes, groupSizePixels);
|
|
1330
|
+
const isLastPanel = panelDataArray.indexOf(panelData) === panelDataArray.length - 1;
|
|
1331
|
+
const delta = isLastPanel ? panelSizePercentage - sizePercentage : sizePercentage - panelSizePercentage;
|
|
1332
|
+
const nextLayout = adjustLayoutByDelta({
|
|
1333
|
+
delta,
|
|
1334
|
+
groupSizePixels,
|
|
1335
|
+
layout: prevLayout,
|
|
1336
|
+
panelConstraints: panelConstraintsArray,
|
|
1337
|
+
pivotIndices,
|
|
1338
|
+
trigger: "imperative-api"
|
|
1339
|
+
});
|
|
1340
|
+
if (!compareLayouts(prevLayout, nextLayout)) {
|
|
1341
|
+
setLayout(nextLayout);
|
|
1342
|
+
if (onLayout) {
|
|
1343
|
+
onLayout(nextLayout.map(sizePercentage => ({
|
|
1344
|
+
sizePercentage,
|
|
1345
|
+
sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
|
|
1346
|
+
})));
|
|
1347
|
+
}
|
|
1348
|
+
callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
|
|
1201
1349
|
}
|
|
1202
|
-
}, []);
|
|
1203
|
-
const
|
|
1350
|
+
}, [groupId]);
|
|
1351
|
+
const startDragging = useCallback((dragHandleId, event) => {
|
|
1204
1352
|
const {
|
|
1205
|
-
|
|
1206
|
-
|
|
1353
|
+
direction,
|
|
1354
|
+
layout
|
|
1207
1355
|
} = committedValuesRef.current;
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
}
|
|
1216
|
-
const sizeBeforeCollapse = panelSizeBeforeCollapse.current.get(id) || minSize;
|
|
1217
|
-
if (!sizeBeforeCollapse) {
|
|
1218
|
-
return;
|
|
1219
|
-
}
|
|
1220
|
-
const panelsArray = panelsMapToSortedArray(panels);
|
|
1221
|
-
const index = panelsArray.indexOf(panel);
|
|
1222
|
-
if (index < 0) {
|
|
1223
|
-
return;
|
|
1224
|
-
}
|
|
1225
|
-
const currentSize = prevSizes[index];
|
|
1226
|
-
if (currentSize !== collapsedSize) {
|
|
1227
|
-
// Panel is already expanded.
|
|
1228
|
-
return;
|
|
1229
|
-
}
|
|
1230
|
-
const [idBefore, idAfter] = getBeforeAndAfterIds(id, panelsArray);
|
|
1231
|
-
if (idBefore == null || idAfter == null) {
|
|
1232
|
-
return;
|
|
1233
|
-
}
|
|
1234
|
-
const isLastPanel = index === panelsArray.length - 1;
|
|
1235
|
-
const delta = isLastPanel ? collapsedSize - sizeBeforeCollapse : sizeBeforeCollapse;
|
|
1236
|
-
const nextSizes = adjustByDelta(null, committedValuesRef.current, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
|
|
1237
|
-
if (prevSizes !== nextSizes) {
|
|
1238
|
-
const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
|
|
1239
|
-
setSizes(nextSizes);
|
|
1240
|
-
|
|
1241
|
-
// If resize change handlers have been declared, this is the time to call them.
|
|
1242
|
-
// Trigger user callbacks after updating state, so that user code can override the sizes.
|
|
1243
|
-
callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
|
|
1244
|
-
}
|
|
1356
|
+
const handleElement = getResizeHandleElement(dragHandleId);
|
|
1357
|
+
const initialCursorPosition = getResizeEventCursorPosition(direction, event);
|
|
1358
|
+
setDragState({
|
|
1359
|
+
dragHandleId,
|
|
1360
|
+
dragHandleRect: handleElement.getBoundingClientRect(),
|
|
1361
|
+
initialCursorPosition,
|
|
1362
|
+
initialLayout: layout
|
|
1363
|
+
});
|
|
1245
1364
|
}, []);
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
const panel = panels.get(id);
|
|
1258
|
-
if (panel == null) {
|
|
1259
|
-
return;
|
|
1260
|
-
}
|
|
1261
|
-
let {
|
|
1262
|
-
collapsedSize,
|
|
1263
|
-
collapsible,
|
|
1264
|
-
maxSize,
|
|
1265
|
-
minSize
|
|
1266
|
-
} = panel.current;
|
|
1267
|
-
if (units === "pixels") {
|
|
1268
|
-
const groupSizePixels = getAvailableGroupSizePixels(groupId);
|
|
1269
|
-
minSize = minSize / groupSizePixels * 100;
|
|
1270
|
-
if (maxSize != null) {
|
|
1271
|
-
maxSize = maxSize / groupSizePixels * 100;
|
|
1365
|
+
const stopDragging = useCallback(() => {
|
|
1366
|
+
resetGlobalCursorStyle();
|
|
1367
|
+
setDragState(null);
|
|
1368
|
+
}, []);
|
|
1369
|
+
const unregisterPanel = useCallback(panelData => {
|
|
1370
|
+
delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
|
|
1371
|
+
setPanelDataArray(panelDataArray => {
|
|
1372
|
+
const index = panelDataArray.indexOf(panelData);
|
|
1373
|
+
if (index >= 0) {
|
|
1374
|
+
panelDataArray = [...panelDataArray];
|
|
1375
|
+
panelDataArray.splice(index, 1);
|
|
1272
1376
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
const index = panelsArray.indexOf(panel);
|
|
1276
|
-
if (index < 0) {
|
|
1277
|
-
return;
|
|
1278
|
-
}
|
|
1279
|
-
const currentSize = prevSizes[index];
|
|
1280
|
-
if (currentSize === nextSize) {
|
|
1281
|
-
return;
|
|
1282
|
-
}
|
|
1283
|
-
if (collapsible && nextSize === collapsedSize) ; else {
|
|
1284
|
-
nextSize = Math.min(maxSize != null ? maxSize : 100, Math.max(minSize, nextSize));
|
|
1285
|
-
}
|
|
1286
|
-
const [idBefore, idAfter] = getBeforeAndAfterIds(id, panelsArray);
|
|
1287
|
-
if (idBefore == null || idAfter == null) {
|
|
1288
|
-
return;
|
|
1289
|
-
}
|
|
1290
|
-
const isLastPanel = index === panelsArray.length - 1;
|
|
1291
|
-
const delta = isLastPanel ? currentSize - nextSize : nextSize - currentSize;
|
|
1292
|
-
const nextSizes = adjustByDelta(null, committedValuesRef.current, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
|
|
1293
|
-
if (prevSizes !== nextSizes) {
|
|
1294
|
-
const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
|
|
1295
|
-
setSizes(nextSizes);
|
|
1296
|
-
|
|
1297
|
-
// If resize change handlers have been declared, this is the time to call them.
|
|
1298
|
-
// Trigger user callbacks after updating state, so that user code can override the sizes.
|
|
1299
|
-
callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
|
|
1300
|
-
}
|
|
1377
|
+
return panelDataArray;
|
|
1378
|
+
});
|
|
1301
1379
|
}, []);
|
|
1302
1380
|
const context = useMemo(() => ({
|
|
1303
|
-
activeHandleId,
|
|
1304
1381
|
collapsePanel,
|
|
1305
1382
|
direction,
|
|
1383
|
+
dragState,
|
|
1306
1384
|
expandPanel,
|
|
1307
1385
|
getPanelSize,
|
|
1308
1386
|
getPanelStyle,
|
|
1309
1387
|
groupId,
|
|
1388
|
+
isPanelCollapsed,
|
|
1389
|
+
isPanelExpanded,
|
|
1310
1390
|
registerPanel,
|
|
1311
1391
|
registerResizeHandle,
|
|
1312
1392
|
resizePanel,
|
|
1313
|
-
startDragging
|
|
1314
|
-
|
|
1315
|
-
if (isMouseEvent(event) || isTouchEvent(event)) {
|
|
1316
|
-
const handleElement = getResizeHandle(id);
|
|
1317
|
-
initialDragStateRef.current = {
|
|
1318
|
-
dragHandleRect: handleElement.getBoundingClientRect(),
|
|
1319
|
-
dragOffset: getDragOffset(event, id, direction),
|
|
1320
|
-
sizes: committedValuesRef.current.sizes
|
|
1321
|
-
};
|
|
1322
|
-
}
|
|
1323
|
-
},
|
|
1324
|
-
stopDragging: () => {
|
|
1325
|
-
resetGlobalCursorStyle();
|
|
1326
|
-
setActiveHandleId(null);
|
|
1327
|
-
initialDragStateRef.current = null;
|
|
1328
|
-
},
|
|
1329
|
-
units,
|
|
1393
|
+
startDragging,
|
|
1394
|
+
stopDragging,
|
|
1330
1395
|
unregisterPanel
|
|
1331
|
-
}), [
|
|
1396
|
+
}), [collapsePanel, dragState, direction, expandPanel, getPanelSize, getPanelStyle, groupId, isPanelCollapsed, isPanelExpanded, registerPanel, registerResizeHandle, resizePanel, startDragging, stopDragging, unregisterPanel]);
|
|
1332
1397
|
const style = {
|
|
1333
1398
|
display: "flex",
|
|
1334
1399
|
flexDirection: direction === "horizontal" ? "row" : "column",
|
|
@@ -1337,20 +1402,20 @@ function PanelGroupWithForwardedRef({
|
|
|
1337
1402
|
width: "100%"
|
|
1338
1403
|
};
|
|
1339
1404
|
return createElement(PanelGroupContext.Provider, {
|
|
1340
|
-
children: createElement(Type, {
|
|
1341
|
-
children,
|
|
1342
|
-
className: classNameFromProps,
|
|
1343
|
-
"data-panel-group": "",
|
|
1344
|
-
"data-panel-group-direction": direction,
|
|
1345
|
-
"data-panel-group-id": groupId,
|
|
1346
|
-
"data-panel-group-units": units,
|
|
1347
|
-
style: {
|
|
1348
|
-
...style,
|
|
1349
|
-
...styleFromProps
|
|
1350
|
-
}
|
|
1351
|
-
}),
|
|
1352
1405
|
value: context
|
|
1353
|
-
}
|
|
1406
|
+
}, createElement(Type, {
|
|
1407
|
+
children,
|
|
1408
|
+
className: classNameFromProps,
|
|
1409
|
+
style: {
|
|
1410
|
+
...style,
|
|
1411
|
+
...styleFromProps
|
|
1412
|
+
},
|
|
1413
|
+
// CSS selectors
|
|
1414
|
+
"data-panel-group": "",
|
|
1415
|
+
// e2e test attributes
|
|
1416
|
+
"data-panel-group-direction": undefined,
|
|
1417
|
+
"data-panel-group-id": undefined
|
|
1418
|
+
}));
|
|
1354
1419
|
}
|
|
1355
1420
|
const PanelGroup = forwardRef((props, ref) => createElement(PanelGroupWithForwardedRef, {
|
|
1356
1421
|
...props,
|
|
@@ -1358,6 +1423,77 @@ const PanelGroup = forwardRef((props, ref) => createElement(PanelGroupWithForwar
|
|
|
1358
1423
|
}));
|
|
1359
1424
|
PanelGroupWithForwardedRef.displayName = "PanelGroup";
|
|
1360
1425
|
PanelGroup.displayName = "forwardRef(PanelGroup)";
|
|
1426
|
+
function panelDataHelper(groupId, panelDataArray, panelData, layout) {
|
|
1427
|
+
const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
|
|
1428
|
+
const panelIndex = panelDataArray.indexOf(panelData);
|
|
1429
|
+
const panelConstraints = panelConstraintsArray[panelIndex];
|
|
1430
|
+
const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
|
|
1431
|
+
const percentagePanelConstraints = computePercentagePanelConstraints(panelConstraintsArray, panelIndex, groupSizePixels);
|
|
1432
|
+
const isLastPanel = panelIndex === panelDataArray.length - 1;
|
|
1433
|
+
const pivotIndices = isLastPanel ? [panelIndex - 1, panelIndex] : [panelIndex, panelIndex + 1];
|
|
1434
|
+
const panelSizePercentage = layout[panelIndex];
|
|
1435
|
+
const panelSizePixels = convertPercentageToPixels(panelSizePercentage, groupSizePixels);
|
|
1436
|
+
return {
|
|
1437
|
+
...percentagePanelConstraints,
|
|
1438
|
+
collapsible: panelConstraints.collapsible,
|
|
1439
|
+
panelSizePercentage,
|
|
1440
|
+
panelSizePixels,
|
|
1441
|
+
groupSizePixels,
|
|
1442
|
+
pivotIndices
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
// https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/
|
|
1447
|
+
|
|
1448
|
+
function useWindowSplitterResizeHandlerBehavior({
|
|
1449
|
+
disabled,
|
|
1450
|
+
handleId,
|
|
1451
|
+
resizeHandler
|
|
1452
|
+
}) {
|
|
1453
|
+
useEffect(() => {
|
|
1454
|
+
if (disabled || resizeHandler == null) {
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
const handleElement = getResizeHandleElement(handleId);
|
|
1458
|
+
if (handleElement == null) {
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
const onKeyDown = event => {
|
|
1462
|
+
if (event.defaultPrevented) {
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
switch (event.key) {
|
|
1466
|
+
case "ArrowDown":
|
|
1467
|
+
case "ArrowLeft":
|
|
1468
|
+
case "ArrowRight":
|
|
1469
|
+
case "ArrowUp":
|
|
1470
|
+
case "End":
|
|
1471
|
+
case "Home":
|
|
1472
|
+
{
|
|
1473
|
+
event.preventDefault();
|
|
1474
|
+
resizeHandler(event);
|
|
1475
|
+
break;
|
|
1476
|
+
}
|
|
1477
|
+
case "F6":
|
|
1478
|
+
{
|
|
1479
|
+
event.preventDefault();
|
|
1480
|
+
const groupId = handleElement.getAttribute("data-panel-group-id");
|
|
1481
|
+
const handles = getResizeHandleElementsForGroup(groupId);
|
|
1482
|
+
const index = getResizeHandleElementIndex(groupId, handleId);
|
|
1483
|
+
assert(index !== null);
|
|
1484
|
+
const nextIndex = event.shiftKey ? index > 0 ? index - 1 : handles.length - 1 : index + 1 < handles.length ? index + 1 : 0;
|
|
1485
|
+
const nextHandle = handles[nextIndex];
|
|
1486
|
+
nextHandle.focus();
|
|
1487
|
+
break;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
handleElement.addEventListener("keydown", onKeyDown);
|
|
1492
|
+
return () => {
|
|
1493
|
+
handleElement.removeEventListener("keydown", onKeyDown);
|
|
1494
|
+
};
|
|
1495
|
+
}, [disabled, handleId, resizeHandler]);
|
|
1496
|
+
}
|
|
1361
1497
|
|
|
1362
1498
|
function PanelResizeHandle({
|
|
1363
1499
|
children = null,
|
|
@@ -1382,15 +1518,15 @@ function PanelResizeHandle({
|
|
|
1382
1518
|
throw Error(`PanelResizeHandle components must be rendered within a PanelGroup container`);
|
|
1383
1519
|
}
|
|
1384
1520
|
const {
|
|
1385
|
-
activeHandleId,
|
|
1386
1521
|
direction,
|
|
1522
|
+
dragState,
|
|
1387
1523
|
groupId,
|
|
1388
1524
|
registerResizeHandle,
|
|
1389
1525
|
startDragging,
|
|
1390
1526
|
stopDragging
|
|
1391
1527
|
} = panelGroupContext;
|
|
1392
1528
|
const resizeHandleId = useUniqueId(idFromProps);
|
|
1393
|
-
const isDragging =
|
|
1529
|
+
const isDragging = dragState?.dragHandleId === resizeHandleId;
|
|
1394
1530
|
const [isFocused, setIsFocused] = useState(false);
|
|
1395
1531
|
const [resizeHandler, setResizeHandler] = useState(null);
|
|
1396
1532
|
const stopDraggingAndBlur = useCallback(() => {
|
|
@@ -1454,6 +1590,8 @@ function PanelResizeHandle({
|
|
|
1454
1590
|
return createElement(Type, {
|
|
1455
1591
|
children,
|
|
1456
1592
|
className: classNameFromProps,
|
|
1593
|
+
// CSS selectors
|
|
1594
|
+
"data-resize-handle": "",
|
|
1457
1595
|
"data-resize-handle-active": isDragging ? "pointer" : isFocused ? "keyboard" : undefined,
|
|
1458
1596
|
"data-panel-group-direction": direction,
|
|
1459
1597
|
"data-panel-group-id": groupId,
|
|
@@ -1493,4 +1631,4 @@ function PanelResizeHandle({
|
|
|
1493
1631
|
}
|
|
1494
1632
|
PanelResizeHandle.displayName = "PanelResizeHandle";
|
|
1495
1633
|
|
|
1496
|
-
export { Panel, PanelGroup, PanelResizeHandle
|
|
1634
|
+
export { Panel, PanelGroup, PanelResizeHandle };
|