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