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