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
@@ -70,6 +70,7 @@ function PanelWithForwardedRef({
70
70
  collapsedSizePercentage,
71
71
  collapsedSizePixels,
72
72
  collapsible,
73
+ dataAttributes,
73
74
  defaultSizePercentage,
74
75
  defaultSizePixels,
75
76
  forwardedRef,
@@ -194,6 +195,7 @@ function PanelWithForwardedRef({
194
195
  ...style,
195
196
  ...styleFromProps
196
197
  },
198
+ ...dataAttributes,
197
199
  // CSS selectors
198
200
  "data-panel": "",
199
201
  "data-panel-id": panelId,
@@ -209,8 +211,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
209
211
  PanelWithForwardedRef.displayName = "Panel";
210
212
  Panel.displayName = "forwardRef(Panel)";
211
213
 
212
- const PRECISION = 10;
213
-
214
214
  function convertPixelsToPercentage(pixels, groupSizePixels) {
215
215
  return pixels / groupSizePixels * 100;
216
216
  }
@@ -288,6 +288,8 @@ function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, gr
288
288
  };
289
289
  }
290
290
 
291
+ const PRECISION = 10;
292
+
291
293
  function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
292
294
  actual = parseFloat(actual.toFixed(fractionDigits));
293
295
  expected = parseFloat(expected.toFixed(fractionDigits));
@@ -331,7 +333,13 @@ function resizePanel({
331
333
  if (minSizePercentage != null) {
332
334
  if (fuzzyCompareNumbers(size, minSizePercentage) < 0) {
333
335
  if (collapsible) {
334
- size = collapsedSizePercentage;
336
+ // Collapsible panels should snap closed or open only once they cross the halfway point between collapsed and min size.
337
+ const halfwayPoint = (collapsedSizePercentage + minSizePercentage) / 2;
338
+ if (fuzzyCompareNumbers(size, halfwayPoint) < 0) {
339
+ size = collapsedSizePercentage;
340
+ } else {
341
+ size = minSizePercentage;
342
+ }
335
343
  } else {
336
344
  size = minSizePercentage;
337
345
  }
@@ -358,60 +366,123 @@ function adjustLayoutByDelta({
358
366
  const nextLayout = [...prevLayout];
359
367
  let deltaApplied = 0;
360
368
 
369
+ //const DEBUG = [];
370
+ //DEBUG.push(`adjustLayoutByDelta() ${prevLayout.join(", ")}`);
371
+ //DEBUG.push(` delta: ${delta}`);
372
+ //DEBUG.push(` pivotIndices: ${pivotIndices.join(", ")}`);
373
+ //DEBUG.push(` trigger: ${trigger}`);
374
+ //DEBUG.push("");
375
+
361
376
  // A resizing panel affects the panels before or after it.
362
377
  //
363
- // A negative delta means the panel immediately after the resizer should grow/expand by decreasing its offset.
378
+ // A negative delta means the panel(s) immediately after the resize handle should grow/expand by decreasing its offset.
364
379
  // Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
365
380
  //
366
- // A positive delta means the panel immediately before the resizer should "expand".
367
- // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.
381
+ // A positive delta means the panel(s) immediately before the resize handle should "expand".
382
+ // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resize handle.
368
383
 
369
- // First, check the panel we're pivoting around;
370
- // We should only expand or contract by as much as its constraints allow
371
384
  {
372
- const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
373
- const initialSize = nextLayout[pivotIndex];
374
- const {
375
- collapsible
376
- } = panelConstraints[pivotIndex];
377
- const {
378
- collapsedSizePercentage,
379
- minSizePercentage
380
- } = computePercentagePanelConstraints(panelConstraints, pivotIndex, groupSizePixels);
381
- const isCollapsed = collapsible && fuzzyNumbersEqual(initialSize, collapsedSizePercentage);
382
- let unsafeSize = initialSize + Math.abs(delta);
383
- if (isCollapsed) {
384
- switch (trigger) {
385
- case "keyboard":
386
- if (minSizePercentage > unsafeSize) {
387
- unsafeSize = minSizePercentage;
385
+ // If this is a resize triggered by a keyboard event, our logic for expanding/collapsing is different.
386
+ // We no longer check the halfway threshold because this may prevent the panel from expanding at all.
387
+ if (trigger === "keyboard") {
388
+ {
389
+ // Check if we should expand a collapsed panel
390
+ const index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
391
+ const constraints = panelConstraints[index];
392
+ //DEBUG.push(`edge case check 1: ${index}`);
393
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
394
+ if (constraints.collapsible) {
395
+ const prevSize = prevLayout[index];
396
+ const {
397
+ collapsedSizePercentage,
398
+ minSizePercentage
399
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
400
+ if (fuzzyNumbersEqual(prevSize, collapsedSizePercentage)) {
401
+ const localDelta = minSizePercentage - prevSize;
402
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
403
+
404
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
405
+ delta = delta < 0 ? 0 - localDelta : localDelta;
406
+ //DEBUG.push(` -> delta: ${delta}`);
407
+ }
408
+ }
409
+ }
410
+ }
411
+
412
+ {
413
+ // Check if we should collapse a panel at its minimum size
414
+ const index = delta < 0 ? pivotIndices[0] : pivotIndices[1];
415
+ const constraints = panelConstraints[index];
416
+ //DEBUG.push(`edge case check 2: ${index}`);
417
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
418
+ if (constraints.collapsible) {
419
+ const prevSize = prevLayout[index];
420
+ const {
421
+ collapsedSizePercentage,
422
+ minSizePercentage
423
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
424
+ if (fuzzyNumbersEqual(prevSize, minSizePercentage)) {
425
+ const localDelta = prevSize - collapsedSizePercentage;
426
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
427
+
428
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
429
+ delta = delta < 0 ? 0 - localDelta : localDelta;
430
+ //DEBUG.push(` -> delta: ${delta}`);
431
+ }
388
432
  }
433
+ }
389
434
  }
390
435
  }
391
- const safeSize = resizePanel({
392
- groupSizePixels,
393
- panelConstraints,
394
- panelIndex: pivotIndex,
395
- size: unsafeSize
396
- });
397
- if (fuzzyNumbersEqual(initialSize, safeSize)) {
398
- // If there's no room for the pivot panel to grow, we should ignore this change
399
- return nextLayout;
400
- } else {
401
- delta = delta < 0 ? initialSize - safeSize : safeSize - initialSize;
436
+ //DEBUG.push("");
437
+ }
438
+
439
+ {
440
+ // Pre-calculate max available delta in the opposite direction of our pivot.
441
+ // This will be the maximum amount we're allowed to expand/contract the panels in the primary direction.
442
+ // If this amount is less than the requested delta, adjust the requested delta.
443
+ // If this amount is greater than the requested delta, that's useful information too–
444
+ // as an expanding panel might change from collapsed to min size.
445
+
446
+ const increment = delta < 0 ? 1 : -1;
447
+ let index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
448
+ let maxAvailableDelta = 0;
449
+
450
+ //DEBUG.push("pre calc...");
451
+ while (true) {
452
+ const prevSize = prevLayout[index];
453
+ const maxSafeSize = resizePanel({
454
+ groupSizePixels,
455
+ panelConstraints,
456
+ panelIndex: index,
457
+ size: 100
458
+ });
459
+ const delta = maxSafeSize - prevSize;
460
+ //DEBUG.push(` ${index}: ${prevSize} -> ${maxSafeSize}`);
461
+
462
+ maxAvailableDelta += delta;
463
+ index += increment;
464
+ if (index < 0 || index >= panelConstraints.length) {
465
+ break;
466
+ }
402
467
  }
468
+
469
+ //DEBUG.push(` -> max available delta: ${maxAvailableDelta}`);
470
+ const minAbsDelta = Math.min(Math.abs(delta), Math.abs(maxAvailableDelta));
471
+ delta = delta < 0 ? 0 - minAbsDelta : minAbsDelta;
472
+ //DEBUG.push(` -> adjusted delta: ${delta}`);
473
+ //DEBUG.push("");
403
474
  }
404
475
 
405
- // Delta added to a panel needs to be subtracted from other panels
406
- // within the constraints that those panels allow
407
476
  {
477
+ // Delta added to a panel needs to be subtracted from other panels (within the constraints that those panels allow).
478
+
408
479
  const pivotIndex = delta < 0 ? pivotIndices[0] : pivotIndices[1];
409
480
  let index = pivotIndex;
410
481
  while (index >= 0 && index < panelConstraints.length) {
411
482
  const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
412
483
  const prevSize = prevLayout[index];
413
484
  const unsafeSize = prevSize - deltaRemaining;
414
- let safeSize = resizePanel({
485
+ const safeSize = resizePanel({
415
486
  groupSizePixels,
416
487
  panelConstraints,
417
488
  panelIndex: index,
@@ -433,13 +504,18 @@ function adjustLayoutByDelta({
433
504
  }
434
505
  }
435
506
  }
507
+ //DEBUG.push(`after 1: ${nextLayout.join(", ")}`);
508
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
509
+ //DEBUG.push("");
436
510
 
437
511
  // If we were unable to resize any of the panels panels, return the previous state.
438
512
  // This will essentially bailout and ignore e.g. drags past a panel's boundaries
439
513
  if (fuzzyNumbersEqual(deltaApplied, 0)) {
514
+ //console.log(DEBUG.join("\n"));
440
515
  return prevLayout;
441
516
  }
442
517
  {
518
+ // Now distribute the applied delta to the panels in the other direction
443
519
  const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
444
520
  const unsafeSize = prevLayout[pivotIndex] + deltaApplied;
445
521
  const safeSize = resizePanel({
@@ -479,29 +555,21 @@ function adjustLayoutByDelta({
479
555
  index++;
480
556
  }
481
557
  }
482
-
483
- // If we can't redistribute, this layout is invalid;
484
- // There may be an incremental layout that is valid though
485
- if (!fuzzyNumbersEqual(deltaRemaining, 0)) {
486
- try {
487
- return adjustLayoutByDelta({
488
- delta: delta < 0 ? delta + 1 : delta - 1,
489
- groupSizePixels,
490
- layout: prevLayout,
491
- panelConstraints,
492
- pivotIndices,
493
- trigger
494
- });
495
- } catch (error) {
496
- if (error instanceof RangeError) {
497
- console.error(`Could not apply delta ${delta} to layout`);
498
- return prevLayout;
499
- }
500
- } finally {
501
- }
502
- }
503
558
  }
504
559
  }
560
+ //DEBUG.push(`after 2: ${nextLayout.join(", ")}`);
561
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
562
+ //DEBUG.push("");
563
+
564
+ const totalSize = nextLayout.reduce((total, size) => size + total, 0);
565
+ deltaApplied = 100 - totalSize;
566
+ //DEBUG.push(`total size: ${totalSize}`);
567
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
568
+ //console.log(DEBUG.join("\n"));
569
+
570
+ if (!fuzzyNumbersEqual(totalSize, 100)) {
571
+ return prevLayout;
572
+ }
505
573
  return nextLayout;
506
574
  }
507
575
 
@@ -705,15 +773,10 @@ function useWindowSplitterPanelGroupBehavior({
705
773
  }, [groupId, layout, panelDataArray]);
706
774
  useEffect(() => {
707
775
  const {
708
- direction,
709
776
  panelDataArray
710
777
  } = committedValuesRef.current;
711
778
  const groupElement = getPanelGroupElement(groupId);
712
779
  assert(groupElement != null, `No group found for id "${groupId}"`);
713
- const {
714
- height,
715
- width
716
- } = groupElement.getBoundingClientRect();
717
780
  const handles = getResizeHandleElementsForGroup(groupId);
718
781
  const cleanupFunctions = handles.map(handle => {
719
782
  const handleId = handle.getAttribute("data-panel-resize-handle-id");
@@ -733,21 +796,19 @@ function useWindowSplitterPanelGroupBehavior({
733
796
  if (index >= 0) {
734
797
  const panelData = panelDataArray[index];
735
798
  const size = layout[index];
736
- if (size != null) {
737
- var _getPercentageSizeFro;
799
+ if (size != null && panelData.constraints.collapsible) {
800
+ var _getPercentageSizeFro, _getPercentageSizeFro2;
738
801
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
739
- const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
802
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
803
+ sizePercentage: panelData.constraints.collapsedSizePercentage,
804
+ sizePixels: panelData.constraints.collapsedSizePixels
805
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
806
+ const minSize = (_getPercentageSizeFro2 = getPercentageSizeFromMixedSizes({
740
807
  sizePercentage: panelData.constraints.minSizePercentage,
741
808
  sizePixels: panelData.constraints.minSizePixels
742
- }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
743
- let delta = 0;
744
- if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
745
- delta = direction === "horizontal" ? width : height;
746
- } else {
747
- delta = -(direction === "horizontal" ? width : height);
748
- }
809
+ }, groupSizePixels)) !== null && _getPercentageSizeFro2 !== void 0 ? _getPercentageSizeFro2 : 0;
749
810
  const nextLayout = adjustLayoutByDelta({
750
- delta,
811
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
751
812
  groupSizePixels,
752
813
  layout,
753
814
  panelConstraints: panelDataArray.map(panelData => panelData.constraints),
@@ -1285,6 +1346,7 @@ function PanelGroupWithForwardedRef({
1285
1346
  autoSaveId,
1286
1347
  children,
1287
1348
  className: classNameFromProps = "",
1349
+ dataAttributes,
1288
1350
  direction,
1289
1351
  forwardedRef,
1290
1352
  id: idFromProps,
@@ -1302,6 +1364,7 @@ function PanelGroupWithForwardedRef({
1302
1364
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1303
1365
  const panelSizeBeforeCollapseRef = useRef(new Map());
1304
1366
  const prevDeltaRef = useRef(0);
1367
+ const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1305
1368
  const committedValuesRef = useRef({
1306
1369
  direction,
1307
1370
  dragState,
@@ -1410,8 +1473,12 @@ function PanelGroupWithForwardedRef({
1410
1473
  }
1411
1474
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1412
1475
  if (groupSizePixels <= 0) {
1413
- // Wait until the group has rendered a non-zero size before computing layout.
1414
- return;
1476
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1477
+ constraints
1478
+ }) => constraints))) {
1479
+ // Wait until the group has rendered a non-zero size before computing layout.
1480
+ return;
1481
+ }
1415
1482
  }
1416
1483
  if (unsafeLayout == null) {
1417
1484
  unsafeLayout = calculateUnsafeDefaultLayout({
@@ -1531,6 +1598,17 @@ function PanelGroupWithForwardedRef({
1531
1598
  onLayout,
1532
1599
  panelDataArray
1533
1600
  } = committedValuesRef.current;
1601
+
1602
+ // See issues/211
1603
+ if (panelDataArray.find(({
1604
+ id
1605
+ }) => id === panelData.id) == null) {
1606
+ setImperativeApiQueue(prev => [...prev, {
1607
+ panelData,
1608
+ type: "collapse"
1609
+ }]);
1610
+ return;
1611
+ }
1534
1612
  if (panelData.constraints.collapsible) {
1535
1613
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1536
1614
  const {
@@ -1574,6 +1652,17 @@ function PanelGroupWithForwardedRef({
1574
1652
  onLayout,
1575
1653
  panelDataArray
1576
1654
  } = committedValuesRef.current;
1655
+
1656
+ // See issues/211
1657
+ if (panelDataArray.find(({
1658
+ id
1659
+ }) => id === panelData.id) == null) {
1660
+ setImperativeApiQueue(prev => [...prev, {
1661
+ panelData,
1662
+ type: "expand"
1663
+ }]);
1664
+ return;
1665
+ }
1577
1666
  if (panelData.constraints.collapsible) {
1578
1667
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1579
1668
  const {
@@ -1769,6 +1858,18 @@ function PanelGroupWithForwardedRef({
1769
1858
  onLayout,
1770
1859
  panelDataArray
1771
1860
  } = committedValuesRef.current;
1861
+
1862
+ // See issues/211
1863
+ if (panelDataArray.find(({
1864
+ id
1865
+ }) => id === panelData.id) == null) {
1866
+ setImperativeApiQueue(prev => [...prev, {
1867
+ panelData,
1868
+ mixedSizes,
1869
+ type: "resize"
1870
+ }]);
1871
+ return;
1872
+ }
1772
1873
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1773
1874
  const {
1774
1875
  groupSizePixels,
@@ -1826,6 +1927,31 @@ function PanelGroupWithForwardedRef({
1826
1927
  return panelDataArray;
1827
1928
  });
1828
1929
  }, []);
1930
+
1931
+ // Handle imperative API calls that were made before panels were registered
1932
+ useIsomorphicLayoutEffect(() => {
1933
+ const queue = imperativeApiQueue;
1934
+ while (queue.length > 0) {
1935
+ const current = queue.shift();
1936
+ switch (current.type) {
1937
+ case "collapse":
1938
+ {
1939
+ collapsePanel(current.panelData);
1940
+ break;
1941
+ }
1942
+ case "expand":
1943
+ {
1944
+ expandPanel(current.panelData);
1945
+ break;
1946
+ }
1947
+ case "resize":
1948
+ {
1949
+ resizePanel(current.panelData, current.mixedSizes);
1950
+ break;
1951
+ }
1952
+ }
1953
+ }
1954
+ }, [collapsePanel, expandPanel, imperativeApiQueue, layout, panelDataArray, resizePanel]);
1829
1955
  const context = useMemo(() => ({
1830
1956
  collapsePanel,
1831
1957
  direction,
@@ -1859,6 +1985,7 @@ function PanelGroupWithForwardedRef({
1859
1985
  ...style,
1860
1986
  ...styleFromProps
1861
1987
  },
1988
+ ...dataAttributes,
1862
1989
  // CSS selectors
1863
1990
  "data-panel-group": "",
1864
1991
  "data-panel-group-direction": direction,
@@ -1946,6 +2073,7 @@ function useWindowSplitterResizeHandlerBehavior({
1946
2073
  function PanelResizeHandle({
1947
2074
  children = null,
1948
2075
  className: classNameFromProps = "",
2076
+ dataAttributes,
1949
2077
  disabled = false,
1950
2078
  id: idFromProps = null,
1951
2079
  onDragging,
@@ -2068,6 +2196,7 @@ function PanelResizeHandle({
2068
2196
  ...styleFromProps
2069
2197
  },
2070
2198
  tabIndex: 0,
2199
+ ...dataAttributes,
2071
2200
  // CSS selectors
2072
2201
  "data-panel-group-direction": direction,
2073
2202
  "data-panel-group-id": groupId,