react-resizable-panels 0.0.63 → 1.0.0-rc.2

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.
Files changed (74) hide show
  1. package/.eslintrc.cjs +1 -0
  2. package/CHANGELOG.md +5 -0
  3. package/dist/declarations/src/Panel.d.ts +19 -34
  4. package/dist/declarations/src/PanelGroup.d.ts +9 -13
  5. package/dist/declarations/src/PanelResizeHandle.d.ts +5 -7
  6. package/dist/declarations/src/index.d.ts +2 -2
  7. package/dist/declarations/src/types.d.ts +0 -7
  8. package/dist/declarations/src/utils/assert.d.ts +1 -0
  9. package/dist/declarations/src/vendor/react.d.ts +2 -2
  10. package/dist/react-resizable-panels.browser.cjs.js +253 -518
  11. package/dist/react-resizable-panels.browser.cjs.mjs +2 -1
  12. package/dist/react-resizable-panels.browser.development.cjs.js +279 -574
  13. package/dist/react-resizable-panels.browser.development.cjs.mjs +2 -1
  14. package/dist/react-resizable-panels.browser.development.esm.js +279 -575
  15. package/dist/react-resizable-panels.browser.esm.js +253 -519
  16. package/dist/react-resizable-panels.cjs.d.ts +88 -1
  17. package/dist/react-resizable-panels.cjs.d.ts.map +1 -1
  18. package/dist/react-resizable-panels.cjs.js +1481 -1983
  19. package/dist/react-resizable-panels.cjs.js.map +1 -1
  20. package/dist/react-resizable-panels.cjs.mjs +2 -1
  21. package/dist/react-resizable-panels.development.cjs.js +281 -576
  22. package/dist/react-resizable-panels.development.cjs.mjs +2 -1
  23. package/dist/react-resizable-panels.development.esm.js +281 -577
  24. package/dist/react-resizable-panels.development.node.cjs.js +267 -502
  25. package/dist/react-resizable-panels.development.node.cjs.mjs +2 -1
  26. package/dist/react-resizable-panels.development.node.esm.js +267 -503
  27. package/dist/react-resizable-panels.esm.js +1476 -1959
  28. package/dist/react-resizable-panels.esm.js.map +1 -1
  29. package/dist/react-resizable-panels.node.cjs.js +239 -444
  30. package/dist/react-resizable-panels.node.cjs.mjs +2 -1
  31. package/dist/react-resizable-panels.node.esm.js +239 -445
  32. package/package.json +1 -1
  33. package/src/Panel.test.tsx +74 -73
  34. package/src/Panel.ts +44 -68
  35. package/src/PanelGroup.test.tsx +43 -42
  36. package/src/PanelGroup.ts +221 -411
  37. package/src/PanelGroupContext.ts +2 -3
  38. package/src/PanelResizeHandle.test.tsx +68 -0
  39. package/src/PanelResizeHandle.ts +31 -22
  40. package/src/hooks/useWindowSplitterBehavior.ts +2 -1
  41. package/src/hooks/useWindowSplitterPanelGroupBehavior.ts +22 -33
  42. package/src/index.ts +4 -3
  43. package/src/types.ts +0 -9
  44. package/src/utils/adjustLayoutByDelta.test.ts +206 -336
  45. package/src/utils/adjustLayoutByDelta.ts +59 -51
  46. package/src/utils/assert.ts +1 -1
  47. package/src/utils/calculateAriaValues.test.ts +6 -11
  48. package/src/utils/calculateAriaValues.ts +7 -29
  49. package/src/utils/calculateDeltaPercentage.ts +8 -15
  50. package/src/utils/calculateDragOffsetPercentage.ts +11 -5
  51. package/src/utils/calculateUnsafeDefaultLayout.test.ts +4 -9
  52. package/src/utils/calculateUnsafeDefaultLayout.ts +13 -18
  53. package/src/utils/callPanelCallbacks.ts +11 -46
  54. package/src/utils/getResizeEventCursorPosition.ts +2 -0
  55. package/src/utils/resizePanel.test.ts +6 -52
  56. package/src/utils/resizePanel.ts +24 -46
  57. package/src/utils/test-utils.ts +6 -7
  58. package/src/utils/validatePanelConstraints.test.ts +12 -65
  59. package/src/utils/validatePanelConstraints.ts +26 -67
  60. package/src/utils/validatePanelGroupLayout.test.ts +27 -142
  61. package/src/utils/validatePanelGroupLayout.ts +17 -13
  62. package/src/vendor/react.ts +2 -0
  63. package/src/utils/computePercentagePanelConstraints.test.ts +0 -98
  64. package/src/utils/computePercentagePanelConstraints.ts +0 -56
  65. package/src/utils/convertPercentageToPixels.test.ts +0 -9
  66. package/src/utils/convertPercentageToPixels.ts +0 -6
  67. package/src/utils/convertPixelConstraintsToPercentages.test.ts +0 -47
  68. package/src/utils/convertPixelConstraintsToPercentages.ts +0 -72
  69. package/src/utils/convertPixelsToPercentage.test.ts +0 -9
  70. package/src/utils/convertPixelsToPercentage.ts +0 -6
  71. package/src/utils/getPercentageSizeFromMixedSizes.test.ts +0 -47
  72. package/src/utils/getPercentageSizeFromMixedSizes.ts +0 -15
  73. package/src/utils/shouldMonitorPixelBasedConstraints.test.ts +0 -23
  74. package/src/utils/shouldMonitorPixelBasedConstraints.ts +0 -13
package/src/PanelGroup.ts CHANGED
@@ -4,35 +4,31 @@ import { DragState, PanelGroupContext, ResizeEvent } from "./PanelGroupContext";
4
4
  import useIsomorphicLayoutEffect from "./hooks/useIsomorphicEffect";
5
5
  import useUniqueId from "./hooks/useUniqueId";
