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