react-resizable-panels 0.0.57 → 0.0.59

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 (30) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/declarations/src/Panel.d.ts +7 -3
  3. package/dist/declarations/src/PanelGroup.d.ts +3 -1
  4. package/dist/declarations/src/PanelResizeHandle.d.ts +3 -1
  5. package/dist/declarations/src/types.d.ts +3 -0
  6. package/dist/react-resizable-panels.browser.cjs.js +206 -77
  7. package/dist/react-resizable-panels.browser.development.cjs.js +206 -77
  8. package/dist/react-resizable-panels.browser.development.esm.js +206 -77
  9. package/dist/react-resizable-panels.browser.esm.js +206 -77
  10. package/dist/react-resizable-panels.cjs.js +206 -77
  11. package/dist/react-resizable-panels.cjs.js.map +1 -1
  12. package/dist/react-resizable-panels.development.cjs.js +206 -77
  13. package/dist/react-resizable-panels.development.esm.js +206 -77
  14. package/dist/react-resizable-panels.development.node.cjs.js +175 -75
  15. package/dist/react-resizable-panels.development.node.esm.js +175 -75
  16. package/dist/react-resizable-panels.esm.js +206 -77
  17. package/dist/react-resizable-panels.esm.js.map +1 -1
  18. package/dist/react-resizable-panels.node.cjs.js +175 -75
  19. package/dist/react-resizable-panels.node.esm.js +175 -75
  20. package/package.json +1 -1
  21. package/src/Panel.ts +8 -2
  22. package/src/PanelGroup.ts +89 -3
  23. package/src/PanelResizeHandle.ts +5 -0
  24. package/src/hooks/useWindowSplitterPanelGroupBehavior.ts +15 -15
  25. package/src/types.ts +4 -0
  26. package/src/utils/adjustLayoutByDelta.test.ts +238 -8
  27. package/src/utils/adjustLayoutByDelta.ts +122 -72
  28. package/src/utils/resizePanel.test.ts +61 -1
  29. package/src/utils/resizePanel.ts +7 -1
  30. package/src/utils/validatePanelGroupLayout.test.ts +36 -6
