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.
@@ -93,6 +93,7 @@ function PanelWithForwardedRef({
93
93
  expandPanel,
94
94
  getPanelSize,
95
95
  getPanelStyle,
96
+ groupId,
96
97
  isPanelCollapsed,
97
98
  registerPanel,
98
99
  resizePanel,
@@ -192,6 +193,7 @@ function PanelWithForwardedRef({
192
193
  // CSS selectors
193
194
  "data-panel": "",
194
195
  "data-panel-id": panelId,
196
+ "data-panel-group-id": groupId,
195
197
  // e2e test attributes
196
198
  "data-panel-collapsible": collapsible || undefined ,
197
199
  "data-panel-size": parseFloat("" + style.flexGrow).toFixed(1)
@@ -204,8 +206,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
204
206
  PanelWithForwardedRef.displayName = "Panel";
205
207
  Panel.displayName = "forwardRef(Panel)";
206
208
 
207
- const PRECISION = 10;
208
-
209
209
  function convertPixelsToPercentage(pixels, groupSizePixels) {
210
210
  return pixels / groupSizePixels * 100;
211
211
  }
@@ -283,6 +283,8 @@ function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, gr
283
283
  };
284
284
  }
285
285
 
286
+ const PRECISION = 10;
287
+
286
288
  function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
287
289
  actual = parseFloat(actual.toFixed(fractionDigits));
288
290
  expected = parseFloat(expected.toFixed(fractionDigits));
@@ -766,15 +768,10 @@ function useWindowSplitterPanelGroupBehavior({
766
768
  }, [groupId, layout, panelDataArray]);
767
769
  useEffect(() => {
768
770
  const {
769
- direction,
770
771
  panelDataArray
771
772
  } = committedValuesRef.current;
772
773
  const groupElement = getPanelGroupElement(groupId);
773
774
  assert(groupElement != null, `No group found for id "${groupId}"`);
774
- const {
775
- height,
776
- width
777
- } = groupElement.getBoundingClientRect();
778
775
  const handles = getResizeHandleElementsForGroup(groupId);
779
776
  const cleanupFunctions = handles.map(handle => {
780
777
  const handleId = handle.getAttribute("data-panel-resize-handle-id");
@@ -794,21 +791,19 @@ function useWindowSplitterPanelGroupBehavior({
794
791
  if (index >= 0) {
795
792
  const panelData = panelDataArray[index];
796
793
  const size = layout[index];
797
- if (size != null) {
798
- var _getPercentageSizeFro;
794
+ if (size != null && panelData.constraints.collapsible) {
795
+ var _getPercentageSizeFro, _getPercentageSizeFro2;
799
796
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
800
- const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
797
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
798
+ sizePercentage: panelData.constraints.collapsedSizePercentage,
799
+ sizePixels: panelData.constraints.collapsedSizePixels
800
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
801
+ const minSize = (_getPercentageSizeFro2 = getPercentageSizeFromMixedSizes({
801
802
  sizePercentage: panelData.constraints.minSizePercentage,
802
803
  sizePixels: panelData.constraints.minSizePixels
803
- }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
804
- let delta = 0;
805
- if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
806
- delta = direction === "horizontal" ? width : height;
807
- } else {
808
- delta = -(direction === "horizontal" ? width : height);
809
- }
804
+ }, groupSizePixels)) !== null && _getPercentageSizeFro2 !== void 0 ? _getPercentageSizeFro2 : 0;
810
805
  const nextLayout = adjustLayoutByDelta({
811
- delta,
806
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
812
807
  groupSizePixels,
813
808
  layout,
814
809
  panelConstraints: panelDataArray.map(panelData => panelData.constraints),
@@ -1117,6 +1112,10 @@ function debounce(callback, durationMs = 10) {
1117
1112
  return callable;
1118
1113
  }
1119
1114
 
1115
+ function getPanelElementsForGroup(groupId) {
1116
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
1117
+ }
1118
+
1120
1119
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
1121
1120
  // or on a browser with cookies/storage disabled.
1122
1121
  // In either case, this function avoids accessing localStorage until needed,
@@ -1343,7 +1342,7 @@ const defaultStorage = {
1343
1342
  };
1344
1343
  const debounceMap = {};
1345
1344
  function PanelGroupWithForwardedRef({
1346
- autoSaveId,
1345
+ autoSaveId = null,
1347
1346
  children,
1348
1347
  className: classNameFromProps = "",
1349
1348
  dataAttributes,
@@ -1360,11 +1359,11 @@ function PanelGroupWithForwardedRef({
1360
1359
  const groupId = useUniqueId(idFromProps);
1361
1360
  const [dragState, setDragState] = useState(null);
1362
1361
  const [layout, setLayout] = useState([]);
1363
- const [panelDataArray, setPanelDataArray] = useState([]);
1364
1362
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1365
1363
  const panelSizeBeforeCollapseRef = useRef(new Map());
1366
1364
  const prevDeltaRef = useRef(0);
1367
1365
  const committedValuesRef = useRef({
1366
+ autoSaveId,
1368
1367
  direction,
1369
1368
  dragState,
1370
1369
  id: groupId,
@@ -1372,7 +1371,8 @@ function PanelGroupWithForwardedRef({
1372
1371
  keyboardResizeByPixels,
1373
1372
  layout,
1374
1373
  onLayout,
1375
- panelDataArray
1374
+ panelDataArray: [],
1375
+ storage
1376
1376
  });
1377
1377
  const devWarningsRef = useRef({
1378
1378
  didLogIdAndOrderWarning: false,
@@ -1410,6 +1410,7 @@ function PanelGroupWithForwardedRef({
1410
1410
  });
1411
1411
  if (!areEqual(prevLayout, safeLayout)) {
1412
1412
  setLayout(safeLayout);
1413
+ committedValuesRef.current.layout = safeLayout;
1413
1414
  if (onLayout) {
1414
1415
  onLayout(safeLayout.map(sizePercentage => ({
1415
1416
  sizePercentage,
@@ -1421,21 +1422,29 @@ function PanelGroupWithForwardedRef({
1421
1422
  }
1422
1423
  }), []);
1423
1424
  useIsomorphicLayoutEffect(() => {
1425
+ committedValuesRef.current.autoSaveId = autoSaveId;
1424
1426
  committedValuesRef.current.direction = direction;
1425
1427
  committedValuesRef.current.dragState = dragState;
1426
1428
  committedValuesRef.current.id = groupId;
1427
- committedValuesRef.current.layout = layout;
1428
1429
  committedValuesRef.current.onLayout = onLayout;
1429
- committedValuesRef.current.panelDataArray = panelDataArray;
1430
+ committedValuesRef.current.storage = storage;
1431
+
1432
+ // panelDataArray and layout are updated in-sync with scheduled state updates.
1433
+ // TODO [217] Move these values into a separate ref
1430
1434
  });
1435
+
1431
1436
  useWindowSplitterPanelGroupBehavior({
1432
1437
  committedValuesRef,
1433
1438
  groupId,
1434
1439
  layout,
1435
- panelDataArray,
1440
+ panelDataArray: committedValuesRef.current.panelDataArray,
1436
1441
  setLayout
1437
1442
  });
1438
1443
  useEffect(() => {
1444
+ const {
1445
+ panelDataArray
1446
+ } = committedValuesRef.current;
1447
+
1439
1448
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1440
1449
  if (autoSaveId) {
1441
1450
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1448,59 +1457,11 @@ function PanelGroupWithForwardedRef({
1448
1457
  }
1449
1458
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1450
1459
  }
1451
- }, [autoSaveId, layout, panelDataArray, storage]);
1452
-
1453
- // Once all panels have registered themselves,
1454
- // Compute the initial sizes based on default weights.
1455
- // This assumes that panels register during initial mount (no conditional rendering)!
1460
+ }, [autoSaveId, layout, storage]);
1456
1461
  useIsomorphicLayoutEffect(() => {
1457
1462
  const {
1458
- id: groupId,
1459
- layout,
1460
- onLayout
1463
+ panelDataArray
1461
1464
  } = committedValuesRef.current;
1462
- if (layout.length === panelDataArray.length) {
1463
- // Only compute (or restore) default layout once per panel configuration.
1464
- return;
1465
- }
1466
-
1467
- // If this panel has been configured to persist sizing information,
1468
- // default size should be restored from local storage if possible.
1469
- let unsafeLayout = null;
1470
- if (autoSaveId) {
1471
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1472
- }
1473
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1474
- if (groupSizePixels <= 0) {
1475
- // Wait until the group has rendered a non-zero size before computing layout.
1476
- return;
1477
- }
1478
- if (unsafeLayout == null) {
1479
- unsafeLayout = calculateUnsafeDefaultLayout({
1480
- groupSizePixels,
1481
- panelDataArray
1482
- });
1483
- }
1484
-
1485
- // Validate even saved layouts in case something has changed since last render
1486
- // e.g. for pixel groups, this could be the size of the window
1487
- const validatedLayout = validatePanelGroupLayout({
1488
- groupSizePixels,
1489
- layout: unsafeLayout,
1490
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1491
- });
1492
- if (!areEqual(layout, validatedLayout)) {
1493
- setLayout(validatedLayout);
1494
- }
1495
- if (onLayout) {
1496
- onLayout(validatedLayout.map(sizePercentage => ({
1497
- sizePercentage,
1498
- sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1499
- })));
1500
- }
1501
- callPanelCallbacks(groupId, panelDataArray, validatedLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1502
- }, [autoSaveId, layout, panelDataArray, storage]);
1503
- useIsomorphicLayoutEffect(() => {
1504
1465
  const constraints = panelDataArray.map(({
1505
1466
  constraints
1506
1467
  }) => constraints);
@@ -1524,6 +1485,7 @@ function PanelGroupWithForwardedRef({
1524
1485
  });
1525
1486
  if (!areEqual(prevLayout, nextLayout)) {
1526
1487
  setLayout(nextLayout);
1488
+ committedValuesRef.current.layout = nextLayout;
1527
1489
  if (onLayout) {
1528
1490
  onLayout(nextLayout.map(sizePercentage => ({
1529
1491
  sizePercentage,
@@ -1538,20 +1500,20 @@ function PanelGroupWithForwardedRef({
1538
1500
  resizeObserver.disconnect();
1539
1501
  };
1540
1502
  }
1541
- }, [groupId, panelDataArray]);
1503
+ }, [groupId]);
1542
1504
 
1543
1505
  // DEV warnings
1544
1506
  useEffect(() => {
1545
1507
  {
1508
+ const {
1509
+ panelDataArray
1510
+ } = committedValuesRef.current;
1546
1511
  const {
1547
1512
  didLogIdAndOrderWarning,
1548
1513
  didLogPanelConstraintsWarning,
1549
1514
  prevPanelIds
1550
1515
  } = devWarningsRef.current;
1551
1516
  if (!didLogIdAndOrderWarning) {
1552
- const {
1553
- panelDataArray
1554
- } = committedValuesRef.current;
1555
1517
  const panelIds = panelDataArray.map(({
1556
1518
  id
1557
1519
  }) => id);
@@ -1617,6 +1579,7 @@ function PanelGroupWithForwardedRef({
1617
1579
  });
1618
1580
  if (!compareLayouts(prevLayout, nextLayout)) {
1619
1581
  setLayout(nextLayout);
1582
+ committedValuesRef.current.layout = nextLayout;
1620
1583
  if (onLayout) {
1621
1584
  onLayout(nextLayout.map(sizePercentage => ({
1622
1585
  sizePercentage,
@@ -1661,6 +1624,7 @@ function PanelGroupWithForwardedRef({
1661
1624
  });
1662
1625
  if (!compareLayouts(prevLayout, nextLayout)) {
1663
1626
  setLayout(nextLayout);
1627
+ committedValuesRef.current.layout = nextLayout;
1664
1628
  if (onLayout) {
1665
1629
  onLayout(nextLayout.map(sizePercentage => ({
1666
1630
  sizePercentage,
@@ -1691,6 +1655,9 @@ function PanelGroupWithForwardedRef({
1691
1655
 
1692
1656
  // This API should never read from committedValuesRef
1693
1657
  const getPanelStyle = useCallback(panelData => {
1658
+ const {
1659
+ panelDataArray
1660
+ } = committedValuesRef.current;
1694
1661
  const panelIndex = panelDataArray.indexOf(panelData);
1695
1662
  return computePanelFlexBoxStyle({
1696
1663
  dragState,
@@ -1698,7 +1665,7 @@ function PanelGroupWithForwardedRef({
1698
1665
  panelData: panelDataArray,
1699
1666
  panelIndex
1700
1667
  });
1701
- }, [dragState, layout, panelDataArray]);
1668
+ }, [dragState, layout]);
1702
1669
 
1703
1670
  // External APIs are safe to memoize via committed values ref
1704
1671
  const isPanelCollapsed = useCallback(panelData => {
@@ -1728,22 +1695,76 @@ function PanelGroupWithForwardedRef({
1728
1695
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1729
1696
  }, [groupId]);
1730
1697
  const registerPanel = useCallback(panelData => {
1731
- setPanelDataArray(prevPanelDataArray => {
1732
- const nextPanelDataArray = [...prevPanelDataArray, panelData];
1733
- return nextPanelDataArray.sort((panelA, panelB) => {
1734
- const orderA = panelA.order;
1735
- const orderB = panelB.order;
1736
- if (orderA == null && orderB == null) {
1737
- return 0;
1738
- } else if (orderA == null) {
1739
- return -1;
1740
- } else if (orderB == null) {
1741
- return 1;
1742
- } else {
1743
- return orderA - orderB;
1744
- }
1698
+ const {
1699
+ autoSaveId,
1700
+ id: groupId,
1701
+ layout: prevLayout,
1702
+ onLayout,
1703
+ panelDataArray,
1704
+ storage
1705
+ } = committedValuesRef.current;
1706
+ panelDataArray.push(panelData);
1707
+ panelDataArray.sort((panelA, panelB) => {
1708
+ const orderA = panelA.order;
1709
+ const orderB = panelB.order;
1710
+ if (orderA == null && orderB == null) {
1711
+ return 0;
1712
+ } else if (orderA == null) {
1713
+ return -1;
1714
+ } else if (orderB == null) {
1715
+ return 1;
1716
+ } else {
1717
+ return orderA - orderB;
1718
+ }
1719
+ });
1720
+
1721
+ // Wait until all panels have registered before we try to compute layout;
1722
+ // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1723
+ const panelElements = getPanelElementsForGroup(groupId);
1724
+ if (panelElements.length !== panelDataArray.length) {
1725
+ return;
1726
+ }
1727
+
1728
+ // If this panel has been configured to persist sizing information,
1729
+ // default size should be restored from local storage if possible.
1730
+ let unsafeLayout = null;
1731
+ if (autoSaveId) {
1732
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1733
+ }
1734
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1735
+ if (groupSizePixels <= 0) {
1736
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1737
+ constraints
1738
+ }) => constraints))) {
1739
+ // Wait until the group has rendered a non-zero size before computing layout.
1740
+ return;
1741
+ }
1742
+ }
1743
+ if (unsafeLayout == null) {
1744
+ unsafeLayout = calculateUnsafeDefaultLayout({
1745
+ groupSizePixels,
1746
+ panelDataArray
1745
1747
  });
1748
+ }
1749
+
1750
+ // Validate even saved layouts in case something has changed since last render
1751
+ // e.g. for pixel groups, this could be the size of the window
1752
+ const nextLayout = validatePanelGroupLayout({
1753
+ groupSizePixels,
1754
+ layout: unsafeLayout,
1755
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1746
1756
  });
1757
+ if (!areEqual(prevLayout, nextLayout)) {
1758
+ setLayout(nextLayout);
1759
+ committedValuesRef.current.layout = nextLayout;
1760
+ if (onLayout) {
1761
+ onLayout(nextLayout.map(sizePercentage => ({
1762
+ sizePercentage,
1763
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1764
+ })));
1765
+ }
1766
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1767
+ }
1747
1768
  }, []);
1748
1769
  const registerResizeHandle = useCallback(dragHandleId => {
1749
1770
  return function resizeHandler(event) {
@@ -1813,6 +1834,7 @@ function PanelGroupWithForwardedRef({
1813
1834
  }
1814
1835
  if (layoutChanged) {
1815
1836
  setLayout(nextLayout);
1837
+ committedValuesRef.current.layout = nextLayout;
1816
1838
  if (onLayout) {
1817
1839
  onLayout(nextLayout.map(sizePercentage => ({
1818
1840
  sizePercentage,
@@ -1850,6 +1872,7 @@ function PanelGroupWithForwardedRef({
1850
1872
  });
1851
1873
  if (!compareLayouts(prevLayout, nextLayout)) {
1852
1874
  setLayout(nextLayout);
1875
+ committedValuesRef.current.layout = nextLayout;
1853
1876
  if (onLayout) {
1854
1877
  onLayout(nextLayout.map(sizePercentage => ({
1855
1878
  sizePercentage,
@@ -1877,16 +1900,84 @@ function PanelGroupWithForwardedRef({
1877
1900
  resetGlobalCursorStyle();
1878
1901
  setDragState(null);
1879
1902
  }, []);
1903
+ const unregisterPanelRef = useRef({
1904
+ pendingPanelIds: new Set(),
1905
+ timeout: null
1906
+ });
1880
1907
  const unregisterPanel = useCallback(panelData => {
1881
- delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1882
- setPanelDataArray(panelDataArray => {
1883
- const index = panelDataArray.indexOf(panelData);
1884
- if (index >= 0) {
1885
- panelDataArray = [...panelDataArray];
1886
- panelDataArray.splice(index, 1);
1908
+ const {
1909
+ id: groupId,
1910
+ layout: prevLayout,
1911
+ onLayout,
1912
+ panelDataArray
1913
+ } = committedValuesRef.current;
1914
+ const index = panelDataArray.indexOf(panelData);
1915
+ if (index >= 0) {
1916
+ panelDataArray.splice(index, 1);
1917
+ unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1918
+ }
1919
+ if (unregisterPanelRef.current.timeout != null) {
1920
+ clearTimeout(unregisterPanelRef.current.timeout);
1921
+ }
1922
+
1923
+ // Batch panel unmounts so that we only calculate layout once;
1924
+ // This is more efficient and avoids misleading warnings in development mode.
1925
+ // We can't check the DOM to detect this because Panel elements have not yet been removed.
1926
+ unregisterPanelRef.current.timeout = setTimeout(() => {
1927
+ const {
1928
+ pendingPanelIds
1929
+ } = unregisterPanelRef.current;
1930
+ panelIdToLastNotifiedMixedSizesMapRef.current;
1931
+
1932
+ // TRICKY
1933
+ // Strict effects mode
1934
+ let unmountDueToStrictMode = false;
1935
+ pendingPanelIds.forEach(panelId => {
1936
+ pendingPanelIds.delete(panelId);
1937
+ if (panelDataArray.find(({
1938
+ id
1939
+ }) => id === panelId) == null) {
1940
+ unmountDueToStrictMode = true;
1941
+
1942
+ // TRICKY
1943
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1944
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1945
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1946
+ delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1947
+ }
1948
+ });
1949
+ if (!unmountDueToStrictMode) {
1950
+ return;
1887
1951
  }
1888
- return panelDataArray;
1889
- });
1952
+ if (panelDataArray.length === 0) {
1953
+ // The group is unmounting; skip layout calculation.
1954
+ return;
1955
+ }
1956
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1957
+ let unsafeLayout = calculateUnsafeDefaultLayout({
1958
+ groupSizePixels,
1959
+ panelDataArray
1960
+ });
1961
+
1962
+ // Validate even saved layouts in case something has changed since last render
1963
+ // e.g. for pixel groups, this could be the size of the window
1964
+ const nextLayout = validatePanelGroupLayout({
1965
+ groupSizePixels,
1966
+ layout: unsafeLayout,
1967
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1968
+ });
1969
+ if (!areEqual(prevLayout, nextLayout)) {
1970
+ setLayout(nextLayout);
1971
+ committedValuesRef.current.layout = nextLayout;
1972
+ if (onLayout) {
1973
+ onLayout(nextLayout.map(sizePercentage => ({
1974
+ sizePercentage,
1975
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1976
+ })));
1977
+ }
1978
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1979
+ }
1980
+ }, 0);
1890
1981
  }, []);
1891
1982
  const context = useMemo(() => ({
1892
1983
  collapsePanel,