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