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