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.
@@ -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)
@@ -1110,6 +1112,10 @@ function debounce(callback, durationMs = 10) {
1110
1112
  return callable;
1111
1113
  }
1112
1114
 
1115
+ function getPanelElementsForGroup(groupId) {
1116
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
1117
+ }
1118
+
1113
1119
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
1114
1120
  // or on a browser with cookies/storage disabled.
1115
1121
  // In either case, this function avoids accessing localStorage until needed,
@@ -1336,7 +1342,7 @@ const defaultStorage = {
1336
1342
  };
1337
1343
  const debounceMap = {};
1338
1344
  function PanelGroupWithForwardedRef({
1339
- autoSaveId,
1345
+ autoSaveId = null,
1340
1346
  children,
1341
1347
  className: classNameFromProps = "",
1342
1348
  dataAttributes,
@@ -1353,12 +1359,11 @@ function PanelGroupWithForwardedRef({
1353
1359
  const groupId = useUniqueId(idFromProps);
1354
1360
  const [dragState, setDragState] = useState(null);
1355
1361
  const [layout, setLayout] = useState([]);
1356
- const [panelDataArray, setPanelDataArray] = useState([]);
1357
1362
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1358
1363
  const panelSizeBeforeCollapseRef = useRef(new Map());
1359
1364
  const prevDeltaRef = useRef(0);
1360
- const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1361
1365
  const committedValuesRef = useRef({
1366
+ autoSaveId,
1362
1367
  direction,
1363
1368
  dragState,
1364
1369
  id: groupId,
@@ -1366,7 +1371,8 @@ function PanelGroupWithForwardedRef({
1366
1371
  keyboardResizeByPixels,
1367
1372
  layout,
1368
1373
  onLayout,
1369
- panelDataArray
1374
+ panelDataArray: [],
1375
+ storage
1370
1376
  });
1371
1377
  const devWarningsRef = useRef({
1372
1378
  didLogIdAndOrderWarning: false,
@@ -1404,6 +1410,7 @@ function PanelGroupWithForwardedRef({
1404
1410
  });
1405
1411
  if (!areEqual(prevLayout, safeLayout)) {
1406
1412
  setLayout(safeLayout);
1413
+ committedValuesRef.current.layout = safeLayout;
1407
1414
  if (onLayout) {
1408
1415
  onLayout(safeLayout.map(sizePercentage => ({
1409
1416
  sizePercentage,
@@ -1415,21 +1422,29 @@ function PanelGroupWithForwardedRef({
1415
1422
  }
1416
1423
  }), []);
1417
1424
  useIsomorphicLayoutEffect(() => {
1425
+ committedValuesRef.current.autoSaveId = autoSaveId;
1418
1426
  committedValuesRef.current.direction = direction;
1419
1427
  committedValuesRef.current.dragState = dragState;
1420
1428
  committedValuesRef.current.id = groupId;
1421
- committedValuesRef.current.layout = layout;
1422
1429
  committedValuesRef.current.onLayout = onLayout;
1423
- 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
1424
1434
  });
1435
+
1425
1436
  useWindowSplitterPanelGroupBehavior({
1426
1437
  committedValuesRef,
1427
1438
  groupId,
1428
1439
  layout,
1429
- panelDataArray,
1440
+ panelDataArray: committedValuesRef.current.panelDataArray,
1430
1441
  setLayout
1431
1442
  });
1432
1443
  useEffect(() => {
1444
+ const {
1445
+ panelDataArray
1446
+ } = committedValuesRef.current;
1447
+
1433
1448
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1434
1449
  if (autoSaveId) {
1435
1450
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1442,63 +1457,11 @@ function PanelGroupWithForwardedRef({
1442
1457
  }
1443
1458
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1444
1459
  }
1445
- }, [autoSaveId, layout, panelDataArray, storage]);
1446
-
1447
- // Once all panels have registered themselves,
1448
- // Compute the initial sizes based on default weights.
1449
- // This assumes that panels register during initial mount (no conditional rendering)!
1460
+ }, [autoSaveId, layout, storage]);
1450
1461
  useIsomorphicLayoutEffect(() => {
1451
1462
  const {
1452
- id: groupId,
1453
- layout,
1454
- onLayout
1463
+ panelDataArray
1455
1464
  } = committedValuesRef.current;
1456
- if (layout.length === panelDataArray.length) {
1457
- // Only compute (or restore) default layout once per panel configuration.
1458
- return;
1459
- }
1460
-
1461
- // If this panel has been configured to persist sizing information,
1462
- // default size should be restored from local storage if possible.
1463
- let unsafeLayout = null;
1464
- if (autoSaveId) {
1465
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1466
- }
1467
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1468
- if (groupSizePixels <= 0) {
1469
- if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1470
- constraints
1471
- }) => constraints))) {
1472
- // Wait until the group has rendered a non-zero size before computing layout.
1473
- return;
1474
- }
1475
- }
1476
- if (unsafeLayout == null) {
1477
- unsafeLayout = calculateUnsafeDefaultLayout({
1478
- groupSizePixels,
1479
- panelDataArray
1480
- });
1481
- }
1482
-
1483
- // Validate even saved layouts in case something has changed since last render
1484
- // e.g. for pixel groups, this could be the size of the window
1485
- const validatedLayout = validatePanelGroupLayout({
1486
- groupSizePixels,
1487
- layout: unsafeLayout,
1488
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1489
- });
1490
- if (!areEqual(layout, validatedLayout)) {
1491
- setLayout(validatedLayout);
1492
- }
1493
- if (onLayout) {
1494
- onLayout(validatedLayout.map(sizePercentage => ({
1495
- sizePercentage,
1496
- sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1497
- })));
1498
- }
1499
- callPanelCallbacks(groupId, panelDataArray, validatedLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1500
- }, [autoSaveId, layout, panelDataArray, storage]);
1501
- useIsomorphicLayoutEffect(() => {
1502
1465
  const constraints = panelDataArray.map(({
1503
1466
  constraints
1504
1467
  }) => constraints);
@@ -1522,6 +1485,7 @@ function PanelGroupWithForwardedRef({
1522
1485
  });
1523
1486
  if (!areEqual(prevLayout, nextLayout)) {
1524
1487
  setLayout(nextLayout);
1488
+ committedValuesRef.current.layout = nextLayout;
1525
1489
  if (onLayout) {
1526
1490
  onLayout(nextLayout.map(sizePercentage => ({
1527
1491
  sizePercentage,
@@ -1536,20 +1500,20 @@ function PanelGroupWithForwardedRef({
1536
1500
  resizeObserver.disconnect();
1537
1501
  };
1538
1502
  }
1539
- }, [groupId, panelDataArray]);
1503
+ }, [groupId]);
1540
1504
 
1541
1505
  // DEV warnings
1542
1506
  useEffect(() => {
1543
1507
  {
1508
+ const {
1509
+ panelDataArray
1510
+ } = committedValuesRef.current;
1544
1511
  const {
1545
1512
  didLogIdAndOrderWarning,
1546
1513
  didLogPanelConstraintsWarning,
1547
1514
  prevPanelIds
1548
1515
  } = devWarningsRef.current;
1549
1516
  if (!didLogIdAndOrderWarning) {
1550
- const {
1551
- panelDataArray
1552
- } = committedValuesRef.current;
1553
1517
  const panelIds = panelDataArray.map(({
1554
1518
  id
1555
1519
  }) => id);
@@ -1591,17 +1555,6 @@ function PanelGroupWithForwardedRef({
1591
1555
  onLayout,
1592
1556
  panelDataArray
1593
1557
  } = committedValuesRef.current;
1594
-
1595
- // See issues/211
1596
- if (panelDataArray.find(({
1597
- id
1598
- }) => id === panelData.id) == null) {
1599
- setImperativeApiQueue(prev => [...prev, {
1600
- panelData,
1601
- type: "collapse"
1602
- }]);
1603
- return;
1604
- }
1605
1558
  if (panelData.constraints.collapsible) {
1606
1559
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1607
1560
  const {
@@ -1626,6 +1579,7 @@ function PanelGroupWithForwardedRef({
1626
1579
  });
1627
1580
  if (!compareLayouts(prevLayout, nextLayout)) {
1628
1581
  setLayout(nextLayout);
1582
+ committedValuesRef.current.layout = nextLayout;
1629
1583
  if (onLayout) {
1630
1584
  onLayout(nextLayout.map(sizePercentage => ({
1631
1585
  sizePercentage,
@@ -1645,17 +1599,6 @@ function PanelGroupWithForwardedRef({
1645
1599
  onLayout,
1646
1600
  panelDataArray
1647
1601
  } = committedValuesRef.current;
1648
-
1649
- // See issues/211
1650
- if (panelDataArray.find(({
1651
- id
1652
- }) => id === panelData.id) == null) {
1653
- setImperativeApiQueue(prev => [...prev, {
1654
- panelData,
1655
- type: "expand"
1656
- }]);
1657
- return;
1658
- }
1659
1602
  if (panelData.constraints.collapsible) {
1660
1603
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1661
1604
  const {
@@ -1681,6 +1624,7 @@ function PanelGroupWithForwardedRef({
1681
1624
  });
1682
1625
  if (!compareLayouts(prevLayout, nextLayout)) {
1683
1626
  setLayout(nextLayout);
1627
+ committedValuesRef.current.layout = nextLayout;
1684
1628
  if (onLayout) {
1685
1629
  onLayout(nextLayout.map(sizePercentage => ({
1686
1630
  sizePercentage,
@@ -1711,6 +1655,9 @@ function PanelGroupWithForwardedRef({
1711
1655
 
1712
1656
  // This API should never read from committedValuesRef
1713
1657
  const getPanelStyle = useCallback(panelData => {
1658
+ const {
1659
+ panelDataArray
1660
+ } = committedValuesRef.current;
1714
1661
  const panelIndex = panelDataArray.indexOf(panelData);
1715
1662
  return computePanelFlexBoxStyle({
1716
1663
  dragState,
@@ -1718,7 +1665,7 @@ function PanelGroupWithForwardedRef({
1718
1665
  panelData: panelDataArray,
1719
1666
  panelIndex
1720
1667
  });
1721
- }, [dragState, layout, panelDataArray]);
1668
+ }, [dragState, layout]);
1722
1669
 
1723
1670
  // External APIs are safe to memoize via committed values ref
1724
1671
  const isPanelCollapsed = useCallback(panelData => {
@@ -1748,22 +1695,76 @@ function PanelGroupWithForwardedRef({
1748
1695
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1749
1696
  }, [groupId]);
1750
1697
  const registerPanel = useCallback(panelData => {
1751
- setPanelDataArray(prevPanelDataArray => {
1752
- const nextPanelDataArray = [...prevPanelDataArray, panelData];
1753
- return nextPanelDataArray.sort((panelA, panelB) => {
1754
- const orderA = panelA.order;
1755
- const orderB = panelB.order;
1756
- if (orderA == null && orderB == null) {
1757
- return 0;
1758
- } else if (orderA == null) {
1759
- return -1;
1760
- } else if (orderB == null) {
1761
- return 1;
1762
- } else {
1763
- return orderA - orderB;
1764
- }
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
1765
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)
1766
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
+ }
1767
1768
  }, []);
1768
1769
  const registerResizeHandle = useCallback(dragHandleId => {
1769
1770
  return function resizeHandler(event) {
@@ -1833,6 +1834,7 @@ function PanelGroupWithForwardedRef({
1833
1834
  }
1834
1835
  if (layoutChanged) {
1835
1836
  setLayout(nextLayout);
1837
+ committedValuesRef.current.layout = nextLayout;
1836
1838
  if (onLayout) {
1837
1839
  onLayout(nextLayout.map(sizePercentage => ({
1838
1840
  sizePercentage,
@@ -1851,18 +1853,6 @@ function PanelGroupWithForwardedRef({
1851
1853
  onLayout,
1852
1854
  panelDataArray
1853
1855
  } = committedValuesRef.current;
1854
-
1855
- // See issues/211
1856
- if (panelDataArray.find(({
1857
- id
1858
- }) => id === panelData.id) == null) {
1859
- setImperativeApiQueue(prev => [...prev, {
1860
- panelData,
1861
- mixedSizes,
1862
- type: "resize"
1863
- }]);
1864
- return;
1865
- }
1866
1856
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1867
1857
  const {
1868
1858
  groupSizePixels,
@@ -1882,6 +1872,7 @@ function PanelGroupWithForwardedRef({
1882
1872
  });
1883
1873
  if (!compareLayouts(prevLayout, nextLayout)) {
1884
1874
  setLayout(nextLayout);
1875
+ committedValuesRef.current.layout = nextLayout;
1885
1876
  if (onLayout) {
1886
1877
  onLayout(nextLayout.map(sizePercentage => ({
1887
1878
  sizePercentage,
@@ -1909,42 +1900,85 @@ function PanelGroupWithForwardedRef({
1909
1900
  resetGlobalCursorStyle();
1910
1901
  setDragState(null);
1911
1902
  }, []);
1903
+ const unregisterPanelRef = useRef({
1904
+ pendingPanelIds: new Set(),
1905
+ timeout: null
1906
+ });
1912
1907
  const unregisterPanel = useCallback(panelData => {
1913
- delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1914
- setPanelDataArray(panelDataArray => {
1915
- const index = panelDataArray.indexOf(panelData);
1916
- if (index >= 0) {
1917
- panelDataArray = [...panelDataArray];
1918
- 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;
1919
1951
  }
1920
- return panelDataArray;
1921
- });
1922
- }, []);
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
+ });
1923
1961
 
1924
- // Handle imperative API calls that were made before panels were registered
1925
- useIsomorphicLayoutEffect(() => {
1926
- const queue = imperativeApiQueue;
1927
- while (queue.length > 0) {
1928
- const current = queue.shift();
1929
- switch (current.type) {
1930
- case "collapse":
1931
- {
1932
- collapsePanel(current.panelData);
1933
- break;
1934
- }
1935
- case "expand":
1936
- {
1937
- expandPanel(current.panelData);
1938
- break;
1939
- }
1940
- case "resize":
1941
- {
1942
- resizePanel(current.panelData, current.mixedSizes);
1943
- break;
1944
- }
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);
1945
1979
  }
1946
- }
1947
- }, [collapsePanel, expandPanel, imperativeApiQueue, layout, panelDataArray, resizePanel]);
1980
+ }, 0);
1981
+ }, []);
1948
1982
  const context = useMemo(() => ({
1949
1983
  collapsePanel,
1950
1984
  direction,