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.
@@ -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)
@@ -1093,6 +1095,10 @@ function debounce(callback, durationMs = 10) {
1093
1095
  return callable;
1094
1096
  }
1095
1097
 
1098
+ function getPanelElementsForGroup(groupId) {
1099
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
1100
+ }
1101
+
1096
1102
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
1097
1103
  // or on a browser with cookies/storage disabled.
1098
1104
  // In either case, this function avoids accessing localStorage until needed,
@@ -1319,7 +1325,7 @@ const defaultStorage = {
1319
1325
  };
1320
1326
  const debounceMap = {};
1321
1327
  function PanelGroupWithForwardedRef({
1322
- autoSaveId,
1328
+ autoSaveId = null,
1323
1329
  children,
1324
1330
  className: classNameFromProps = "",
1325
1331
  dataAttributes,
@@ -1336,12 +1342,11 @@ function PanelGroupWithForwardedRef({
1336
1342
  const groupId = useUniqueId(idFromProps);
1337
1343
  const [dragState, setDragState] = useState(null);
1338
1344
  const [layout, setLayout] = useState([]);
1339
- const [panelDataArray, setPanelDataArray] = useState([]);
1340
1345
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1341
1346
  const panelSizeBeforeCollapseRef = useRef(new Map());
1342
1347
  const prevDeltaRef = useRef(0);
1343
- const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1344
1348
  const committedValuesRef = useRef({
1349
+ autoSaveId,
1345
1350
  direction,
1346
1351
  dragState,
1347
1352
  id: groupId,
@@ -1349,7 +1354,8 @@ function PanelGroupWithForwardedRef({
1349
1354
  keyboardResizeByPixels,
1350
1355
  layout,
1351
1356
  onLayout,
1352
- panelDataArray
1357
+ panelDataArray: [],
1358
+ storage
1353
1359
  });
1354
1360
  const devWarningsRef = useRef({
1355
1361
  didLogIdAndOrderWarning: false,
@@ -1387,6 +1393,7 @@ function PanelGroupWithForwardedRef({
1387
1393
  });
1388
1394
  if (!areEqual(prevLayout, safeLayout)) {
1389
1395
  setLayout(safeLayout);
1396
+ committedValuesRef.current.layout = safeLayout;
1390
1397
  if (onLayout) {
1391
1398
  onLayout(safeLayout.map(sizePercentage => ({
1392
1399
  sizePercentage,
@@ -1398,21 +1405,29 @@ function PanelGroupWithForwardedRef({
1398
1405
  }
1399
1406
  }), []);
1400
1407
  useIsomorphicLayoutEffect(() => {
1408
+ committedValuesRef.current.autoSaveId = autoSaveId;
1401
1409
  committedValuesRef.current.direction = direction;
1402
1410
  committedValuesRef.current.dragState = dragState;
1403
1411
  committedValuesRef.current.id = groupId;
1404
- committedValuesRef.current.layout = layout;
1405
1412
  committedValuesRef.current.onLayout = onLayout;
1406
- 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
1407
1417
  });
1418
+
1408
1419
  useWindowSplitterPanelGroupBehavior({
1409
1420
  committedValuesRef,
1410
1421
  groupId,
1411
1422
  layout,
1412
- panelDataArray,
1423
+ panelDataArray: committedValuesRef.current.panelDataArray,
1413
1424
  setLayout
1414
1425
  });
1415
1426
  useEffect(() => {
1427
+ const {
1428
+ panelDataArray
1429
+ } = committedValuesRef.current;
1430
+
1416
1431
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1417
1432
  if (autoSaveId) {
1418
1433
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1425,63 +1440,11 @@ function PanelGroupWithForwardedRef({
1425
1440
  }
1426
1441
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1427
1442
  }
1428
- }, [autoSaveId, layout, panelDataArray, storage]);
1429
-
1430
- // Once all panels have registered themselves,
1431
- // Compute the initial sizes based on default weights.
1432
- // This assumes that panels register during initial mount (no conditional rendering)!
1443
+ }, [autoSaveId, layout, storage]);
1433
1444
  useIsomorphicLayoutEffect(() => {
1434
1445
  const {
1435
- id: groupId,
1436
- layout,
1437
- onLayout
1446
+ panelDataArray
1438
1447
  } = committedValuesRef.current;
1439
- if (layout.length === panelDataArray.length) {
1440
- // Only compute (or restore) default layout once per panel configuration.
1441
- return;
1442
- }
1443
-
1444
- // If this panel has been configured to persist sizing information,
1445
- // default size should be restored from local storage if possible.
1446
- let unsafeLayout = null;
1447
- if (autoSaveId) {
1448
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1449
- }
1450
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1451
- if (groupSizePixels <= 0) {
1452
- if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1453
- constraints
1454
- }) => constraints))) {
1455
- // Wait until the group has rendered a non-zero size before computing layout.
1456
- return;
1457
- }
1458
- }
1459
- if (unsafeLayout == null) {
1460
- unsafeLayout = calculateUnsafeDefaultLayout({
1461
- groupSizePixels,
1462
- panelDataArray
1463
- });
1464
- }
1465
-
1466
- // Validate even saved layouts in case something has changed since last render
1467
- // e.g. for pixel groups, this could be the size of the window
1468
- const validatedLayout = validatePanelGroupLayout({
1469
- groupSizePixels,
1470
- layout: unsafeLayout,
1471
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1472
- });
1473
- if (!areEqual(layout, validatedLayout)) {
1474
- setLayout(validatedLayout);
1475
- }
1476
- if (onLayout) {
1477
- onLayout(validatedLayout.map(sizePercentage => ({
1478
- sizePercentage,
1479
- sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1480
- })));
1481
- }
1482
- callPanelCallbacks(groupId, panelDataArray, validatedLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1483
- }, [autoSaveId, layout, panelDataArray, storage]);
1484
- useIsomorphicLayoutEffect(() => {
1485
1448
  const constraints = panelDataArray.map(({
1486
1449
  constraints
1487
1450
  }) => constraints);
@@ -1505,6 +1468,7 @@ function PanelGroupWithForwardedRef({
1505
1468
  });
1506
1469
  if (!areEqual(prevLayout, nextLayout)) {
1507
1470
  setLayout(nextLayout);
1471
+ committedValuesRef.current.layout = nextLayout;
1508
1472
  if (onLayout) {
1509
1473
  onLayout(nextLayout.map(sizePercentage => ({
1510
1474
  sizePercentage,
@@ -1519,20 +1483,20 @@ function PanelGroupWithForwardedRef({
1519
1483
  resizeObserver.disconnect();
1520
1484
  };
1521
1485
  }
1522
- }, [groupId, panelDataArray]);
1486
+ }, [groupId]);
1523
1487
 
1524
1488
  // DEV warnings
1525
1489
  useEffect(() => {
1526
1490
  {
1491
+ const {
1492
+ panelDataArray
1493
+ } = committedValuesRef.current;
1527
1494
  const {
1528
1495
  didLogIdAndOrderWarning,
1529
1496
  didLogPanelConstraintsWarning,
1530
1497
  prevPanelIds
1531
1498
  } = devWarningsRef.current;
1532
1499
  if (!didLogIdAndOrderWarning) {
1533
- const {
1534
- panelDataArray
1535
- } = committedValuesRef.current;
1536
1500
  const panelIds = panelDataArray.map(({
1537
1501
  id
1538
1502
  }) => id);
@@ -1574,17 +1538,6 @@ function PanelGroupWithForwardedRef({
1574
1538
  onLayout,
1575
1539
  panelDataArray
1576
1540
  } = committedValuesRef.current;
1577
-
1578
- // See issues/211
1579
- if (panelDataArray.find(({
1580
- id
1581
- }) => id === panelData.id) == null) {
1582
- setImperativeApiQueue(prev => [...prev, {
1583
- panelData,
1584
- type: "collapse"
1585
- }]);
1586
- return;
1587
- }
1588
1541
  if (panelData.constraints.collapsible) {
1589
1542
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1590
1543
  const {
@@ -1609,6 +1562,7 @@ function PanelGroupWithForwardedRef({
1609
1562
  });
1610
1563
  if (!compareLayouts(prevLayout, nextLayout)) {
1611
1564
  setLayout(nextLayout);
1565
+ committedValuesRef.current.layout = nextLayout;
1612
1566
  if (onLayout) {
1613
1567
  onLayout(nextLayout.map(sizePercentage => ({
1614
1568
  sizePercentage,
@@ -1628,17 +1582,6 @@ function PanelGroupWithForwardedRef({
1628
1582
  onLayout,
1629
1583
  panelDataArray
1630
1584
  } = committedValuesRef.current;
1631
-
1632
- // See issues/211
1633
- if (panelDataArray.find(({
1634
- id
1635
- }) => id === panelData.id) == null) {
1636
- setImperativeApiQueue(prev => [...prev, {
1637
- panelData,
1638
- type: "expand"
1639
- }]);
1640
- return;
1641
- }
1642
1585
  if (panelData.constraints.collapsible) {
1643
1586
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1644
1587
  const {
@@ -1664,6 +1607,7 @@ function PanelGroupWithForwardedRef({
1664
1607
  });
1665
1608
  if (!compareLayouts(prevLayout, nextLayout)) {
1666
1609
  setLayout(nextLayout);
1610
+ committedValuesRef.current.layout = nextLayout;
1667
1611
  if (onLayout) {
1668
1612
  onLayout(nextLayout.map(sizePercentage => ({
1669
1613
  sizePercentage,
@@ -1694,6 +1638,9 @@ function PanelGroupWithForwardedRef({
1694
1638
 
1695
1639
  // This API should never read from committedValuesRef
1696
1640
  const getPanelStyle = useCallback(panelData => {
1641
+ const {
1642
+ panelDataArray
1643
+ } = committedValuesRef.current;
1697
1644
  const panelIndex = panelDataArray.indexOf(panelData);
1698
1645
  return computePanelFlexBoxStyle({
1699
1646
  dragState,
@@ -1701,7 +1648,7 @@ function PanelGroupWithForwardedRef({
1701
1648
  panelData: panelDataArray,
1702
1649
  panelIndex
1703
1650
  });
1704
- }, [dragState, layout, panelDataArray]);
1651
+ }, [dragState, layout]);
1705
1652
 
1706
1653
  // External APIs are safe to memoize via committed values ref
1707
1654
  const isPanelCollapsed = useCallback(panelData => {
@@ -1731,22 +1678,76 @@ function PanelGroupWithForwardedRef({
1731
1678
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1732
1679
  }, [groupId]);
1733
1680
  const registerPanel = useCallback(panelData => {
1734
- setPanelDataArray(prevPanelDataArray => {
1735
- const nextPanelDataArray = [...prevPanelDataArray, panelData];
1736
- return nextPanelDataArray.sort((panelA, panelB) => {
1737
- const orderA = panelA.order;
1738
- const orderB = panelB.order;
1739
- if (orderA == null && orderB == null) {
1740
- return 0;
1741
- } else if (orderA == null) {
1742
- return -1;
1743
- } else if (orderB == null) {
1744
- return 1;
1745
- } else {
1746
- return orderA - orderB;
1747
- }
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
1748
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)
1749
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
+ }
1750
1751
  }, []);
1751
1752
  const registerResizeHandle = useCallback(dragHandleId => {
1752
1753
  return function resizeHandler(event) {
@@ -1816,6 +1817,7 @@ function PanelGroupWithForwardedRef({
1816
1817
  }
1817
1818
  if (layoutChanged) {
1818
1819
  setLayout(nextLayout);
1820
+ committedValuesRef.current.layout = nextLayout;
1819
1821
  if (onLayout) {
1820
1822
  onLayout(nextLayout.map(sizePercentage => ({
1821
1823
  sizePercentage,
@@ -1834,18 +1836,6 @@ function PanelGroupWithForwardedRef({
1834
1836
  onLayout,
1835
1837
  panelDataArray
1836
1838
  } = committedValuesRef.current;
1837
-
1838
- // See issues/211
1839
- if (panelDataArray.find(({
1840
- id
1841
- }) => id === panelData.id) == null) {
1842
- setImperativeApiQueue(prev => [...prev, {
1843
- panelData,
1844
- mixedSizes,
1845
- type: "resize"
1846
- }]);
1847
- return;
1848
- }
1849
1839
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1850
1840
  const {
1851
1841
  groupSizePixels,
@@ -1865,6 +1855,7 @@ function PanelGroupWithForwardedRef({
1865
1855
  });
1866
1856
  if (!compareLayouts(prevLayout, nextLayout)) {
1867
1857
  setLayout(nextLayout);
1858
+ committedValuesRef.current.layout = nextLayout;
1868
1859
  if (onLayout) {
1869
1860
  onLayout(nextLayout.map(sizePercentage => ({
1870
1861
  sizePercentage,
@@ -1892,42 +1883,85 @@ function PanelGroupWithForwardedRef({
1892
1883
  resetGlobalCursorStyle();
1893
1884
  setDragState(null);
1894
1885
  }, []);
1886
+ const unregisterPanelRef = useRef({
1887
+ pendingPanelIds: new Set(),
1888
+ timeout: null
1889
+ });
1895
1890
  const unregisterPanel = useCallback(panelData => {
1896
- delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1897
- setPanelDataArray(panelDataArray => {
1898
- const index = panelDataArray.indexOf(panelData);
1899
- if (index >= 0) {
1900
- panelDataArray = [...panelDataArray];
1901
- 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;
1902
1934
  }
1903
- return panelDataArray;
1904
- });
1905
- }, []);
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
+ });
1906
1944
 
1907
- // Handle imperative API calls that were made before panels were registered
1908
- useIsomorphicLayoutEffect(() => {
1909
- const queue = imperativeApiQueue;
1910
- while (queue.length > 0) {
1911
- const current = queue.shift();
1912
- switch (current.type) {
1913
- case "collapse":
1914
- {
1915
- collapsePanel(current.panelData);
1916
- break;
1917
- }
1918
- case "expand":
1919
- {
1920
- expandPanel(current.panelData);
1921
- break;
1922
- }
1923
- case "resize":
1924
- {
1925
- resizePanel(current.panelData, current.mixedSizes);
1926
- break;
1927
- }
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);
1928
1962
  }
1929
- }
1930
- }, [collapsePanel, expandPanel, imperativeApiQueue, layout, panelDataArray, resizePanel]);
1963
+ }, 0);
1964
+ }, []);
1931
1965
  const context = useMemo(() => ({
1932
1966
  collapsePanel,
1933
1967
  direction,