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