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