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