react-resizable-panels 0.0.59 → 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)
@@ -1117,6 +1119,10 @@ function debounce(callback, durationMs = 10) {
1117
1119
  return callable;
1118
1120
  }
1119
1121
 
1122
+ function getPanelElementsForGroup(groupId) {
1123
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
1124
+ }
1125
+
1120
1126
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
1121
1127
  // or on a browser with cookies/storage disabled.
1122
1128
  // In either case, this function avoids accessing localStorage until needed,
@@ -1343,7 +1349,7 @@ const defaultStorage = {
1343
1349
  };
1344
1350
  const debounceMap = {};
1345
1351
  function PanelGroupWithForwardedRef({
1346
- autoSaveId,
1352
+ autoSaveId = null,
1347
1353
  children,
1348
1354
  className: classNameFromProps = "",
1349
1355
  dataAttributes,
@@ -1360,12 +1366,11 @@ function PanelGroupWithForwardedRef({
1360
1366
  const groupId = useUniqueId(idFromProps);
1361
1367
  const [dragState, setDragState] = useState(null);
1362
1368
  const [layout, setLayout] = useState([]);
1363
- const [panelDataArray, setPanelDataArray] = useState([]);
1364
1369
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1365
1370
  const panelSizeBeforeCollapseRef = useRef(new Map());
1366
1371
  const prevDeltaRef = useRef(0);
1367
- const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1368
1372
  const committedValuesRef = useRef({
1373
+ autoSaveId,
1369
1374
  direction,
1370
1375
  dragState,
1371
1376
  id: groupId,
@@ -1373,7 +1378,8 @@ function PanelGroupWithForwardedRef({
1373
1378
  keyboardResizeByPixels,
1374
1379
  layout,
1375
1380
  onLayout,
1376
- panelDataArray
1381
+ panelDataArray: [],
1382
+ storage
1377
1383
  });
1378
1384
  const devWarningsRef = useRef({
1379
1385
  didLogIdAndOrderWarning: false,
@@ -1411,6 +1417,7 @@ function PanelGroupWithForwardedRef({
1411
1417
  });
1412
1418
  if (!areEqual(prevLayout, safeLayout)) {
1413
1419
  setLayout(safeLayout);
1420
+ committedValuesRef.current.layout = safeLayout;
1414
1421
  if (onLayout) {
1415
1422
  onLayout(safeLayout.map(sizePercentage => ({
1416
1423
  sizePercentage,
@@ -1422,21 +1429,29 @@ function PanelGroupWithForwardedRef({
1422
1429
  }
1423
1430
  }), []);
1424
1431
  useIsomorphicLayoutEffect(() => {
1432
+ committedValuesRef.current.autoSaveId = autoSaveId;
1425
1433
  committedValuesRef.current.direction = direction;
1426
1434
  committedValuesRef.current.dragState = dragState;
1427
1435
  committedValuesRef.current.id = groupId;
1428
- committedValuesRef.current.layout = layout;
1429
1436
  committedValuesRef.current.onLayout = onLayout;
1430
- 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
1431
1441
  });
1442
+
1432
1443
  useWindowSplitterPanelGroupBehavior({
1433
1444
  committedValuesRef,
1434
1445
  groupId,
1435
1446
  layout,
1436
- panelDataArray,
1447
+ panelDataArray: committedValuesRef.current.panelDataArray,
1437
1448
  setLayout
1438
1449
  });
1439
1450
  useEffect(() => {
1451
+ const {
1452
+ panelDataArray
1453
+ } = committedValuesRef.current;
1454
+
1440
1455
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1441
1456
  if (autoSaveId) {
1442
1457
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1449,63 +1464,11 @@ function PanelGroupWithForwardedRef({
1449
1464
  }
1450
1465
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1451
1466
  }
1452
- }, [autoSaveId, layout, panelDataArray, storage]);
1453
-
1454
- // Once all panels have registered themselves,
1455
- // Compute the initial sizes based on default weights.
1456
- // This assumes that panels register during initial mount (no conditional rendering)!
1467
+ }, [autoSaveId, layout, storage]);
1457
1468
  useIsomorphicLayoutEffect(() => {
1458
1469
  const {
1459
- id: groupId,
1460
- layout,
1461
- onLayout
1470
+ panelDataArray
1462
1471
  } = committedValuesRef.current;
1463
- if (layout.length === panelDataArray.length) {
1464
- // Only compute (or restore) default layout once per panel configuration.
1465
- return;
1466
- }
1467
-
1468
- // If this panel has been configured to persist sizing information,
1469
- // default size should be restored from local storage if possible.
1470
- let unsafeLayout = null;
1471
- if (autoSaveId) {
1472
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1473
- }
1474
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1475
- if (groupSizePixels <= 0) {
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
- }
1482
- }
1483
- if (unsafeLayout == null) {
1484
- unsafeLayout = calculateUnsafeDefaultLayout({
1485
- groupSizePixels,
1486
- panelDataArray
1487
- });
1488
- }
1489
-
1490
- // Validate even saved layouts in case something has changed since last render
1491
- // e.g. for pixel groups, this could be the size of the window
1492
- const validatedLayout = validatePanelGroupLayout({
1493
- groupSizePixels,
1494
- layout: unsafeLayout,
1495
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1496
- });
1497
- if (!areEqual(layout, validatedLayout)) {
1498
- setLayout(validatedLayout);
1499
- }
1500
- if (onLayout) {
1501
- onLayout(validatedLayout.map(sizePercentage => ({
1502
- sizePercentage,
1503
- sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1504
- })));
1505
- }
1506
- callPanelCallbacks(groupId, panelDataArray, validatedLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1507
- }, [autoSaveId, layout, panelDataArray, storage]);
1508
- useIsomorphicLayoutEffect(() => {
1509
1472
  const constraints = panelDataArray.map(({
1510
1473
  constraints
1511
1474
  }) => constraints);
@@ -1529,6 +1492,7 @@ function PanelGroupWithForwardedRef({
1529
1492
  });
1530
1493
  if (!areEqual(prevLayout, nextLayout)) {
1531
1494
  setLayout(nextLayout);
1495
+ committedValuesRef.current.layout = nextLayout;
1532
1496
  if (onLayout) {
1533
1497
  onLayout(nextLayout.map(sizePercentage => ({
1534
1498
  sizePercentage,
@@ -1543,20 +1507,20 @@ function PanelGroupWithForwardedRef({
1543
1507
  resizeObserver.disconnect();
1544
1508
  };
1545
1509
  }
1546
- }, [groupId, panelDataArray]);
1510
+ }, [groupId]);
1547
1511
 
1548
1512
  // DEV warnings
1549
1513
  useEffect(() => {
1550
1514
  {
1515
+ const {
1516
+ panelDataArray
1517
+ } = committedValuesRef.current;
1551
1518
  const {
1552
1519
  didLogIdAndOrderWarning,
1553
1520
  didLogPanelConstraintsWarning,
1554
1521
  prevPanelIds
1555
1522
  } = devWarningsRef.current;
1556
1523
  if (!didLogIdAndOrderWarning) {
1557
- const {
1558
- panelDataArray
1559
- } = committedValuesRef.current;
1560
1524
  const panelIds = panelDataArray.map(({
1561
1525
  id
1562
1526
  }) => id);
@@ -1598,17 +1562,6 @@ function PanelGroupWithForwardedRef({
1598
1562
  onLayout,
1599
1563
  panelDataArray
1600
1564
  } = 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
- }
1612
1565
  if (panelData.constraints.collapsible) {
1613
1566
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1614
1567
  const {
@@ -1633,6 +1586,7 @@ function PanelGroupWithForwardedRef({
1633
1586
  });
1634
1587
  if (!compareLayouts(prevLayout, nextLayout)) {
1635
1588
  setLayout(nextLayout);
1589
+ committedValuesRef.current.layout = nextLayout;
1636
1590
  if (onLayout) {
1637
1591
  onLayout(nextLayout.map(sizePercentage => ({
1638
1592
  sizePercentage,
@@ -1652,17 +1606,6 @@ function PanelGroupWithForwardedRef({
1652
1606
  onLayout,
1653
1607
  panelDataArray
1654
1608
  } = 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
- }
1666
1609
  if (panelData.constraints.collapsible) {
1667
1610
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1668
1611
  const {
@@ -1688,6 +1631,7 @@ function PanelGroupWithForwardedRef({
1688
1631
  });
1689
1632
  if (!compareLayouts(prevLayout, nextLayout)) {
1690
1633
  setLayout(nextLayout);
1634
+ committedValuesRef.current.layout = nextLayout;
1691
1635
  if (onLayout) {
1692
1636
  onLayout(nextLayout.map(sizePercentage => ({
1693
1637
  sizePercentage,
@@ -1718,6 +1662,9 @@ function PanelGroupWithForwardedRef({
1718
1662
 
1719
1663
  // This API should never read from committedValuesRef
1720
1664
  const getPanelStyle = useCallback(panelData => {
1665
+ const {
1666
+ panelDataArray
1667
+ } = committedValuesRef.current;
1721
1668
  const panelIndex = panelDataArray.indexOf(panelData);
1722
1669
  return computePanelFlexBoxStyle({
1723
1670
  dragState,
@@ -1725,7 +1672,7 @@ function PanelGroupWithForwardedRef({
1725
1672
  panelData: panelDataArray,
1726
1673
  panelIndex
1727
1674
  });
1728
- }, [dragState, layout, panelDataArray]);
1675
+ }, [dragState, layout]);
1729
1676
 
1730
1677
  // External APIs are safe to memoize via committed values ref
1731
1678
  const isPanelCollapsed = useCallback(panelData => {
@@ -1755,22 +1702,76 @@ function PanelGroupWithForwardedRef({
1755
1702
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1756
1703
  }, [groupId]);
1757
1704
  const registerPanel = useCallback(panelData => {
1758
- setPanelDataArray(prevPanelDataArray => {
1759
- const nextPanelDataArray = [...prevPanelDataArray, panelData];
1760
- return nextPanelDataArray.sort((panelA, panelB) => {
1761
- const orderA = panelA.order;
1762
- const orderB = panelB.order;
1763
- if (orderA == null && orderB == null) {
1764
- return 0;
1765
- } else if (orderA == null) {
1766
- return -1;
1767
- } else if (orderB == null) {
1768
- return 1;
1769
- } else {
1770
- return orderA - orderB;
1771
- }
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
1772
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)
1773
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
+ }
1774
1775
  }, []);
1775
1776
  const registerResizeHandle = useCallback(dragHandleId => {
1776
1777
  return function resizeHandler(event) {
@@ -1840,6 +1841,7 @@ function PanelGroupWithForwardedRef({
1840
1841
  }
1841
1842
  if (layoutChanged) {
1842
1843
  setLayout(nextLayout);
1844
+ committedValuesRef.current.layout = nextLayout;
1843
1845
  if (onLayout) {
1844
1846
  onLayout(nextLayout.map(sizePercentage => ({
1845
1847
  sizePercentage,
@@ -1858,18 +1860,6 @@ function PanelGroupWithForwardedRef({
1858
1860
  onLayout,
1859
1861
  panelDataArray
1860
1862
  } = 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
- }
1873
1863
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1874
1864
  const {
1875
1865
  groupSizePixels,
@@ -1889,6 +1879,7 @@ function PanelGroupWithForwardedRef({
1889
1879
  });
1890
1880
  if (!compareLayouts(prevLayout, nextLayout)) {
1891
1881
  setLayout(nextLayout);
1882
+ committedValuesRef.current.layout = nextLayout;
1892
1883
  if (onLayout) {
1893
1884
  onLayout(nextLayout.map(sizePercentage => ({
1894
1885
  sizePercentage,
@@ -1916,42 +1907,85 @@ function PanelGroupWithForwardedRef({
1916
1907
  resetGlobalCursorStyle();
1917
1908
  setDragState(null);
1918
1909
  }, []);
1910
+ const unregisterPanelRef = useRef({
1911
+ pendingPanelIds: new Set(),
1912
+ timeout: null
1913
+ });
1919
1914
  const unregisterPanel = useCallback(panelData => {
1920
- delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1921
- setPanelDataArray(panelDataArray => {
1922
- const index = panelDataArray.indexOf(panelData);
1923
- if (index >= 0) {
1924
- panelDataArray = [...panelDataArray];
1925
- 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;
1926
1958
  }
1927
- return panelDataArray;
1928
- });
1929
- }, []);
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
+ });
1930
1968
 
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
- }
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);
1952
1986
  }
1953
- }
1954
- }, [collapsePanel, expandPanel, imperativeApiQueue, layout, panelDataArray, resizePanel]);
1987
+ }, 0);
1988
+ }, []);
1955
1989
  const context = useMemo(() => ({
1956
1990
  collapsePanel,
1957
1991
  direction,