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