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