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.
@@ -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)
@@ -698,6 +700,7 @@ function getResizeHandlePanelIds(groupId, handleId, panelsArray) {
698
700
 
699
701
  function useWindowSplitterPanelGroupBehavior({
700
702
  committedValuesRef,
703
+ eagerValuesRef,
701
704
  groupId,
702
705
  layout,
703
706
  panelDataArray,
@@ -750,7 +753,7 @@ function useWindowSplitterPanelGroupBehavior({
750
753
  useEffect(() => {
751
754
  const {
752
755
  panelDataArray
753
- } = committedValuesRef.current;
756
+ } = eagerValuesRef.current;
754
757
  const groupElement = getPanelGroupElement(groupId);
755
758
  assert(groupElement != null, `No group found for id "${groupId}"`);
756
759
  const handles = getResizeHandleElementsForGroup(groupId);
@@ -808,7 +811,7 @@ function useWindowSplitterPanelGroupBehavior({
808
811
  return () => {
809
812
  cleanupFunctions.forEach(cleanupFunction => cleanupFunction());
810
813
  };
811
- }, [committedValuesRef, groupId, layout, panelDataArray, setLayout]);
814
+ }, [committedValuesRef, eagerValuesRef, groupId, layout, panelDataArray, setLayout]);
812
815
  }
813
816
 
814
817
  function areEqual(arrayA, arrayB) {
@@ -1093,6 +1096,10 @@ function debounce(callback, durationMs = 10) {
1093
1096
  return callable;
1094
1097
  }
1095
1098
 
1099
+ function getPanelElementsForGroup(groupId) {
1100
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
1101
+ }
1102
+
1096
1103
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
1097
1104
  // or on a browser with cookies/storage disabled.
1098
1105
  // In either case, this function avoids accessing localStorage until needed,
@@ -1319,7 +1326,7 @@ const defaultStorage = {
1319
1326
  };
1320
1327
  const debounceMap = {};
1321
1328
  function PanelGroupWithForwardedRef({
1322
- autoSaveId,
1329
+ autoSaveId = null,
1323
1330
  children,
1324
1331
  className: classNameFromProps = "",
1325
1332
  dataAttributes,
@@ -1336,20 +1343,22 @@ function PanelGroupWithForwardedRef({
1336
1343
  const groupId = useUniqueId(idFromProps);
1337
1344
  const [dragState, setDragState] = useState(null);
1338
1345
  const [layout, setLayout] = useState([]);
1339
- const [panelDataArray, setPanelDataArray] = useState([]);
1340
1346
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1341
1347
  const panelSizeBeforeCollapseRef = useRef(new Map());
1342
1348
  const prevDeltaRef = useRef(0);
1343
- const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1344
1349
  const committedValuesRef = useRef({
1350
+ autoSaveId,
1345
1351
  direction,
1346
1352
  dragState,
1347
1353
  id: groupId,
1348
1354
  keyboardResizeByPercentage,
1349
1355
  keyboardResizeByPixels,
1350
- layout,
1351
1356
  onLayout,
1352
- panelDataArray
1357
+ storage
1358
+ });
1359
+ const eagerValuesRef = useRef({
1360
+ layout,
1361
+ panelDataArray: []
1353
1362
  });
1354
1363
  const devWarningsRef = useRef({
1355
1364
  didLogIdAndOrderWarning: false,
@@ -1360,9 +1369,11 @@ function PanelGroupWithForwardedRef({
1360
1369
  getId: () => committedValuesRef.current.id,
1361
1370
  getLayout: () => {
1362
1371
  const {
1363
- id: groupId,
1364
- layout
1372
+ id: groupId
1365
1373
  } = committedValuesRef.current;
1374
+ const {
1375
+ layout
1376
+ } = eagerValuesRef.current;
1366
1377
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1367
1378
  return layout.map(sizePercentage => {
1368
1379
  return {
@@ -1374,10 +1385,12 @@ function PanelGroupWithForwardedRef({
1374
1385
  setLayout: mixedSizes => {
1375
1386
  const {
1376
1387
  id: groupId,
1388
+ onLayout
1389
+ } = committedValuesRef.current;
1390
+ const {
1377
1391
  layout: prevLayout,
1378
- onLayout,
1379
1392
  panelDataArray
1380
- } = committedValuesRef.current;
1393
+ } = eagerValuesRef.current;
1381
1394
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1382
1395
  const unsafeLayout = mixedSizes.map(mixedSize => getPercentageSizeFromMixedSizes(mixedSize, groupSizePixels));
1383
1396
  const safeLayout = validatePanelGroupLayout({
@@ -1387,6 +1400,7 @@ function PanelGroupWithForwardedRef({
1387
1400
  });
1388
1401
  if (!areEqual(prevLayout, safeLayout)) {
1389
1402
  setLayout(safeLayout);
1403
+ eagerValuesRef.current.layout = safeLayout;
1390
1404
  if (onLayout) {
1391
1405
  onLayout(safeLayout.map(sizePercentage => ({
1392
1406
  sizePercentage,
@@ -1398,21 +1412,30 @@ function PanelGroupWithForwardedRef({
1398
1412
  }
1399
1413
  }), []);
1400
1414
  useIsomorphicLayoutEffect(() => {
1415
+ committedValuesRef.current.autoSaveId = autoSaveId;
1401
1416
  committedValuesRef.current.direction = direction;
1402
1417
  committedValuesRef.current.dragState = dragState;
1403
1418
  committedValuesRef.current.id = groupId;
1404
- committedValuesRef.current.layout = layout;
1405
1419
  committedValuesRef.current.onLayout = onLayout;
1406
- committedValuesRef.current.panelDataArray = panelDataArray;
1420
+ committedValuesRef.current.storage = storage;
1421
+
1422
+ // panelDataArray and layout are updated in-sync with scheduled state updates.
1423
+ // TODO [217] Move these values into a separate ref
1407
1424
  });
1425
+
1408
1426
  useWindowSplitterPanelGroupBehavior({
1409
1427
  committedValuesRef,
1428
+ eagerValuesRef,
1410
1429
  groupId,
1411
1430
  layout,
1412
- panelDataArray,
1431
+ panelDataArray: eagerValuesRef.current.panelDataArray,
1413
1432
  setLayout
1414
1433
  });
1415
1434
  useEffect(() => {
1435
+ const {
1436
+ panelDataArray
1437
+ } = eagerValuesRef.current;
1438
+
1416
1439
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1417
1440
  if (autoSaveId) {
1418
1441
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1425,63 +1448,12 @@ function PanelGroupWithForwardedRef({
1425
1448
  }
1426
1449
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1427
1450
  }
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)!
1451
+ }, [autoSaveId, layout, storage]);
1433
1452
  useIsomorphicLayoutEffect(() => {
1434
1453
  const {
1435
- id: groupId,
1436
- layout,
1437
- onLayout
1438
- } = 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(() => {
1454
+ layout: prevLayout,
1455
+ panelDataArray
1456
+ } = eagerValuesRef.current;
1485
1457
  const constraints = panelDataArray.map(({
1486
1458
  constraints
1487
1459
  }) => constraints);
@@ -1495,7 +1467,6 @@ function PanelGroupWithForwardedRef({
1495
1467
  const resizeObserver = new ResizeObserver(() => {
1496
1468
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1497
1469
  const {
1498
- layout: prevLayout,
1499
1470
  onLayout
1500
1471
  } = committedValuesRef.current;
1501
1472
  const nextLayout = validatePanelGroupLayout({
@@ -1505,6 +1476,7 @@ function PanelGroupWithForwardedRef({
1505
1476
  });
1506
1477
  if (!areEqual(prevLayout, nextLayout)) {
1507
1478
  setLayout(nextLayout);
1479
+ eagerValuesRef.current.layout = nextLayout;
1508
1480
  if (onLayout) {
1509
1481
  onLayout(nextLayout.map(sizePercentage => ({
1510
1482
  sizePercentage,
@@ -1519,20 +1491,20 @@ function PanelGroupWithForwardedRef({
1519
1491
  resizeObserver.disconnect();
1520
1492
  };
1521
1493
  }
1522
- }, [groupId, panelDataArray]);
1494
+ }, [groupId]);
1523
1495
 
1524
1496
  // DEV warnings
1525
1497
  useEffect(() => {
1526
1498
  {
1499
+ const {
1500
+ panelDataArray
1501
+ } = eagerValuesRef.current;
1527
1502
  const {
1528
1503
  didLogIdAndOrderWarning,
1529
1504
  didLogPanelConstraintsWarning,
1530
1505
  prevPanelIds
1531
1506
  } = devWarningsRef.current;
1532
1507
  if (!didLogIdAndOrderWarning) {
1533
- const {
1534
- panelDataArray
1535
- } = committedValuesRef.current;
1536
1508
  const panelIds = panelDataArray.map(({
1537
1509
  id
1538
1510
  }) => id);
@@ -1569,22 +1541,13 @@ function PanelGroupWithForwardedRef({
1569
1541
 
1570
1542
  // External APIs are safe to memoize via committed values ref
1571
1543
  const collapsePanel = useCallback(panelData => {
1544
+ const {
1545
+ onLayout
1546
+ } = committedValuesRef.current;
1572
1547
  const {
1573
1548
  layout: prevLayout,
1574
- onLayout,
1575
1549
  panelDataArray
1576
- } = 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
- }
1550
+ } = eagerValuesRef.current;
1588
1551
  if (panelData.constraints.collapsible) {
1589
1552
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1590
1553
  const {
@@ -1609,6 +1572,7 @@ function PanelGroupWithForwardedRef({
1609
1572
  });
1610
1573
  if (!compareLayouts(prevLayout, nextLayout)) {
1611
1574
  setLayout(nextLayout);
1575
+ eagerValuesRef.current.layout = nextLayout;
1612
1576
  if (onLayout) {
1613
1577
  onLayout(nextLayout.map(sizePercentage => ({
1614
1578
  sizePercentage,
@@ -1623,22 +1587,13 @@ function PanelGroupWithForwardedRef({
1623
1587
 
1624
1588
  // External APIs are safe to memoize via committed values ref
1625
1589
  const expandPanel = useCallback(panelData => {
1590
+ const {
1591
+ onLayout
1592
+ } = committedValuesRef.current;
1626
1593
  const {
1627
1594
  layout: prevLayout,
1628
- onLayout,
1629
1595
  panelDataArray
1630
- } = 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
- }
1596
+ } = eagerValuesRef.current;
1642
1597
  if (panelData.constraints.collapsible) {
1643
1598
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1644
1599
  const {
@@ -1664,6 +1619,7 @@ function PanelGroupWithForwardedRef({
1664
1619
  });
1665
1620
  if (!compareLayouts(prevLayout, nextLayout)) {
1666
1621
  setLayout(nextLayout);
1622
+ eagerValuesRef.current.layout = nextLayout;
1667
1623
  if (onLayout) {
1668
1624
  onLayout(nextLayout.map(sizePercentage => ({
1669
1625
  sizePercentage,
@@ -1681,7 +1637,7 @@ function PanelGroupWithForwardedRef({
1681
1637
  const {
1682
1638
  layout,
1683
1639
  panelDataArray
1684
- } = committedValuesRef.current;
1640
+ } = eagerValuesRef.current;
1685
1641
  const {
1686
1642
  panelSizePercentage,
1687
1643
  panelSizePixels
@@ -1694,6 +1650,9 @@ function PanelGroupWithForwardedRef({
1694
1650
 
1695
1651
  // This API should never read from committedValuesRef
1696
1652
  const getPanelStyle = useCallback(panelData => {
1653
+ const {
1654
+ panelDataArray
1655
+ } = eagerValuesRef.current;
1697
1656
  const panelIndex = panelDataArray.indexOf(panelData);
1698
1657
  return computePanelFlexBoxStyle({
1699
1658
  dragState,
@@ -1701,14 +1660,14 @@ function PanelGroupWithForwardedRef({
1701
1660
  panelData: panelDataArray,
1702
1661
  panelIndex
1703
1662
  });
1704
- }, [dragState, layout, panelDataArray]);
1663
+ }, [dragState, layout]);
1705
1664
 
1706
1665
  // External APIs are safe to memoize via committed values ref
1707
1666
  const isPanelCollapsed = useCallback(panelData => {
1708
1667
  const {
1709
1668
  layout,
1710
1669
  panelDataArray
1711
- } = committedValuesRef.current;
1670
+ } = eagerValuesRef.current;
1712
1671
  const {
1713
1672
  collapsedSizePercentage,
1714
1673
  collapsible,
@@ -1722,7 +1681,7 @@ function PanelGroupWithForwardedRef({
1722
1681
  const {
1723
1682
  layout,
1724
1683
  panelDataArray
1725
- } = committedValuesRef.current;
1684
+ } = eagerValuesRef.current;
1726
1685
  const {
1727
1686
  collapsedSizePercentage,
1728
1687
  collapsible,
@@ -1731,22 +1690,82 @@ function PanelGroupWithForwardedRef({
1731
1690
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1732
1691
  }, [groupId]);
1733
1692
  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
- }
1693
+ const {
1694
+ autoSaveId,
1695
+ id: groupId,
1696
+ onLayout,
1697
+ storage
1698
+ } = committedValuesRef.current;
1699
+ const {
1700
+ layout: prevLayout,
1701
+ panelDataArray
1702
+ } = eagerValuesRef.current;
1703
+ panelDataArray.push(panelData);
1704
+ panelDataArray.sort((panelA, panelB) => {
1705
+ const orderA = panelA.order;
1706
+ const orderB = panelB.order;
1707
+ if (orderA == null && orderB == null) {
1708
+ return 0;
1709
+ } else if (orderA == null) {
1710
+ return -1;
1711
+ } else if (orderB == null) {
1712
+ return 1;
1713
+ } else {
1714
+ return orderA - orderB;
1715
+ }
1716
+ });
1717
+
1718
+ // Wait until all panels have registered before we try to compute layout;
1719
+ // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1720
+ const panelElements = getPanelElementsForGroup(groupId);
1721
+ if (panelElements.length !== panelDataArray.length) {
1722
+ return;
1723
+ }
1724
+
1725
+ // If this panel has been configured to persist sizing information,
1726
+ // default size should be restored from local storage if possible.
1727
+ let unsafeLayout = null;
1728
+ if (autoSaveId) {
1729
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1730
+ }
1731
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1732
+ if (groupSizePixels <= 0) {
1733
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1734
+ constraints
1735
+ }) => constraints))) {
1736
+ // Wait until the group has rendered a non-zero size before computing layout.
1737
+ return;
1738
+ }
1739
+ }
1740
+ if (unsafeLayout == null) {
1741
+ unsafeLayout = calculateUnsafeDefaultLayout({
1742
+ groupSizePixels,
1743
+ panelDataArray
1748
1744
  });
1745
+ }
1746
+
1747
+ // Validate even saved layouts in case something has changed since last render
1748
+ // e.g. for pixel groups, this could be the size of the window
1749
+ const nextLayout = validatePanelGroupLayout({
1750
+ groupSizePixels,
1751
+ layout: unsafeLayout,
1752
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1749
1753
  });
1754
+
1755
+ // Offscreen mode makes this a bit weird;
1756
+ // Panels unregister when hidden and re-register when shown again,
1757
+ // but the overall layout doesn't change between these two cases.
1758
+ setLayout(nextLayout);
1759
+ eagerValuesRef.current.layout = nextLayout;
1760
+ if (!areEqual(prevLayout, nextLayout)) {
1761
+ if (onLayout) {
1762
+ onLayout(nextLayout.map(sizePercentage => ({
1763
+ sizePercentage,
1764
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1765
+ })));
1766
+ }
1767
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1768
+ }
1750
1769
  }, []);
1751
1770
  const registerResizeHandle = useCallback(dragHandleId => {
1752
1771
  return function resizeHandler(event) {
@@ -1757,10 +1776,12 @@ function PanelGroupWithForwardedRef({
1757
1776
  id: groupId,
1758
1777
  keyboardResizeByPercentage,
1759
1778
  keyboardResizeByPixels,
1760
- onLayout,
1761
- panelDataArray,
1762
- layout: prevLayout
1779
+ onLayout
1763
1780
  } = committedValuesRef.current;
1781
+ const {
1782
+ layout: prevLayout,
1783
+ panelDataArray
1784
+ } = eagerValuesRef.current;
1764
1785
  const {
1765
1786
  initialLayout
1766
1787
  } = dragState !== null && dragState !== void 0 ? dragState : {};
@@ -1816,6 +1837,7 @@ function PanelGroupWithForwardedRef({
1816
1837
  }
1817
1838
  if (layoutChanged) {
1818
1839
  setLayout(nextLayout);
1840
+ eagerValuesRef.current.layout = nextLayout;
1819
1841
  if (onLayout) {
1820
1842
  onLayout(nextLayout.map(sizePercentage => ({
1821
1843
  sizePercentage,
@@ -1829,23 +1851,13 @@ function PanelGroupWithForwardedRef({
1829
1851
 
1830
1852
  // External APIs are safe to memoize via committed values ref
1831
1853
  const resizePanel = useCallback((panelData, mixedSizes) => {
1854
+ const {
1855
+ onLayout
1856
+ } = committedValuesRef.current;
1832
1857
  const {
1833
1858
  layout: prevLayout,
1834
- onLayout,
1835
1859
  panelDataArray
1836
- } = 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
- }
1860
+ } = eagerValuesRef.current;
1849
1861
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1850
1862
  const {
1851
1863
  groupSizePixels,
@@ -1865,6 +1877,7 @@ function PanelGroupWithForwardedRef({
1865
1877
  });
1866
1878
  if (!compareLayouts(prevLayout, nextLayout)) {
1867
1879
  setLayout(nextLayout);
1880
+ eagerValuesRef.current.layout = nextLayout;
1868
1881
  if (onLayout) {
1869
1882
  onLayout(nextLayout.map(sizePercentage => ({
1870
1883
  sizePercentage,
@@ -1876,9 +1889,11 @@ function PanelGroupWithForwardedRef({
1876
1889
  }, [groupId]);
1877
1890
  const startDragging = useCallback((dragHandleId, event) => {
1878
1891
  const {
1879
- direction,
1880
- layout
1892
+ direction
1881
1893
  } = committedValuesRef.current;
1894
+ const {
1895
+ layout
1896
+ } = eagerValuesRef.current;
1882
1897
  const handleElement = getResizeHandleElement(dragHandleId);
1883
1898
  const initialCursorPosition = getResizeEventCursorPosition(direction, event);
1884
1899
  setDragState({
@@ -1892,42 +1907,87 @@ function PanelGroupWithForwardedRef({
1892
1907
  resetGlobalCursorStyle();
1893
1908
  setDragState(null);
1894
1909
  }, []);
1910
+ const unregisterPanelRef = useRef({
1911
+ pendingPanelIds: new Set(),
1912
+ timeout: null
1913
+ });
1895
1914
  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);
1915
+ const {
1916
+ id: groupId,
1917
+ onLayout
1918
+ } = committedValuesRef.current;
1919
+ const {
1920
+ layout: prevLayout,
1921
+ panelDataArray
1922
+ } = eagerValuesRef.current;
1923
+ const index = panelDataArray.indexOf(panelData);
1924
+ if (index >= 0) {
1925
+ panelDataArray.splice(index, 1);
1926
+ unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1927
+ }
1928
+ if (unregisterPanelRef.current.timeout != null) {
1929
+ clearTimeout(unregisterPanelRef.current.timeout);
1930
+ }
1931
+
1932
+ // Batch panel unmounts so that we only calculate layout once;
1933
+ // This is more efficient and avoids misleading warnings in development mode.
1934
+ // We can't check the DOM to detect this because Panel elements have not yet been removed.
1935
+ unregisterPanelRef.current.timeout = setTimeout(() => {
1936
+ const {
1937
+ pendingPanelIds
1938
+ } = unregisterPanelRef.current;
1939
+ const map = panelIdToLastNotifiedMixedSizesMapRef.current;
1940
+
1941
+ // TRICKY
1942
+ // Strict effects mode
1943
+ let unmountDueToStrictMode = false;
1944
+ pendingPanelIds.forEach(panelId => {
1945
+ pendingPanelIds.delete(panelId);
1946
+ if (panelDataArray.find(({
1947
+ id
1948
+ }) => id === panelId) == null) {
1949
+ unmountDueToStrictMode = true;
1950
+
1951
+ // TRICKY
1952
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1953
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1954
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1955
+ delete map[panelData.id];
1956
+ }
1957
+ });
1958
+ if (!unmountDueToStrictMode) {
1959
+ return;
1902
1960
  }
1903
- return panelDataArray;
1904
- });
1905
- }, []);
1961
+ if (panelDataArray.length === 0) {
1962
+ // The group is unmounting; skip layout calculation.
1963
+ return;
1964
+ }
1965
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1966
+ let unsafeLayout = calculateUnsafeDefaultLayout({
1967
+ groupSizePixels,
1968
+ panelDataArray
1969
+ });
1906
1970
 
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
- }
1971
+ // Validate even saved layouts in case something has changed since last render
1972
+ // e.g. for pixel groups, this could be the size of the window
1973
+ const nextLayout = validatePanelGroupLayout({
1974
+ groupSizePixels,
1975
+ layout: unsafeLayout,
1976
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1977
+ });
1978
+ if (!areEqual(prevLayout, nextLayout)) {
1979
+ setLayout(nextLayout);
1980
+ eagerValuesRef.current.layout = nextLayout;
1981
+ if (onLayout) {
1982
+ onLayout(nextLayout.map(sizePercentage => ({
1983
+ sizePercentage,
1984
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1985
+ })));
1986
+ }
1987
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1928
1988
  }
1929
- }
1930
- }, [collapsePanel, expandPanel, imperativeApiQueue, layout, panelDataArray, resizePanel]);
1989
+ }, 0);
1990
+ }, []);
1931
1991
  const context = useMemo(() => ({
1932
1992
  collapsePanel,
1933
1993
  direction,