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