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.
@@ -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)
@@ -715,6 +717,7 @@ function getResizeHandlePanelIds(groupId, handleId, panelsArray) {
715
717
 
716
718
  function useWindowSplitterPanelGroupBehavior({
717
719
  committedValuesRef,
720
+ eagerValuesRef,
718
721
  groupId,
719
722
  layout,
720
723
  panelDataArray,
@@ -767,7 +770,7 @@ function useWindowSplitterPanelGroupBehavior({
767
770
  useEffect(() => {
768
771
  const {
769
772
  panelDataArray
770
- } = committedValuesRef.current;
773
+ } = eagerValuesRef.current;
771
774
  const groupElement = getPanelGroupElement(groupId);
772
775
  assert(groupElement != null, `No group found for id "${groupId}"`);
773
776
  const handles = getResizeHandleElementsForGroup(groupId);
@@ -825,7 +828,7 @@ function useWindowSplitterPanelGroupBehavior({
825
828
  return () => {
826
829
  cleanupFunctions.forEach(cleanupFunction => cleanupFunction());
827
830
  };
828
- }, [committedValuesRef, groupId, layout, panelDataArray, setLayout]);
831
+ }, [committedValuesRef, eagerValuesRef, groupId, layout, panelDataArray, setLayout]);
829
832
  }
830
833
 
831
834
  function areEqual(arrayA, arrayB) {
@@ -1110,6 +1113,10 @@ function debounce(callback, durationMs = 10) {
1110
1113
  return callable;
1111
1114
  }
1112
1115
 
1116
+ function getPanelElementsForGroup(groupId) {
1117
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
1118
+ }
1119
+
1113
1120
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
1114
1121
  // or on a browser with cookies/storage disabled.
1115
1122
  // In either case, this function avoids accessing localStorage until needed,
@@ -1336,7 +1343,7 @@ const defaultStorage = {
1336
1343
  };
1337
1344
  const debounceMap = {};
1338
1345
  function PanelGroupWithForwardedRef({
1339
- autoSaveId,
1346
+ autoSaveId = null,
1340
1347
  children,
1341
1348
  className: classNameFromProps = "",
1342
1349
  dataAttributes,
@@ -1353,20 +1360,22 @@ function PanelGroupWithForwardedRef({
1353
1360
  const groupId = useUniqueId(idFromProps);
1354
1361
  const [dragState, setDragState] = useState(null);
1355
1362
  const [layout, setLayout] = useState([]);
1356
- const [panelDataArray, setPanelDataArray] = useState([]);
1357
1363
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1358
1364
  const panelSizeBeforeCollapseRef = useRef(new Map());
1359
1365
  const prevDeltaRef = useRef(0);
1360
- const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1361
1366
  const committedValuesRef = useRef({
1367
+ autoSaveId,
1362
1368
  direction,
1363
1369
  dragState,
1364
1370
  id: groupId,
1365
1371
  keyboardResizeByPercentage,
1366
1372
  keyboardResizeByPixels,
1367
- layout,
1368
1373
  onLayout,
1369
- panelDataArray
1374
+ storage
1375
+ });
1376
+ const eagerValuesRef = useRef({
1377
+ layout,
1378
+ panelDataArray: []
1370
1379
  });
1371
1380
  const devWarningsRef = useRef({
1372
1381
  didLogIdAndOrderWarning: false,
@@ -1377,9 +1386,11 @@ function PanelGroupWithForwardedRef({
1377
1386
  getId: () => committedValuesRef.current.id,
1378
1387
  getLayout: () => {
1379
1388
  const {
1380
- id: groupId,
1381
- layout
1389
+ id: groupId
1382
1390
  } = committedValuesRef.current;
1391
+ const {
1392
+ layout
1393
+ } = eagerValuesRef.current;
1383
1394
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1384
1395
  return layout.map(sizePercentage => {
1385
1396
  return {
@@ -1391,10 +1402,12 @@ function PanelGroupWithForwardedRef({
1391
1402
  setLayout: mixedSizes => {
1392
1403
  const {
1393
1404
  id: groupId,
1405
+ onLayout
1406
+ } = committedValuesRef.current;
1407
+ const {
1394
1408
  layout: prevLayout,
1395
- onLayout,
1396
1409
  panelDataArray
1397
- } = committedValuesRef.current;
1410
+ } = eagerValuesRef.current;
1398
1411
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1399
1412
  const unsafeLayout = mixedSizes.map(mixedSize => getPercentageSizeFromMixedSizes(mixedSize, groupSizePixels));
1400
1413
  const safeLayout = validatePanelGroupLayout({
@@ -1404,6 +1417,7 @@ function PanelGroupWithForwardedRef({
1404
1417
  });
1405
1418
  if (!areEqual(prevLayout, safeLayout)) {
1406
1419
  setLayout(safeLayout);
1420
+ eagerValuesRef.current.layout = safeLayout;
1407
1421
  if (onLayout) {
1408
1422
  onLayout(safeLayout.map(sizePercentage => ({
1409
1423
  sizePercentage,
@@ -1415,21 +1429,30 @@ function PanelGroupWithForwardedRef({
1415
1429
  }
1416
1430
  }), []);
1417
1431
  useIsomorphicLayoutEffect(() => {
1432
+ committedValuesRef.current.autoSaveId = autoSaveId;
1418
1433
  committedValuesRef.current.direction = direction;
1419
1434
  committedValuesRef.current.dragState = dragState;
1420
1435
  committedValuesRef.current.id = groupId;
1421
- committedValuesRef.current.layout = layout;
1422
1436
  committedValuesRef.current.onLayout = onLayout;
1423
- 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
1424
1441
  });
1442
+
1425
1443
  useWindowSplitterPanelGroupBehavior({
1426
1444
  committedValuesRef,
1445
+ eagerValuesRef,
1427
1446
  groupId,
1428
1447
  layout,
1429
- panelDataArray,
1448
+ panelDataArray: eagerValuesRef.current.panelDataArray,
1430
1449
  setLayout
1431
1450
  });
1432
1451
  useEffect(() => {
1452
+ const {
1453
+ panelDataArray
1454
+ } = eagerValuesRef.current;
1455
+
1433
1456
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1434
1457
  if (autoSaveId) {
1435
1458
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1442,63 +1465,12 @@ function PanelGroupWithForwardedRef({
1442
1465
  }
1443
1466
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1444
1467
  }
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)!
1468
+ }, [autoSaveId, layout, storage]);
1450
1469
  useIsomorphicLayoutEffect(() => {
1451
1470
  const {
1452
- id: groupId,
1453
- layout,
1454
- onLayout
1455
- } = 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(() => {
1471
+ layout: prevLayout,
1472
+ panelDataArray
1473
+ } = eagerValuesRef.current;
1502
1474
  const constraints = panelDataArray.map(({
1503
1475
  constraints
1504
1476
  }) => constraints);
@@ -1512,7 +1484,6 @@ function PanelGroupWithForwardedRef({
1512
1484
  const resizeObserver = new ResizeObserver(() => {
1513
1485
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1514
1486
  const {
1515
- layout: prevLayout,
1516
1487
  onLayout
1517
1488
  } = committedValuesRef.current;
1518
1489
  const nextLayout = validatePanelGroupLayout({
@@ -1522,6 +1493,7 @@ function PanelGroupWithForwardedRef({
1522
1493
  });
1523
1494
  if (!areEqual(prevLayout, nextLayout)) {
1524
1495
  setLayout(nextLayout);
1496
+ eagerValuesRef.current.layout = nextLayout;
1525
1497
  if (onLayout) {
1526
1498
  onLayout(nextLayout.map(sizePercentage => ({
1527
1499
  sizePercentage,
@@ -1536,20 +1508,20 @@ function PanelGroupWithForwardedRef({
1536
1508
  resizeObserver.disconnect();
1537
1509
  };
1538
1510
  }
1539
- }, [groupId, panelDataArray]);
1511
+ }, [groupId]);
1540
1512
 
1541
1513
  // DEV warnings
1542
1514
  useEffect(() => {
1543
1515
  {
1516
+ const {
1517
+ panelDataArray
1518
+ } = eagerValuesRef.current;
1544
1519
  const {
1545
1520
  didLogIdAndOrderWarning,
1546
1521
  didLogPanelConstraintsWarning,
1547
1522
  prevPanelIds
1548
1523
  } = devWarningsRef.current;
1549
1524
  if (!didLogIdAndOrderWarning) {
1550
- const {
1551
- panelDataArray
1552
- } = committedValuesRef.current;
1553
1525
  const panelIds = panelDataArray.map(({
1554
1526
  id
1555
1527
  }) => id);
@@ -1586,22 +1558,13 @@ function PanelGroupWithForwardedRef({
1586
1558
 
1587
1559
  // External APIs are safe to memoize via committed values ref
1588
1560
  const collapsePanel = useCallback(panelData => {
1561
+ const {
1562
+ onLayout
1563
+ } = committedValuesRef.current;
1589
1564
  const {
1590
1565
  layout: prevLayout,
1591
- onLayout,
1592
1566
  panelDataArray
1593
- } = 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
- }
1567
+ } = eagerValuesRef.current;
1605
1568
  if (panelData.constraints.collapsible) {
1606
1569
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1607
1570
  const {
@@ -1626,6 +1589,7 @@ function PanelGroupWithForwardedRef({
1626
1589
  });
1627
1590
  if (!compareLayouts(prevLayout, nextLayout)) {
1628
1591
  setLayout(nextLayout);
1592
+ eagerValuesRef.current.layout = nextLayout;
1629
1593
  if (onLayout) {
1630
1594
  onLayout(nextLayout.map(sizePercentage => ({
1631
1595
  sizePercentage,
@@ -1640,22 +1604,13 @@ function PanelGroupWithForwardedRef({
1640
1604
 
1641
1605
  // External APIs are safe to memoize via committed values ref
1642
1606
  const expandPanel = useCallback(panelData => {
1607
+ const {
1608
+ onLayout
1609
+ } = committedValuesRef.current;
1643
1610
  const {
1644
1611
  layout: prevLayout,
1645
- onLayout,
1646
1612
  panelDataArray
1647
- } = 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
- }
1613
+ } = eagerValuesRef.current;
1659
1614
  if (panelData.constraints.collapsible) {
1660
1615
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1661
1616
  const {
@@ -1681,6 +1636,7 @@ function PanelGroupWithForwardedRef({
1681
1636
  });
1682
1637
  if (!compareLayouts(prevLayout, nextLayout)) {
1683
1638
  setLayout(nextLayout);
1639
+ eagerValuesRef.current.layout = nextLayout;
1684
1640
  if (onLayout) {
1685
1641
  onLayout(nextLayout.map(sizePercentage => ({
1686
1642
  sizePercentage,
@@ -1698,7 +1654,7 @@ function PanelGroupWithForwardedRef({
1698
1654
  const {
1699
1655
  layout,
1700
1656
  panelDataArray
1701
- } = committedValuesRef.current;
1657
+ } = eagerValuesRef.current;
1702
1658
  const {
1703
1659
  panelSizePercentage,
1704
1660
  panelSizePixels
@@ -1711,6 +1667,9 @@ function PanelGroupWithForwardedRef({
1711
1667
 
1712
1668
  // This API should never read from committedValuesRef
1713
1669
  const getPanelStyle = useCallback(panelData => {
1670
+ const {
1671
+ panelDataArray
1672
+ } = eagerValuesRef.current;
1714
1673
  const panelIndex = panelDataArray.indexOf(panelData);
1715
1674
  return computePanelFlexBoxStyle({
1716
1675
  dragState,
@@ -1718,14 +1677,14 @@ function PanelGroupWithForwardedRef({
1718
1677
  panelData: panelDataArray,
1719
1678
  panelIndex
1720
1679
  });
1721
- }, [dragState, layout, panelDataArray]);
1680
+ }, [dragState, layout]);
1722
1681
 
1723
1682
  // External APIs are safe to memoize via committed values ref
1724
1683
  const isPanelCollapsed = useCallback(panelData => {
1725
1684
  const {
1726
1685
  layout,
1727
1686
  panelDataArray
1728
- } = committedValuesRef.current;
1687
+ } = eagerValuesRef.current;
1729
1688
  const {
1730
1689
  collapsedSizePercentage,
1731
1690
  collapsible,
@@ -1739,7 +1698,7 @@ function PanelGroupWithForwardedRef({
1739
1698
  const {
1740
1699
  layout,
1741
1700
  panelDataArray
1742
- } = committedValuesRef.current;
1701
+ } = eagerValuesRef.current;
1743
1702
  const {
1744
1703
  collapsedSizePercentage,
1745
1704
  collapsible,
@@ -1748,22 +1707,82 @@ function PanelGroupWithForwardedRef({
1748
1707
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1749
1708
  }, [groupId]);
1750
1709
  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
- }
1710
+ const {
1711
+ autoSaveId,
1712
+ id: groupId,
1713
+ onLayout,
1714
+ storage
1715
+ } = committedValuesRef.current;
1716
+ const {
1717
+ layout: prevLayout,
1718
+ panelDataArray
1719
+ } = eagerValuesRef.current;
1720
+ panelDataArray.push(panelData);
1721
+ panelDataArray.sort((panelA, panelB) => {
1722
+ const orderA = panelA.order;
1723
+ const orderB = panelB.order;
1724
+ if (orderA == null && orderB == null) {
1725
+ return 0;
1726
+ } else if (orderA == null) {
1727
+ return -1;
1728
+ } else if (orderB == null) {
1729
+ return 1;
1730
+ } else {
1731
+ return orderA - orderB;
1732
+ }
1733
+ });
1734
+
1735
+ // Wait until all panels have registered before we try to compute layout;
1736
+ // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1737
+ const panelElements = getPanelElementsForGroup(groupId);
1738
+ if (panelElements.length !== panelDataArray.length) {
1739
+ return;
1740
+ }
1741
+
1742
+ // If this panel has been configured to persist sizing information,
1743
+ // default size should be restored from local storage if possible.
1744
+ let unsafeLayout = null;
1745
+ if (autoSaveId) {
1746
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1747
+ }
1748
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1749
+ if (groupSizePixels <= 0) {
1750
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1751
+ constraints
1752
+ }) => constraints))) {
1753
+ // Wait until the group has rendered a non-zero size before computing layout.
1754
+ return;
1755
+ }
1756
+ }
1757
+ if (unsafeLayout == null) {
1758
+ unsafeLayout = calculateUnsafeDefaultLayout({
1759
+ groupSizePixels,
1760
+ panelDataArray
1765
1761
  });
1762
+ }
1763
+
1764
+ // Validate even saved layouts in case something has changed since last render
1765
+ // e.g. for pixel groups, this could be the size of the window
1766
+ const nextLayout = validatePanelGroupLayout({
1767
+ groupSizePixels,
1768
+ layout: unsafeLayout,
1769
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1766
1770
  });
1771
+
1772
+ // Offscreen mode makes this a bit weird;
1773
+ // Panels unregister when hidden and re-register when shown again,
1774
+ // but the overall layout doesn't change between these two cases.
1775
+ setLayout(nextLayout);
1776
+ eagerValuesRef.current.layout = nextLayout;
1777
+ if (!areEqual(prevLayout, nextLayout)) {
1778
+ if (onLayout) {
1779
+ onLayout(nextLayout.map(sizePercentage => ({
1780
+ sizePercentage,
1781
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1782
+ })));
1783
+ }
1784
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1785
+ }
1767
1786
  }, []);
1768
1787
  const registerResizeHandle = useCallback(dragHandleId => {
1769
1788
  return function resizeHandler(event) {
@@ -1774,10 +1793,12 @@ function PanelGroupWithForwardedRef({
1774
1793
  id: groupId,
1775
1794
  keyboardResizeByPercentage,
1776
1795
  keyboardResizeByPixels,
1777
- onLayout,
1778
- panelDataArray,
1779
- layout: prevLayout
1796
+ onLayout
1780
1797
  } = committedValuesRef.current;
1798
+ const {
1799
+ layout: prevLayout,
1800
+ panelDataArray
1801
+ } = eagerValuesRef.current;
1781
1802
  const {
1782
1803
  initialLayout
1783
1804
  } = dragState !== null && dragState !== void 0 ? dragState : {};
@@ -1833,6 +1854,7 @@ function PanelGroupWithForwardedRef({
1833
1854
  }
1834
1855
  if (layoutChanged) {
1835
1856
  setLayout(nextLayout);
1857
+ eagerValuesRef.current.layout = nextLayout;
1836
1858
  if (onLayout) {
1837
1859
  onLayout(nextLayout.map(sizePercentage => ({
1838
1860
  sizePercentage,
@@ -1846,23 +1868,13 @@ function PanelGroupWithForwardedRef({
1846
1868
 
1847
1869
  // External APIs are safe to memoize via committed values ref
1848
1870
  const resizePanel = useCallback((panelData, mixedSizes) => {
1871
+ const {
1872
+ onLayout
1873
+ } = committedValuesRef.current;
1849
1874
  const {
1850
1875
  layout: prevLayout,
1851
- onLayout,
1852
1876
  panelDataArray
1853
- } = 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
- }
1877
+ } = eagerValuesRef.current;
1866
1878
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1867
1879
  const {
1868
1880
  groupSizePixels,
@@ -1882,6 +1894,7 @@ function PanelGroupWithForwardedRef({
1882
1894
  });
1883
1895
  if (!compareLayouts(prevLayout, nextLayout)) {
1884
1896
  setLayout(nextLayout);
1897
+ eagerValuesRef.current.layout = nextLayout;
1885
1898
  if (onLayout) {
1886
1899
  onLayout(nextLayout.map(sizePercentage => ({
1887
1900
  sizePercentage,
@@ -1893,9 +1906,11 @@ function PanelGroupWithForwardedRef({
1893
1906
  }, [groupId]);
1894
1907
  const startDragging = useCallback((dragHandleId, event) => {
1895
1908
  const {
1896
- direction,
1897
- layout
1909
+ direction
1898
1910
  } = committedValuesRef.current;
1911
+ const {
1912
+ layout
1913
+ } = eagerValuesRef.current;
1899
1914
  const handleElement = getResizeHandleElement(dragHandleId);
1900
1915
  const initialCursorPosition = getResizeEventCursorPosition(direction, event);
1901
1916
  setDragState({
@@ -1909,42 +1924,87 @@ function PanelGroupWithForwardedRef({
1909
1924
  resetGlobalCursorStyle();
1910
1925
  setDragState(null);
1911
1926
  }, []);
1927
+ const unregisterPanelRef = useRef({
1928
+ pendingPanelIds: new Set(),
1929
+ timeout: null
1930
+ });
1912
1931
  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);
1932
+ const {
1933
+ id: groupId,
1934
+ onLayout
1935
+ } = committedValuesRef.current;
1936
+ const {
1937
+ layout: prevLayout,
1938
+ panelDataArray
1939
+ } = eagerValuesRef.current;
1940
+ const index = panelDataArray.indexOf(panelData);
1941
+ if (index >= 0) {
1942
+ panelDataArray.splice(index, 1);
1943
+ unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1944
+ }
1945
+ if (unregisterPanelRef.current.timeout != null) {
1946
+ clearTimeout(unregisterPanelRef.current.timeout);
1947
+ }
1948
+
1949
+ // Batch panel unmounts so that we only calculate layout once;
1950
+ // This is more efficient and avoids misleading warnings in development mode.
1951
+ // We can't check the DOM to detect this because Panel elements have not yet been removed.
1952
+ unregisterPanelRef.current.timeout = setTimeout(() => {
1953
+ const {
1954
+ pendingPanelIds
1955
+ } = unregisterPanelRef.current;
1956
+ const map = panelIdToLastNotifiedMixedSizesMapRef.current;
1957
+
1958
+ // TRICKY
1959
+ // Strict effects mode
1960
+ let unmountDueToStrictMode = false;
1961
+ pendingPanelIds.forEach(panelId => {
1962
+ pendingPanelIds.delete(panelId);
1963
+ if (panelDataArray.find(({
1964
+ id
1965
+ }) => id === panelId) == null) {
1966
+ unmountDueToStrictMode = true;
1967
+
1968
+ // TRICKY
1969
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1970
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1971
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1972
+ delete map[panelData.id];
1973
+ }
1974
+ });
1975
+ if (!unmountDueToStrictMode) {
1976
+ return;
1919
1977
  }
1920
- return panelDataArray;
1921
- });
1922
- }, []);
1978
+ if (panelDataArray.length === 0) {
1979
+ // The group is unmounting; skip layout calculation.
1980
+ return;
1981
+ }
1982
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1983
+ let unsafeLayout = calculateUnsafeDefaultLayout({
1984
+ groupSizePixels,
1985
+ panelDataArray
1986
+ });
1923
1987
 
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
- }
1988
+ // Validate even saved layouts in case something has changed since last render
1989
+ // e.g. for pixel groups, this could be the size of the window
1990
+ const nextLayout = validatePanelGroupLayout({
1991
+ groupSizePixels,
1992
+ layout: unsafeLayout,
1993
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1994
+ });
1995
+ if (!areEqual(prevLayout, nextLayout)) {
1996
+ setLayout(nextLayout);
1997
+ eagerValuesRef.current.layout = nextLayout;
1998
+ if (onLayout) {
1999
+ onLayout(nextLayout.map(sizePercentage => ({
2000
+ sizePercentage,
2001
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
2002
+ })));
2003
+ }
2004
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1945
2005
  }
1946
- }
1947
- }, [collapsePanel, expandPanel, imperativeApiQueue, layout, panelDataArray, resizePanel]);
2006
+ }, 0);
2007
+ }, []);
1948
2008
  const context = useMemo(() => ({
1949
2009
  collapsePanel,
1950
2010
  direction,