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
@@ -68,6 +68,7 @@ function PanelWithForwardedRef({
68
68
  collapsedSizePercentage,
69
69
  collapsedSizePixels,
70
70
  collapsible,
71
+ dataAttributes,
71
72
  defaultSizePercentage,
72
73
  defaultSizePixels,
73
74
  forwardedRef,
@@ -187,6 +188,7 @@ function PanelWithForwardedRef({
187
188
  ...style,
188
189
  ...styleFromProps
189
190
  },
191
+ ...dataAttributes,
190
192
  // CSS selectors
191
193
  "data-panel": "",
192
194
  "data-panel-id": panelId,
@@ -202,8 +204,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
202
204
  PanelWithForwardedRef.displayName = "Panel";
203
205
  Panel.displayName = "forwardRef(Panel)";
204
206
 
205
- const PRECISION = 10;
206
-
207
207
  function convertPixelsToPercentage(pixels, groupSizePixels) {
208
208
  return pixels / groupSizePixels * 100;
209
209
  }
@@ -281,6 +281,8 @@ function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, gr
281
281
  };
282
282
  }
283
283
 
284
+ const PRECISION = 10;
285
+
284
286
  function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
285
287
  actual = parseFloat(actual.toFixed(fractionDigits));
286
288
  expected = parseFloat(expected.toFixed(fractionDigits));
@@ -324,7 +326,13 @@ function resizePanel({
324
326
  if (minSizePercentage != null) {
325
327
  if (fuzzyCompareNumbers(size, minSizePercentage) < 0) {
326
328
  if (collapsible) {
327
- size = collapsedSizePercentage;
329
+ // Collapsible panels should snap closed or open only once they cross the halfway point between collapsed and min size.
330
+ const halfwayPoint = (collapsedSizePercentage + minSizePercentage) / 2;
331
+ if (fuzzyCompareNumbers(size, halfwayPoint) < 0) {
332
+ size = collapsedSizePercentage;
333
+ } else {
334
+ size = minSizePercentage;
335
+ }
328
336
  } else {
329
337
  size = minSizePercentage;
330
338
  }
@@ -351,60 +359,123 @@ function adjustLayoutByDelta({
351
359
  const nextLayout = [...prevLayout];
352
360
  let deltaApplied = 0;
353
361
 
362
+ //const DEBUG = [];
363
+ //DEBUG.push(`adjustLayoutByDelta() ${prevLayout.join(", ")}`);
364
+ //DEBUG.push(` delta: ${delta}`);
365
+ //DEBUG.push(` pivotIndices: ${pivotIndices.join(", ")}`);
366
+ //DEBUG.push(` trigger: ${trigger}`);
367
+ //DEBUG.push("");
368
+
354
369
  // A resizing panel affects the panels before or after it.
355
370
  //
356
- // A negative delta means the panel immediately after the resizer should grow/expand by decreasing its offset.
371
+ // A negative delta means the panel(s) immediately after the resize handle should grow/expand by decreasing its offset.
357
372
  // Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
358
373
  //
359
- // A positive delta means the panel immediately before the resizer should "expand".
360
- // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.
374
+ // A positive delta means the panel(s) immediately before the resize handle should "expand".
375
+ // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resize handle.
361
376
 
362
- // First, check the panel we're pivoting around;
363
- // We should only expand or contract by as much as its constraints allow
364
377
  {
365
- const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
366
- const initialSize = nextLayout[pivotIndex];
367
- const {
368
- collapsible
369
- } = panelConstraints[pivotIndex];
370
- const {
371
- collapsedSizePercentage,
372
- minSizePercentage
373
- } = computePercentagePanelConstraints(panelConstraints, pivotIndex, groupSizePixels);
374
- const isCollapsed = collapsible && fuzzyNumbersEqual(initialSize, collapsedSizePercentage);
375
- let unsafeSize = initialSize + Math.abs(delta);
376
- if (isCollapsed) {
377
- switch (trigger) {
378
- case "keyboard":
379
- if (minSizePercentage > unsafeSize) {
380
- unsafeSize = minSizePercentage;
378
+ // If this is a resize triggered by a keyboard event, our logic for expanding/collapsing is different.
379
+ // We no longer check the halfway threshold because this may prevent the panel from expanding at all.
380
+ if (trigger === "keyboard") {
381
+ {
382
+ // Check if we should expand a collapsed panel
383
+ const index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
384
+ const constraints = panelConstraints[index];
385
+ //DEBUG.push(`edge case check 1: ${index}`);
386
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
387
+ if (constraints.collapsible) {
388
+ const prevSize = prevLayout[index];
389
+ const {
390
+ collapsedSizePercentage,
391
+ minSizePercentage
392
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
393
+ if (fuzzyNumbersEqual(prevSize, collapsedSizePercentage)) {
394
+ const localDelta = minSizePercentage - prevSize;
395
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
396
+
397
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
398
+ delta = delta < 0 ? 0 - localDelta : localDelta;
399
+ //DEBUG.push(` -> delta: ${delta}`);
400
+ }
401
+ }
402
+ }
403
+ }
404
+
405
+ {
406
+ // Check if we should collapse a panel at its minimum size
407
+ const index = delta < 0 ? pivotIndices[0] : pivotIndices[1];
408
+ const constraints = panelConstraints[index];
409
+ //DEBUG.push(`edge case check 2: ${index}`);
410
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
411
+ if (constraints.collapsible) {
412
+ const prevSize = prevLayout[index];
413
+ const {
414
+ collapsedSizePercentage,
415
+ minSizePercentage
416
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
417
+ if (fuzzyNumbersEqual(prevSize, minSizePercentage)) {
418
+ const localDelta = prevSize - collapsedSizePercentage;
419
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
420
+
421
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
422
+ delta = delta < 0 ? 0 - localDelta : localDelta;
423
+ //DEBUG.push(` -> delta: ${delta}`);
424
+ }
381
425
  }
426
+ }
382
427
  }
383
428
  }
384
- const safeSize = resizePanel({
385
- groupSizePixels,
386
- panelConstraints,
387
- panelIndex: pivotIndex,
388
- size: unsafeSize
389
- });
390
- if (fuzzyNumbersEqual(initialSize, safeSize)) {
391
- // If there's no room for the pivot panel to grow, we should ignore this change
392
- return nextLayout;
393
- } else {
394
- delta = delta < 0 ? initialSize - safeSize : safeSize - initialSize;
429
+ //DEBUG.push("");
430
+ }
431
+
432
+ {
433
+ // Pre-calculate max available delta in the opposite direction of our pivot.
434
+ // This will be the maximum amount we're allowed to expand/contract the panels in the primary direction.
435
+ // If this amount is less than the requested delta, adjust the requested delta.
436
+ // If this amount is greater than the requested delta, that's useful information too–
437
+ // as an expanding panel might change from collapsed to min size.
438
+
439
+ const increment = delta < 0 ? 1 : -1;
440
+ let index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
441
+ let maxAvailableDelta = 0;
442
+
443
+ //DEBUG.push("pre calc...");
444
+ while (true) {
445
+ const prevSize = prevLayout[index];
446
+ const maxSafeSize = resizePanel({
447
+ groupSizePixels,
448
+ panelConstraints,
449
+ panelIndex: index,
450
+ size: 100
451
+ });
452
+ const delta = maxSafeSize - prevSize;
453
+ //DEBUG.push(` ${index}: ${prevSize} -> ${maxSafeSize}`);
454
+
455
+ maxAvailableDelta += delta;
456
+ index += increment;
457
+ if (index < 0 || index >= panelConstraints.length) {
458
+ break;
459
+ }
395
460
  }
461
+
462
+ //DEBUG.push(` -> max available delta: ${maxAvailableDelta}`);
463
+ const minAbsDelta = Math.min(Math.abs(delta), Math.abs(maxAvailableDelta));
464
+ delta = delta < 0 ? 0 - minAbsDelta : minAbsDelta;
465
+ //DEBUG.push(` -> adjusted delta: ${delta}`);
466
+ //DEBUG.push("");
396
467
  }
397
468
 
398
- // Delta added to a panel needs to be subtracted from other panels
399
- // within the constraints that those panels allow
400
469
  {
470
+ // Delta added to a panel needs to be subtracted from other panels (within the constraints that those panels allow).
471
+
401
472
  const pivotIndex = delta < 0 ? pivotIndices[0] : pivotIndices[1];
402
473
  let index = pivotIndex;
403
474
  while (index >= 0 && index < panelConstraints.length) {
404
475
  const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
405
476
  const prevSize = prevLayout[index];
406
477
  const unsafeSize = prevSize - deltaRemaining;
407
- let safeSize = resizePanel({
478
+ const safeSize = resizePanel({
408
479
  groupSizePixels,
409
480
  panelConstraints,
410
481
  panelIndex: index,
@@ -426,13 +497,18 @@ function adjustLayoutByDelta({
426
497
  }
427
498
  }
428
499
  }
500
+ //DEBUG.push(`after 1: ${nextLayout.join(", ")}`);
501
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
502
+ //DEBUG.push("");
429
503
 
430
504
  // If we were unable to resize any of the panels panels, return the previous state.
431
505
  // This will essentially bailout and ignore e.g. drags past a panel's boundaries
432
506
  if (fuzzyNumbersEqual(deltaApplied, 0)) {
507
+ //console.log(DEBUG.join("\n"));
433
508
  return prevLayout;
434
509
  }
435
510
  {
511
+ // Now distribute the applied delta to the panels in the other direction
436
512
  const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
437
513
  const unsafeSize = prevLayout[pivotIndex] + deltaApplied;
438
514
  const safeSize = resizePanel({
@@ -472,29 +548,21 @@ function adjustLayoutByDelta({
472
548
  index++;
473
549
  }
474
550
  }
475
-
476
- // If we can't redistribute, this layout is invalid;
477
- // There may be an incremental layout that is valid though
478
- if (!fuzzyNumbersEqual(deltaRemaining, 0)) {
479
- try {
480
- return adjustLayoutByDelta({
481
- delta: delta < 0 ? delta + 1 : delta - 1,
482
- groupSizePixels,
483
- layout: prevLayout,
484
- panelConstraints,
485
- pivotIndices,
486
- trigger
487
- });
488
- } catch (error) {
489
- if (error instanceof RangeError) {
490
- console.error(`Could not apply delta ${delta} to layout`);
491
- return prevLayout;
492
- }
493
- } finally {
494
- }
495
- }
496
551
  }
497
552
  }
553
+ //DEBUG.push(`after 2: ${nextLayout.join(", ")}`);
554
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
555
+ //DEBUG.push("");
556
+
557
+ const totalSize = nextLayout.reduce((total, size) => size + total, 0);
558
+ deltaApplied = 100 - totalSize;
559
+ //DEBUG.push(`total size: ${totalSize}`);
560
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
561
+ //console.log(DEBUG.join("\n"));
562
+
563
+ if (!fuzzyNumbersEqual(totalSize, 100)) {
564
+ return prevLayout;
565
+ }
498
566
  return nextLayout;
499
567
  }
500
568
 
@@ -698,15 +766,10 @@ function useWindowSplitterPanelGroupBehavior({
698
766
  }, [groupId, layout, panelDataArray]);
699
767
  useEffect(() => {
700
768
  const {
701
- direction,
702
769
  panelDataArray
703
770
  } = committedValuesRef.current;
704
771
  const groupElement = getPanelGroupElement(groupId);
705
772
  assert(groupElement != null, `No group found for id "${groupId}"`);
706
- const {
707
- height,
708
- width
709
- } = groupElement.getBoundingClientRect();
710
773
  const handles = getResizeHandleElementsForGroup(groupId);
711
774
  const cleanupFunctions = handles.map(handle => {
712
775
  const handleId = handle.getAttribute("data-panel-resize-handle-id");
@@ -726,21 +789,19 @@ function useWindowSplitterPanelGroupBehavior({
726
789
  if (index >= 0) {
727
790
  const panelData = panelDataArray[index];
728
791
  const size = layout[index];
729
- if (size != null) {
730
- var _getPercentageSizeFro;
792
+ if (size != null && panelData.constraints.collapsible) {
793
+ var _getPercentageSizeFro, _getPercentageSizeFro2;
731
794
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
732
- const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
795
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
796
+ sizePercentage: panelData.constraints.collapsedSizePercentage,
797
+ sizePixels: panelData.constraints.collapsedSizePixels
798
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
799
+ const minSize = (_getPercentageSizeFro2 = getPercentageSizeFromMixedSizes({
733
800
  sizePercentage: panelData.constraints.minSizePercentage,
734
801
  sizePixels: panelData.constraints.minSizePixels
735
- }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
736
- let delta = 0;
737
- if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
738
- delta = direction === "horizontal" ? width : height;
739
- } else {
740
- delta = -(direction === "horizontal" ? width : height);
741
- }
802
+ }, groupSizePixels)) !== null && _getPercentageSizeFro2 !== void 0 ? _getPercentageSizeFro2 : 0;
742
803
  const nextLayout = adjustLayoutByDelta({
743
- delta,
804
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
744
805
  groupSizePixels,
745
806
  layout,
746
807
  panelConstraints: panelDataArray.map(panelData => panelData.constraints),
@@ -1278,6 +1339,7 @@ function PanelGroupWithForwardedRef({
1278
1339
  autoSaveId,
1279
1340
  children,
1280
1341
  className: classNameFromProps = "",
1342
+ dataAttributes,
1281
1343
  direction,
1282
1344
  forwardedRef,
1283
1345
  id: idFromProps,
@@ -1295,6 +1357,7 @@ function PanelGroupWithForwardedRef({
1295
1357
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1296
1358
  const panelSizeBeforeCollapseRef = useRef(new Map());
1297
1359
  const prevDeltaRef = useRef(0);
1360
+ const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1298
1361
  const committedValuesRef = useRef({
1299
1362
  direction,
1300
1363
  dragState,
@@ -1403,8 +1466,12 @@ function PanelGroupWithForwardedRef({
1403
1466
  }
1404
1467
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1405
1468
  if (groupSizePixels <= 0) {
1406
- // Wait until the group has rendered a non-zero size before computing layout.
1407
- return;
1469
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1470
+ constraints
1471
+ }) => constraints))) {
1472
+ // Wait until the group has rendered a non-zero size before computing layout.
1473
+ return;
1474
+ }
1408
1475
  }
1409
1476
  if (unsafeLayout == null) {
1410
1477
  unsafeLayout = calculateUnsafeDefaultLayout({
@@ -1524,6 +1591,17 @@ function PanelGroupWithForwardedRef({
1524
1591
  onLayout,
1525
1592
  panelDataArray
1526
1593
  } = committedValuesRef.current;
1594
+
1595
+ // See issues/211
1596
+ if (panelDataArray.find(({
1597
+ id
1598
+ }) => id === panelData.id) == null) {
1599
+ setImperativeApiQueue(prev => [...prev, {
1600
+ panelData,
1601
+ type: "collapse"
1602
+ }]);
1603
+ return;
1604
+ }
1527
1605
  if (panelData.constraints.collapsible) {
1528
1606
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1529
1607
  const {
@@ -1567,6 +1645,17 @@ function PanelGroupWithForwardedRef({
1567
1645
  onLayout,
1568
1646
  panelDataArray
1569
1647
  } = committedValuesRef.current;
1648
+
1649
+ // See issues/211
1650
+ if (panelDataArray.find(({
1651
+ id
1652
+ }) => id === panelData.id) == null) {
1653
+ setImperativeApiQueue(prev => [...prev, {
1654
+ panelData,
1655
+ type: "expand"
1656
+ }]);
1657
+ return;
1658
+ }
1570
1659
  if (panelData.constraints.collapsible) {
1571
1660
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1572
1661
  const {
@@ -1762,6 +1851,18 @@ function PanelGroupWithForwardedRef({
1762
1851
  onLayout,
1763
1852
  panelDataArray
1764
1853
  } = committedValuesRef.current;
1854
+
1855
+ // See issues/211
1856
+ if (panelDataArray.find(({
1857
+ id
1858
+ }) => id === panelData.id) == null) {
1859
+ setImperativeApiQueue(prev => [...prev, {
1860
+ panelData,
1861
+ mixedSizes,
1862
+ type: "resize"
1863
+ }]);
1864
+ return;
1865
+ }
1765
1866
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1766
1867
  const {
1767
1868
  groupSizePixels,
@@ -1819,6 +1920,31 @@ function PanelGroupWithForwardedRef({
1819
1920
  return panelDataArray;
1820
1921
  });
1821
1922
  }, []);
1923
+
1924
+ // Handle imperative API calls that were made before panels were registered
1925
+ useIsomorphicLayoutEffect(() => {
1926
+ const queue = imperativeApiQueue;
1927
+ while (queue.length > 0) {
1928
+ const current = queue.shift();
1929
+ switch (current.type) {
1930
+ case "collapse":
1931
+ {
1932
+ collapsePanel(current.panelData);
1933
+ break;
1934
+ }
1935
+ case "expand":
1936
+ {
1937
+ expandPanel(current.panelData);
1938
+ break;
1939
+ }
1940
+ case "resize":
1941
+ {
1942
+ resizePanel(current.panelData, current.mixedSizes);
1943
+ break;
1944
+ }
1945
+ }
1946
+ }
1947
+ }, [collapsePanel, expandPanel, imperativeApiQueue, layout, panelDataArray, resizePanel]);
1822
1948
  const context = useMemo(() => ({
1823
1949
  collapsePanel,
1824
1950
  direction,
@@ -1852,6 +1978,7 @@ function PanelGroupWithForwardedRef({
1852
1978
  ...style,
1853
1979
  ...styleFromProps
1854
1980
  },
1981
+ ...dataAttributes,
1855
1982
  // CSS selectors
1856
1983
  "data-panel-group": "",
1857
1984
  "data-panel-group-direction": direction,
@@ -1939,6 +2066,7 @@ function useWindowSplitterResizeHandlerBehavior({
1939
2066
  function PanelResizeHandle({
1940
2067
  children = null,
1941
2068
  className: classNameFromProps = "",
2069
+ dataAttributes,
1942
2070
  disabled = false,
1943
2071
  id: idFromProps = null,
1944
2072
  onDragging,
@@ -2061,6 +2189,7 @@ function PanelResizeHandle({
2061
2189
  ...styleFromProps
2062
2190
  },
2063
2191
  tabIndex: 0,
2192
+ ...dataAttributes,
2064
2193
  // CSS selectors
2065
2194
  "data-panel-group-direction": direction,
2066
2195
  "data-panel-group-id": groupId,