react-resizable-panels 0.0.59 → 0.0.61

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)
@@ -722,6 +724,7 @@ function getResizeHandlePanelIds(groupId, handleId, panelsArray) {
722
724
 
723
725
  function useWindowSplitterPanelGroupBehavior({
724
726
  committedValuesRef,
727
+ eagerValuesRef,
725
728
  groupId,
726
729
  layout,
727
730
  panelDataArray,
@@ -774,7 +777,7 @@ function useWindowSplitterPanelGroupBehavior({
774
777
  useEffect(() => {
775
778
  const {
776
779
  panelDataArray
777
- } = committedValuesRef.current;
780
+ } = eagerValuesRef.current;
778
781
  const groupElement = getPanelGroupElement(groupId);
779
782
  assert(groupElement != null, `No group found for id "${groupId}"`);
780
783
  const handles = getResizeHandleElementsForGroup(groupId);
@@ -832,7 +835,7 @@ function useWindowSplitterPanelGroupBehavior({
832
835
  return () => {
833
836
  cleanupFunctions.forEach(cleanupFunction => cleanupFunction());
834
837
  };
835
- }, [committedValuesRef, groupId, layout, panelDataArray, setLayout]);
838
+ }, [committedValuesRef, eagerValuesRef, groupId, layout, panelDataArray, setLayout]);
836
839
  }
837
840
 
838
841
  function areEqual(arrayA, arrayB) {
@@ -1117,6 +1120,10 @@ function debounce(callback, durationMs = 10) {
1117
1120
  return callable;
1118
1121
  }
1119
1122
 
1123
+ function getPanelElementsForGroup(groupId) {
1124
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
1125
+ }
1126
+
1120
1127
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
1121
1128
  // or on a browser with cookies/storage disabled.
1122
1129
  // In either case, this function avoids accessing localStorage until needed,
@@ -1343,7 +1350,7 @@ const defaultStorage = {
1343
1350
  };
1344
1351
  const debounceMap = {};
1345
1352
  function PanelGroupWithForwardedRef({
1346
- autoSaveId,
1353
+ autoSaveId = null,
1347
1354
  children,
1348
1355
  className: classNameFromProps = "",
1349
1356
  dataAttributes,
@@ -1360,20 +1367,22 @@ function PanelGroupWithForwardedRef({
1360
1367
  const groupId = useUniqueId(idFromProps);
1361
1368
  const [dragState, setDragState] = useState(null);
1362
1369
  const [layout, setLayout] = useState([]);
1363
- const [panelDataArray, setPanelDataArray] = useState([]);
1364
1370
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1365
1371
  const panelSizeBeforeCollapseRef = useRef(new Map());
1366
1372
  const prevDeltaRef = useRef(0);
1367
- const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1368
1373
  const committedValuesRef = useRef({
1374
+ autoSaveId,
1369
1375
  direction,
1370
1376
  dragState,
1371
1377
  id: groupId,
1372
1378
  keyboardResizeByPercentage,
1373
1379
  keyboardResizeByPixels,
1374
- layout,
1375
1380
  onLayout,
1376
- panelDataArray
1381
+ storage
1382
+ });
1383
+ const eagerValuesRef = useRef({
1384
+ layout,
1385
+ panelDataArray: []
1377
1386
  });
1378
1387
  const devWarningsRef = useRef({
1379
1388
  didLogIdAndOrderWarning: false,
@@ -1384,9 +1393,11 @@ function PanelGroupWithForwardedRef({
1384
1393
  getId: () => committedValuesRef.current.id,
1385
1394
  getLayout: () => {
1386
1395
  const {
1387
- id: groupId,
1388
- layout
1396
+ id: groupId
1389
1397
  } = committedValuesRef.current;
1398
+ const {
1399
+ layout
1400
+ } = eagerValuesRef.current;
1390
1401
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1391
1402
  return layout.map(sizePercentage => {
1392
1403
  return {
@@ -1398,10 +1409,12 @@ function PanelGroupWithForwardedRef({
1398
1409
  setLayout: mixedSizes => {
1399
1410
  const {
1400
1411
  id: groupId,
1412
+ onLayout
1413
+ } = committedValuesRef.current;
1414
+ const {
1401
1415
  layout: prevLayout,
1402
- onLayout,
1403
1416
  panelDataArray
1404
- } = committedValuesRef.current;
1417
+ } = eagerValuesRef.current;
1405
1418
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1406
1419
  const unsafeLayout = mixedSizes.map(mixedSize => getPercentageSizeFromMixedSizes(mixedSize, groupSizePixels));
1407
1420
  const safeLayout = validatePanelGroupLayout({
@@ -1411,6 +1424,7 @@ function PanelGroupWithForwardedRef({
1411
1424
  });
1412
1425
  if (!areEqual(prevLayout, safeLayout)) {
1413
1426
  setLayout(safeLayout);
1427
+ eagerValuesRef.current.layout = safeLayout;
1414
1428
  if (onLayout) {
1415
1429
  onLayout(safeLayout.map(sizePercentage => ({
1416
1430
  sizePercentage,
@@ -1422,21 +1436,30 @@ function PanelGroupWithForwardedRef({
1422
1436
  }
1423
1437
  }), []);
1424
1438
  useIsomorphicLayoutEffect(() => {
1439
+ committedValuesRef.current.autoSaveId = autoSaveId;
1425
1440
  committedValuesRef.current.direction = direction;
1426
1441
  committedValuesRef.current.dragState = dragState;
1427
1442
  committedValuesRef.current.id = groupId;
1428
- committedValuesRef.current.layout = layout;
1429
1443
  committedValuesRef.current.onLayout = onLayout;
1430
- committedValuesRef.current.panelDataArray = panelDataArray;
1444
+ committedValuesRef.current.storage = storage;
1445
+
1446
+ // panelDataArray and layout are updated in-sync with scheduled state updates.
1447
+ // TODO [217] Move these values into a separate ref
1431
1448
  });
1449
+
1432
1450
  useWindowSplitterPanelGroupBehavior({
1433
1451
  committedValuesRef,
1452
+ eagerValuesRef,
1434
1453
  groupId,
1435
1454
  layout,
1436
- panelDataArray,
1455
+ panelDataArray: eagerValuesRef.current.panelDataArray,
1437
1456
  setLayout
1438
1457
  });
1439
1458
  useEffect(() => {
1459
+ const {
1460
+ panelDataArray
1461
+ } = eagerValuesRef.current;
1462
+
1440
1463
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1441
1464
  if (autoSaveId) {
1442
1465
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1449,63 +1472,12 @@ function PanelGroupWithForwardedRef({
1449
1472
  }
1450
1473
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1451
1474
  }
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)!
1475
+ }, [autoSaveId, layout, storage]);
1457
1476
  useIsomorphicLayoutEffect(() => {
1458
1477
  const {
1459
- id: groupId,
1460
- layout,
1461
- onLayout
1462
- } = 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(() => {
1478
+ layout: prevLayout,
1479
+ panelDataArray
1480
+ } = eagerValuesRef.current;
1509
1481
  const constraints = panelDataArray.map(({
1510
1482
  constraints
1511
1483
  }) => constraints);
@@ -1519,7 +1491,6 @@ function PanelGroupWithForwardedRef({
1519
1491
  const resizeObserver = new ResizeObserver(() => {
1520
1492
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1521
1493
  const {
1522
- layout: prevLayout,
1523
1494
  onLayout
1524
1495
  } = committedValuesRef.current;
1525
1496
  const nextLayout = validatePanelGroupLayout({
@@ -1529,6 +1500,7 @@ function PanelGroupWithForwardedRef({
1529
1500
  });
1530
1501
  if (!areEqual(prevLayout, nextLayout)) {
1531
1502
  setLayout(nextLayout);
1503
+ eagerValuesRef.current.layout = nextLayout;
1532
1504
  if (onLayout) {
1533
1505
  onLayout(nextLayout.map(sizePercentage => ({
1534
1506
  sizePercentage,
@@ -1543,20 +1515,20 @@ function PanelGroupWithForwardedRef({
1543
1515
  resizeObserver.disconnect();
1544
1516
  };
1545
1517
  }
1546
- }, [groupId, panelDataArray]);
1518
+ }, [groupId]);
1547
1519
 
1548
1520
  // DEV warnings
1549
1521
  useEffect(() => {
1550
1522
  {
1523
+ const {
1524
+ panelDataArray
1525
+ } = eagerValuesRef.current;
1551
1526
  const {
1552
1527
  didLogIdAndOrderWarning,
1553
1528
  didLogPanelConstraintsWarning,
1554
1529
  prevPanelIds
1555
1530
  } = devWarningsRef.current;
1556
1531
  if (!didLogIdAndOrderWarning) {
1557
- const {
1558
- panelDataArray
1559
- } = committedValuesRef.current;
1560
1532
  const panelIds = panelDataArray.map(({
1561
1533
  id
1562
1534
  }) => id);
@@ -1593,22 +1565,13 @@ function PanelGroupWithForwardedRef({
1593
1565
 
1594
1566
  // External APIs are safe to memoize via committed values ref
1595
1567
  const collapsePanel = useCallback(panelData => {
1568
+ const {
1569
+ onLayout
1570
+ } = committedValuesRef.current;
1596
1571
  const {
1597
1572
  layout: prevLayout,
1598
- onLayout,
1599
1573
  panelDataArray
1600
- } = 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
- }
1574
+ } = eagerValuesRef.current;
1612
1575
  if (panelData.constraints.collapsible) {
1613
1576
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1614
1577
  const {
@@ -1633,6 +1596,7 @@ function PanelGroupWithForwardedRef({
1633
1596
  });
1634
1597
  if (!compareLayouts(prevLayout, nextLayout)) {
1635
1598
  setLayout(nextLayout);
1599
+ eagerValuesRef.current.layout = nextLayout;
1636
1600
  if (onLayout) {
1637
1601
  onLayout(nextLayout.map(sizePercentage => ({
1638
1602
  sizePercentage,
@@ -1647,22 +1611,13 @@ function PanelGroupWithForwardedRef({
1647
1611
 
1648
1612
  // External APIs are safe to memoize via committed values ref
1649
1613
  const expandPanel = useCallback(panelData => {
1614
+ const {
1615
+ onLayout
1616
+ } = committedValuesRef.current;
1650
1617
  const {
1651
1618
  layout: prevLayout,
1652
- onLayout,
1653
1619
  panelDataArray
1654
- } = 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
- }
1620
+ } = eagerValuesRef.current;
1666
1621
  if (panelData.constraints.collapsible) {
1667
1622
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1668
1623
  const {
@@ -1688,6 +1643,7 @@ function PanelGroupWithForwardedRef({
1688
1643
  });
1689
1644
  if (!compareLayouts(prevLayout, nextLayout)) {
1690
1645
  setLayout(nextLayout);
1646
+ eagerValuesRef.current.layout = nextLayout;
1691
1647
  if (onLayout) {
1692
1648
  onLayout(nextLayout.map(sizePercentage => ({
1693
1649
  sizePercentage,
@@ -1705,7 +1661,7 @@ function PanelGroupWithForwardedRef({
1705
1661
  const {
1706
1662
  layout,
1707
1663
  panelDataArray
1708
- } = committedValuesRef.current;
1664
+ } = eagerValuesRef.current;
1709
1665
  const {
1710
1666
  panelSizePercentage,
1711
1667
  panelSizePixels
@@ -1718,6 +1674,9 @@ function PanelGroupWithForwardedRef({
1718
1674
 
1719
1675
  // This API should never read from committedValuesRef
1720
1676
  const getPanelStyle = useCallback(panelData => {
1677
+ const {
1678
+ panelDataArray
1679
+ } = eagerValuesRef.current;
1721
1680
  const panelIndex = panelDataArray.indexOf(panelData);
1722
1681
  return computePanelFlexBoxStyle({
1723
1682
  dragState,
@@ -1725,14 +1684,14 @@ function PanelGroupWithForwardedRef({
1725
1684
  panelData: panelDataArray,
1726
1685
  panelIndex
1727
1686
  });
1728
- }, [dragState, layout, panelDataArray]);
1687
+ }, [dragState, layout]);
1729
1688
 
1730
1689
  // External APIs are safe to memoize via committed values ref
1731
1690
  const isPanelCollapsed = useCallback(panelData => {
1732
1691
  const {
1733
1692
  layout,
1734
1693
  panelDataArray
1735
- } = committedValuesRef.current;
1694
+ } = eagerValuesRef.current;
1736
1695
  const {
1737
1696
  collapsedSizePercentage,
1738
1697
  collapsible,
@@ -1746,7 +1705,7 @@ function PanelGroupWithForwardedRef({
1746
1705
  const {
1747
1706
  layout,
1748
1707
  panelDataArray
1749
- } = committedValuesRef.current;
1708
+ } = eagerValuesRef.current;
1750
1709
  const {
1751
1710
  collapsedSizePercentage,
1752
1711
  collapsible,
@@ -1755,22 +1714,82 @@ function PanelGroupWithForwardedRef({
1755
1714
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1756
1715
  }, [groupId]);
1757
1716
  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
- }
1717
+ const {
1718
+ autoSaveId,
1719
+ id: groupId,
1720
+ onLayout,
1721
+ storage
1722
+ } = committedValuesRef.current;
1723
+ const {
1724
+ layout: prevLayout,
1725
+ panelDataArray
1726
+ } = eagerValuesRef.current;
1727
+ panelDataArray.push(panelData);
1728
+ panelDataArray.sort((panelA, panelB) => {
1729
+ const orderA = panelA.order;
1730
+ const orderB = panelB.order;
1731
+ if (orderA == null && orderB == null) {
1732
+ return 0;
1733
+ } else if (orderA == null) {
1734
+ return -1;
1735
+ } else if (orderB == null) {
1736
+ return 1;
1737
+ } else {
1738
+ return orderA - orderB;
1739
+ }
1740
+ });
1741
+
1742
+ // Wait until all panels have registered before we try to compute layout;
1743
+ // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1744
+ const panelElements = getPanelElementsForGroup(groupId);
1745
+ if (panelElements.length !== panelDataArray.length) {
1746
+ return;
1747
+ }
1748
+
1749
+ // If this panel has been configured to persist sizing information,
1750
+ // default size should be restored from local storage if possible.
1751
+ let unsafeLayout = null;
1752
+ if (autoSaveId) {
1753
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1754
+ }
1755
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1756
+ if (groupSizePixels <= 0) {
1757
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1758
+ constraints
1759
+ }) => constraints))) {
1760
+ // Wait until the group has rendered a non-zero size before computing layout.
1761
+ return;
1762
+ }
1763
+ }
1764
+ if (unsafeLayout == null) {
1765
+ unsafeLayout = calculateUnsafeDefaultLayout({
1766
+ groupSizePixels,
1767
+ panelDataArray
1772
1768
  });
1769
+ }
1770
+
1771
+ // Validate even saved layouts in case something has changed since last render
1772
+ // e.g. for pixel groups, this could be the size of the window
1773
+ const nextLayout = validatePanelGroupLayout({
1774
+ groupSizePixels,
1775
+ layout: unsafeLayout,
1776
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1773
1777
  });
1778
+
1779
+ // Offscreen mode makes this a bit weird;
1780
+ // Panels unregister when hidden and re-register when shown again,
1781
+ // but the overall layout doesn't change between these two cases.
1782
+ setLayout(nextLayout);
1783
+ eagerValuesRef.current.layout = nextLayout;
1784
+ if (!areEqual(prevLayout, nextLayout)) {
1785
+ if (onLayout) {
1786
+ onLayout(nextLayout.map(sizePercentage => ({
1787
+ sizePercentage,
1788
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1789
+ })));
1790
+ }
1791
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1792
+ }
1774
1793
  }, []);
1775
1794
  const registerResizeHandle = useCallback(dragHandleId => {
1776
1795
  return function resizeHandler(event) {
@@ -1781,10 +1800,12 @@ function PanelGroupWithForwardedRef({
1781
1800
  id: groupId,
1782
1801
  keyboardResizeByPercentage,
1783
1802
  keyboardResizeByPixels,
1784
- onLayout,
1785
- panelDataArray,
1786
- layout: prevLayout
1803
+ onLayout
1787
1804
  } = committedValuesRef.current;
1805
+ const {
1806
+ layout: prevLayout,
1807
+ panelDataArray
1808
+ } = eagerValuesRef.current;
1788
1809
  const {
1789
1810
  initialLayout
1790
1811
  } = dragState !== null && dragState !== void 0 ? dragState : {};
@@ -1840,6 +1861,7 @@ function PanelGroupWithForwardedRef({
1840
1861
  }
1841
1862
  if (layoutChanged) {
1842
1863
  setLayout(nextLayout);
1864
+ eagerValuesRef.current.layout = nextLayout;
1843
1865
  if (onLayout) {
1844
1866
  onLayout(nextLayout.map(sizePercentage => ({
1845
1867
  sizePercentage,
@@ -1853,23 +1875,13 @@ function PanelGroupWithForwardedRef({
1853
1875
 
1854
1876
  // External APIs are safe to memoize via committed values ref
1855
1877
  const resizePanel = useCallback((panelData, mixedSizes) => {
1878
+ const {
1879
+ onLayout
1880
+ } = committedValuesRef.current;
1856
1881
  const {
1857
1882
  layout: prevLayout,
1858
- onLayout,
1859
1883
  panelDataArray
1860
- } = 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
- }
1884
+ } = eagerValuesRef.current;
1873
1885
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1874
1886
  const {
1875
1887
  groupSizePixels,
@@ -1889,6 +1901,7 @@ function PanelGroupWithForwardedRef({
1889
1901
  });
1890
1902
  if (!compareLayouts(prevLayout, nextLayout)) {
1891
1903
  setLayout(nextLayout);
1904
+ eagerValuesRef.current.layout = nextLayout;
1892
1905
  if (onLayout) {
1893
1906
  onLayout(nextLayout.map(sizePercentage => ({
1894
1907
  sizePercentage,
@@ -1900,9 +1913,11 @@ function PanelGroupWithForwardedRef({
1900
1913
  }, [groupId]);
1901
1914
  const startDragging = useCallback((dragHandleId, event) => {
1902
1915
  const {
1903
- direction,
1904
- layout
1916
+ direction
1905
1917
  } = committedValuesRef.current;
1918
+ const {
1919
+ layout
1920
+ } = eagerValuesRef.current;
1906
1921
  const handleElement = getResizeHandleElement(dragHandleId);
1907
1922
  const initialCursorPosition = getResizeEventCursorPosition(direction, event);
1908
1923
  setDragState({
@@ -1916,42 +1931,87 @@ function PanelGroupWithForwardedRef({
1916
1931
  resetGlobalCursorStyle();
1917
1932
  setDragState(null);
1918
1933
  }, []);
1934
+ const unregisterPanelRef = useRef({
1935
+ pendingPanelIds: new Set(),
1936
+ timeout: null
1937
+ });
1919
1938
  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);
1939
+ const {
1940
+ id: groupId,
1941
+ onLayout
1942
+ } = committedValuesRef.current;
1943
+ const {
1944
+ layout: prevLayout,
1945
+ panelDataArray
1946
+ } = eagerValuesRef.current;
1947
+ const index = panelDataArray.indexOf(panelData);
1948
+ if (index >= 0) {
1949
+ panelDataArray.splice(index, 1);
1950
+ unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1951
+ }
1952
+ if (unregisterPanelRef.current.timeout != null) {
1953
+ clearTimeout(unregisterPanelRef.current.timeout);
1954
+ }
1955
+
1956
+ // Batch panel unmounts so that we only calculate layout once;
1957
+ // This is more efficient and avoids misleading warnings in development mode.
1958
+ // We can't check the DOM to detect this because Panel elements have not yet been removed.
1959
+ unregisterPanelRef.current.timeout = setTimeout(() => {
1960
+ const {
1961
+ pendingPanelIds
1962
+ } = unregisterPanelRef.current;
1963
+ const map = panelIdToLastNotifiedMixedSizesMapRef.current;
1964
+
1965
+ // TRICKY
1966
+ // Strict effects mode
1967
+ let unmountDueToStrictMode = false;
1968
+ pendingPanelIds.forEach(panelId => {
1969
+ pendingPanelIds.delete(panelId);
1970
+ if (panelDataArray.find(({
1971
+ id
1972
+ }) => id === panelId) == null) {
1973
+ unmountDueToStrictMode = true;
1974
+
1975
+ // TRICKY
1976
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1977
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1978
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1979
+ delete map[panelData.id];
1980
+ }
1981
+ });
1982
+ if (!unmountDueToStrictMode) {
1983
+ return;
1926
1984
  }
1927
- return panelDataArray;
1928
- });
1929
- }, []);
1985
+ if (panelDataArray.length === 0) {
1986
+ // The group is unmounting; skip layout calculation.
1987
+ return;
1988
+ }
1989
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1990
+ let unsafeLayout = calculateUnsafeDefaultLayout({
1991
+ groupSizePixels,
1992
+ panelDataArray
1993
+ });
1930
1994
 
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
- }
1995
+ // Validate even saved layouts in case something has changed since last render
1996
+ // e.g. for pixel groups, this could be the size of the window
1997
+ const nextLayout = validatePanelGroupLayout({
1998
+ groupSizePixels,
1999
+ layout: unsafeLayout,
2000
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
2001
+ });
2002
+ if (!areEqual(prevLayout, nextLayout)) {
2003
+ setLayout(nextLayout);
2004
+ eagerValuesRef.current.layout = nextLayout;
2005
+ if (onLayout) {
2006
+ onLayout(nextLayout.map(sizePercentage => ({
2007
+ sizePercentage,
2008
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
2009
+ })));
2010
+ }
2011
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1952
2012
  }
1953
- }
1954
- }, [collapsePanel, expandPanel, imperativeApiQueue, layout, panelDataArray, resizePanel]);
2013
+ }, 0);
2014
+ }, []);
1955
2015
  const context = useMemo(() => ({
1956
2016
  collapsePanel,
1957
2017
  direction,