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.
@@ -71,6 +71,7 @@ function PanelWithForwardedRef({
71
71
  expandPanel,
72
72
  getPanelSize,
73
73
  getPanelStyle,
74
+ groupId,
74
75
  isPanelCollapsed,
75
76
  registerPanel,
76
77
  resizePanel,
@@ -175,6 +176,7 @@ function PanelWithForwardedRef({
175
176
  // CSS selectors
176
177
  "data-panel": "",
177
178
  "data-panel-id": panelId,
179
+ "data-panel-group-id": groupId,
178
180
  // e2e test attributes
179
181
  "data-panel-collapsible": collapsible || undefined ,
180
182
  "data-panel-size": parseFloat("" + style.flexGrow).toFixed(1)
@@ -187,8 +189,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
187
189
  PanelWithForwardedRef.displayName = "Panel";
188
190
  Panel.displayName = "forwardRef(Panel)";
189
191
 
190
- const PRECISION = 10;
191
-
192
192
  function convertPixelsToPercentage(pixels, groupSizePixels) {
193
193
  return pixels / groupSizePixels * 100;
194
194
  }
@@ -266,6 +266,8 @@ function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, gr
266
266
  };
267
267
  }
268
268
 
269
+ const PRECISION = 10;
270
+
269
271
  function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
270
272
  actual = parseFloat(actual.toFixed(fractionDigits));
271
273
  expected = parseFloat(expected.toFixed(fractionDigits));
@@ -749,15 +751,10 @@ function useWindowSplitterPanelGroupBehavior({
749
751
  }, [groupId, layout, panelDataArray]);
750
752
  useEffect(() => {
751
753
  const {
752
- direction,
753
754
  panelDataArray
754
755
  } = committedValuesRef.current;
755
756
  const groupElement = getPanelGroupElement(groupId);
756
757
  assert(groupElement != null, `No group found for id "${groupId}"`);
757
- const {
758
- height,
759
- width
760
- } = groupElement.getBoundingClientRect();
761
758
  const handles = getResizeHandleElementsForGroup(groupId);
762
759
  const cleanupFunctions = handles.map(handle => {
763
760
  const handleId = handle.getAttribute("data-panel-resize-handle-id");
@@ -777,21 +774,19 @@ function useWindowSplitterPanelGroupBehavior({
777
774
  if (index >= 0) {
778
775
  const panelData = panelDataArray[index];
779
776
  const size = layout[index];
780
- if (size != null) {
781
- var _getPercentageSizeFro;
777
+ if (size != null && panelData.constraints.collapsible) {
778
+ var _getPercentageSizeFro, _getPercentageSizeFro2;
782
779
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
783
- const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
780
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
781
+ sizePercentage: panelData.constraints.collapsedSizePercentage,
782
+ sizePixels: panelData.constraints.collapsedSizePixels
783
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
784
+ const minSize = (_getPercentageSizeFro2 = getPercentageSizeFromMixedSizes({
784
785
  sizePercentage: panelData.constraints.minSizePercentage,
785
786
  sizePixels: panelData.constraints.minSizePixels
786
- }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
787
- let delta = 0;
788
- if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
789
- delta = direction === "horizontal" ? width : height;
790
- } else {
791
- delta = -(direction === "horizontal" ? width : height);
792
- }
787
+ }, groupSizePixels)) !== null && _getPercentageSizeFro2 !== void 0 ? _getPercentageSizeFro2 : 0;
793
788
  const nextLayout = adjustLayoutByDelta({
794
- delta,
789
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
795
790
  groupSizePixels,
796
791
  layout,
797
792
  panelConstraints: panelDataArray.map(panelData => panelData.constraints),
@@ -1100,6 +1095,10 @@ function debounce(callback, durationMs = 10) {
1100
1095
  return callable;
1101
1096
  }
1102
1097
 
1098
+ function getPanelElementsForGroup(groupId) {
1099
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
1100
+ }
1101
+
1103
1102
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
1104
1103
  // or on a browser with cookies/storage disabled.
1105
1104
  // In either case, this function avoids accessing localStorage until needed,
@@ -1326,7 +1325,7 @@ const defaultStorage = {
1326
1325
  };
1327
1326
  const debounceMap = {};
1328
1327
  function PanelGroupWithForwardedRef({
1329
- autoSaveId,
1328
+ autoSaveId = null,
1330
1329
  children,
1331
1330
  className: classNameFromProps = "",
1332
1331
  dataAttributes,
@@ -1343,11 +1342,11 @@ function PanelGroupWithForwardedRef({
1343
1342
  const groupId = useUniqueId(idFromProps);
1344
1343
  const [dragState, setDragState] = useState(null);
1345
1344
  const [layout, setLayout] = useState([]);
1346
- const [panelDataArray, setPanelDataArray] = useState([]);
1347
1345
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1348
1346
  const panelSizeBeforeCollapseRef = useRef(new Map());
1349
1347
  const prevDeltaRef = useRef(0);
1350
1348
  const committedValuesRef = useRef({
1349
+ autoSaveId,
1351
1350
  direction,
1352
1351
  dragState,
1353
1352
  id: groupId,
@@ -1355,7 +1354,8 @@ function PanelGroupWithForwardedRef({
1355
1354
  keyboardResizeByPixels,
1356
1355
  layout,
1357
1356
  onLayout,
1358
- panelDataArray
1357
+ panelDataArray: [],
1358
+ storage
1359
1359
  });
1360
1360
  const devWarningsRef = useRef({
1361
1361
  didLogIdAndOrderWarning: false,
@@ -1393,6 +1393,7 @@ function PanelGroupWithForwardedRef({
1393
1393
  });
1394
1394
  if (!areEqual(prevLayout, safeLayout)) {
1395
1395
  setLayout(safeLayout);
1396
+ committedValuesRef.current.layout = safeLayout;
1396
1397
  if (onLayout) {
1397
1398
  onLayout(safeLayout.map(sizePercentage => ({
1398
1399
  sizePercentage,
@@ -1404,21 +1405,29 @@ function PanelGroupWithForwardedRef({
1404
1405
  }
1405
1406
  }), []);
1406
1407
  useIsomorphicLayoutEffect(() => {
1408
+ committedValuesRef.current.autoSaveId = autoSaveId;
1407
1409
  committedValuesRef.current.direction = direction;
1408
1410
  committedValuesRef.current.dragState = dragState;
1409
1411
  committedValuesRef.current.id = groupId;
1410
- committedValuesRef.current.layout = layout;
1411
1412
  committedValuesRef.current.onLayout = onLayout;
1412
- committedValuesRef.current.panelDataArray = panelDataArray;
1413
+ committedValuesRef.current.storage = storage;
1414
+
1415
+ // panelDataArray and layout are updated in-sync with scheduled state updates.
1416
+ // TODO [217] Move these values into a separate ref
1413
1417
  });
1418
+
1414
1419
  useWindowSplitterPanelGroupBehavior({
1415
1420
  committedValuesRef,
1416
1421
  groupId,
1417
1422
  layout,
1418
- panelDataArray,
1423
+ panelDataArray: committedValuesRef.current.panelDataArray,
1419
1424
  setLayout
1420
1425
  });
1421
1426
  useEffect(() => {
1427
+ const {
1428
+ panelDataArray
1429
+ } = committedValuesRef.current;
1430
+
1422
1431
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1423
1432
  if (autoSaveId) {
1424
1433
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1431,59 +1440,11 @@ function PanelGroupWithForwardedRef({
1431
1440
  }
1432
1441
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1433
1442
  }
1434
- }, [autoSaveId, layout, panelDataArray, storage]);
1435
-
1436
- // Once all panels have registered themselves,
1437
- // Compute the initial sizes based on default weights.
1438
- // This assumes that panels register during initial mount (no conditional rendering)!
1443
+ }, [autoSaveId, layout, storage]);
1439
1444
  useIsomorphicLayoutEffect(() => {
1440
1445
  const {
1441
- id: groupId,
1442
- layout,
1443
- onLayout
1446
+ panelDataArray
1444
1447
  } = committedValuesRef.current;
1445
- if (layout.length === panelDataArray.length) {
1446
- // Only compute (or restore) default layout once per panel configuration.
1447
- return;
1448
- }
1449
-
1450
- // If this panel has been configured to persist sizing information,
1451
- // default size should be restored from local storage if possible.
1452
- let unsafeLayout = null;
1453
- if (autoSaveId) {
1454
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1455
- }
1456
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1457
- if (groupSizePixels <= 0) {
1458
- // Wait until the group has rendered a non-zero size before computing layout.
1459
- return;
1460
- }
1461
- if (unsafeLayout == null) {
1462
- unsafeLayout = calculateUnsafeDefaultLayout({
1463
- groupSizePixels,
1464
- panelDataArray
1465
- });
1466
- }
1467
-
1468
- // Validate even saved layouts in case something has changed since last render
1469
- // e.g. for pixel groups, this could be the size of the window
1470
- const validatedLayout = validatePanelGroupLayout({
1471
- groupSizePixels,
1472
- layout: unsafeLayout,
1473
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1474
- });
1475
- if (!areEqual(layout, validatedLayout)) {
1476
- setLayout(validatedLayout);
1477
- }
1478
- if (onLayout) {
1479
- onLayout(validatedLayout.map(sizePercentage => ({
1480
- sizePercentage,
1481
- sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1482
- })));
1483
- }
1484
- callPanelCallbacks(groupId, panelDataArray, validatedLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1485
- }, [autoSaveId, layout, panelDataArray, storage]);
1486
- useIsomorphicLayoutEffect(() => {
1487
1448
  const constraints = panelDataArray.map(({
1488
1449
  constraints
1489
1450
  }) => constraints);
@@ -1507,6 +1468,7 @@ function PanelGroupWithForwardedRef({
1507
1468
  });
1508
1469
  if (!areEqual(prevLayout, nextLayout)) {
1509
1470
  setLayout(nextLayout);
1471
+ committedValuesRef.current.layout = nextLayout;
1510
1472
  if (onLayout) {
1511
1473
  onLayout(nextLayout.map(sizePercentage => ({
1512
1474
  sizePercentage,
@@ -1521,20 +1483,20 @@ function PanelGroupWithForwardedRef({
1521
1483
  resizeObserver.disconnect();
1522
1484
  };
1523
1485
  }
1524
- }, [groupId, panelDataArray]);
1486
+ }, [groupId]);
1525
1487
 
1526
1488
  // DEV warnings
1527
1489
  useEffect(() => {
1528
1490
  {
1491
+ const {
1492
+ panelDataArray
1493
+ } = committedValuesRef.current;
1529
1494
  const {
1530
1495
  didLogIdAndOrderWarning,
1531
1496
  didLogPanelConstraintsWarning,
1532
1497
  prevPanelIds
1533
1498
  } = devWarningsRef.current;
1534
1499
  if (!didLogIdAndOrderWarning) {
1535
- const {
1536
- panelDataArray
1537
- } = committedValuesRef.current;
1538
1500
  const panelIds = panelDataArray.map(({
1539
1501
  id
1540
1502
  }) => id);
@@ -1600,6 +1562,7 @@ function PanelGroupWithForwardedRef({
1600
1562
  });
1601
1563
  if (!compareLayouts(prevLayout, nextLayout)) {
1602
1564
  setLayout(nextLayout);
1565
+ committedValuesRef.current.layout = nextLayout;
1603
1566
  if (onLayout) {
1604
1567
  onLayout(nextLayout.map(sizePercentage => ({
1605
1568
  sizePercentage,
@@ -1644,6 +1607,7 @@ function PanelGroupWithForwardedRef({
1644
1607
  });
1645
1608
  if (!compareLayouts(prevLayout, nextLayout)) {
1646
1609
  setLayout(nextLayout);
1610
+ committedValuesRef.current.layout = nextLayout;
1647
1611
  if (onLayout) {
1648
1612
  onLayout(nextLayout.map(sizePercentage => ({
1649
1613
  sizePercentage,
@@ -1674,6 +1638,9 @@ function PanelGroupWithForwardedRef({
1674
1638
 
1675
1639
  // This API should never read from committedValuesRef
1676
1640
  const getPanelStyle = useCallback(panelData => {
1641
+ const {
1642
+ panelDataArray
1643
+ } = committedValuesRef.current;
1677
1644
  const panelIndex = panelDataArray.indexOf(panelData);
1678
1645
  return computePanelFlexBoxStyle({
1679
1646
  dragState,
@@ -1681,7 +1648,7 @@ function PanelGroupWithForwardedRef({
1681
1648
  panelData: panelDataArray,
1682
1649
  panelIndex
1683
1650
  });
1684
- }, [dragState, layout, panelDataArray]);
1651
+ }, [dragState, layout]);
1685
1652
 
1686
1653
  // External APIs are safe to memoize via committed values ref
1687
1654
  const isPanelCollapsed = useCallback(panelData => {
@@ -1711,22 +1678,76 @@ function PanelGroupWithForwardedRef({
1711
1678
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1712
1679
  }, [groupId]);
1713
1680
  const registerPanel = useCallback(panelData => {
1714
- setPanelDataArray(prevPanelDataArray => {
1715
- const nextPanelDataArray = [...prevPanelDataArray, panelData];
1716
- return nextPanelDataArray.sort((panelA, panelB) => {
1717
- const orderA = panelA.order;
1718
- const orderB = panelB.order;
1719
- if (orderA == null && orderB == null) {
1720
- return 0;
1721
- } else if (orderA == null) {
1722
- return -1;
1723
- } else if (orderB == null) {
1724
- return 1;
1725
- } else {
1726
- return orderA - orderB;
1727
- }
1681
+ const {
1682
+ autoSaveId,
1683
+ id: groupId,
1684
+ layout: prevLayout,
1685
+ onLayout,
1686
+ panelDataArray,
1687
+ storage
1688
+ } = committedValuesRef.current;
1689
+ panelDataArray.push(panelData);
1690
+ panelDataArray.sort((panelA, panelB) => {
1691
+ const orderA = panelA.order;
1692
+ const orderB = panelB.order;
1693
+ if (orderA == null && orderB == null) {
1694
+ return 0;
1695
+ } else if (orderA == null) {
1696
+ return -1;
1697
+ } else if (orderB == null) {
1698
+ return 1;
1699
+ } else {
1700
+ return orderA - orderB;
1701
+ }
1702
+ });
1703
+
1704
+ // Wait until all panels have registered before we try to compute layout;
1705
+ // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1706
+ const panelElements = getPanelElementsForGroup(groupId);
1707
+ if (panelElements.length !== panelDataArray.length) {
1708
+ return;
1709
+ }
1710
+
1711
+ // If this panel has been configured to persist sizing information,
1712
+ // default size should be restored from local storage if possible.
1713
+ let unsafeLayout = null;
1714
+ if (autoSaveId) {
1715
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1716
+ }
1717
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1718
+ if (groupSizePixels <= 0) {
1719
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1720
+ constraints
1721
+ }) => constraints))) {
1722
+ // Wait until the group has rendered a non-zero size before computing layout.
1723
+ return;
1724
+ }
1725
+ }
1726
+ if (unsafeLayout == null) {
1727
+ unsafeLayout = calculateUnsafeDefaultLayout({
1728
+ groupSizePixels,
1729
+ panelDataArray
1728
1730
  });
1731
+ }
1732
+
1733
+ // Validate even saved layouts in case something has changed since last render
1734
+ // e.g. for pixel groups, this could be the size of the window
1735
+ const nextLayout = validatePanelGroupLayout({
1736
+ groupSizePixels,
1737
+ layout: unsafeLayout,
1738
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1729
1739
  });
1740
+ if (!areEqual(prevLayout, nextLayout)) {
1741
+ setLayout(nextLayout);
1742
+ committedValuesRef.current.layout = nextLayout;
1743
+ if (onLayout) {
1744
+ onLayout(nextLayout.map(sizePercentage => ({
1745
+ sizePercentage,
1746
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1747
+ })));
1748
+ }
1749
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1750
+ }
1730
1751
  }, []);
1731
1752
  const registerResizeHandle = useCallback(dragHandleId => {
1732
1753
  return function resizeHandler(event) {
@@ -1796,6 +1817,7 @@ function PanelGroupWithForwardedRef({
1796
1817
  }
1797
1818
  if (layoutChanged) {
1798
1819
  setLayout(nextLayout);
1820
+ committedValuesRef.current.layout = nextLayout;
1799
1821
  if (onLayout) {
1800
1822
  onLayout(nextLayout.map(sizePercentage => ({
1801
1823
  sizePercentage,
@@ -1833,6 +1855,7 @@ function PanelGroupWithForwardedRef({
1833
1855
  });
1834
1856
  if (!compareLayouts(prevLayout, nextLayout)) {
1835
1857
  setLayout(nextLayout);
1858
+ committedValuesRef.current.layout = nextLayout;
1836
1859
  if (onLayout) {
1837
1860
  onLayout(nextLayout.map(sizePercentage => ({
1838
1861
  sizePercentage,
@@ -1860,16 +1883,84 @@ function PanelGroupWithForwardedRef({
1860
1883
  resetGlobalCursorStyle();
1861
1884
  setDragState(null);
1862
1885
  }, []);
1886
+ const unregisterPanelRef = useRef({
1887
+ pendingPanelIds: new Set(),
1888
+ timeout: null
1889
+ });
1863
1890
  const unregisterPanel = useCallback(panelData => {
1864
- delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1865
- setPanelDataArray(panelDataArray => {
1866
- const index = panelDataArray.indexOf(panelData);
1867
- if (index >= 0) {
1868
- panelDataArray = [...panelDataArray];
1869
- panelDataArray.splice(index, 1);
1891
+ const {
1892
+ id: groupId,
1893
+ layout: prevLayout,
1894
+ onLayout,
1895
+ panelDataArray
1896
+ } = committedValuesRef.current;
1897
+ const index = panelDataArray.indexOf(panelData);
1898
+ if (index >= 0) {
1899
+ panelDataArray.splice(index, 1);
1900
+ unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1901
+ }
1902
+ if (unregisterPanelRef.current.timeout != null) {
1903
+ clearTimeout(unregisterPanelRef.current.timeout);
1904
+ }
1905
+
1906
+ // Batch panel unmounts so that we only calculate layout once;
1907
+ // This is more efficient and avoids misleading warnings in development mode.
1908
+ // We can't check the DOM to detect this because Panel elements have not yet been removed.
1909
+ unregisterPanelRef.current.timeout = setTimeout(() => {
1910
+ const {
1911
+ pendingPanelIds
1912
+ } = unregisterPanelRef.current;
1913
+ panelIdToLastNotifiedMixedSizesMapRef.current;
1914
+
1915
+ // TRICKY
1916
+ // Strict effects mode
1917
+ let unmountDueToStrictMode = false;
1918
+ pendingPanelIds.forEach(panelId => {
1919
+ pendingPanelIds.delete(panelId);
1920
+ if (panelDataArray.find(({
1921
+ id
1922
+ }) => id === panelId) == null) {
1923
+ unmountDueToStrictMode = true;
1924
+
1925
+ // TRICKY
1926
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1927
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1928
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1929
+ delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1930
+ }
1931
+ });
1932
+ if (!unmountDueToStrictMode) {
1933
+ return;
1870
1934
  }
1871
- return panelDataArray;
1872
- });
1935
+ if (panelDataArray.length === 0) {
1936
+ // The group is unmounting; skip layout calculation.
1937
+ return;
1938
+ }
1939
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1940
+ let unsafeLayout = calculateUnsafeDefaultLayout({
1941
+ groupSizePixels,
1942
+ panelDataArray
1943
+ });
1944
+
1945
+ // Validate even saved layouts in case something has changed since last render
1946
+ // e.g. for pixel groups, this could be the size of the window
1947
+ const nextLayout = validatePanelGroupLayout({
1948
+ groupSizePixels,
1949
+ layout: unsafeLayout,
1950
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1951
+ });
1952
+ if (!areEqual(prevLayout, nextLayout)) {
1953
+ setLayout(nextLayout);
1954
+ committedValuesRef.current.layout = nextLayout;
1955
+ if (onLayout) {
1956
+ onLayout(nextLayout.map(sizePercentage => ({
1957
+ sizePercentage,
1958
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1959
+ })));
1960
+ }
1961
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1962
+ }
1963
+ }, 0);
1873
1964
  }, []);
1874
1965
  const context = useMemo(() => ({
1875
1966
  collapsePanel,