@@ -42,6 +42,7 @@ function PanelWithForwardedRef({
42
42
  collapsedSizePercentage,
43
43
  collapsedSizePixels,
44
44
  collapsible,
45
+ dataAttributes,
45
46
  defaultSizePercentage,
46
47
  defaultSizePixels,
47
48
  forwardedRef,
@@ -127,6 +128,7 @@ function PanelWithForwardedRef({
127
128
  ...style,
128
129
  ...styleFromProps
129
130
  },
131
+ ...dataAttributes,
130
132
  // CSS selectors
131
133
  "data-panel": "",
132
134
  "data-panel-id": panelId,
@@ -142,8 +144,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
142
144
  PanelWithForwardedRef.displayName = "Panel";
143
145
  Panel.displayName = "forwardRef(Panel)";
144
146
 
145
- const PRECISION = 10;
146
-
147
147
  function convertPixelsToPercentage(pixels, groupSizePixels) {
148
148
  return pixels / groupSizePixels * 100;
149
149
  }
@@ -221,6 +221,8 @@ function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, gr
221
221
  };
222
222
  }
223
223
 
224
+ const PRECISION = 10;
225
+
224
226
  function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
225
227
  actual = parseFloat(actual.toFixed(fractionDigits));
226
228
  expected = parseFloat(expected.toFixed(fractionDigits));
@@ -264,7 +266,13 @@ function resizePanel({
264
266
  if (minSizePercentage != null) {
265
267
  if (fuzzyCompareNumbers(size, minSizePercentage) < 0) {
266
268
  if (collapsible) {
267
- size = collapsedSizePercentage;
269
+ // Collapsible panels should snap closed or open only once they cross the halfway point between collapsed and min size.
270
+ const halfwayPoint = (collapsedSizePercentage + minSizePercentage) / 2;
271
+ if (fuzzyCompareNumbers(size, halfwayPoint) < 0) {
272
+ size = collapsedSizePercentage;
273
+ } else {
274
+ size = minSizePercentage;
275
+ }
268
276
  } else {
269
277
  size = minSizePercentage;
270
278
  }
@@ -291,60 +299,123 @@ function adjustLayoutByDelta({
291
299
  const nextLayout = [...prevLayout];
292
300
  let deltaApplied = 0;
293
301
 
302
+ //const DEBUG = [];
303
+ //DEBUG.push(`adjustLayoutByDelta() ${prevLayout.join(", ")}`);
304
+ //DEBUG.push(` delta: ${delta}`);
305
+ //DEBUG.push(` pivotIndices: ${pivotIndices.join(", ")}`);
306
+ //DEBUG.push(` trigger: ${trigger}`);
307
+ //DEBUG.push("");
308
+
294
309
  // A resizing panel affects the panels before or after it.
295
310
  //
296
- // A negative delta means the panel immediately after the resizer should grow/expand by decreasing its offset.
311
+ // A negative delta means the panel(s) immediately after the resize handle should grow/expand by decreasing its offset.
297
312
  // Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
298
313
  //
299
- // A positive delta means the panel immediately before the resizer should "expand".
300
- // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.
314
+ // A positive delta means the panel(s) immediately before the resize handle should "expand".
315
+ // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resize handle.
301
316
 
302
- // First, check the panel we're pivoting around;
303
- // We should only expand or contract by as much as its constraints allow
304
317
  {
305
- const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
306
- const initialSize = nextLayout[pivotIndex];
307
- const {
308
- collapsible
309
- } = panelConstraints[pivotIndex];
310
- const {
311
- collapsedSizePercentage,
312
- minSizePercentage
313
- } = computePercentagePanelConstraints(panelConstraints, pivotIndex, groupSizePixels);
314
- const isCollapsed = collapsible && fuzzyNumbersEqual(initialSize, collapsedSizePercentage);
315
- let unsafeSize = initialSize + Math.abs(delta);
316
- if (isCollapsed) {
317
- switch (trigger) {
318
- case "keyboard":
319
- if (minSizePercentage > unsafeSize) {
320
- unsafeSize = minSizePercentage;
318
+ // If this is a resize triggered by a keyboard event, our logic for expanding/collapsing is different.
319
+ // We no longer check the halfway threshold because this may prevent the panel from expanding at all.
320
+ if (trigger === "keyboard") {
321
+ {
322
+ // Check if we should expand a collapsed panel
323
+ const index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
324
+ const constraints = panelConstraints[index];
325
+ //DEBUG.push(`edge case check 1: ${index}`);
326
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
327
+ if (constraints.collapsible) {
328
+ const prevSize = prevLayout[index];
329
+ const {
330
+ collapsedSizePercentage,
331
+ minSizePercentage
332
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
333
+ if (fuzzyNumbersEqual(prevSize, collapsedSizePercentage)) {
334
+ const localDelta = minSizePercentage - prevSize;
335
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
336
+
337
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
338
+ delta = delta < 0 ? 0 - localDelta : localDelta;
339
+ //DEBUG.push(` -> delta: ${delta}`);
340
+ }
321
341
  }
342
+ }
343
+ }
344
+
345
+ {
346
+ // Check if we should collapse a panel at its minimum size
347
+ const index = delta < 0 ? pivotIndices[0] : pivotIndices[1];
348
+ const constraints = panelConstraints[index];
349
+ //DEBUG.push(`edge case check 2: ${index}`);
350
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
351
+ if (constraints.collapsible) {
352
+ const prevSize = prevLayout[index];
353
+ const {
354
+ collapsedSizePercentage,
355
+ minSizePercentage
356
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
357
+ if (fuzzyNumbersEqual(prevSize, minSizePercentage)) {
358
+ const localDelta = prevSize - collapsedSizePercentage;
359
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
360
+
361
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
362
+ delta = delta < 0 ? 0 - localDelta : localDelta;
363
+ //DEBUG.push(` -> delta: ${delta}`);
364
+ }
365
+ }
366
+ }
322
367
  }
323
368
  }
324
- const safeSize = resizePanel({
325
- groupSizePixels,
326
- panelConstraints,
327
- panelIndex: pivotIndex,
328
- size: unsafeSize
329
- });
330
- if (fuzzyNumbersEqual(initialSize, safeSize)) {
331
- // If there's no room for the pivot panel to grow, we should ignore this change
332
- return nextLayout;
333
- } else {
334
- delta = delta < 0 ? initialSize - safeSize : safeSize - initialSize;
369
+ //DEBUG.push("");
370
+ }
371
+
372
+ {
373
+ // Pre-calculate max available delta in the opposite direction of our pivot.
374
+ // This will be the maximum amount we're allowed to expand/contract the panels in the primary direction.
375
+ // If this amount is less than the requested delta, adjust the requested delta.
376
+ // If this amount is greater than the requested delta, that's useful information too–
377
+ // as an expanding panel might change from collapsed to min size.
378
+
379
+ const increment = delta < 0 ? 1 : -1;
380
+ let index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
381
+ let maxAvailableDelta = 0;
382
+
383
+ //DEBUG.push("pre calc...");
384
+ while (true) {
385
+ const prevSize = prevLayout[index];
386
+ const maxSafeSize = resizePanel({
387
+ groupSizePixels,
388
+ panelConstraints,
389
+ panelIndex: index,
390
+ size: 100
391
+ });
392
+ const delta = maxSafeSize - prevSize;
393
+ //DEBUG.push(` ${index}: ${prevSize} -> ${maxSafeSize}`);
394
+
395
+ maxAvailableDelta += delta;
396
+ index += increment;
397
+ if (index < 0 || index >= panelConstraints.length) {
398
+ break;
399
+ }
335
400
  }
401
+
402
+ //DEBUG.push(` -> max available delta: ${maxAvailableDelta}`);
403
+ const minAbsDelta = Math.min(Math.abs(delta), Math.abs(maxAvailableDelta));
404
+ delta = delta < 0 ? 0 - minAbsDelta : minAbsDelta;
405
+ //DEBUG.push(` -> adjusted delta: ${delta}`);
406
+ //DEBUG.push("");
336
407
  }
337
408
 
338
- // Delta added to a panel needs to be subtracted from other panels
339
- // within the constraints that those panels allow
340
409
  {
410
+ // Delta added to a panel needs to be subtracted from other panels (within the constraints that those panels allow).
411
+
341
412
  const pivotIndex = delta < 0 ? pivotIndices[0] : pivotIndices[1];
342
413
  let index = pivotIndex;
343
414
  while (index >= 0 && index < panelConstraints.length) {
344
415
  const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
345
416
  const prevSize = prevLayout[index];
346
417
  const unsafeSize = prevSize - deltaRemaining;
347
- let safeSize = resizePanel({
418
+ const safeSize = resizePanel({
348
419
  groupSizePixels,
349
420
  panelConstraints,
350
421
  panelIndex: index,
@@ -366,13 +437,18 @@ function adjustLayoutByDelta({
366
437
  }
367
438
  }
368
439
  }
440
+ //DEBUG.push(`after 1: ${nextLayout.join(", ")}`);
441
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
442
+ //DEBUG.push("");
369
443
 
370
444
  // If we were unable to resize any of the panels panels, return the previous state.
371
445
  // This will essentially bailout and ignore e.g. drags past a panel's boundaries
372
446
  if (fuzzyNumbersEqual(deltaApplied, 0)) {
447
+ //console.log(DEBUG.join("\n"));
373
448
  return prevLayout;
374
449
  }
375
450
  {
451
+ // Now distribute the applied delta to the panels in the other direction
376
452
  const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
377
453
  const unsafeSize = prevLayout[pivotIndex] + deltaApplied;
378
454
  const safeSize = resizePanel({
@@ -412,29 +488,21 @@ function adjustLayoutByDelta({
412
488
  index++;
413
489
  }
414
490
  }
415
-
416
- // If we can't redistribute, this layout is invalid;
417
- // There may be an incremental layout that is valid though
418
- if (!fuzzyNumbersEqual(deltaRemaining, 0)) {
419
- try {
420
- return adjustLayoutByDelta({
421
- delta: delta < 0 ? delta + 1 : delta - 1,
422
- groupSizePixels,
423
- layout: prevLayout,
424
- panelConstraints,
425
- pivotIndices,
426
- trigger
427
- });
428
- } catch (error) {
429
- if (error instanceof RangeError) {
430
- console.error(`Could not apply delta ${delta} to layout`);
431
- return prevLayout;
432
- }
433
- } finally {
434
- }
435
- }
436
491
  }
437
492
  }
493
+ //DEBUG.push(`after 2: ${nextLayout.join(", ")}`);
494
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
495
+ //DEBUG.push("");
496
+
497
+ const totalSize = nextLayout.reduce((total, size) => size + total, 0);
498
+ deltaApplied = 100 - totalSize;
499
+ //DEBUG.push(`total size: ${totalSize}`);
500
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
501
+ //console.log(DEBUG.join("\n"));
502
+
503
+ if (!fuzzyNumbersEqual(totalSize, 100)) {
504
+ return prevLayout;
505
+ }
438
506
  return nextLayout;
439
507
  }
440
508
 
@@ -548,15 +616,10 @@ function useWindowSplitterPanelGroupBehavior({
548
616
  });
549
617
  useEffect(() => {
550
618
  const {
551
- direction,
552
619
  panelDataArray
553
620
  } = committedValuesRef.current;
554
621
  const groupElement = getPanelGroupElement(groupId);
555
622
  assert(groupElement != null, `No group found for id "${groupId}"`);
556
- const {
557
- height,
558
- width
559
- } = groupElement.getBoundingClientRect();
560
623
  const handles = getResizeHandleElementsForGroup(groupId);
561
624
  const cleanupFunctions = handles.map(handle => {
562
625
  const handleId = handle.getAttribute("data-panel-resize-handle-id");
@@ -576,21 +639,19 @@ function useWindowSplitterPanelGroupBehavior({
576
639
  if (index >= 0) {
577
640
  const panelData = panelDataArray[index];
578
641
  const size = layout[index];
579
- if (size != null) {
580
- var _getPercentageSizeFro;
642
+ if (size != null && panelData.constraints.collapsible) {
643
+ var _getPercentageSizeFro, _getPercentageSizeFro2;
581
644
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
582
- const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
645
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
646
+ sizePercentage: panelData.constraints.collapsedSizePercentage,
647
+ sizePixels: panelData.constraints.collapsedSizePixels
648
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
649
+ const minSize = (_getPercentageSizeFro2 = getPercentageSizeFromMixedSizes({
583
650
  sizePercentage: panelData.constraints.minSizePercentage,
584
651
  sizePixels: panelData.constraints.minSizePixels
585
- }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
586
- let delta = 0;
587
- if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
588
- delta = direction === "horizontal" ? width : height;
589
- } else {
590
- delta = -(direction === "horizontal" ? width : height);
591
- }
652
+ }, groupSizePixels)) !== null && _getPercentageSizeFro2 !== void 0 ? _getPercentageSizeFro2 : 0;
592
653
  const nextLayout = adjustLayoutByDelta({
593
- delta,
654
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
594
655
  groupSizePixels,
595
656
  layout,
596
657
  panelConstraints: panelDataArray.map(panelData => panelData.constraints),
@@ -998,6 +1059,7 @@ function PanelGroupWithForwardedRef({
998
1059
  autoSaveId,
999
1060
  children,
1000
1061
  className: classNameFromProps = "",
1062
+ dataAttributes,
1001
1063
  direction,
1002
1064
  forwardedRef,
1003
1065
  id: idFromProps,
@@ -1015,6 +1077,7 @@ function PanelGroupWithForwardedRef({
1015
1077
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1016
1078
  const panelSizeBeforeCollapseRef = useRef(new Map());
1017
1079
  const prevDeltaRef = useRef(0);
1080
+ const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1018
1081
  const committedValuesRef = useRef({
1019
1082
  direction,
1020
1083
  dragState,
@@ -1104,6 +1167,17 @@ function PanelGroupWithForwardedRef({
1104
1167
  onLayout,
1105
1168
  panelDataArray
1106
1169
  } = committedValuesRef.current;
1170
+
1171
+ // See issues/211
1172
+ if (panelDataArray.find(({
1173
+ id
1174
+ }) => id === panelData.id) == null) {
1175
+ setImperativeApiQueue(prev => [...prev, {
1176
+ panelData,
1177
+ type: "collapse"
1178
+ }]);
1179
+ return;
1180
+ }
1107
1181
  if (panelData.constraints.collapsible) {
1108
1182
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1109
1183
  const {
@@ -1147,6 +1221,17 @@ function PanelGroupWithForwardedRef({
1147
1221
  onLayout,
1148
1222
  panelDataArray
1149
1223
  } = committedValuesRef.current;
1224
+
1225
+ // See issues/211
1226
+ if (panelDataArray.find(({
1227
+ id
1228
+ }) => id === panelData.id) == null) {
1229
+ setImperativeApiQueue(prev => [...prev, {
1230
+ panelData,
1231
+ type: "expand"
1232
+ }]);
1233
+ return;
1234
+ }
1150
1235
  if (panelData.constraints.collapsible) {
1151
1236
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1152
1237
  const {
@@ -1342,6 +1427,18 @@ function PanelGroupWithForwardedRef({
1342
1427
  onLayout,
1343
1428
  panelDataArray
1344
1429
  } = committedValuesRef.current;
1430
+
1431
+ // See issues/211
1432
+ if (panelDataArray.find(({
1433
+ id
1434
+ }) => id === panelData.id) == null) {
1435
+ setImperativeApiQueue(prev => [...prev, {
1436
+ panelData,
1437
+ mixedSizes,
1438
+ type: "resize"
1439
+ }]);
1440
+ return;
1441
+ }
1345
1442
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1346
1443
  const {
1347
1444
  groupSizePixels,
@@ -1432,6 +1529,7 @@ function PanelGroupWithForwardedRef({
1432
1529
  ...style,
1433
1530
  ...styleFromProps
1434
1531
  },
1532
+ ...dataAttributes,
1435
1533
  // CSS selectors
1436
1534
  "data-panel-group": "",
1437
1535
  "data-panel-group-direction": direction,
@@ -1519,6 +1617,7 @@ function useWindowSplitterResizeHandlerBehavior({
1519
1617
  function PanelResizeHandle({
1520
1618
  children = null,
1521
1619
  className: classNameFromProps = "",
1620
+ dataAttributes,
1522
1621
  disabled = false,
1523
1622
  id: idFromProps = null,
1524
1623
  onDragging,
@@ -1641,6 +1740,7 @@ function PanelResizeHandle({
1641
1740
  ...styleFromProps
1642
1741
  },
1643
1742
  tabIndex: 0,
1743
+ ...dataAttributes,
1644
1744
  // CSS selectors
1645
1745
  "data-panel-group-direction": direction,
1646
1746
  "data-panel-group-id": groupId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-resizable-panels",
3
- "version": "0.0.57",
3
+ "version": "0.0.59",
4
4
  "description": "React components for resizable panel groups/layouts",
5
5
  "author": "Brian Vaughn <brian.david.vaughn@gmail.com>",
6
6
  "license": "MIT",
package/src/Panel.ts CHANGED
@@ -3,7 +3,7 @@ import { isDevelopment } from "#is-development";
3
3
  import { PanelGroupContext } from "./PanelGroupContext";
4
4
  import useIsomorphicLayoutEffect from "./hooks/useIsomorphicEffect";
5
5
  import useUniqueId from "./hooks/useUniqueId";
6
- import { MixedSizes } from "./types";
6
+ import { DataAttributes, MixedSizes } from "./types";
7
7
  import {
8
8
  ElementType,
9
9
  ForwardedRef,
@@ -19,7 +19,7 @@ export type PanelOnCollapse = () => void;
19
19
  export type PanelOnExpand = () => void;
20
20
  export type PanelOnResize = (
21
21
  mixedSizes: MixedSizes,
22
- prevMixedSizes: MixedSizes
22
+ prevMixedSizes: MixedSizes | undefined
23
23
  ) => void;
24
24
 
25
25
  export type PanelCallbacks = {
@@ -53,6 +53,8 @@ export type ImperativePanelHandle = {
53
53
  expand: () => void;
54
54
  getId(): string;
55
55
  getSize(): MixedSizes;
56
+ isCollapsed: () => boolean;
57
+ isExpanded: () => boolean;
56
58
  resize: (size: Partial<MixedSizes>) => void;
57
59
  };
58
60
 
@@ -61,6 +63,7 @@ export type PanelProps = PropsWithChildren<{
61
63
  collapsedSizePercentage?: number | undefined;
62
64
  collapsedSizePixels?: number | undefined;
63
65
  collapsible?: boolean | undefined;
66
+ dataAttributes?: DataAttributes;
64
67
  defaultSizePercentage?: number | undefined;
65
68
  defaultSizePixels?: number | undefined;
66
69
  id?: string;
@@ -82,6 +85,7 @@ export function PanelWithForwardedRef({
82
85
  collapsedSizePercentage,
83
86
  collapsedSizePixels,
84
87
  collapsible,
88
+ dataAttributes,
85
89
  defaultSizePercentage,
86
90
  defaultSizePixels,
87
91
  forwardedRef,
@@ -241,6 +245,8 @@ export function PanelWithForwardedRef({
241
245
  ...styleFromProps,
242
246
  },
243
247
 
248
+ ...dataAttributes,
249
+
244
250
  // CSS selectors
245
251
  "data-panel": "",
246
252
  "data-panel-id": panelId,
package/src/PanelGroup.ts CHANGED
@@ -4,7 +4,7 @@ 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 { Direction, MixedSizes } from "./types";
7
+ import { DataAttributes, Direction, MixedSizes } from "./types";
8
8
  import { adjustLayoutByDelta } from "./utils/adjustLayoutByDelta";
9
9
  import { areEqual } from "./utils/arrays";
10
10
  import { calculateDeltaPercentage } from "./utils/calculateDeltaPercentage";
@@ -72,6 +72,7 @@ const defaultStorage: PanelGroupStorage = {
72
72
  export type PanelGroupProps = PropsWithChildren<{
73
73
  autoSaveId?: string;
74
74
  className?: string;
75
+ dataAttributes?: DataAttributes;
75
76
  direction: Direction;
76
77
  id?: string | null;
77
78
  keyboardResizeByPercentage?: number | null;
@@ -82,6 +83,12 @@ export type PanelGroupProps = PropsWithChildren<{
82
83
  tagName?: ElementType;
83
84
  }>;
84
85
 
86
+ type ImperativeApiQueue = {
87
+ type: "collapse" | "expand" | "resize";
88
+ mixedSizes?: Partial<MixedSizes>;
89
+ panelData: PanelData;
90
+ };
91
+
85
92
  const debounceMap: {
86
93
  [key: string]: typeof savePanelGroupLayout;
87
94
  } = {};
@@ -90,6 +97,7 @@ function PanelGroupWithForwardedRef({
90
97
  autoSaveId,
91
98
  children,
92
99
  className: classNameFromProps = "",
100
+ dataAttributes,
93
101
  direction,
94
102
  forwardedRef,
95
103
  id: idFromProps,
@@ -114,6 +122,10 @@ function PanelGroupWithForwardedRef({
114
122
  const panelSizeBeforeCollapseRef = useRef<Map<string, number>>(new Map());
115
123
  const prevDeltaRef = useRef<number>(0);
116
124
 
125
+ const [imperativeApiQueue, setImperativeApiQueue] = useState<
126
+ ImperativeApiQueue[]
127
+ >([]);
128
+
117
129
  const committedValuesRef = useRef<{
118
130
  direction: Direction;
119
131
  dragState: DragState | null;
@@ -267,8 +279,14 @@ function PanelGroupWithForwardedRef({
267
279
 
268
280
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
269
281
  if (groupSizePixels <= 0) {
270
- // Wait until the group has rendered a non-zero size before computing layout.
271
- return;
282
+ if (
283
+ shouldMonitorPixelBasedConstraints(
284
+ panelDataArray.map(({ constraints }) => constraints)
285
+ )
286
+ ) {
287
+ // Wait until the group has rendered a non-zero size before computing layout.
288
+ return;
289
+ }
272
290
  }
273
291
 
274
292
  if (unsafeLayout == null) {
@@ -440,6 +458,18 @@ function PanelGroupWithForwardedRef({
440
458
  panelDataArray,
441
459
  } = committedValuesRef.current;
442
460
 
461
+ // See issues/211
462
+ if (panelDataArray.find(({ id }) => id === panelData.id) == null) {
463
+ setImperativeApiQueue((prev) => [
464
+ ...prev,
465
+ {
466
+ panelData,
467
+ type: "collapse",
468
+ },
469
+ ]);
470
+ return;
471
+ }
472
+
443
473
  if (panelData.constraints.collapsible) {
444
474
  const panelConstraintsArray = panelDataArray.map(
445
475
  (panelData) => panelData.constraints
@@ -512,6 +542,18 @@ function PanelGroupWithForwardedRef({
512
542
  panelDataArray,
513
543
  } = committedValuesRef.current;
514
544
 
545
+ // See issues/211
546
+ if (panelDataArray.find(({ id }) => id === panelData.id) == null) {
547
+ setImperativeApiQueue((prev) => [
548
+ ...prev,
549
+ {
550
+ panelData,
551
+ type: "expand",
552
+ },
553
+ ]);
554
+ return;
555
+ }
556
+
515
557
  if (panelData.constraints.collapsible) {
516
558
  const panelConstraintsArray = panelDataArray.map(
517
559
  (panelData) => panelData.constraints
@@ -778,6 +820,19 @@ function PanelGroupWithForwardedRef({
778
820
  panelDataArray,
779
821
  } = committedValuesRef.current;
780
822
 
823
+ // See issues/211
824
+ if (panelDataArray.find(({ id }) => id === panelData.id) == null) {
825
+ setImperativeApiQueue((prev) => [
826
+ ...prev,
827
+ {
828
+ panelData,
829
+ mixedSizes,
830
+ type: "resize",
831
+ },
832
+ ]);
833
+ return;
834
+ }
835
+
781
836
  const panelConstraintsArray = panelDataArray.map(
782
837
  (panelData) => panelData.constraints
783
838
  );
@@ -871,6 +926,35 @@ function PanelGroupWithForwardedRef({
871
926
  });
872
927
  }, []);
873
928
 
929
+ // Handle imperative API calls that were made before panels were registered
930
+ useIsomorphicLayoutEffect(() => {
931
+ const queue = imperativeApiQueue;
932
+ while (queue.length > 0) {
933
+ const current = queue.shift()!;
934
+ switch (current.type) {
935
+ case "collapse": {
936
+ collapsePanel(current.panelData);
937
+ break;
938
+ }
939
+ case "expand": {
940
+ expandPanel(current.panelData);
941
+ break;
942
+ }
943
+ case "resize": {
944
+ resizePanel(current.panelData, current.mixedSizes!);
945
+ break;
946
+ }
947
+ }
948
+ }
949
+ }, [
950
+ collapsePanel,
951
+ expandPanel,
952
+ imperativeApiQueue,
953
+ layout,
954
+ panelDataArray,
955
+ resizePanel,
956
+ ]);
957
+
874
958
  const context = useMemo(
875
959
  () => ({
876
960
  collapsePanel,
@@ -927,6 +1011,8 @@ function PanelGroupWithForwardedRef({
927
1011
  ...styleFromProps,
928
1012
  },
929
1013
 
1014
+ ...dataAttributes,
1015
+
930
1016
  // CSS selectors
931
1017
  "data-panel-group": "",
932
1018
  "data-panel-group-direction": direction,
@@ -20,12 +20,14 @@ import {
20
20
  ResizeHandler,
21
21
  } from "./PanelGroupContext";
22
22
  import { getCursorStyle } from "./utils/cursor";
23
+ import { DataAttributes } from "./types";
23
24
 
24
25
  export type PanelResizeHandleOnDragging = (isDragging: boolean) => void;
25
26
 
26
27
  export type PanelResizeHandleProps = {
27
28
  children?: ReactNode;
28
29
  className?: string;
30
+ dataAttributes?: DataAttributes;
29
31
  disabled?: boolean;
30
32
  id?: string | null;
31
33
  onDragging?: PanelResizeHandleOnDragging;
@@ -36,6 +38,7 @@ export type PanelResizeHandleProps = {
36
38
  export function PanelResizeHandle({
37
39
  children = null,
38
40
  className: classNameFromProps = "",
41
+ dataAttributes,
39
42
  disabled = false,
40
43
  id: idFromProps = null,
41
44
  onDragging,
@@ -180,6 +183,8 @@ export function PanelResizeHandle({
180
183
  },
181
184
  tabIndex: 0,
182
185
 
186
+ ...dataAttributes,
187
+
183
188
  // CSS selectors
184
189
  "data-panel-group-direction": direction,
185
190
  "data-panel-group-id": groupId,