6
6
  import { useWindowSplitterPanelGroupBehavior } from "./hooks/useWindowSplitterPanelGroupBehavior";
7
- import { DataAttributes, Direction, MixedSizes } from "./types";
7
+ import { Direction } from "./types";
8
8
  import { adjustLayoutByDelta } from "./utils/adjustLayoutByDelta";
9
9
  import { areEqual } from "./utils/arrays";
10
+ import { assert } from "./utils/assert";
10
11
  import { calculateDeltaPercentage } from "./utils/calculateDeltaPercentage";
11
12
  import { calculateUnsafeDefaultLayout } from "./utils/calculateUnsafeDefaultLayout";
12
13
  import { callPanelCallbacks } from "./utils/callPanelCallbacks";
13
14
  import { compareLayouts } from "./utils/compareLayouts";
14
15
  import { computePanelFlexBoxStyle } from "./utils/computePanelFlexBoxStyle";
15
- import { computePercentagePanelConstraints } from "./utils/computePercentagePanelConstraints";
16
- import { convertPercentageToPixels } from "./utils/convertPercentageToPixels";
17
16
  import { resetGlobalCursorStyle, setGlobalCursorStyle } from "./utils/cursor";
18
17
  import debounce from "./utils/debounce";
19
18
  import { determinePivotIndices } from "./utils/determinePivotIndices";
20
- import { calculateAvailablePanelSizeInPixels } from "./utils/dom/calculateAvailablePanelSizeInPixels";
21
19
  import { getPanelElementsForGroup } from "./utils/dom/getPanelElementsForGroup";
22
- import { getPanelGroupElement } from "./utils/dom/getPanelGroupElement";
23
20
  import { getResizeHandleElement } from "./utils/dom/getResizeHandleElement";
24
21
  import { isKeyDown, isMouseEvent, isTouchEvent } from "./utils/events";
25
- import { getPercentageSizeFromMixedSizes } from "./utils/getPercentageSizeFromMixedSizes";
26
22
  import { getResizeEventCursorPosition } from "./utils/getResizeEventCursorPosition";
27
23
  import { initializeDefaultStorage } from "./utils/initializeDefaultStorage";
28
24
  import { loadPanelLayout, savePanelGroupLayout } from "./utils/serialization";
29
- import { shouldMonitorPixelBasedConstraints } from "./utils/shouldMonitorPixelBasedConstraints";
30
25
  import { validatePanelConstraints } from "./utils/validatePanelConstraints";
31
26
  import { validatePanelGroupLayout } from "./utils/validatePanelGroupLayout";
