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.
@@ -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)
@@ -691,6 +693,7 @@ function getResizeHandlePanelIds(groupId, handleId, panelsArray) {
691
693
 
692
694
  function useWindowSplitterPanelGroupBehavior({
693
695
  committedValuesRef,
696
+ eagerValuesRef,
694
697
  groupId,
695
698
  layout,
696
699
  panelDataArray,
@@ -743,7 +746,7 @@ function useWindowSplitterPanelGroupBehavior({
743
746
  useEffect(() => {
744
747
  const {
745
748
  panelDataArray
746
- } = committedValuesRef.current;
749
+ } = eagerValuesRef.current;
747
750
  const groupElement = getPanelGroupElement(groupId);
748
751
  assert(groupElement != null, `No group found for id "${groupId}"`);
749
752
  const handles = getResizeHandleElementsForGroup(groupId);
@@ -801,7 +804,7 @@ function useWindowSplitterPanelGroupBehavior({
801
804
  return () => {
802
805
  cleanupFunctions.forEach(cleanupFunction => cleanupFunction());
803
806
  };
804
- }, [committedValuesRef, groupId, layout, panelDataArray, setLayout]);
807
+ }, [committedValuesRef, eagerValuesRef, groupId, layout, panelDataArray, setLayout]);
805
808
  }
806
809
 
807
810
  function areEqual(arrayA, arrayB) {
@@ -1086,6 +1089,10 @@ function debounce(callback, durationMs = 10) {
1086
1089
  return callable;
1087
1090
  }
1088
1091
 
1092
+ function getPanelElementsForGroup(groupId) {
1093
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
1094
+ }
1095
+
1089
1096
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
1090
1097
  // or on a browser with cookies/storage disabled.
1091
1098
  // In either case, this function avoids accessing localStorage until needed,
@@ -1312,7 +1319,7 @@ const defaultStorage = {
1312
1319
  };
1313
1320
  const debounceMap = {};
1314
1321
  function PanelGroupWithForwardedRef({
1315
- autoSaveId,
1322
+ autoSaveId = null,
1316
1323
  children,
1317
1324
  className: classNameFromProps = "",
1318
1325
  dataAttributes,
@@ -1329,20 +1336,22 @@ function PanelGroupWithForwardedRef({
1329
1336
  const groupId = useUniqueId(idFromProps);
1330
1337
  const [dragState, setDragState] = useState(null);
1331
1338
  const [layout, setLayout] = useState([]);
1332
- const [panelDataArray, setPanelDataArray] = useState([]);
1333
1339
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1334
1340
  const panelSizeBeforeCollapseRef = useRef(new Map());
1335
1341
  const prevDeltaRef = useRef(0);
1336
- const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1337
1342
  const committedValuesRef = useRef({
1343
+ autoSaveId,
1338
1344
  direction,
1339
1345
  dragState,
1340
1346
  id: groupId,
1341
1347
  keyboardResizeByPercentage,
1342
1348
  keyboardResizeByPixels,
1343
- layout,
1344
1349
  onLayout,
1345
- panelDataArray
1350
+ storage
1351
+ });
1352
+ const eagerValuesRef = useRef({
1353
+ layout,
1354
+ panelDataArray: []
1346
1355
  });
1347
1356
  const devWarningsRef = useRef({
1348
1357
  didLogIdAndOrderWarning: false,
@@ -1353,9 +1362,11 @@ function PanelGroupWithForwardedRef({
1353
1362
  getId: () => committedValuesRef.current.id,
1354
1363
  getLayout: () => {
1355
1364
  const {
1356
- id: groupId,
1357
- layout
1365
+ id: groupId
1358
1366
  } = committedValuesRef.current;
1367
+ const {
1368
+ layout
1369
+ } = eagerValuesRef.current;
1359
1370
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1360
1371
  return layout.map(sizePercentage => {
1361
1372
  return {
@@ -1367,10 +1378,12 @@ function PanelGroupWithForwardedRef({
1367
1378
  setLayout: mixedSizes => {
1368
1379
  const {
1369
1380
  id: groupId,
1381
+ onLayout
1382
+ } = committedValuesRef.current;
1383
+ const {
1370
1384
  layout: prevLayout,
1371
- onLayout,
1372
1385
  panelDataArray
1373
- } = committedValuesRef.current;
1386
+ } = eagerValuesRef.current;
1374
1387
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1375
1388
  const unsafeLayout = mixedSizes.map(mixedSize => getPercentageSizeFromMixedSizes(mixedSize, groupSizePixels));
1376
1389
  const safeLayout = validatePanelGroupLayout({
@@ -1380,6 +1393,7 @@ function PanelGroupWithForwardedRef({
1380
1393
  });
1381
1394
  if (!areEqual(prevLayout, safeLayout)) {
1382
1395
  setLayout(safeLayout);
1396
+ eagerValuesRef.current.layout = safeLayout;
1383
1397
  if (onLayout) {
1384
1398
  onLayout(safeLayout.map(sizePercentage => ({
1385
1399
  sizePercentage,
@@ -1391,21 +1405,30 @@ function PanelGroupWithForwardedRef({
1391
1405
  }
1392
1406
  }), []);
1393
1407
  useIsomorphicLayoutEffect(() => {
1408
+ committedValuesRef.current.autoSaveId = autoSaveId;
1394
1409
  committedValuesRef.current.direction = direction;
1395
1410
  committedValuesRef.current.dragState = dragState;
1396
1411
  committedValuesRef.current.id = groupId;
1397
- committedValuesRef.current.layout = layout;
1398
1412
  committedValuesRef.current.onLayout = onLayout;
1399
- committedValuesRef.current.panelDataArray = panelDataArray;
1413
+ committedValuesRef.current.storage = storage;
1414
+
1415
+ // panelDataArray and layout are updated in-sync with scheduled state updates.
1416
+ // TODO [217] Move these values into a separate ref
1400
1417
  });
1418
+
1401
1419
  useWindowSplitterPanelGroupBehavior({
1402
1420
  committedValuesRef,
1421
+ eagerValuesRef,
1403
1422
  groupId,
1404
1423
  layout,
1405
- panelDataArray,
1424
+ panelDataArray: eagerValuesRef.current.panelDataArray,
1406
1425
  setLayout
1407
1426
  });
1408
1427
  useEffect(() => {
1428
+ const {
1429
+ panelDataArray
1430
+ } = eagerValuesRef.current;
1431
+
1409
1432
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1410
1433
  if (autoSaveId) {
1411
1434
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1418,63 +1441,12 @@ function PanelGroupWithForwardedRef({
1418
1441
  }
1419
1442
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1420
1443
  }
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)!
1444
+ }, [autoSaveId, layout, storage]);
1426
1445
  useIsomorphicLayoutEffect(() => {
1427
1446
  const {
1428
- id: groupId,
1429
- layout,
1430
- onLayout
1431
- } = 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(() => {
1447
+ layout: prevLayout,
1448
+ panelDataArray
1449
+ } = eagerValuesRef.current;
1478
1450
  const constraints = panelDataArray.map(({
1479
1451
  constraints
1480
1452
  }) => constraints);
@@ -1488,7 +1460,6 @@ function PanelGroupWithForwardedRef({
1488
1460
  const resizeObserver = new ResizeObserver(() => {
1489
1461
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1490
1462
  const {
1491
- layout: prevLayout,
1492
1463
  onLayout
1493
1464
  } = committedValuesRef.current;
1494
1465
  const nextLayout = validatePanelGroupLayout({
@@ -1498,6 +1469,7 @@ function PanelGroupWithForwardedRef({
1498
1469
  });
1499
1470
  if (!areEqual(prevLayout, nextLayout)) {
1500
1471
  setLayout(nextLayout);
1472
+ eagerValuesRef.current.layout = nextLayout;
1501
1473
  if (onLayout) {
1502
1474
  onLayout(nextLayout.map(sizePercentage => ({
1503
1475
  sizePercentage,
@@ -1512,20 +1484,20 @@ function PanelGroupWithForwardedRef({
1512
1484
  resizeObserver.disconnect();
1513
1485
  };
1514
1486
  }
1515
- }, [groupId, panelDataArray]);
1487
+ }, [groupId]);
1516
1488
 
1517
1489
  // DEV warnings
1518
1490
  useEffect(() => {
1519
1491
  {
1492
+ const {
1493
+ panelDataArray
1494
+ } = eagerValuesRef.current;
1520
1495
  const {
1521
1496
  didLogIdAndOrderWarning,
1522
1497
  didLogPanelConstraintsWarning,
1523
1498
  prevPanelIds
1524
1499
  } = devWarningsRef.current;
1525
1500
  if (!didLogIdAndOrderWarning) {
1526
- const {
1527
- panelDataArray
1528
- } = committedValuesRef.current;
1529
1501
  const panelIds = panelDataArray.map(({
1530
1502
  id
1531
1503
  }) => id);
@@ -1562,22 +1534,13 @@ function PanelGroupWithForwardedRef({
1562
1534
 
1563
1535
  // External APIs are safe to memoize via committed values ref
1564
1536
  const collapsePanel = useCallback(panelData => {
1537
+ const {
1538
+ onLayout
1539
+ } = committedValuesRef.current;
1565
1540
  const {
1566
1541
  layout: prevLayout,
1567
- onLayout,
1568
1542
  panelDataArray
1569
- } = 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
- }
1543
+ } = eagerValuesRef.current;
1581
1544
  if (panelData.constraints.collapsible) {
1582
1545
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1583
1546
  const {
@@ -1602,6 +1565,7 @@ function PanelGroupWithForwardedRef({
1602
1565
  });
1603
1566
  if (!compareLayouts(prevLayout, nextLayout)) {
1604
1567
  setLayout(nextLayout);
1568
+ eagerValuesRef.current.layout = nextLayout;
1605
1569
  if (onLayout) {
1606
1570
  onLayout(nextLayout.map(sizePercentage => ({
1607
1571
  sizePercentage,
@@ -1616,22 +1580,13 @@ function PanelGroupWithForwardedRef({
1616
1580
 
1617
1581
  // External APIs are safe to memoize via committed values ref
1618
1582
  const expandPanel = useCallback(panelData => {
1583
+ const {
1584
+ onLayout
1585
+ } = committedValuesRef.current;
1619
1586
  const {
1620
1587
  layout: prevLayout,
1621
- onLayout,
1622
1588
  panelDataArray
1623
- } = 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
- }
1589
+ } = eagerValuesRef.current;
1635
1590
  if (panelData.constraints.collapsible) {
1636
1591
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1637
1592
  const {
@@ -1657,6 +1612,7 @@ function PanelGroupWithForwardedRef({
1657
1612
  });
1658
1613
  if (!compareLayouts(prevLayout, nextLayout)) {
1659
1614
  setLayout(nextLayout);
1615
+ eagerValuesRef.current.layout = nextLayout;
1660
1616
  if (onLayout) {
1661
1617
  onLayout(nextLayout.map(sizePercentage => ({
1662
1618
  sizePercentage,
@@ -1674,7 +1630,7 @@ function PanelGroupWithForwardedRef({
1674
1630
  const {
1675
1631
  layout,
1676
1632
  panelDataArray
1677
- } = committedValuesRef.current;
1633
+ } = eagerValuesRef.current;
1678
1634
  const {
1679
1635
  panelSizePercentage,
1680
1636
  panelSizePixels
@@ -1687,6 +1643,9 @@ function PanelGroupWithForwardedRef({
1687
1643
 
1688
1644
  // This API should never read from committedValuesRef
1689
1645
  const getPanelStyle = useCallback(panelData => {
1646
+ const {
1647
+ panelDataArray
1648
+ } = eagerValuesRef.current;
1690
1649
  const panelIndex = panelDataArray.indexOf(panelData);
1691
1650
  return computePanelFlexBoxStyle({
1692
1651
  dragState,
@@ -1694,14 +1653,14 @@ function PanelGroupWithForwardedRef({
1694
1653
  panelData: panelDataArray,
1695
1654
  panelIndex
1696
1655
  });
1697
- }, [dragState, layout, panelDataArray]);
1656
+ }, [dragState, layout]);
1698
1657
 
1699
1658
  // External APIs are safe to memoize via committed values ref
1700
1659
  const isPanelCollapsed = useCallback(panelData => {
1701
1660
  const {
1702
1661
  layout,
1703
1662
  panelDataArray
1704
- } = committedValuesRef.current;
1663
+ } = eagerValuesRef.current;
1705
1664
  const {
1706
1665
  collapsedSizePercentage,
1707
1666
  collapsible,
@@ -1715,7 +1674,7 @@ function PanelGroupWithForwardedRef({
1715
1674
  const {
1716
1675
  layout,
1717
1676
  panelDataArray
1718
- } = committedValuesRef.current;
1677
+ } = eagerValuesRef.current;
1719
1678
  const {
1720
1679
  collapsedSizePercentage,
1721
1680
  collapsible,
@@ -1724,22 +1683,82 @@ function PanelGroupWithForwardedRef({
1724
1683
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1725
1684
  }, [groupId]);
1726
1685
  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
- }
1686
+ const {
1687
+ autoSaveId,
1688
+ id: groupId,
1689
+ onLayout,
1690
+ storage
1691
+ } = committedValuesRef.current;
1692
+ const {
1693
+ layout: prevLayout,
1694
+ panelDataArray
1695
+ } = eagerValuesRef.current;
1696
+ panelDataArray.push(panelData);
1697
+ panelDataArray.sort((panelA, panelB) => {
1698
+ const orderA = panelA.order;
1699
+ const orderB = panelB.order;
1700
+ if (orderA == null && orderB == null) {
1701
+ return 0;
1702
+ } else if (orderA == null) {
1703
+ return -1;
1704
+ } else if (orderB == null) {
1705
+ return 1;
1706
+ } else {
1707
+ return orderA - orderB;
1708
+ }
1709
+ });
1710
+
1711
+ // Wait until all panels have registered before we try to compute layout;
1712
+ // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1713
+ const panelElements = getPanelElementsForGroup(groupId);
1714
+ if (panelElements.length !== panelDataArray.length) {
1715
+ return;
1716
+ }
1717
+
1718
+ // If this panel has been configured to persist sizing information,
1719
+ // default size should be restored from local storage if possible.
1720
+ let unsafeLayout = null;
1721
+ if (autoSaveId) {
1722
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1723
+ }
1724
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1725
+ if (groupSizePixels <= 0) {
1726
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1727
+ constraints
1728
+ }) => constraints))) {
1729
+ // Wait until the group has rendered a non-zero size before computing layout.
1730
+ return;
1731
+ }
1732
+ }
1733
+ if (unsafeLayout == null) {
1734
+ unsafeLayout = calculateUnsafeDefaultLayout({
1735
+ groupSizePixels,
1736
+ panelDataArray
1741
1737
  });
1738
+ }
1739
+
1740
+ // Validate even saved layouts in case something has changed since last render
1741
+ // e.g. for pixel groups, this could be the size of the window
1742
+ const nextLayout = validatePanelGroupLayout({
1743
+ groupSizePixels,
1744
+ layout: unsafeLayout,
1745
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1742
1746
  });
1747
+
1748
+ // Offscreen mode makes this a bit weird;
1749
+ // Panels unregister when hidden and re-register when shown again,
1750
+ // but the overall layout doesn't change between these two cases.
1751
+ setLayout(nextLayout);
1752
+ eagerValuesRef.current.layout = nextLayout;
1753
+ if (!areEqual(prevLayout, nextLayout)) {
1754
+ if (onLayout) {
1755
+ onLayout(nextLayout.map(sizePercentage => ({
1756
+ sizePercentage,
1757
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1758
+ })));
1759
+ }
1760
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1761
+ }
1743
1762
  }, []);
1744
1763
  const registerResizeHandle = useCallback(dragHandleId => {
1745
1764
  return function resizeHandler(event) {
@@ -1750,10 +1769,12 @@ function PanelGroupWithForwardedRef({
1750
1769
  id: groupId,
1751
1770
  keyboardResizeByPercentage,
1752
1771
  keyboardResizeByPixels,
1753
- onLayout,
1754
- panelDataArray,
1755
- layout: prevLayout
1772
+ onLayout
1756
1773
  } = committedValuesRef.current;
1774
+ const {
1775
+ layout: prevLayout,
1776
+ panelDataArray
1777
+ } = eagerValuesRef.current;
1757
1778
  const {
1758
1779
  initialLayout
1759
1780
  } = dragState !== null && dragState !== void 0 ? dragState : {};
@@ -1809,6 +1830,7 @@ function PanelGroupWithForwardedRef({
1809
1830
  }
1810
1831
  if (layoutChanged) {
1811
1832
  setLayout(nextLayout);
1833
+ eagerValuesRef.current.layout = nextLayout;
1812
1834
  if (onLayout) {
1813
1835
  onLayout(nextLayout.map(sizePercentage => ({
1814
1836
  sizePercentage,
@@ -1822,23 +1844,13 @@ function PanelGroupWithForwardedRef({
1822
1844
 
1823
1845
  // External APIs are safe to memoize via committed values ref
1824
1846
  const resizePanel = useCallback((panelData, mixedSizes) => {
1847
+ const {
1848
+ onLayout
1849
+ } = committedValuesRef.current;
1825
1850
  const {
1826
1851
  layout: prevLayout,
1827
- onLayout,
1828
1852
  panelDataArray
1829
- } = 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
- }
1853
+ } = eagerValuesRef.current;
1842
1854
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1843
1855
  const {
1844
1856
  groupSizePixels,
@@ -1858,6 +1870,7 @@ function PanelGroupWithForwardedRef({
1858
1870
  });
1859
1871
  if (!compareLayouts(prevLayout, nextLayout)) {
1860
1872
  setLayout(nextLayout);
1873
+ eagerValuesRef.current.layout = nextLayout;
1861
1874
  if (onLayout) {
1862
1875
  onLayout(nextLayout.map(sizePercentage => ({
1863
1876
  sizePercentage,
@@ -1869,9 +1882,11 @@ function PanelGroupWithForwardedRef({
1869
1882
  }, [groupId]);
1870
1883
  const startDragging = useCallback((dragHandleId, event) => {
1871
1884
  const {
1872
- direction,
1873
- layout
1885
+ direction
1874
1886
  } = committedValuesRef.current;
1887
+ const {
1888
+ layout
1889
+ } = eagerValuesRef.current;
1875
1890
  const handleElement = getResizeHandleElement(dragHandleId);
1876
1891
  const initialCursorPosition = getResizeEventCursorPosition(direction, event);
1877
1892
  setDragState({
@@ -1885,42 +1900,87 @@ function PanelGroupWithForwardedRef({
1885
1900
  resetGlobalCursorStyle();
1886
1901
  setDragState(null);
1887
1902
  }, []);
1903
+ const unregisterPanelRef = useRef({
1904
+ pendingPanelIds: new Set(),
1905
+ timeout: null
1906
+ });
1888
1907
  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);
1908
+ const {
1909
+ id: groupId,
1910
+ onLayout
1911
+ } = committedValuesRef.current;
1912
+ const {
1913
+ layout: prevLayout,
1914
+ panelDataArray
1915
+ } = eagerValuesRef.current;
1916
+ const index = panelDataArray.indexOf(panelData);
1917
+ if (index >= 0) {
1918
+ panelDataArray.splice(index, 1);
1919
+ unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1920
+ }
1921
+ if (unregisterPanelRef.current.timeout != null) {
1922
+ clearTimeout(unregisterPanelRef.current.timeout);
1923
+ }
1924
+
1925
+ // Batch panel unmounts so that we only calculate layout once;
1926
+ // This is more efficient and avoids misleading warnings in development mode.
1927
+ // We can't check the DOM to detect this because Panel elements have not yet been removed.
1928
+ unregisterPanelRef.current.timeout = setTimeout(() => {
1929
+ const {
1930
+ pendingPanelIds
1931
+ } = unregisterPanelRef.current;
1932
+ const map = panelIdToLastNotifiedMixedSizesMapRef.current;
1933
+
1934
+ // TRICKY
1935
+ // Strict effects mode
1936
+ let unmountDueToStrictMode = false;
1937
+ pendingPanelIds.forEach(panelId => {
1938
+ pendingPanelIds.delete(panelId);
1939
+ if (panelDataArray.find(({
1940
+ id
1941
+ }) => id === panelId) == null) {
1942
+ unmountDueToStrictMode = true;
1943
+
1944
+ // TRICKY
1945
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1946
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1947
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1948
+ delete map[panelData.id];
1949
+ }
1950
+ });
1951
+ if (!unmountDueToStrictMode) {
1952
+ return;
1895
1953
  }
1896
- return panelDataArray;
1897
- });
1898
- }, []);
1954
+ if (panelDataArray.length === 0) {
1955
+ // The group is unmounting; skip layout calculation.
1956
+ return;
1957
+ }
1958
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1959
+ let unsafeLayout = calculateUnsafeDefaultLayout({
1960
+ groupSizePixels,
1961
+ panelDataArray
1962
+ });
1899
1963
 
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
- }
1964
+ // Validate even saved layouts in case something has changed since last render
1965
+ // e.g. for pixel groups, this could be the size of the window
1966
+ const nextLayout = validatePanelGroupLayout({
1967
+ groupSizePixels,
1968
+ layout: unsafeLayout,
1969
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1970
+ });
1971
+ if (!areEqual(prevLayout, nextLayout)) {
1972
+ setLayout(nextLayout);
1973
+ eagerValuesRef.current.layout = nextLayout;
1974
+ if (onLayout) {
1975
+ onLayout(nextLayout.map(sizePercentage => ({
1976
+ sizePercentage,
1977
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1978
+ })));
1979
+ }
1980
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1921
1981
  }
1922
- }
1923
- }, [collapsePanel, expandPanel, imperativeApiQueue, layout, panelDataArray, resizePanel]);
1982
+ }, 0);
1983
+ }, []);
1924
1984
  const context = useMemo(() => ({
1925
1985
  collapsePanel,
1926
1986
  direction,