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,
@@ -188,6 +189,7 @@ function PanelWithForwardedRef({
188
189
  // CSS selectors
189
190
  "data-panel": "",
190
191
  "data-panel-id": panelId,
192
+ "data-panel-group-id": groupId,
191
193
  // e2e test attributes
192
194
  "data-panel-collapsible": undefined,
193
195
  "data-panel-size": undefined
@@ -711,6 +713,7 @@ function getResizeHandlePanelIds(groupId, handleId, panelsArray) {
711
713
 
712
714
  function useWindowSplitterPanelGroupBehavior({
713
715
  committedValuesRef,
716
+ eagerValuesRef,
714
717
  groupId,
715
718
  layout,
716
719
  panelDataArray,
@@ -753,7 +756,7 @@ function useWindowSplitterPanelGroupBehavior({
753
756
  useEffect(() => {
754
757
  const {
755
758
  panelDataArray
756
- } = committedValuesRef.current;
759
+ } = eagerValuesRef.current;
757
760
  const groupElement = getPanelGroupElement(groupId);
758
761
  assert(groupElement != null, `No group found for id "${groupId}"`);
759
762
  const handles = getResizeHandleElementsForGroup(groupId);
@@ -811,7 +814,7 @@ function useWindowSplitterPanelGroupBehavior({
811
814
  return () => {
812
815
  cleanupFunctions.forEach(cleanupFunction => cleanupFunction());
813
816
  };
814
- }, [committedValuesRef, groupId, layout, panelDataArray, setLayout]);
817
+ }, [committedValuesRef, eagerValuesRef, groupId, layout, panelDataArray, setLayout]);
815
818
  }
816
819
 
817
820
  function areEqual(arrayA, arrayB) {
@@ -1096,6 +1099,10 @@ function debounce(callback, durationMs = 10) {
1096
1099
  return callable;
1097
1100
  }
1098
1101
 
1102
+ function getPanelElementsForGroup(groupId) {
1103
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
1104
+ }
1105
+
1099
1106
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
1100
1107
  // or on a browser with cookies/storage disabled.
1101
1108
  // In either case, this function avoids accessing localStorage until needed,
@@ -1245,7 +1252,7 @@ const defaultStorage = {
1245
1252
  };
1246
1253
  const debounceMap = {};
1247
1254
  function PanelGroupWithForwardedRef({
1248
- autoSaveId,
1255
+ autoSaveId = null,
1249
1256
  children,
1250
1257
  className: classNameFromProps = "",
1251
1258
  dataAttributes,
@@ -1262,20 +1269,22 @@ function PanelGroupWithForwardedRef({
1262
1269
  const groupId = useUniqueId(idFromProps);
1263
1270
  const [dragState, setDragState] = useState(null);
1264
1271
  const [layout, setLayout] = useState([]);
1265
- const [panelDataArray, setPanelDataArray] = useState([]);
1266
1272
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1267
1273
  const panelSizeBeforeCollapseRef = useRef(new Map());
1268
1274
  const prevDeltaRef = useRef(0);
1269
- const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1270
1275
  const committedValuesRef = useRef({
1276
+ autoSaveId,
1271
1277
  direction,
1272
1278
  dragState,
1273
1279
  id: groupId,
1274
1280
  keyboardResizeByPercentage,
1275
1281
  keyboardResizeByPixels,
1276
- layout,
1277
1282
  onLayout,
1278
- panelDataArray
1283
+ storage
1284
+ });
1285
+ const eagerValuesRef = useRef({
1286
+ layout,
1287
+ panelDataArray: []
1279
1288
  });
1280
1289
  useRef({
1281
1290
  didLogIdAndOrderWarning: false,
@@ -1286,9 +1295,11 @@ function PanelGroupWithForwardedRef({
1286
1295
  getId: () => committedValuesRef.current.id,
1287
1296
  getLayout: () => {
1288
1297
  const {
1289
- id: groupId,
1290
- layout
1298
+ id: groupId
1291
1299
  } = committedValuesRef.current;
1300
+ const {
1301
+ layout
1302
+ } = eagerValuesRef.current;
1292
1303
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1293
1304
  return layout.map(sizePercentage => {
1294
1305
  return {
@@ -1300,10 +1311,12 @@ function PanelGroupWithForwardedRef({
1300
1311
  setLayout: mixedSizes => {
1301
1312
  const {
1302
1313
  id: groupId,
1314
+ onLayout
1315
+ } = committedValuesRef.current;
1316
+ const {
1303
1317
  layout: prevLayout,
1304
- onLayout,
1305
1318
  panelDataArray
1306
- } = committedValuesRef.current;
1319
+ } = eagerValuesRef.current;
1307
1320
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1308
1321
  const unsafeLayout = mixedSizes.map(mixedSize => getPercentageSizeFromMixedSizes(mixedSize, groupSizePixels));
1309
1322
  const safeLayout = validatePanelGroupLayout({
@@ -1313,6 +1326,7 @@ function PanelGroupWithForwardedRef({
1313
1326
  });
1314
1327
  if (!areEqual(prevLayout, safeLayout)) {
1315
1328
  setLayout(safeLayout);
1329
+ eagerValuesRef.current.layout = safeLayout;
1316
1330
  if (onLayout) {
1317
1331
  onLayout(safeLayout.map(sizePercentage => ({
1318
1332
  sizePercentage,
@@ -1324,21 +1338,30 @@ function PanelGroupWithForwardedRef({
1324
1338
  }
1325
1339
  }), []);
1326
1340
  useIsomorphicLayoutEffect(() => {
1341
+ committedValuesRef.current.autoSaveId = autoSaveId;
1327
1342
  committedValuesRef.current.direction = direction;
1328
1343
  committedValuesRef.current.dragState = dragState;
1329
1344
  committedValuesRef.current.id = groupId;
1330
- committedValuesRef.current.layout = layout;
1331
1345
  committedValuesRef.current.onLayout = onLayout;
1332
- committedValuesRef.current.panelDataArray = panelDataArray;
1346
+ committedValuesRef.current.storage = storage;
1347
+
1348
+ // panelDataArray and layout are updated in-sync with scheduled state updates.
1349
+ // TODO [217] Move these values into a separate ref
1333
1350
  });
1351
+
1334
1352
  useWindowSplitterPanelGroupBehavior({
1335
1353
  committedValuesRef,
1354
+ eagerValuesRef,
1336
1355
  groupId,
1337
1356
  layout,
1338
- panelDataArray,
1357
+ panelDataArray: eagerValuesRef.current.panelDataArray,
1339
1358
  setLayout
1340
1359
  });
1341
1360
  useEffect(() => {
1361
+ const {
1362
+ panelDataArray
1363
+ } = eagerValuesRef.current;
1364
+
1342
1365
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1343
1366
  if (autoSaveId) {
1344
1367
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1351,63 +1374,12 @@ function PanelGroupWithForwardedRef({
1351
1374
  }
1352
1375
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1353
1376
  }
1354
- }, [autoSaveId, layout, panelDataArray, storage]);
1355
-
1356
- // Once all panels have registered themselves,
1357
- // Compute the initial sizes based on default weights.
1358
- // This assumes that panels register during initial mount (no conditional rendering)!
1377
+ }, [autoSaveId, layout, storage]);
1359
1378
  useIsomorphicLayoutEffect(() => {
1360
1379
  const {
1361
- id: groupId,
1362
- layout,
1363
- onLayout
1364
- } = committedValuesRef.current;
1365
- if (layout.length === panelDataArray.length) {
1366
- // Only compute (or restore) default layout once per panel configuration.
1367
- return;
1368
- }
1369
-
1370
- // If this panel has been configured to persist sizing information,
1371
- // default size should be restored from local storage if possible.
1372
- let unsafeLayout = null;
1373
- if (autoSaveId) {
1374
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1375
- }
1376
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1377
- if (groupSizePixels <= 0) {
1378
- if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1379
- constraints
1380
- }) => constraints))) {
1381
- // Wait until the group has rendered a non-zero size before computing layout.
1382
- return;
1383
- }
1384
- }
1385
- if (unsafeLayout == null) {
1386
- unsafeLayout = calculateUnsafeDefaultLayout({
1387
- groupSizePixels,
1388
- panelDataArray
1389
- });
1390
- }
1391
-
1392
- // Validate even saved layouts in case something has changed since last render
1393
- // e.g. for pixel groups, this could be the size of the window
1394
- const validatedLayout = validatePanelGroupLayout({
1395
- groupSizePixels,
1396
- layout: unsafeLayout,
1397
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1398
- });
1399
- if (!areEqual(layout, validatedLayout)) {
1400
- setLayout(validatedLayout);
1401
- }
1402
- if (onLayout) {
1403
- onLayout(validatedLayout.map(sizePercentage => ({
1404
- sizePercentage,
1405
- sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1406
- })));
1407
- }
1408
- callPanelCallbacks(groupId, panelDataArray, validatedLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1409
- }, [autoSaveId, layout, panelDataArray, storage]);
1410
- useIsomorphicLayoutEffect(() => {
1380
+ layout: prevLayout,
1381
+ panelDataArray
1382
+ } = eagerValuesRef.current;
1411
1383
  const constraints = panelDataArray.map(({
1412
1384
  constraints
1413
1385
  }) => constraints);
@@ -1421,7 +1393,6 @@ function PanelGroupWithForwardedRef({
1421
1393
  const resizeObserver = new ResizeObserver(() => {
1422
1394
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1423
1395
  const {
1424
- layout: prevLayout,
1425
1396
  onLayout
1426
1397
  } = committedValuesRef.current;
1427
1398
  const nextLayout = validatePanelGroupLayout({
@@ -1431,6 +1402,7 @@ function PanelGroupWithForwardedRef({
1431
1402
  });
1432
1403
  if (!areEqual(prevLayout, nextLayout)) {
1433
1404
  setLayout(nextLayout);
1405
+ eagerValuesRef.current.layout = nextLayout;
1434
1406
  if (onLayout) {
1435
1407
  onLayout(nextLayout.map(sizePercentage => ({
1436
1408
  sizePercentage,
@@ -1445,7 +1417,7 @@ function PanelGroupWithForwardedRef({
1445
1417
  resizeObserver.disconnect();
1446
1418
  };
1447
1419
  }
1448
- }, [groupId, panelDataArray]);
1420
+ }, [groupId]);
1449
1421
 
1450
1422
  // DEV warnings
1451
1423
  useEffect(() => {
@@ -1453,22 +1425,13 @@ function PanelGroupWithForwardedRef({
1453
1425
 
1454
1426
  // External APIs are safe to memoize via committed values ref
1455
1427
  const collapsePanel = useCallback(panelData => {
1428
+ const {
1429
+ onLayout
1430
+ } = committedValuesRef.current;
1456
1431
  const {
1457
1432
  layout: prevLayout,
1458
- onLayout,
1459
1433
  panelDataArray
1460
- } = committedValuesRef.current;
1461
-
1462
- // See issues/211
1463
- if (panelDataArray.find(({
1464
- id
1465
- }) => id === panelData.id) == null) {
1466
- setImperativeApiQueue(prev => [...prev, {
1467
- panelData,
1468
- type: "collapse"
1469
- }]);
1470
- return;
1471
- }
1434
+ } = eagerValuesRef.current;
1472
1435
  if (panelData.constraints.collapsible) {
1473
1436
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1474
1437
  const {
@@ -1493,6 +1456,7 @@ function PanelGroupWithForwardedRef({
1493
1456
  });
1494
1457
  if (!compareLayouts(prevLayout, nextLayout)) {
1495
1458
  setLayout(nextLayout);
1459
+ eagerValuesRef.current.layout = nextLayout;
1496
1460
  if (onLayout) {
1497
1461
  onLayout(nextLayout.map(sizePercentage => ({
1498
1462
  sizePercentage,
@@ -1507,22 +1471,13 @@ function PanelGroupWithForwardedRef({
1507
1471
 
1508
1472
  // External APIs are safe to memoize via committed values ref
1509
1473
  const expandPanel = useCallback(panelData => {
1474
+ const {
1475
+ onLayout
1476
+ } = committedValuesRef.current;
1510
1477
  const {
1511
1478
  layout: prevLayout,
1512
- onLayout,
1513
1479
  panelDataArray
1514
- } = committedValuesRef.current;
1515
-
1516
- // See issues/211
1517
- if (panelDataArray.find(({
1518
- id
1519
- }) => id === panelData.id) == null) {
1520
- setImperativeApiQueue(prev => [...prev, {
1521
- panelData,
1522
- type: "expand"
1523
- }]);
1524
- return;
1525
- }
1480
+ } = eagerValuesRef.current;
1526
1481
  if (panelData.constraints.collapsible) {
1527
1482
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1528
1483
  const {
@@ -1548,6 +1503,7 @@ function PanelGroupWithForwardedRef({
1548
1503
  });
1549
1504
  if (!compareLayouts(prevLayout, nextLayout)) {
1550
1505
  setLayout(nextLayout);
1506
+ eagerValuesRef.current.layout = nextLayout;
1551
1507
  if (onLayout) {
1552
1508
  onLayout(nextLayout.map(sizePercentage => ({
1553
1509
  sizePercentage,
@@ -1565,7 +1521,7 @@ function PanelGroupWithForwardedRef({
1565
1521
  const {
1566
1522
  layout,
1567
1523
  panelDataArray
1568
- } = committedValuesRef.current;
1524
+ } = eagerValuesRef.current;
1569
1525
  const {
1570
1526
  panelSizePercentage,
1571
1527
  panelSizePixels
@@ -1578,6 +1534,9 @@ function PanelGroupWithForwardedRef({
1578
1534
 
1579
1535
  // This API should never read from committedValuesRef
1580
1536
  const getPanelStyle = useCallback(panelData => {
1537
+ const {
1538
+ panelDataArray
1539
+ } = eagerValuesRef.current;
1581
1540
  const panelIndex = panelDataArray.indexOf(panelData);
1582
1541
  return computePanelFlexBoxStyle({
1583
1542
  dragState,
@@ -1585,14 +1544,14 @@ function PanelGroupWithForwardedRef({
1585
1544
  panelData: panelDataArray,
1586
1545
  panelIndex
1587
1546
  });
1588
- }, [dragState, layout, panelDataArray]);
1547
+ }, [dragState, layout]);
1589
1548
 
1590
1549
  // External APIs are safe to memoize via committed values ref
1591
1550
  const isPanelCollapsed = useCallback(panelData => {
1592
1551
  const {
1593
1552
  layout,
1594
1553
  panelDataArray
1595
- } = committedValuesRef.current;
1554
+ } = eagerValuesRef.current;
1596
1555
  const {
1597
1556
  collapsedSizePercentage,
1598
1557
  collapsible,
@@ -1606,7 +1565,7 @@ function PanelGroupWithForwardedRef({
1606
1565
  const {
1607
1566
  layout,
1608
1567
  panelDataArray
1609
- } = committedValuesRef.current;
1568
+ } = eagerValuesRef.current;
1610
1569
  const {
1611
1570
  collapsedSizePercentage,
1612
1571
  collapsible,
@@ -1615,22 +1574,82 @@ function PanelGroupWithForwardedRef({
1615
1574
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1616
1575
  }, [groupId]);
1617
1576
  const registerPanel = useCallback(panelData => {
1618
- setPanelDataArray(prevPanelDataArray => {
1619
- const nextPanelDataArray = [...prevPanelDataArray, panelData];
1620
- return nextPanelDataArray.sort((panelA, panelB) => {
1621
- const orderA = panelA.order;
1622
- const orderB = panelB.order;
1623
- if (orderA == null && orderB == null) {
1624
- return 0;
1625
- } else if (orderA == null) {
1626
- return -1;
1627
- } else if (orderB == null) {
1628
- return 1;
1629
- } else {
1630
- return orderA - orderB;
1631
- }
1577
+ const {
1578
+ autoSaveId,
1579
+ id: groupId,
1580
+ onLayout,
1581
+ storage
1582
+ } = committedValuesRef.current;
1583
+ const {
1584
+ layout: prevLayout,
1585
+ panelDataArray
1586
+ } = eagerValuesRef.current;
1587
+ panelDataArray.push(panelData);
1588
+ panelDataArray.sort((panelA, panelB) => {
1589
+ const orderA = panelA.order;
1590
+ const orderB = panelB.order;
1591
+ if (orderA == null && orderB == null) {
1592
+ return 0;
1593
+ } else if (orderA == null) {
1594
+ return -1;
1595
+ } else if (orderB == null) {
1596
+ return 1;
1597
+ } else {
1598
+ return orderA - orderB;
1599
+ }
1600
+ });
1601
+
1602
+ // Wait until all panels have registered before we try to compute layout;
1603
+ // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1604
+ const panelElements = getPanelElementsForGroup(groupId);
1605
+ if (panelElements.length !== panelDataArray.length) {
1606
+ return;
1607
+ }
1608
+
1609
+ // If this panel has been configured to persist sizing information,
1610
+ // default size should be restored from local storage if possible.
1611
+ let unsafeLayout = null;
1612
+ if (autoSaveId) {
1613
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1614
+ }
1615
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1616
+ if (groupSizePixels <= 0) {
1617
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1618
+ constraints
1619
+ }) => constraints))) {
1620
+ // Wait until the group has rendered a non-zero size before computing layout.
1621
+ return;
1622
+ }
1623
+ }
1624
+ if (unsafeLayout == null) {
1625
+ unsafeLayout = calculateUnsafeDefaultLayout({
1626
+ groupSizePixels,
1627
+ panelDataArray
1632
1628
  });
1629
+ }
1630
+
1631
+ // Validate even saved layouts in case something has changed since last render
1632
+ // e.g. for pixel groups, this could be the size of the window
1633
+ const nextLayout = validatePanelGroupLayout({
1634
+ groupSizePixels,
1635
+ layout: unsafeLayout,
1636
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1633
1637
  });
1638
+
1639
+ // Offscreen mode makes this a bit weird;
1640
+ // Panels unregister when hidden and re-register when shown again,
1641
+ // but the overall layout doesn't change between these two cases.
1642
+ setLayout(nextLayout);
1643
+ eagerValuesRef.current.layout = nextLayout;
1644
+ if (!areEqual(prevLayout, nextLayout)) {
1645
+ if (onLayout) {
1646
+ onLayout(nextLayout.map(sizePercentage => ({
1647
+ sizePercentage,
1648
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1649
+ })));
1650
+ }
1651
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1652
+ }
1634
1653
  }, []);
1635
1654
  const registerResizeHandle = useCallback(dragHandleId => {
1636
1655
  return function resizeHandler(event) {
@@ -1641,10 +1660,12 @@ function PanelGroupWithForwardedRef({
1641
1660
  id: groupId,
1642
1661
  keyboardResizeByPercentage,
1643
1662
  keyboardResizeByPixels,
1644
- onLayout,
1645
- panelDataArray,
1646
- layout: prevLayout
1663
+ onLayout
1647
1664
  } = committedValuesRef.current;
1665
+ const {
1666
+ layout: prevLayout,
1667
+ panelDataArray
1668
+ } = eagerValuesRef.current;
1648
1669
  const {
1649
1670
  initialLayout
1650
1671
  } = dragState !== null && dragState !== void 0 ? dragState : {};
@@ -1700,6 +1721,7 @@ function PanelGroupWithForwardedRef({
1700
1721
  }
1701
1722
  if (layoutChanged) {
1702
1723
  setLayout(nextLayout);
1724
+ eagerValuesRef.current.layout = nextLayout;
1703
1725
  if (onLayout) {
1704
1726
  onLayout(nextLayout.map(sizePercentage => ({
1705
1727
  sizePercentage,
@@ -1713,23 +1735,13 @@ function PanelGroupWithForwardedRef({
1713
1735
 
1714
1736
  // External APIs are safe to memoize via committed values ref
1715
1737
  const resizePanel = useCallback((panelData, mixedSizes) => {
1738
+ const {
1739
+ onLayout
1740
+ } = committedValuesRef.current;
1716
1741
  const {
1717
1742
  layout: prevLayout,
1718
- onLayout,
1719
1743
  panelDataArray
1720
- } = committedValuesRef.current;
1721
-
1722
- // See issues/211
1723
- if (panelDataArray.find(({
1724
- id
1725
- }) => id === panelData.id) == null) {
1726
- setImperativeApiQueue(prev => [...prev, {
1727
- panelData,
1728
- mixedSizes,
1729
- type: "resize"
1730
- }]);
1731
- return;
1732
- }
1744
+ } = eagerValuesRef.current;
1733
1745
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1734
1746
  const {
1735
1747
  groupSizePixels,
@@ -1749,6 +1761,7 @@ function PanelGroupWithForwardedRef({
1749
1761
  });
1750
1762
  if (!compareLayouts(prevLayout, nextLayout)) {
1751
1763
  setLayout(nextLayout);
1764
+ eagerValuesRef.current.layout = nextLayout;
1752
1765
  if (onLayout) {
1753
1766
  onLayout(nextLayout.map(sizePercentage => ({
1754
1767
  sizePercentage,
@@ -1760,9 +1773,11 @@ function PanelGroupWithForwardedRef({
1760
1773
  }, [groupId]);
1761
1774
  const startDragging = useCallback((dragHandleId, event) => {
1762
1775
  const {
1763
- direction,
1764
- layout
1776
+ direction
1765
1777
  } = committedValuesRef.current;
1778
+ const {
1779
+ layout
1780
+ } = eagerValuesRef.current;
1766
1781
  const handleElement = getResizeHandleElement(dragHandleId);
1767
1782
  const initialCursorPosition = getResizeEventCursorPosition(direction, event);
1768
1783
  setDragState({
@@ -1776,42 +1791,87 @@ function PanelGroupWithForwardedRef({
1776
1791
  resetGlobalCursorStyle();
1777
1792
  setDragState(null);
1778
1793
  }, []);
1794
+ const unregisterPanelRef = useRef({
1795
+ pendingPanelIds: new Set(),
1796
+ timeout: null
1797
+ });
1779
1798
  const unregisterPanel = useCallback(panelData => {
1780
- delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1781
- setPanelDataArray(panelDataArray => {
1782
- const index = panelDataArray.indexOf(panelData);
1783
- if (index >= 0) {
1784
- panelDataArray = [...panelDataArray];
1785
- panelDataArray.splice(index, 1);
1799
+ const {
1800
+ id: groupId,
1801
+ onLayout
1802
+ } = committedValuesRef.current;
1803
+ const {
1804
+ layout: prevLayout,
1805
+ panelDataArray
1806
+ } = eagerValuesRef.current;
1807
+ const index = panelDataArray.indexOf(panelData);
1808
+ if (index >= 0) {
1809
+ panelDataArray.splice(index, 1);
1810
+ unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1811
+ }
1812
+ if (unregisterPanelRef.current.timeout != null) {
1813
+ clearTimeout(unregisterPanelRef.current.timeout);
1814
+ }
1815
+
1816
+ // Batch panel unmounts so that we only calculate layout once;
1817
+ // This is more efficient and avoids misleading warnings in development mode.
1818
+ // We can't check the DOM to detect this because Panel elements have not yet been removed.
1819
+ unregisterPanelRef.current.timeout = setTimeout(() => {
1820
+ const {
1821
+ pendingPanelIds
1822
+ } = unregisterPanelRef.current;
1823
+ const map = panelIdToLastNotifiedMixedSizesMapRef.current;
1824
+
1825
+ // TRICKY
1826
+ // Strict effects mode
1827
+ let unmountDueToStrictMode = false;
1828
+ pendingPanelIds.forEach(panelId => {
1829
+ pendingPanelIds.delete(panelId);
1830
+ if (panelDataArray.find(({
1831
+ id
1832
+ }) => id === panelId) == null) {
1833
+ unmountDueToStrictMode = true;
1834
+
1835
+ // TRICKY
1836
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1837
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1838
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1839
+ delete map[panelData.id];
1840
+ }
1841
+ });
1842
+ if (!unmountDueToStrictMode) {
1843
+ return;
1786
1844
  }
1787
- return panelDataArray;
1788
- });
1789
- }, []);
1845
+ if (panelDataArray.length === 0) {
1846
+ // The group is unmounting; skip layout calculation.
1847
+ return;
1848
+ }
1849
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1850
+ let unsafeLayout = calculateUnsafeDefaultLayout({
1851
+ groupSizePixels,
1852
+ panelDataArray
1853
+ });
1790
1854
 
1791
- // Handle imperative API calls that were made before panels were registered
1792
- useIsomorphicLayoutEffect(() => {
1793
- const queue = imperativeApiQueue;
1794
- while (queue.length > 0) {
1795
- const current = queue.shift();
1796
- switch (current.type) {
1797
- case "collapse":
1798
- {
1799
- collapsePanel(current.panelData);
1800
- break;
1801
- }
1802
- case "expand":
1803
- {
1804
- expandPanel(current.panelData);
1805
- break;
1806
- }
1807
- case "resize":
1808
- {
1809
- resizePanel(current.panelData, current.mixedSizes);
1810
- break;
1811
- }
1855
+ // Validate even saved layouts in case something has changed since last render
1856
+ // e.g. for pixel groups, this could be the size of the window
1857
+ const nextLayout = validatePanelGroupLayout({
1858
+ groupSizePixels,
1859
+ layout: unsafeLayout,
1860
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1861
+ });
1862
+ if (!areEqual(prevLayout, nextLayout)) {
1863
+ setLayout(nextLayout);
1864
+ eagerValuesRef.current.layout = nextLayout;
1865
+ if (onLayout) {
1866
+ onLayout(nextLayout.map(sizePercentage => ({
1867
+ sizePercentage,
1868
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1869
+ })));
1870
+ }
1871
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1812
1872
  }
1813
- }
1814
- }, [collapsePanel, expandPanel, imperativeApiQueue, layout, panelDataArray, resizePanel]);
1873
+ }, 0);
1874
+ }, []);
1815
1875
  const context = useMemo(() => ({
1816
1876
  collapsePanel,
1817
1877
  direction,