32
27
  import {
33
28
  CSSProperties,
34
29
  ElementType,
35
30
  ForwardedRef,
31
+ HTMLAttributes,
36
32
  PropsWithChildren,
37
33
  createElement,
38
34
  forwardRef,
@@ -48,8 +44,8 @@ const LOCAL_STORAGE_DEBOUNCE_INTERVAL = 100;
48
44
 
49
45
  export type ImperativePanelGroupHandle = {
50
46
  getId: () => string;
51
- getLayout: () => MixedSizes[];
52
- setLayout: (layout: Partial<MixedSizes>[]) => void;
47
+ getLayout: () => number[];
48
+ setLayout: (layout: number[]) => void;
53
49
  };
54
50
 
55
51
  export type PanelGroupStorage = {
@@ -57,7 +53,7 @@ export type PanelGroupStorage = {
57
53
  setItem(name: string, value: string): void;
58
54
  };
59
55
 
60
- export type PanelGroupOnLayout = (layout: MixedSizes[]) => void;
56
+ export type PanelGroupOnLayout = (layout: number[]) => void;
61
57
 
62
58
  const defaultStorage: PanelGroupStorage = {
63
59
  getItem: (name: string) => {
@@ -70,19 +66,18 @@ const defaultStorage: PanelGroupStorage = {
70
66
  },
71
67
  };
72
68
 
73
- export type PanelGroupProps = PropsWithChildren<{
74
- autoSaveId?: string | null;
75
- className?: string;
76
- dataAttributes?: DataAttributes;
77
- direction: Direction;
78
- id?: string | null;
79
- keyboardResizeByPercentage?: number | null;
80
- keyboardResizeByPixels?: number | null;
81
- onLayout?: PanelGroupOnLayout | null;
82
- storage?: PanelGroupStorage;
83
- style?: CSSProperties;
84
- tagName?: ElementType;
85
- }>;
69
+ export type PanelGroupProps = Omit<HTMLAttributes<ElementType>, "id"> &
70
+ PropsWithChildren<{
71
+ autoSaveId?: string | null;
72
+ className?: string;
73
+ direction: Direction;
74
+ id?: string | null;
75
+ keyboardResizeBy?: number | null;
76
+ onLayout?: PanelGroupOnLayout | null;
77
+ storage?: PanelGroupStorage;
78
+ style?: CSSProperties;
79
+ tagName?: ElementType;
80
+ }>;
86
81
 
87
82
  const debounceMap: {
88
83
  [key: string]: typeof savePanelGroupLayout;
@@ -92,16 +87,15 @@ function PanelGroupWithForwardedRef({
92
87
  autoSaveId = null,
93
88
  children,
94
89
  className: classNameFromProps = "",
95
- dataAttributes,
96
90
  direction,
97
91
  forwardedRef,
98
- id: idFromProps,
92
+ id: idFromProps = null,
99
93
  onLayout = null,
100
- keyboardResizeByPercentage = null,
101
- keyboardResizeByPixels = null,
94
+ keyboardResizeBy = null,
102
95
  storage = defaultStorage,
103
96
  style: styleFromProps,
104
97
  tagName: Type = "div",
98
+ ...rest
105
99
  }: PanelGroupProps & {
106
100
  forwardedRef: ForwardedRef<ImperativePanelGroupHandle>;
107
101
  }) {
@@ -110,9 +104,7 @@ function PanelGroupWithForwardedRef({
110
104
  const [dragState, setDragState] = useState<DragState | null>(null);
111
105
  const [layout, setLayout] = useState<number[]>([]);
112
106
 
113
- const panelIdToLastNotifiedMixedSizesMapRef = useRef<
114
- Record<string, MixedSizes>
115
- >({});
107
+ const panelIdToLastNotifiedSizeMapRef = useRef<Record<string, number>>({});
116
108
  const panelSizeBeforeCollapseRef = useRef<Map<string, number>>(new Map());
117
109
  const prevDeltaRef = useRef<number>(0);
118
110
 
@@ -121,8 +113,7 @@ function PanelGroupWithForwardedRef({
121
113
  direction: Direction;
122
114
  dragState: DragState | null;
123
115
  id: string;
124
- keyboardResizeByPercentage: number | null;
125
- keyboardResizeByPixels: number | null;
116
+ keyboardResizeBy: number | null;
126
117
  onLayout: PanelGroupOnLayout | null;
127
118
  storage: PanelGroupStorage;
128
119
  }>({
@@ -130,8 +121,7 @@ function PanelGroupWithForwardedRef({
130
121
  direction,
131
122
  dragState,
132
123
  id: groupId,
133
- keyboardResizeByPercentage,
134
- keyboardResizeByPixels,
124
+ keyboardResizeBy,
135
125
  onLayout,
136
126
  storage,
137
127
  });
@@ -159,34 +149,15 @@ function PanelGroupWithForwardedRef({
159
149
  () => ({
160
150
  getId: () => committedValuesRef.current.id,
161
151
  getLayout: () => {
162
- const { id: groupId } = committedValuesRef.current;
163
152
  const { layout } = eagerValuesRef.current;
164
153
 
165
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
166
-
167
- return layout.map((sizePercentage) => {
168
- return {
169
- sizePercentage,
170
- sizePixels: convertPercentageToPixels(
171
- sizePercentage,
172
- groupSizePixels
173
- ),
174
- };
175
- });
154
+ return layout;
176
155
  },
177
- setLayout: (mixedSizes: Partial<MixedSizes>[]) => {
178
- const { id: groupId, onLayout } = committedValuesRef.current;
156
+ setLayout: (unsafeLayout: number[]) => {
157
+ const { onLayout } = committedValuesRef.current;
179
158
  const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
180
159
 
181
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
182
-
183
- const unsafeLayout = mixedSizes.map(
184
- (mixedSize) =>
185
- getPercentageSizeFromMixedSizes(mixedSize, groupSizePixels)!
186
- );
187
-
188
160
  const safeLayout = validatePanelGroupLayout({
189
- groupSizePixels,
190
161
  layout: unsafeLayout,
191
162
  panelConstraints: panelDataArray.map(
192
163
  (panelData) => panelData.constraints
@@ -199,22 +170,13 @@ function PanelGroupWithForwardedRef({
199
170
  eagerValuesRef.current.layout = safeLayout;
200
171
 
201
172
  if (onLayout) {
202
- onLayout(
203
- safeLayout.map((sizePercentage) => ({
204
- sizePercentage,
205
- sizePixels: convertPercentageToPixels(
206
- sizePercentage,
207
- groupSizePixels
208
- ),
209
- }))
210
- );
173
+ onLayout(safeLayout);
211
174
  }
212
175
 
213
176
  callPanelCallbacks(
214
- groupId,
215
177
  panelDataArray,
216
178
  safeLayout,
217
- panelIdToLastNotifiedMixedSizesMapRef.current
179
+ panelIdToLastNotifiedSizeMapRef.current
218
180
  );
219
181
  }
220
182
  },
@@ -229,9 +191,6 @@ function PanelGroupWithForwardedRef({
229
191
  committedValuesRef.current.id = groupId;
230
192
  committedValuesRef.current.onLayout = onLayout;
231
193
  committedValuesRef.current.storage = storage;
232
-
233
- // panelDataArray and layout are updated in-sync with scheduled state updates.
234
- // TODO [217] Move these values into a separate ref
235
194
  });
236
195
 
237
196
  useWindowSplitterPanelGroupBehavior({
@@ -252,77 +211,21 @@ function PanelGroupWithForwardedRef({
252
211
  return;
253
212
  }
254
213
 
214
+ let debouncedSave = debounceMap[autoSaveId];
215
+
255
216
  // Limit the frequency of localStorage updates.
256
- if (!debounceMap[autoSaveId]) {
257
- debounceMap[autoSaveId] = debounce(
217
+ if (debouncedSave == null) {
218
+ debouncedSave = debounce(
258
219
  savePanelGroupLayout,
259
220
  LOCAL_STORAGE_DEBOUNCE_INTERVAL
260
221
  );
261
- }
262
- debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
263
- }
264
- }, [autoSaveId, layout, storage]);
265
-
266
- useIsomorphicLayoutEffect(() => {
267
- const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
268
-
269
- const constraints = panelDataArray.map(({ constraints }) => constraints);
270
- if (!shouldMonitorPixelBasedConstraints(constraints)) {
271
- // Avoid the overhead of ResizeObserver if no pixel constraints require monitoring
272
- return;
273
- }
274
-
275
- if (typeof ResizeObserver === "undefined") {
276
- console.warn(
277
- `WARNING: Pixel based constraints require ResizeObserver but it is not supported by the current browser.`
278
- );
279
- } else {
280
- const resizeObserver = new ResizeObserver(() => {
281
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
282
222
 
283
- const { onLayout } = committedValuesRef.current;
284
-
285
- const nextLayout = validatePanelGroupLayout({
286
- groupSizePixels,
287
- layout: prevLayout,
288
- panelConstraints: panelDataArray.map(
289
- (panelData) => panelData.constraints
290
- ),
291
- });
292
-
293
- if (!areEqual(prevLayout, nextLayout)) {
294
- setLayout(nextLayout);
295
-
296
- eagerValuesRef.current.layout = nextLayout;
297
-
298
- if (onLayout) {
299
- onLayout(
300
- nextLayout.map((sizePercentage) => ({
301
- sizePercentage,
302
- sizePixels: convertPercentageToPixels(
303
- sizePercentage,
304
- groupSizePixels
305
- ),
306
- }))
307
- );
308
- }
309
-
310
- callPanelCallbacks(
311
- groupId,
312
- panelDataArray,
313
- nextLayout,
314
- panelIdToLastNotifiedMixedSizesMapRef.current
315
- );
316
- }
317
- });
318
-
319
- resizeObserver.observe(getPanelGroupElement(groupId)!);
223
+ debounceMap[autoSaveId] = debouncedSave;
224
+ }
320
225
 
321
- return () => {
322
- resizeObserver.disconnect();
323
- };
226
+ debouncedSave(autoSaveId, panelDataArray, layout, storage);
324
227
  }
325
- }, [groupId]);
228
+ }, [autoSaveId, layout, storage]);
326
229
 
327
230
  // DEV warnings
328
231
  useEffect(() => {
@@ -362,17 +265,17 @@ function PanelGroupWithForwardedRef({
362
265
  (panelData) => panelData.constraints
363
266
  );
364
267
 
365
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
366
-
367
268
  for (
368
269
  let panelIndex = 0;
369
270
  panelIndex < panelConstraints.length;
370
271
  panelIndex++
371
272
  ) {
273
+ const panelData = panelDataArray[panelIndex];
274
+ assert(panelData);
275
+
372
276
  const isValid = validatePanelConstraints({
373
- groupSizePixels,
374
277
  panelConstraints,
375
- panelId: panelDataArray[panelIndex].id,
278
+ panelId: panelData.id,
376
279
  panelIndex,
377
280
  });
378
281
 
@@ -387,177 +290,139 @@ function PanelGroupWithForwardedRef({
387
290
  });
388
291
 
389
292
  // External APIs are safe to memoize via committed values ref
390
- const collapsePanel = useCallback(
391
- (panelData: PanelData) => {
392
- const { onLayout } = committedValuesRef.current;
393
- const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
293
+ const collapsePanel = useCallback((panelData: PanelData) => {
294
+ const { onLayout } = committedValuesRef.current;
295
+ const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
394
296
 
395
- if (panelData.constraints.collapsible) {
396
- const panelConstraintsArray = panelDataArray.map(
397
- (panelData) => panelData.constraints
398
- );
297
+ if (panelData.constraints.collapsible) {
298
+ const panelConstraintsArray = panelDataArray.map(
299
+ (panelData) => panelData.constraints
300
+ );
399
301
 
400
- const {
401
- collapsedSizePercentage,
402
- panelSizePercentage,
403
- pivotIndices,
404
- groupSizePixels,
405
- } = panelDataHelper(groupId, panelDataArray, panelData, prevLayout);
406
-
407
- if (panelSizePercentage !== collapsedSizePercentage) {
408
- // Store size before collapse;
409
- // This is the size that gets restored if the expand() API is used.
410
- panelSizeBeforeCollapseRef.current.set(
411
- panelData.id,
412
- panelSizePercentage
413
- );
302
+ const {
303
+ collapsedSize = 0,
304
+ panelSize,
305
+ pivotIndices,
306
+ } = panelDataHelper(panelDataArray, panelData, prevLayout);
414
307
 
415
- const isLastPanel =
416
- panelDataArray.indexOf(panelData) === panelDataArray.length - 1;
417
- const delta = isLastPanel
418
- ? panelSizePercentage - collapsedSizePercentage
419
- : collapsedSizePercentage - panelSizePercentage;
420
-
421
- const nextLayout = adjustLayoutByDelta({
422
- delta,
423
- groupSizePixels,
424
- layout: prevLayout,
425
- panelConstraints: panelConstraintsArray,
426
- pivotIndices,
427
- trigger: "imperative-api",
428
- });
308
+ assert(panelSize != null);
429
309
 
430
- if (!compareLayouts(prevLayout, nextLayout)) {
431
- setLayout(nextLayout);
310
+ if (panelSize !== collapsedSize) {
311
+ // Store size before collapse;
312
+ // This is the size that gets restored if the expand() API is used.
313
+ panelSizeBeforeCollapseRef.current.set(panelData.id, panelSize);
432
314
 
433
- eagerValuesRef.current.layout = nextLayout;
315
+ const isLastPanel =
316
+ findPanelDataIndex(panelDataArray, panelData) ===
317
+ panelDataArray.length - 1;
318
+ const delta = isLastPanel
319
+ ? panelSize - collapsedSize
320
+ : collapsedSize - panelSize;
434
321
 
435
- if (onLayout) {
436
- onLayout(
437
- nextLayout.map((sizePercentage) => ({
438
- sizePercentage,
439
- sizePixels: convertPercentageToPixels(
440
- sizePercentage,
441
- groupSizePixels
442
- ),
443
- }))
444
- );
445
- }
322
+ const nextLayout = adjustLayoutByDelta({
323
+ delta,
324
+ layout: prevLayout,
325
+ panelConstraints: panelConstraintsArray,
326
+ pivotIndices,
327
+ trigger: "imperative-api",
328
+ });
446
329
 
447
- callPanelCallbacks(
448
- groupId,
449
- panelDataArray,
450
- nextLayout,
451
- panelIdToLastNotifiedMixedSizesMapRef.current
452
- );
330
+ if (!compareLayouts(prevLayout, nextLayout)) {
331
+ setLayout(nextLayout);
332
+
333
+ eagerValuesRef.current.layout = nextLayout;
334
+
335
+ if (onLayout) {
336
+ onLayout(nextLayout);
453
337
  }
338
+
339
+ callPanelCallbacks(
340
+ panelDataArray,
341
+ nextLayout,
342
+ panelIdToLastNotifiedSizeMapRef.current
343
+ );
454
344
  }
455
345
  }
456
- },
457
- [groupId]
458
- );
346
+ }
347
+ }, []);
459
348
 
460
349
  // External APIs are safe to memoize via committed values ref
461
- const expandPanel = useCallback(
462
- (panelData: PanelData) => {
463
- const { onLayout } = committedValuesRef.current;
464
- const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
350
+ const expandPanel = useCallback((panelData: PanelData) => {
351
+ const { onLayout } = committedValuesRef.current;
352
+ const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
465
353
 
466
- if (panelData.constraints.collapsible) {
467
- const panelConstraintsArray = panelDataArray.map(
468
- (panelData) => panelData.constraints
354
+ if (panelData.constraints.collapsible) {
355
+ const panelConstraintsArray = panelDataArray.map(
356
+ (panelData) => panelData.constraints
357
+ );
358
+
359
+ const {
360
+ collapsedSize = 0,
361
+ panelSize,
362
+ minSize = 0,
363
+ pivotIndices,
364
+ } = panelDataHelper(panelDataArray, panelData, prevLayout);
365
+
366
+ if (panelSize === collapsedSize) {
367
+ // Restore this panel to the size it was before it was collapsed, if possible.
368
+ const prevPanelSize = panelSizeBeforeCollapseRef.current.get(
369
+ panelData.id
469
370
  );
470
371
 
471
- const {
472
- collapsedSizePercentage,
473
- panelSizePercentage,
474
- minSizePercentage,
475
- pivotIndices,
476
- groupSizePixels,
477
- } = panelDataHelper(groupId, panelDataArray, panelData, prevLayout);
478
-
479
- if (panelSizePercentage === collapsedSizePercentage) {
480
- // Restore this panel to the size it was before it was collapsed, if possible.
481
- const prevPanelSizePercentage =
482
- panelSizeBeforeCollapseRef.current.get(panelData.id);
483
-
484
- const baseSizePercentage =
485
- prevPanelSizePercentage != null &&
486
- prevPanelSizePercentage >= minSizePercentage
487
- ? prevPanelSizePercentage
488
- : minSizePercentage;
489
-
490
- const isLastPanel =
491
- panelDataArray.indexOf(panelData) === panelDataArray.length - 1;
492
- const delta = isLastPanel
493
- ? panelSizePercentage - baseSizePercentage
494
- : baseSizePercentage - panelSizePercentage;
495
-
496
- const nextLayout = adjustLayoutByDelta({
497
- delta,
498
- groupSizePixels,
499
- layout: prevLayout,
500
- panelConstraints: panelConstraintsArray,
501
- pivotIndices,
502
- trigger: "imperative-api",
503
- });
372
+ const baseSize =
373
+ prevPanelSize != null && prevPanelSize >= minSize
374
+ ? prevPanelSize
375
+ : minSize;
504
376
 
505
- if (!compareLayouts(prevLayout, nextLayout)) {
506
- setLayout(nextLayout);
377
+ const isLastPanel =
378
+ findPanelDataIndex(panelDataArray, panelData) ===
379
+ panelDataArray.length - 1;
380
+ const delta = isLastPanel ? panelSize - baseSize : baseSize - panelSize;
507
381
 
508
- eagerValuesRef.current.layout = nextLayout;
382
+ const nextLayout = adjustLayoutByDelta({
383
+ delta,
384
+ layout: prevLayout,
385
+ panelConstraints: panelConstraintsArray,
386
+ pivotIndices,
387
+ trigger: "imperative-api",
388
+ });
509
389
 
510
- if (onLayout) {
511
- onLayout(
512
- nextLayout.map((sizePercentage) => ({
513
- sizePercentage,
514
- sizePixels: convertPercentageToPixels(
515
- sizePercentage,
516
- groupSizePixels
517
- ),
518
- }))
519
- );
520
- }
390
+ if (!compareLayouts(prevLayout, nextLayout)) {
391
+ setLayout(nextLayout);
521
392
 
522
- callPanelCallbacks(
523
- groupId,
524
- panelDataArray,
525
- nextLayout,
526
- panelIdToLastNotifiedMixedSizesMapRef.current
527
- );
393
+ eagerValuesRef.current.layout = nextLayout;
394
+
395
+ if (onLayout) {
396
+ onLayout(nextLayout);
528
397
  }
398
+
399
+ callPanelCallbacks(
400
+ panelDataArray,
401
+ nextLayout,
402
+ panelIdToLastNotifiedSizeMapRef.current
403
+ );
529
404
  }
530
405
  }
531
- },
532
- [groupId]
533
- );
406
+ }
407
+ }, []);
534
408
 
535
409
  // External APIs are safe to memoize via committed values ref
536
- const getPanelSize = useCallback(
537
- (panelData: PanelData) => {
538
- const { layout, panelDataArray } = eagerValuesRef.current;
410
+ const getPanelSize = useCallback((panelData: PanelData) => {
411
+ const { layout, panelDataArray } = eagerValuesRef.current;
539
412
 
540
- const { panelSizePercentage, panelSizePixels } = panelDataHelper(
541
- groupId,
542
- panelDataArray,
543
- panelData,
544
- layout
545
- );
413
+ const { panelSize } = panelDataHelper(panelDataArray, panelData, layout);
546
414
 
547
- return {
548
- sizePercentage: panelSizePercentage,
549
- sizePixels: panelSizePixels,
550
- };
551
- },
552
- [groupId]
553
- );
415
+ assert(panelSize != null);
416
+
417
+ return panelSize;
418
+ }, []);
554
419
 
555
420
  // This API should never read from committedValuesRef
556
421
  const getPanelStyle = useCallback(
557
422
  (panelData: PanelData) => {
558
423
  const { panelDataArray } = eagerValuesRef.current;
559
424
 
560
- const panelIndex = panelDataArray.indexOf(panelData);
425
+ const panelIndex = findPanelDataIndex(panelDataArray, panelData);
561
426
 
562
427
  return computePanelFlexBoxStyle({
563
428
  dragState,
@@ -570,32 +435,32 @@ function PanelGroupWithForwardedRef({
570
435
  );
571
436
 
572
437
  // External APIs are safe to memoize via committed values ref
573
- const isPanelCollapsed = useCallback(
574
- (panelData: PanelData) => {
575
- const { layout, panelDataArray } = eagerValuesRef.current;
438
+ const isPanelCollapsed = useCallback((panelData: PanelData) => {
439
+ const { layout, panelDataArray } = eagerValuesRef.current;
576
440
 
577
- const { collapsedSizePercentage, collapsible, panelSizePercentage } =
578
- panelDataHelper(groupId, panelDataArray, panelData, layout);
441
+ const { collapsedSize, collapsible, panelSize } = panelDataHelper(
442
+ panelDataArray,
443
+ panelData,
444
+ layout
445
+ );
579
446
 
580
- return (
581
- collapsible === true && panelSizePercentage === collapsedSizePercentage
582
- );
583
- },
584
- [groupId]
585
- );
447
+ return collapsible === true && panelSize === collapsedSize;
448
+ }, []);
586
449
 
587
450
  // External APIs are safe to memoize via committed values ref
588
- const isPanelExpanded = useCallback(
589
- (panelData: PanelData) => {
590
- const { layout, panelDataArray } = eagerValuesRef.current;
451
+ const isPanelExpanded = useCallback((panelData: PanelData) => {
452
+ const { layout, panelDataArray } = eagerValuesRef.current;
591
453
 
592
- const { collapsedSizePercentage, collapsible, panelSizePercentage } =
593
- panelDataHelper(groupId, panelDataArray, panelData, layout);
454
+ const {
455
+ collapsedSize = 0,
456
+ collapsible,
457
+ panelSize,
458
+ } = panelDataHelper(panelDataArray, panelData, layout);
594
459
 
595
- return !collapsible || panelSizePercentage > collapsedSizePercentage;
596
- },
597
- [groupId]
598
- );
460
+ assert(panelSize != null);
461
+
462
+ return !collapsible || panelSize > collapsedSize;
463
+ }, []);
599
464
 
600
465
  const registerPanel = useCallback((panelData: PanelData) => {
601
466
  const {
@@ -606,6 +471,19 @@ function PanelGroupWithForwardedRef({
606
471
  } = committedValuesRef.current;
607
472
  const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
608
473
 
474
+ // HACK
475
+ // This appears to be triggered by some React Suspense+Offscreen+StrictMode bug;
476
+ // see app.replay.io/recording/17b6e11d-4500-4173-b23d-61dfd141fed1
477
+ const index = findPanelDataIndex(panelDataArray, panelData);
478
+ if (index >= 0) {
479
+ if (panelData.idIsFromProps) {
480
+ console.warn(`Panel with id "${panelData.id}" registered twice`);
481
+ } else {
482
+ console.warn(`Panel registered twice`);
483
+ }
484
+ return;
485
+ }
486
+
609
487
  panelDataArray.push(panelData);
610
488
  panelDataArray.sort((panelA, panelB) => {
611
489
  const orderA = panelA.order;
@@ -635,21 +513,8 @@ function PanelGroupWithForwardedRef({
635
513
  unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
636
514
  }
637
515
 
638
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
639
- if (groupSizePixels <= 0) {
640
- if (
641
- shouldMonitorPixelBasedConstraints(
642
- panelDataArray.map(({ constraints }) => constraints)
643
- )
644
- ) {
645
- // Wait until the group has rendered a non-zero size before computing layout.
646
- return;
647
- }
648
- }
649
-
650
516
  if (unsafeLayout == null) {
651
517
  unsafeLayout = calculateUnsafeDefaultLayout({
652
- groupSizePixels,
653
518
  panelDataArray,
654
519
  });
655
520
  }
@@ -657,7 +522,6 @@ function PanelGroupWithForwardedRef({
657
522
  // Validate even saved layouts in case something has changed since last render
658
523
  // e.g. for pixel groups, this could be the size of the window
659
524
  const nextLayout = validatePanelGroupLayout({
660
- groupSizePixels,
661
525
  layout: unsafeLayout,
662
526
  panelConstraints: panelDataArray.map(
663
527
  (panelData) => panelData.constraints
@@ -673,22 +537,13 @@ function PanelGroupWithForwardedRef({
673
537
 
674
538
  if (!areEqual(prevLayout, nextLayout)) {
675
539
  if (onLayout) {
676
- onLayout(
677
- nextLayout.map((sizePercentage) => ({
678
- sizePercentage,
679
- sizePixels: convertPercentageToPixels(
680
- sizePercentage,
681
- groupSizePixels
682
- ),
683
- }))
684
- );
540
+ onLayout(nextLayout);
685
541
  }
686
542
 
687
543
  callPanelCallbacks(
688
- groupId,
689
544
  panelDataArray,
690
545
  nextLayout,
691
- panelIdToLastNotifiedMixedSizesMapRef.current
546
+ panelIdToLastNotifiedSizeMapRef.current
692
547
  );
693
548
  }
694
549
  }, []);
@@ -701,8 +556,7 @@ function PanelGroupWithForwardedRef({
701
556
  direction,
702
557
  dragState,
703
558
  id: groupId,
704
- keyboardResizeByPercentage,
705
- keyboardResizeByPixels,
559
+ keyboardResizeBy,
706
560
  onLayout,
707
561
  } = committedValuesRef.current;
708
562
  const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
@@ -713,14 +567,10 @@ function PanelGroupWithForwardedRef({
713
567
 
714
568
  let delta = calculateDeltaPercentage(
715
569
  event,
716
- groupId,
717
570
  dragHandleId,
718
571
  direction,
719
- dragState!,
720
- {
721
- percentage: keyboardResizeByPercentage,
722
- pixels: keyboardResizeByPixels,
723
- }
572
+ dragState,
573
+ keyboardResizeBy
724
574
  );
725
575
  if (delta === 0) {
726
576
  return;
@@ -732,14 +582,12 @@ function PanelGroupWithForwardedRef({
732
582
  delta = -delta;
733
583
  }
734
584
 
735
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
736
585
  const panelConstraints = panelDataArray.map(
737
586
  (panelData) => panelData.constraints
738
587
  );
739
588
 
740
589
  const nextLayout = adjustLayoutByDelta({
741
590
  delta,
742
- groupSizePixels,
743
591
  layout: initialLayout ?? prevLayout,
744
592
  panelConstraints,
745
593
  pivotIndices,
@@ -782,22 +630,13 @@ function PanelGroupWithForwardedRef({
782
630
  eagerValuesRef.current.layout = nextLayout;
783
631
 
784
632
  if (onLayout) {
785
- onLayout(
786
- nextLayout.map((sizePercentage) => ({
787
- sizePercentage,
788
- sizePixels: convertPercentageToPixels(
789
- sizePercentage,
790
- groupSizePixels
791
- ),
792
- }))
793
- );
633
+ onLayout(nextLayout);
794
634
  }
795
635
 
796
636
  callPanelCallbacks(
797
- groupId,
798
637
  panelDataArray,
799
638
  nextLayout,
800
- panelIdToLastNotifiedMixedSizesMapRef.current
639
+ panelIdToLastNotifiedSizeMapRef.current
801
640
  );
802
641
  }
803
642
  };
@@ -805,7 +644,7 @@ function PanelGroupWithForwardedRef({
805
644
 
806
645
  // External APIs are safe to memoize via committed values ref
807
646
  const resizePanel = useCallback(
808
- (panelData: PanelData, mixedSizes: Partial<MixedSizes>) => {
647
+ (panelData: PanelData, unsafePanelSize: number) => {
809
648
  const { onLayout } = committedValuesRef.current;
810
649
 
811
650
  const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
@@ -814,23 +653,23 @@ function PanelGroupWithForwardedRef({
814
653
  (panelData) => panelData.constraints
815
654
  );
816
655
 
817
- const { groupSizePixels, panelSizePercentage, pivotIndices } =
818
- panelDataHelper(groupId, panelDataArray, panelData, prevLayout);
656
+ const { panelSize, pivotIndices } = panelDataHelper(
657
+ panelDataArray,
658
+ panelData,
659
+ prevLayout
660
+ );
819
661
 
820
- const sizePercentage = getPercentageSizeFromMixedSizes(
821
- mixedSizes,
822
- groupSizePixels
823
- )!;
662
+ assert(panelSize != null);
824
663
 
825
664
  const isLastPanel =
826
- panelDataArray.indexOf(panelData) === panelDataArray.length - 1;
665
+ findPanelDataIndex(panelDataArray, panelData) ===
666
+ panelDataArray.length - 1;
827
667
  const delta = isLastPanel
828
- ? panelSizePercentage - sizePercentage
829
- : sizePercentage - panelSizePercentage;
668
+ ? panelSize - unsafePanelSize
669
+ : unsafePanelSize - panelSize;
830
670
 
831
671
  const nextLayout = adjustLayoutByDelta({
832
672
  delta,
833
- groupSizePixels,
834
673
  layout: prevLayout,
835
674
  panelConstraints: panelConstraintsArray,
836
675
  pivotIndices,
@@ -843,26 +682,17 @@ function PanelGroupWithForwardedRef({
843
682
  eagerValuesRef.current.layout = nextLayout;
844
683
 
845
684
  if (onLayout) {
846
- onLayout(
847
- nextLayout.map((sizePercentage) => ({
848
- sizePercentage,
849
- sizePixels: convertPercentageToPixels(
850
- sizePercentage,
851
- groupSizePixels
852
- ),
853
- }))
854
- );
685
+ onLayout(nextLayout);
855
686
  }
856
687
 
857
688
  callPanelCallbacks(
858
- groupId,
859
689
  panelDataArray,
860
690
  nextLayout,
861
- panelIdToLastNotifiedMixedSizesMapRef.current
691
+ panelIdToLastNotifiedSizeMapRef.current
862
692
  );
863
693
  }
864
694
  },
865
- [groupId]
695
+ []
866
696
  );
867
697
 
868
698
  const startDragging = useCallback(
@@ -870,7 +700,8 @@ function PanelGroupWithForwardedRef({
870
700
  const { direction } = committedValuesRef.current;
871
701
  const { layout } = eagerValuesRef.current;
872
702
 
873
- const handleElement = getResizeHandleElement(dragHandleId)!;
703
+ const handleElement = getResizeHandleElement(dragHandleId);
704
+ assert(handleElement);
874
705
 
875
706
  const initialCursorPosition = getResizeEventCursorPosition(
876
707
  direction,
@@ -900,10 +731,10 @@ function PanelGroupWithForwardedRef({
900
731
  timeout: null,
901
732
  });
902
733
  const unregisterPanel = useCallback((panelData: PanelData) => {
903
- const { id: groupId, onLayout } = committedValuesRef.current;
734
+ const { onLayout } = committedValuesRef.current;
904
735
  const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
905
736
 
906
- const index = panelDataArray.indexOf(panelData);
737
+ const index = findPanelDataIndex(panelDataArray, panelData);
907
738
  if (index >= 0) {
908
739
  panelDataArray.splice(index, 1);
909
740
  unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
@@ -918,7 +749,8 @@ function PanelGroupWithForwardedRef({
918
749
  // We can't check the DOM to detect this because Panel elements have not yet been removed.
919
750
  unregisterPanelRef.current.timeout = setTimeout(() => {
920
751
  const { pendingPanelIds } = unregisterPanelRef.current;
921
- const map = panelIdToLastNotifiedMixedSizesMapRef.current;
752
+ const panelIdToLastNotifiedSizeMap =
753
+ panelIdToLastNotifiedSizeMapRef.current;
922
754
 
923
755
  // TRICKY
924
756
  // Strict effects mode
@@ -926,18 +758,18 @@ function PanelGroupWithForwardedRef({
926
758
  pendingPanelIds.forEach((panelId) => {
927
759
  pendingPanelIds.delete(panelId);
928
760
 
929
- if (panelDataArray.find(({ id }) => id === panelId) == null) {
761
+ if (panelDataArray.find(({ id }) => id === panelId) != null) {
930
762
  unmountDueToStrictMode = true;
931
-
763
+ } else {
932
764
  // TRICKY
933
765
  // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
934
766
  // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
935
767
  // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
936
- delete map[panelData.id];
768
+ delete panelIdToLastNotifiedSizeMap[panelId];
937
769
  }
938
770
  });
939
771
 
940
- if (!unmountDueToStrictMode) {
772
+ if (unmountDueToStrictMode) {
941
773
  return;
942
774
  }
943
775
 
@@ -946,17 +778,13 @@ function PanelGroupWithForwardedRef({
946
778
  return;
947
779
  }
948
780
 
949
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
950
-
951
781
  let unsafeLayout: number[] = calculateUnsafeDefaultLayout({
952
- groupSizePixels,
953
782
  panelDataArray,
954
783
  });
955
784
 
956
785
  // Validate even saved layouts in case something has changed since last render
957
786
  // e.g. for pixel groups, this could be the size of the window
958
787
  const nextLayout = validatePanelGroupLayout({
959
- groupSizePixels,
960
788
  layout: unsafeLayout,
961
789
  panelConstraints: panelDataArray.map(
962
790
  (panelData) => panelData.constraints
@@ -969,22 +797,13 @@ function PanelGroupWithForwardedRef({
969
797
  eagerValuesRef.current.layout = nextLayout;
970
798
 
971
799
  if (onLayout) {
972
- onLayout(
973
- nextLayout.map((sizePercentage) => ({
974
- sizePercentage,
975
- sizePixels: convertPercentageToPixels(
976
- sizePercentage,
977
- groupSizePixels
978
- ),
979
- }))
980
- );
800
+ onLayout(nextLayout);
981
801
  }
982
802
 
983
803
  callPanelCallbacks(
984
- groupId,
985
804
  panelDataArray,
986
805
  nextLayout,
987
- panelIdToLastNotifiedMixedSizesMapRef.current
806
+ panelIdToLastNotifiedSizeMapRef.current
988
807
  );
989
808
  }
990
809
  }, 0);
@@ -1039,6 +858,8 @@ function PanelGroupWithForwardedRef({
1039
858
  PanelGroupContext.Provider,
1040
859
  { value: context },
1041
860
  createElement(Type, {
861
+ ...rest,
862
+
1042
863
  children,
1043
864
  className: classNameFromProps,
1044
865
  style: {
@@ -1046,8 +867,6 @@ function PanelGroupWithForwardedRef({
1046
867
  ...styleFromProps,
1047
868
  },
1048
869
 
1049
- ...dataAttributes,
1050
-
1051
870
  // CSS selectors
1052
871
  "data-panel-group": "",
1053
872
  "data-panel-group-direction": direction,
@@ -1066,8 +885,14 @@ export const PanelGroup = forwardRef<
1066
885
  PanelGroupWithForwardedRef.displayName = "PanelGroup";
1067
886
  PanelGroup.displayName = "forwardRef(PanelGroup)";
1068
887
 
888
+ function findPanelDataIndex(panelDataArray: PanelData[], panelData: PanelData) {
889
+ return panelDataArray.findIndex(
890
+ (prevPanelData) =>
891
+ prevPanelData === panelData || prevPanelData.id === panelData.id
892
+ );
893
+ }
894
+
1069
895
  function panelDataHelper(
1070
- groupId: string,
1071
896
  panelDataArray: PanelData[],
1072
897
  panelData: PanelData,
1073
898
  layout: number[]
@@ -1076,34 +901,19 @@ function panelDataHelper(
1076
901
  (panelData) => panelData.constraints
1077
902
  );
1078
903
 
1079
- const panelIndex = panelDataArray.indexOf(panelData);
904
+ const panelIndex = findPanelDataIndex(panelDataArray, panelData);
1080
905
  const panelConstraints = panelConstraintsArray[panelIndex];
1081
906
 
1082
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1083
-
1084
- const percentagePanelConstraints = computePercentagePanelConstraints(
1085
- panelConstraintsArray,
1086
- panelIndex,
1087
- groupSizePixels
1088
- );
1089
-
1090
907
  const isLastPanel = panelIndex === panelDataArray.length - 1;
1091
908
  const pivotIndices = isLastPanel
1092
909
  ? [panelIndex - 1, panelIndex]
1093
910
  : [panelIndex, panelIndex + 1];
1094
911
 
1095
- const panelSizePercentage = layout[panelIndex];
1096
- const panelSizePixels = convertPercentageToPixels(
1097
- panelSizePercentage,
1098
- groupSizePixels
1099
- );
912
+ const panelSize = layout[panelIndex];
1100
913
 
1101
914
  return {
1102
- ...percentagePanelConstraints,
1103
- collapsible: panelConstraints.collapsible,
1104
- panelSizePercentage,
1105
- panelSizePixels,
1106
- groupSizePixels,
915
+ ...panelConstraints,
916
+ panelSize,
1107
917
  pivotIndices,
1108
918
  };
1109
919
  }