react-resizable-panels 0.0.58 → 0.0.60

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.
@@ -95,6 +95,7 @@ function PanelWithForwardedRef({
95
95
  expandPanel,
96
96
  getPanelSize,
97
97
  getPanelStyle,
98
+ groupId,
98
99
  isPanelCollapsed,
99
100
  registerPanel,
100
101
  resizePanel,
@@ -199,6 +200,7 @@ function PanelWithForwardedRef({
199
200
  // CSS selectors
200
201
  "data-panel": "",
201
202
  "data-panel-id": panelId,
203
+ "data-panel-group-id": groupId,
202
204
  // e2e test attributes
203
205
  "data-panel-collapsible": collapsible || undefined ,
204
206
  "data-panel-size": parseFloat("" + style.flexGrow).toFixed(1)
@@ -211,8 +213,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
211
213
  PanelWithForwardedRef.displayName = "Panel";
212
214
  Panel.displayName = "forwardRef(Panel)";
213
215
 
214
- const PRECISION = 10;
215
-
216
216
  function convertPixelsToPercentage(pixels, groupSizePixels) {
217
217
  return pixels / groupSizePixels * 100;
218
218
  }
@@ -290,6 +290,8 @@ function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, gr
290
290
  };
291
291
  }
292
292
 
293
+ const PRECISION = 10;
294
+
293
295
  function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
294
296
  actual = parseFloat(actual.toFixed(fractionDigits));
295
297
  expected = parseFloat(expected.toFixed(fractionDigits));
@@ -773,15 +775,10 @@ function useWindowSplitterPanelGroupBehavior({
773
775
  }, [groupId, layout, panelDataArray]);
774
776
  useEffect(() => {
775
777
  const {
776
- direction,
777
778
  panelDataArray
778
779
  } = committedValuesRef.current;
779
780
  const groupElement = getPanelGroupElement(groupId);
780
781
  assert(groupElement != null, `No group found for id "${groupId}"`);
781
- const {
782
- height,
783
- width
784
- } = groupElement.getBoundingClientRect();
785
782
  const handles = getResizeHandleElementsForGroup(groupId);
786
783
  const cleanupFunctions = handles.map(handle => {
787
784
  const handleId = handle.getAttribute("data-panel-resize-handle-id");
@@ -801,21 +798,19 @@ function useWindowSplitterPanelGroupBehavior({
801
798
  if (index >= 0) {
802
799
  const panelData = panelDataArray[index];
803
800
  const size = layout[index];
804
- if (size != null) {
805
- var _getPercentageSizeFro;
801
+ if (size != null && panelData.constraints.collapsible) {
802
+ var _getPercentageSizeFro, _getPercentageSizeFro2;
806
803
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
807
- const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
804
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
805
+ sizePercentage: panelData.constraints.collapsedSizePercentage,
806
+ sizePixels: panelData.constraints.collapsedSizePixels
807
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
808
+ const minSize = (_getPercentageSizeFro2 = getPercentageSizeFromMixedSizes({
808
809
  sizePercentage: panelData.constraints.minSizePercentage,
809
810
  sizePixels: panelData.constraints.minSizePixels
810
- }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
811
- let delta = 0;
812
- if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
813
- delta = direction === "horizontal" ? width : height;
814
- } else {
815
- delta = -(direction === "horizontal" ? width : height);
816
- }
811
+ }, groupSizePixels)) !== null && _getPercentageSizeFro2 !== void 0 ? _getPercentageSizeFro2 : 0;
817
812
  const nextLayout = adjustLayoutByDelta({
818
- delta,
813
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
819
814
  groupSizePixels,
820
815
  layout,
821
816
  panelConstraints: panelDataArray.map(panelData => panelData.constraints),
@@ -1124,6 +1119,10 @@ function debounce(callback, durationMs = 10) {
1124
1119
  return callable;
1125
1120
  }
1126
1121
 
1122
+ function getPanelElementsForGroup(groupId) {
1123
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
1124
+ }
1125
+
1127
1126
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
1128
1127
  // or on a browser with cookies/storage disabled.
1129
1128
  // In either case, this function avoids accessing localStorage until needed,
@@ -1350,7 +1349,7 @@ const defaultStorage = {
1350
1349
  };
1351
1350
  const debounceMap = {};
1352
1351
  function PanelGroupWithForwardedRef({
1353
- autoSaveId,
1352
+ autoSaveId = null,
1354
1353
  children,
1355
1354
  className: classNameFromProps = "",
1356
1355
  dataAttributes,
@@ -1367,11 +1366,11 @@ function PanelGroupWithForwardedRef({
1367
1366
  const groupId = useUniqueId(idFromProps);
1368
1367
  const [dragState, setDragState] = useState(null);
1369
1368
  const [layout, setLayout] = useState([]);
1370
- const [panelDataArray, setPanelDataArray] = useState([]);
1371
1369
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1372
1370
  const panelSizeBeforeCollapseRef = useRef(new Map());
1373
1371
  const prevDeltaRef = useRef(0);
1374
1372
  const committedValuesRef = useRef({
1373
+ autoSaveId,
1375
1374
  direction,
1376
1375
  dragState,
1377
1376
  id: groupId,
@@ -1379,7 +1378,8 @@ function PanelGroupWithForwardedRef({
1379
1378
  keyboardResizeByPixels,
1380
1379
  layout,
1381
1380
  onLayout,
1382
- panelDataArray
1381
+ panelDataArray: [],
1382
+ storage
1383
1383
  });
1384
1384
  const devWarningsRef = useRef({
1385
1385
  didLogIdAndOrderWarning: false,
@@ -1417,6 +1417,7 @@ function PanelGroupWithForwardedRef({
1417
1417
  });
1418
1418
  if (!areEqual(prevLayout, safeLayout)) {
1419
1419
  setLayout(safeLayout);
1420
+ committedValuesRef.current.layout = safeLayout;
1420
1421
  if (onLayout) {
1421
1422
  onLayout(safeLayout.map(sizePercentage => ({
1422
1423
  sizePercentage,
@@ -1428,21 +1429,29 @@ function PanelGroupWithForwardedRef({
1428
1429
  }
1429
1430
  }), []);
1430
1431
  useIsomorphicLayoutEffect(() => {
1432
+ committedValuesRef.current.autoSaveId = autoSaveId;
1431
1433
  committedValuesRef.current.direction = direction;
1432
1434
  committedValuesRef.current.dragState = dragState;
1433
1435
  committedValuesRef.current.id = groupId;
1434
- committedValuesRef.current.layout = layout;
1435
1436
  committedValuesRef.current.onLayout = onLayout;
1436
- committedValuesRef.current.panelDataArray = panelDataArray;
1437
+ committedValuesRef.current.storage = storage;
1438
+
1439
+ // panelDataArray and layout are updated in-sync with scheduled state updates.
1440
+ // TODO [217] Move these values into a separate ref
1437
1441
  });
1442
+
1438
1443
  useWindowSplitterPanelGroupBehavior({
1439
1444
  committedValuesRef,
1440
1445
  groupId,
1441
1446
  layout,
1442
- panelDataArray,
1447
+ panelDataArray: committedValuesRef.current.panelDataArray,
1443
1448
  setLayout
1444
1449
  });
1445
1450
  useEffect(() => {
1451
+ const {
1452
+ panelDataArray
1453
+ } = committedValuesRef.current;
1454
+
1446
1455
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1447
1456
  if (autoSaveId) {
1448
1457
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1455,59 +1464,11 @@ function PanelGroupWithForwardedRef({
1455
1464
  }
1456
1465
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1457
1466
  }
1458
- }, [autoSaveId, layout, panelDataArray, storage]);
1459
-
1460
- // Once all panels have registered themselves,
1461
- // Compute the initial sizes based on default weights.
1462
- // This assumes that panels register during initial mount (no conditional rendering)!
1467
+ }, [autoSaveId, layout, storage]);
1463
1468
  useIsomorphicLayoutEffect(() => {
1464
1469
  const {
1465
- id: groupId,
1466
- layout,
1467
- onLayout
1470
+ panelDataArray
1468
1471
  } = committedValuesRef.current;
1469
- if (layout.length === panelDataArray.length) {
1470
- // Only compute (or restore) default layout once per panel configuration.
1471
- return;
1472
- }
1473
-
1474
- // If this panel has been configured to persist sizing information,
1475
- // default size should be restored from local storage if possible.
1476
- let unsafeLayout = null;
1477
- if (autoSaveId) {
1478
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1479
- }
1480
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1481
- if (groupSizePixels <= 0) {
1482
- // Wait until the group has rendered a non-zero size before computing layout.
1483
- return;
1484
- }
1485
- if (unsafeLayout == null) {
1486
- unsafeLayout = calculateUnsafeDefaultLayout({
1487
- groupSizePixels,
1488
- panelDataArray
1489
- });
1490
- }
1491
-
1492
- // Validate even saved layouts in case something has changed since last render
1493
- // e.g. for pixel groups, this could be the size of the window
1494
- const validatedLayout = validatePanelGroupLayout({
1495
- groupSizePixels,
1496
- layout: unsafeLayout,
1497
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1498
- });
1499
- if (!areEqual(layout, validatedLayout)) {
1500
- setLayout(validatedLayout);
1501
- }
1502
- if (onLayout) {
1503
- onLayout(validatedLayout.map(sizePercentage => ({
1504
- sizePercentage,
1505
- sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1506
- })));
1507
- }
1508
- callPanelCallbacks(groupId, panelDataArray, validatedLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1509
- }, [autoSaveId, layout, panelDataArray, storage]);
1510
- useIsomorphicLayoutEffect(() => {
1511
1472
  const constraints = panelDataArray.map(({
1512
1473
  constraints
1513
1474
  }) => constraints);
@@ -1531,6 +1492,7 @@ function PanelGroupWithForwardedRef({
1531
1492
  });
1532
1493
  if (!areEqual(prevLayout, nextLayout)) {
1533
1494
  setLayout(nextLayout);
1495
+ committedValuesRef.current.layout = nextLayout;
1534
1496
  if (onLayout) {
1535
1497
  onLayout(nextLayout.map(sizePercentage => ({
1536
1498
  sizePercentage,
@@ -1545,20 +1507,20 @@ function PanelGroupWithForwardedRef({
1545
1507
  resizeObserver.disconnect();
1546
1508
  };
1547
1509
  }
1548
- }, [groupId, panelDataArray]);
1510
+ }, [groupId]);
1549
1511
 
1550
1512
  // DEV warnings
1551
1513
  useEffect(() => {
1552
1514
  {
1515
+ const {
1516
+ panelDataArray
1517
+ } = committedValuesRef.current;
1553
1518
  const {
1554
1519
  didLogIdAndOrderWarning,
1555
1520
  didLogPanelConstraintsWarning,
1556
1521
  prevPanelIds
1557
1522
  } = devWarningsRef.current;
1558
1523
  if (!didLogIdAndOrderWarning) {
1559
- const {
1560
- panelDataArray
1561
- } = committedValuesRef.current;
1562
1524
  const panelIds = panelDataArray.map(({
1563
1525
  id
1564
1526
  }) => id);
@@ -1624,6 +1586,7 @@ function PanelGroupWithForwardedRef({
1624
1586
  });
1625
1587
  if (!compareLayouts(prevLayout, nextLayout)) {
1626
1588
  setLayout(nextLayout);
1589
+ committedValuesRef.current.layout = nextLayout;
1627
1590
  if (onLayout) {
1628
1591
  onLayout(nextLayout.map(sizePercentage => ({
1629
1592
  sizePercentage,
@@ -1668,6 +1631,7 @@ function PanelGroupWithForwardedRef({
1668
1631
  });
1669
1632
  if (!compareLayouts(prevLayout, nextLayout)) {
1670
1633
  setLayout(nextLayout);
1634
+ committedValuesRef.current.layout = nextLayout;
1671
1635
  if (onLayout) {
1672
1636
  onLayout(nextLayout.map(sizePercentage => ({
1673
1637
  sizePercentage,
@@ -1698,6 +1662,9 @@ function PanelGroupWithForwardedRef({
1698
1662
 
1699
1663
  // This API should never read from committedValuesRef
1700
1664
  const getPanelStyle = useCallback(panelData => {
1665
+ const {
1666
+ panelDataArray
1667
+ } = committedValuesRef.current;
1701
1668
  const panelIndex = panelDataArray.indexOf(panelData);
1702
1669
  return computePanelFlexBoxStyle({
1703
1670
  dragState,
@@ -1705,7 +1672,7 @@ function PanelGroupWithForwardedRef({
1705
1672
  panelData: panelDataArray,
1706
1673
  panelIndex
1707
1674
  });
1708
- }, [dragState, layout, panelDataArray]);
1675
+ }, [dragState, layout]);
1709
1676
 
1710
1677
  // External APIs are safe to memoize via committed values ref
1711
1678
  const isPanelCollapsed = useCallback(panelData => {
@@ -1735,22 +1702,76 @@ function PanelGroupWithForwardedRef({
1735
1702
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1736
1703
  }, [groupId]);
1737
1704
  const registerPanel = useCallback(panelData => {
1738
- setPanelDataArray(prevPanelDataArray => {
1739
- const nextPanelDataArray = [...prevPanelDataArray, panelData];
1740
- return nextPanelDataArray.sort((panelA, panelB) => {
1741
- const orderA = panelA.order;
1742
- const orderB = panelB.order;
1743
- if (orderA == null && orderB == null) {
1744
- return 0;
1745
- } else if (orderA == null) {
1746
- return -1;
1747
- } else if (orderB == null) {
1748
- return 1;
1749
- } else {
1750
- return orderA - orderB;
1751
- }
1705
+ const {
1706
+ autoSaveId,
1707
+ id: groupId,
1708
+ layout: prevLayout,
1709
+ onLayout,
1710
+ panelDataArray,
1711
+ storage
1712
+ } = committedValuesRef.current;
1713
+ panelDataArray.push(panelData);
1714
+ panelDataArray.sort((panelA, panelB) => {
1715
+ const orderA = panelA.order;
1716
+ const orderB = panelB.order;
1717
+ if (orderA == null && orderB == null) {
1718
+ return 0;
1719
+ } else if (orderA == null) {
1720
+ return -1;
1721
+ } else if (orderB == null) {
1722
+ return 1;
1723
+ } else {
1724
+ return orderA - orderB;
1725
+ }
1726
+ });
1727
+
1728
+ // Wait until all panels have registered before we try to compute layout;
1729
+ // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1730
+ const panelElements = getPanelElementsForGroup(groupId);
1731
+ if (panelElements.length !== panelDataArray.length) {
1732
+ return;
1733
+ }
1734
+
1735
+ // If this panel has been configured to persist sizing information,
1736
+ // default size should be restored from local storage if possible.
1737
+ let unsafeLayout = null;
1738
+ if (autoSaveId) {
1739
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1740
+ }
1741
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1742
+ if (groupSizePixels <= 0) {
1743
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1744
+ constraints
1745
+ }) => constraints))) {
1746
+ // Wait until the group has rendered a non-zero size before computing layout.
1747
+ return;
1748
+ }
1749
+ }
1750
+ if (unsafeLayout == null) {
1751
+ unsafeLayout = calculateUnsafeDefaultLayout({
1752
+ groupSizePixels,
1753
+ panelDataArray
1752
1754
  });
1755
+ }
1756
+
1757
+ // Validate even saved layouts in case something has changed since last render
1758
+ // e.g. for pixel groups, this could be the size of the window
1759
+ const nextLayout = validatePanelGroupLayout({
1760
+ groupSizePixels,
1761
+ layout: unsafeLayout,
1762
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1753
1763
  });
1764
+ if (!areEqual(prevLayout, nextLayout)) {
1765
+ setLayout(nextLayout);
1766
+ committedValuesRef.current.layout = nextLayout;
1767
+ if (onLayout) {
1768
+ onLayout(nextLayout.map(sizePercentage => ({
1769
+ sizePercentage,
1770
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1771
+ })));
1772
+ }
1773
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1774
+ }
1754
1775
  }, []);
1755
1776
  const registerResizeHandle = useCallback(dragHandleId => {
1756
1777
  return function resizeHandler(event) {
@@ -1820,6 +1841,7 @@ function PanelGroupWithForwardedRef({
1820
1841
  }
1821
1842
  if (layoutChanged) {
1822
1843
  setLayout(nextLayout);
1844
+ committedValuesRef.current.layout = nextLayout;
1823
1845
  if (onLayout) {
1824
1846
  onLayout(nextLayout.map(sizePercentage => ({
1825
1847
  sizePercentage,
@@ -1857,6 +1879,7 @@ function PanelGroupWithForwardedRef({
1857
1879
  });
1858
1880
  if (!compareLayouts(prevLayout, nextLayout)) {
1859
1881
  setLayout(nextLayout);
1882
+ committedValuesRef.current.layout = nextLayout;
1860
1883
  if (onLayout) {
1861
1884
  onLayout(nextLayout.map(sizePercentage => ({
1862
1885
  sizePercentage,
@@ -1884,16 +1907,84 @@ function PanelGroupWithForwardedRef({
1884
1907
  resetGlobalCursorStyle();
1885
1908
  setDragState(null);
1886
1909
  }, []);
1910
+ const unregisterPanelRef = useRef({
1911
+ pendingPanelIds: new Set(),
1912
+ timeout: null
1913
+ });
1887
1914
  const unregisterPanel = useCallback(panelData => {
1888
- delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1889
- setPanelDataArray(panelDataArray => {
1890
- const index = panelDataArray.indexOf(panelData);
1891
- if (index >= 0) {
1892
- panelDataArray = [...panelDataArray];
1893
- panelDataArray.splice(index, 1);
1915
+ const {
1916
+ id: groupId,
1917
+ layout: prevLayout,
1918
+ onLayout,
1919
+ panelDataArray
1920
+ } = committedValuesRef.current;
1921
+ const index = panelDataArray.indexOf(panelData);
1922
+ if (index >= 0) {
1923
+ panelDataArray.splice(index, 1);
1924
+ unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1925
+ }
1926
+ if (unregisterPanelRef.current.timeout != null) {
1927
+ clearTimeout(unregisterPanelRef.current.timeout);
1928
+ }
1929
+
1930
+ // Batch panel unmounts so that we only calculate layout once;
1931
+ // This is more efficient and avoids misleading warnings in development mode.
1932
+ // We can't check the DOM to detect this because Panel elements have not yet been removed.
1933
+ unregisterPanelRef.current.timeout = setTimeout(() => {
1934
+ const {
1935
+ pendingPanelIds
1936
+ } = unregisterPanelRef.current;
1937
+ panelIdToLastNotifiedMixedSizesMapRef.current;
1938
+
1939
+ // TRICKY
1940
+ // Strict effects mode
1941
+ let unmountDueToStrictMode = false;
1942
+ pendingPanelIds.forEach(panelId => {
1943
+ pendingPanelIds.delete(panelId);
1944
+ if (panelDataArray.find(({
1945
+ id
1946
+ }) => id === panelId) == null) {
1947
+ unmountDueToStrictMode = true;
1948
+
1949
+ // TRICKY
1950
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1951
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1952
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1953
+ delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1954
+ }
1955
+ });
1956
+ if (!unmountDueToStrictMode) {
1957
+ return;
1894
1958
  }
1895
- return panelDataArray;
1896
- });
1959
+ if (panelDataArray.length === 0) {
1960
+ // The group is unmounting; skip layout calculation.
1961
+ return;
1962
+ }
1963
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1964
+ let unsafeLayout = calculateUnsafeDefaultLayout({
1965
+ groupSizePixels,
1966
+ panelDataArray
1967
+ });
1968
+
1969
+ // Validate even saved layouts in case something has changed since last render
1970
+ // e.g. for pixel groups, this could be the size of the window
1971
+ const nextLayout = validatePanelGroupLayout({
1972
+ groupSizePixels,
1973
+ layout: unsafeLayout,
1974
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1975
+ });
1976
+ if (!areEqual(prevLayout, nextLayout)) {
1977
+ setLayout(nextLayout);
1978
+ committedValuesRef.current.layout = nextLayout;
1979
+ if (onLayout) {
1980
+ onLayout(nextLayout.map(sizePercentage => ({
1981
+ sizePercentage,
1982
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1983
+ })));
1984
+ }
1985
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1986
+ }
1987
+ }, 0);
1897
1988
  }, []);
1898
1989
  const context = useMemo(() => ({
1899
1990
  collapsePanel,