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.
@@ -69,6 +69,7 @@ function PanelWithForwardedRef({
69
69
  expandPanel,
70
70
  getPanelSize,
71
71
  getPanelStyle,
72
+ groupId,
72
73
  isPanelCollapsed,
73
74
  registerPanel,
74
75
  resizePanel,
@@ -168,6 +169,7 @@ function PanelWithForwardedRef({
168
169
  // CSS selectors
169
170
  "data-panel": "",
170
171
  "data-panel-id": panelId,
172
+ "data-panel-group-id": groupId,
171
173
  // e2e test attributes
172
174
  "data-panel-collapsible": collapsible || undefined ,
173
175
  "data-panel-size": parseFloat("" + style.flexGrow).toFixed(1)
@@ -1086,6 +1088,10 @@ function debounce(callback, durationMs = 10) {
1086
1088
  return callable;
1087
1089
  }
1088
1090
 
1091
+ function getPanelElementsForGroup(groupId) {
1092
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
1093
+ }
1094
+
1089
1095
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
1090
1096
  // or on a browser with cookies/storage disabled.
1091
1097
  // In either case, this function avoids accessing localStorage until needed,
@@ -1312,7 +1318,7 @@ const defaultStorage = {
1312
1318
  };
1313
1319
  const debounceMap = {};
1314
1320
  function PanelGroupWithForwardedRef({
1315
- autoSaveId,
1321
+ autoSaveId = null,
1316
1322
  children,
1317
1323
  className: classNameFromProps = "",
1318
1324
  dataAttributes,
@@ -1329,12 +1335,11 @@ function PanelGroupWithForwardedRef({
1329
1335
  const groupId = useUniqueId(idFromProps);
1330
1336
  const [dragState, setDragState] = useState(null);
1331
1337
  const [layout, setLayout] = useState([]);
1332
- const [panelDataArray, setPanelDataArray] = useState([]);
1333
1338
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1334
1339
  const panelSizeBeforeCollapseRef = useRef(new Map());
1335
1340
  const prevDeltaRef = useRef(0);
1336
- const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1337
1341
  const committedValuesRef = useRef({
1342
+ autoSaveId,
1338
1343
  direction,
1339
1344
  dragState,
1340
1345
  id: groupId,
@@ -1342,7 +1347,8 @@ function PanelGroupWithForwardedRef({
1342
1347
  keyboardResizeByPixels,
1343
1348
  layout,
1344
1349
  onLayout,
1345
- panelDataArray
1350
+ panelDataArray: [],
1351
+ storage
1346
1352
  });
1347
1353
  const devWarningsRef = useRef({
1348
1354
  didLogIdAndOrderWarning: false,
@@ -1380,6 +1386,7 @@ function PanelGroupWithForwardedRef({
1380
1386
  });
1381
1387
  if (!areEqual(prevLayout, safeLayout)) {
1382
1388
  setLayout(safeLayout);
1389
+ committedValuesRef.current.layout = safeLayout;
1383
1390
  if (onLayout) {
1384
1391
  onLayout(safeLayout.map(sizePercentage => ({
1385
1392
  sizePercentage,
@@ -1391,21 +1398,29 @@ function PanelGroupWithForwardedRef({
1391
1398
  }
1392
1399
  }), []);
1393
1400
  useIsomorphicLayoutEffect(() => {
1401
+ committedValuesRef.current.autoSaveId = autoSaveId;
1394
1402
  committedValuesRef.current.direction = direction;
1395
1403
  committedValuesRef.current.dragState = dragState;
1396
1404
  committedValuesRef.current.id = groupId;
1397
- committedValuesRef.current.layout = layout;
1398
1405
  committedValuesRef.current.onLayout = onLayout;
1399
- committedValuesRef.current.panelDataArray = panelDataArray;
1406
+ committedValuesRef.current.storage = storage;
1407
+
1408
+ // panelDataArray and layout are updated in-sync with scheduled state updates.
1409
+ // TODO [217] Move these values into a separate ref
1400
1410
  });
1411
+
1401
1412
  useWindowSplitterPanelGroupBehavior({
1402
1413
  committedValuesRef,
1403
1414
  groupId,
1404
1415
  layout,
1405
- panelDataArray,
1416
+ panelDataArray: committedValuesRef.current.panelDataArray,
1406
1417
  setLayout
1407
1418
  });
1408
1419
  useEffect(() => {
1420
+ const {
1421
+ panelDataArray
1422
+ } = committedValuesRef.current;
1423
+
1409
1424
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1410
1425
  if (autoSaveId) {
1411
1426
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1418,63 +1433,11 @@ function PanelGroupWithForwardedRef({
1418
1433
  }
1419
1434
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1420
1435
  }
1421
- }, [autoSaveId, layout, panelDataArray, storage]);
1422
-
1423
- // Once all panels have registered themselves,
1424
- // Compute the initial sizes based on default weights.
1425
- // This assumes that panels register during initial mount (no conditional rendering)!
1436
+ }, [autoSaveId, layout, storage]);
1426
1437
  useIsomorphicLayoutEffect(() => {
1427
1438
  const {
1428
- id: groupId,
1429
- layout,
1430
- onLayout
1439
+ panelDataArray
1431
1440
  } = committedValuesRef.current;
1432
- if (layout.length === panelDataArray.length) {
1433
- // Only compute (or restore) default layout once per panel configuration.
1434
- return;
1435
- }
1436
-
1437
- // If this panel has been configured to persist sizing information,
1438
- // default size should be restored from local storage if possible.
1439
- let unsafeLayout = null;
1440
- if (autoSaveId) {
1441
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1442
- }
1443
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1444
- if (groupSizePixels <= 0) {
1445
- if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1446
- constraints
1447
- }) => constraints))) {
1448
- // Wait until the group has rendered a non-zero size before computing layout.
1449
- return;
1450
- }
1451
- }
1452
- if (unsafeLayout == null) {
1453
- unsafeLayout = calculateUnsafeDefaultLayout({
1454
- groupSizePixels,
1455
- panelDataArray
1456
- });
1457
- }
1458
-
1459
- // Validate even saved layouts in case something has changed since last render
1460
- // e.g. for pixel groups, this could be the size of the window
1461
- const validatedLayout = validatePanelGroupLayout({
1462
- groupSizePixels,
1463
- layout: unsafeLayout,
1464
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1465
- });
1466
- if (!areEqual(layout, validatedLayout)) {
1467
- setLayout(validatedLayout);
1468
- }
1469
- if (onLayout) {
1470
- onLayout(validatedLayout.map(sizePercentage => ({
1471
- sizePercentage,
1472
- sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1473
- })));
1474
- }
1475
- callPanelCallbacks(groupId, panelDataArray, validatedLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1476
- }, [autoSaveId, layout, panelDataArray, storage]);
1477
- useIsomorphicLayoutEffect(() => {
1478
1441
  const constraints = panelDataArray.map(({
1479
1442
  constraints
1480
1443
  }) => constraints);
@@ -1498,6 +1461,7 @@ function PanelGroupWithForwardedRef({
1498
1461
  });
1499
1462
  if (!areEqual(prevLayout, nextLayout)) {
1500
1463
  setLayout(nextLayout);
1464
+ committedValuesRef.current.layout = nextLayout;
1501
1465
  if (onLayout) {
1502
1466
  onLayout(nextLayout.map(sizePercentage => ({
1503
1467
  sizePercentage,
@@ -1512,20 +1476,20 @@ function PanelGroupWithForwardedRef({
1512
1476
  resizeObserver.disconnect();
1513
1477
  };
1514
1478
  }
1515
- }, [groupId, panelDataArray]);
1479
+ }, [groupId]);
1516
1480
 
1517
1481
  // DEV warnings
1518
1482
  useEffect(() => {
1519
1483
  {
1484
+ const {
1485
+ panelDataArray
1486
+ } = committedValuesRef.current;
1520
1487
  const {
1521
1488
  didLogIdAndOrderWarning,
1522
1489
  didLogPanelConstraintsWarning,
1523
1490
  prevPanelIds
1524
1491
  } = devWarningsRef.current;
1525
1492
  if (!didLogIdAndOrderWarning) {
1526
- const {
1527
- panelDataArray
1528
- } = committedValuesRef.current;
1529
1493
  const panelIds = panelDataArray.map(({
1530
1494
  id
1531
1495
  }) => id);
@@ -1567,17 +1531,6 @@ function PanelGroupWithForwardedRef({
1567
1531
  onLayout,
1568
1532
  panelDataArray
1569
1533
  } = committedValuesRef.current;
1570
-
1571
- // See issues/211
1572
- if (panelDataArray.find(({
1573
- id
1574
- }) => id === panelData.id) == null) {
1575
- setImperativeApiQueue(prev => [...prev, {
1576
- panelData,
1577
- type: "collapse"
1578
- }]);
1579
- return;
1580
- }
1581
1534
  if (panelData.constraints.collapsible) {
1582
1535
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1583
1536
  const {
@@ -1602,6 +1555,7 @@ function PanelGroupWithForwardedRef({
1602
1555
  });
1603
1556
  if (!compareLayouts(prevLayout, nextLayout)) {
1604
1557
  setLayout(nextLayout);
1558
+ committedValuesRef.current.layout = nextLayout;
1605
1559
  if (onLayout) {
1606
1560
  onLayout(nextLayout.map(sizePercentage => ({
1607
1561
  sizePercentage,
@@ -1621,17 +1575,6 @@ function PanelGroupWithForwardedRef({
1621
1575
  onLayout,
1622
1576
  panelDataArray
1623
1577
  } = committedValuesRef.current;
1624
-
1625
- // See issues/211
1626
- if (panelDataArray.find(({
1627
- id
1628
- }) => id === panelData.id) == null) {
1629
- setImperativeApiQueue(prev => [...prev, {
1630
- panelData,
1631
- type: "expand"
1632
- }]);
1633
- return;
1634
- }
1635
1578
  if (panelData.constraints.collapsible) {
1636
1579
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1637
1580
  const {
@@ -1657,6 +1600,7 @@ function PanelGroupWithForwardedRef({
1657
1600
  });
1658
1601
  if (!compareLayouts(prevLayout, nextLayout)) {
1659
1602
  setLayout(nextLayout);
1603
+ committedValuesRef.current.layout = nextLayout;
1660
1604
  if (onLayout) {
1661
1605
  onLayout(nextLayout.map(sizePercentage => ({
1662
1606
  sizePercentage,
@@ -1687,6 +1631,9 @@ function PanelGroupWithForwardedRef({
1687
1631
 
1688
1632
  // This API should never read from committedValuesRef
1689
1633
  const getPanelStyle = useCallback(panelData => {
1634
+ const {
1635
+ panelDataArray
1636
+ } = committedValuesRef.current;
1690
1637
  const panelIndex = panelDataArray.indexOf(panelData);
1691
1638
  return computePanelFlexBoxStyle({
1692
1639
  dragState,
@@ -1694,7 +1641,7 @@ function PanelGroupWithForwardedRef({
1694
1641
  panelData: panelDataArray,
1695
1642
  panelIndex
1696
1643
  });
1697
- }, [dragState, layout, panelDataArray]);
1644
+ }, [dragState, layout]);
1698
1645
 
1699
1646
  // External APIs are safe to memoize via committed values ref
1700
1647
  const isPanelCollapsed = useCallback(panelData => {
@@ -1724,22 +1671,76 @@ function PanelGroupWithForwardedRef({
1724
1671
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1725
1672
  }, [groupId]);
1726
1673
  const registerPanel = useCallback(panelData => {
1727
- setPanelDataArray(prevPanelDataArray => {
1728
- const nextPanelDataArray = [...prevPanelDataArray, panelData];
1729
- return nextPanelDataArray.sort((panelA, panelB) => {
1730
- const orderA = panelA.order;
1731
- const orderB = panelB.order;
1732
- if (orderA == null && orderB == null) {
1733
- return 0;
1734
- } else if (orderA == null) {
1735
- return -1;
1736
- } else if (orderB == null) {
1737
- return 1;
1738
- } else {
1739
- return orderA - orderB;
1740
- }
1674
+ const {
1675
+ autoSaveId,
1676
+ id: groupId,
1677
+ layout: prevLayout,
1678
+ onLayout,
1679
+ panelDataArray,
1680
+ storage
1681
+ } = committedValuesRef.current;
1682
+ panelDataArray.push(panelData);
1683
+ panelDataArray.sort((panelA, panelB) => {
1684
+ const orderA = panelA.order;
1685
+ const orderB = panelB.order;
1686
+ if (orderA == null && orderB == null) {
1687
+ return 0;
1688
+ } else if (orderA == null) {
1689
+ return -1;
1690
+ } else if (orderB == null) {
1691
+ return 1;
1692
+ } else {
1693
+ return orderA - orderB;
1694
+ }
1695
+ });
1696
+
1697
+ // Wait until all panels have registered before we try to compute layout;
1698
+ // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1699
+ const panelElements = getPanelElementsForGroup(groupId);
1700
+ if (panelElements.length !== panelDataArray.length) {
1701
+ return;
1702
+ }
1703
+
1704
+ // If this panel has been configured to persist sizing information,
1705
+ // default size should be restored from local storage if possible.
1706
+ let unsafeLayout = null;
1707
+ if (autoSaveId) {
1708
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1709
+ }
1710
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1711
+ if (groupSizePixels <= 0) {
1712
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1713
+ constraints
1714
+ }) => constraints))) {
1715
+ // Wait until the group has rendered a non-zero size before computing layout.
1716
+ return;
1717
+ }
1718
+ }
1719
+ if (unsafeLayout == null) {
1720
+ unsafeLayout = calculateUnsafeDefaultLayout({
1721
+ groupSizePixels,
1722
+ panelDataArray
1741
1723
  });
1724
+ }
1725
+
1726
+ // Validate even saved layouts in case something has changed since last render
1727
+ // e.g. for pixel groups, this could be the size of the window
1728
+ const nextLayout = validatePanelGroupLayout({
1729
+ groupSizePixels,
1730
+ layout: unsafeLayout,
1731
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1742
1732
  });
1733
+ if (!areEqual(prevLayout, nextLayout)) {
1734
+ setLayout(nextLayout);
1735
+ committedValuesRef.current.layout = nextLayout;
1736
+ if (onLayout) {
1737
+ onLayout(nextLayout.map(sizePercentage => ({
1738
+ sizePercentage,
1739
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1740
+ })));
1741
+ }
1742
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1743
+ }
1743
1744
  }, []);
1744
1745
  const registerResizeHandle = useCallback(dragHandleId => {
1745
1746
  return function resizeHandler(event) {
@@ -1809,6 +1810,7 @@ function PanelGroupWithForwardedRef({
1809
1810
  }
1810
1811
  if (layoutChanged) {
1811
1812
  setLayout(nextLayout);
1813
+ committedValuesRef.current.layout = nextLayout;
1812
1814
  if (onLayout) {
1813
1815
  onLayout(nextLayout.map(sizePercentage => ({
1814
1816
  sizePercentage,
@@ -1827,18 +1829,6 @@ function PanelGroupWithForwardedRef({
1827
1829
  onLayout,
1828
1830
  panelDataArray
1829
1831
  } = committedValuesRef.current;
1830
-
1831
- // See issues/211
1832
- if (panelDataArray.find(({
1833
- id
1834
- }) => id === panelData.id) == null) {
1835
- setImperativeApiQueue(prev => [...prev, {
1836
- panelData,
1837
- mixedSizes,
1838
- type: "resize"
1839
- }]);
1840
- return;
1841
- }
1842
1832
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1843
1833
  const {
1844
1834
  groupSizePixels,
@@ -1858,6 +1848,7 @@ function PanelGroupWithForwardedRef({
1858
1848
  });
1859
1849
  if (!compareLayouts(prevLayout, nextLayout)) {
1860
1850
  setLayout(nextLayout);
1851
+ committedValuesRef.current.layout = nextLayout;
1861
1852
  if (onLayout) {
1862
1853
  onLayout(nextLayout.map(sizePercentage => ({
1863
1854
  sizePercentage,
@@ -1885,42 +1876,85 @@ function PanelGroupWithForwardedRef({
1885
1876
  resetGlobalCursorStyle();
1886
1877
  setDragState(null);
1887
1878
  }, []);
1879
+ const unregisterPanelRef = useRef({
1880
+ pendingPanelIds: new Set(),
1881
+ timeout: null
1882
+ });
1888
1883
  const unregisterPanel = useCallback(panelData => {
1889
- delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1890
- setPanelDataArray(panelDataArray => {
1891
- const index = panelDataArray.indexOf(panelData);
1892
- if (index >= 0) {
1893
- panelDataArray = [...panelDataArray];
1894
- panelDataArray.splice(index, 1);
1884
+ const {
1885
+ id: groupId,
1886
+ layout: prevLayout,
1887
+ onLayout,
1888
+ panelDataArray
1889
+ } = committedValuesRef.current;
1890
+ const index = panelDataArray.indexOf(panelData);
1891
+ if (index >= 0) {
1892
+ panelDataArray.splice(index, 1);
1893
+ unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1894
+ }
1895
+ if (unregisterPanelRef.current.timeout != null) {
1896
+ clearTimeout(unregisterPanelRef.current.timeout);
1897
+ }
1898
+
1899
+ // Batch panel unmounts so that we only calculate layout once;
1900
+ // This is more efficient and avoids misleading warnings in development mode.
1901
+ // We can't check the DOM to detect this because Panel elements have not yet been removed.
1902
+ unregisterPanelRef.current.timeout = setTimeout(() => {
1903
+ const {
1904
+ pendingPanelIds
1905
+ } = unregisterPanelRef.current;
1906
+ panelIdToLastNotifiedMixedSizesMapRef.current;
1907
+
1908
+ // TRICKY
1909
+ // Strict effects mode
1910
+ let unmountDueToStrictMode = false;
1911
+ pendingPanelIds.forEach(panelId => {
1912
+ pendingPanelIds.delete(panelId);
1913
+ if (panelDataArray.find(({
1914
+ id
1915
+ }) => id === panelId) == null) {
1916
+ unmountDueToStrictMode = true;
1917
+
1918
+ // TRICKY
1919
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1920
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1921
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1922
+ delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1923
+ }
1924
+ });
1925
+ if (!unmountDueToStrictMode) {
1926
+ return;
1895
1927
  }
1896
- return panelDataArray;
1897
- });
1898
- }, []);
1928
+ if (panelDataArray.length === 0) {
1929
+ // The group is unmounting; skip layout calculation.
1930
+ return;
1931
+ }
1932
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1933
+ let unsafeLayout = calculateUnsafeDefaultLayout({
1934
+ groupSizePixels,
1935
+ panelDataArray
1936
+ });
1899
1937
 
1900
- // Handle imperative API calls that were made before panels were registered
1901
- useIsomorphicLayoutEffect(() => {
1902
- const queue = imperativeApiQueue;
1903
- while (queue.length > 0) {
1904
- const current = queue.shift();
1905
- switch (current.type) {
1906
- case "collapse":
1907
- {
1908
- collapsePanel(current.panelData);
1909
- break;
1910
- }
1911
- case "expand":
1912
- {
1913
- expandPanel(current.panelData);
1914
- break;
1915
- }
1916
- case "resize":
1917
- {
1918
- resizePanel(current.panelData, current.mixedSizes);
1919
- break;
1920
- }
1938
+ // Validate even saved layouts in case something has changed since last render
1939
+ // e.g. for pixel groups, this could be the size of the window
1940
+ const nextLayout = validatePanelGroupLayout({
1941
+ groupSizePixels,
1942
+ layout: unsafeLayout,
1943
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1944
+ });
1945
+ if (!areEqual(prevLayout, nextLayout)) {
1946
+ setLayout(nextLayout);
1947
+ committedValuesRef.current.layout = nextLayout;
1948
+ if (onLayout) {
1949
+ onLayout(nextLayout.map(sizePercentage => ({
1950
+ sizePercentage,
1951
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1952
+ })));
1953
+ }
1954
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1921
1955
  }
1922
- }
1923
- }, [collapsePanel, expandPanel, imperativeApiQueue, layout, panelDataArray, resizePanel]);
1956
+ }, 0);
1957
+ }, []);
1924
1958
  const context = useMemo(() => ({
1925
1959
  collapsePanel,
1926
1960
  direction,