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,
@@ -164,6 +165,7 @@ function PanelWithForwardedRef({
164
165
  // CSS selectors
165
166
  "data-panel": "",
166
167
  "data-panel-id": panelId,
168
+ "data-panel-group-id": groupId,
167
169
  // e2e test attributes
168
170
  "data-panel-collapsible": undefined,
169
171
  "data-panel-size": undefined
@@ -687,6 +689,7 @@ function getResizeHandlePanelIds(groupId, handleId, panelsArray) {
687
689
 
688
690
  function useWindowSplitterPanelGroupBehavior({
689
691
  committedValuesRef,
692
+ eagerValuesRef,
690
693
  groupId,
691
694
  layout,
692
695
  panelDataArray,
@@ -729,7 +732,7 @@ function useWindowSplitterPanelGroupBehavior({
729
732
  useEffect(() => {
730
733
  const {
731
734
  panelDataArray
732
- } = committedValuesRef.current;
735
+ } = eagerValuesRef.current;
733
736
  const groupElement = getPanelGroupElement(groupId);
734
737
  assert(groupElement != null, `No group found for id "${groupId}"`);
735
738
  const handles = getResizeHandleElementsForGroup(groupId);
@@ -787,7 +790,7 @@ function useWindowSplitterPanelGroupBehavior({
787
790
  return () => {
788
791
  cleanupFunctions.forEach(cleanupFunction => cleanupFunction());
789
792
  };
790
- }, [committedValuesRef, groupId, layout, panelDataArray, setLayout]);
793
+ }, [committedValuesRef, eagerValuesRef, groupId, layout, panelDataArray, setLayout]);
791
794
  }
792
795
 
793
796
  function areEqual(arrayA, arrayB) {
@@ -1072,6 +1075,10 @@ function debounce(callback, durationMs = 10) {
1072
1075
  return callable;
1073
1076
  }
1074
1077
 
1078
+ function getPanelElementsForGroup(groupId) {
1079
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
1080
+ }
1081
+
1075
1082
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
1076
1083
  // or on a browser with cookies/storage disabled.
1077
1084
  // In either case, this function avoids accessing localStorage until needed,
@@ -1221,7 +1228,7 @@ const defaultStorage = {
1221
1228
  };
1222
1229
  const debounceMap = {};
1223
1230
  function PanelGroupWithForwardedRef({
1224
- autoSaveId,
1231
+ autoSaveId = null,
1225
1232
  children,
1226
1233
  className: classNameFromProps = "",
1227
1234
  dataAttributes,
@@ -1238,20 +1245,22 @@ function PanelGroupWithForwardedRef({
1238
1245
  const groupId = useUniqueId(idFromProps);
1239
1246
  const [dragState, setDragState] = useState(null);
1240
1247
  const [layout, setLayout] = useState([]);
1241
- const [panelDataArray, setPanelDataArray] = useState([]);
1242
1248
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1243
1249
  const panelSizeBeforeCollapseRef = useRef(new Map());
1244
1250
  const prevDeltaRef = useRef(0);
1245
- const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1246
1251
  const committedValuesRef = useRef({
1252
+ autoSaveId,
1247
1253
  direction,
1248
1254
  dragState,
1249
1255
  id: groupId,
1250
1256
  keyboardResizeByPercentage,
1251
1257
  keyboardResizeByPixels,
1252
- layout,
1253
1258
  onLayout,
1254
- panelDataArray
1259
+ storage
1260
+ });
1261
+ const eagerValuesRef = useRef({
1262
+ layout,
1263
+ panelDataArray: []
1255
1264
  });
1256
1265
  useRef({
1257
1266
  didLogIdAndOrderWarning: false,
@@ -1262,9 +1271,11 @@ function PanelGroupWithForwardedRef({
1262
1271
  getId: () => committedValuesRef.current.id,
1263
1272
  getLayout: () => {
1264
1273
  const {
1265
- id: groupId,
1266
- layout
1274
+ id: groupId
1267
1275
  } = committedValuesRef.current;
1276
+ const {
1277
+ layout
1278
+ } = eagerValuesRef.current;
1268
1279
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1269
1280
  return layout.map(sizePercentage => {
1270
1281
  return {
@@ -1276,10 +1287,12 @@ function PanelGroupWithForwardedRef({
1276
1287
  setLayout: mixedSizes => {
1277
1288
  const {
1278
1289
  id: groupId,
1290
+ onLayout
1291
+ } = committedValuesRef.current;
1292
+ const {
1279
1293
  layout: prevLayout,
1280
- onLayout,
1281
1294
  panelDataArray
1282
- } = committedValuesRef.current;
1295
+ } = eagerValuesRef.current;
1283
1296
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1284
1297
  const unsafeLayout = mixedSizes.map(mixedSize => getPercentageSizeFromMixedSizes(mixedSize, groupSizePixels));
1285
1298
  const safeLayout = validatePanelGroupLayout({
@@ -1289,6 +1302,7 @@ function PanelGroupWithForwardedRef({
1289
1302
  });
1290
1303
  if (!areEqual(prevLayout, safeLayout)) {
1291
1304
  setLayout(safeLayout);
1305
+ eagerValuesRef.current.layout = safeLayout;
1292
1306
  if (onLayout) {
1293
1307
  onLayout(safeLayout.map(sizePercentage => ({
1294
1308
  sizePercentage,
@@ -1300,21 +1314,30 @@ function PanelGroupWithForwardedRef({
1300
1314
  }
1301
1315
  }), []);
1302
1316
  useIsomorphicLayoutEffect(() => {
1317
+ committedValuesRef.current.autoSaveId = autoSaveId;
1303
1318
  committedValuesRef.current.direction = direction;
1304
1319
  committedValuesRef.current.dragState = dragState;
1305
1320
  committedValuesRef.current.id = groupId;
1306
- committedValuesRef.current.layout = layout;
1307
1321
  committedValuesRef.current.onLayout = onLayout;
1308
- committedValuesRef.current.panelDataArray = panelDataArray;
1322
+ committedValuesRef.current.storage = storage;
1323
+
1324
+ // panelDataArray and layout are updated in-sync with scheduled state updates.
1325
+ // TODO [217] Move these values into a separate ref
1309
1326
  });
1327
+
1310
1328
  useWindowSplitterPanelGroupBehavior({
1311
1329
  committedValuesRef,
1330
+ eagerValuesRef,
1312
1331
  groupId,
1313
1332
  layout,
1314
- panelDataArray,
1333
+ panelDataArray: eagerValuesRef.current.panelDataArray,
1315
1334
  setLayout
1316
1335
  });
1317
1336
  useEffect(() => {
1337
+ const {
1338
+ panelDataArray
1339
+ } = eagerValuesRef.current;
1340
+
1318
1341
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1319
1342
  if (autoSaveId) {
1320
1343
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1327,63 +1350,12 @@ function PanelGroupWithForwardedRef({
1327
1350
  }
1328
1351
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1329
1352
  }
1330
- }, [autoSaveId, layout, panelDataArray, storage]);
1331
-
1332
- // Once all panels have registered themselves,
1333
- // Compute the initial sizes based on default weights.
1334
- // This assumes that panels register during initial mount (no conditional rendering)!
1353
+ }, [autoSaveId, layout, storage]);
1335
1354
  useIsomorphicLayoutEffect(() => {
1336
1355
  const {
1337
- id: groupId,
1338
- layout,
1339
- onLayout
1340
- } = committedValuesRef.current;
1341
- if (layout.length === panelDataArray.length) {
1342
- // Only compute (or restore) default layout once per panel configuration.
1343
- return;
1344
- }
1345
-
1346
- // If this panel has been configured to persist sizing information,
1347
- // default size should be restored from local storage if possible.
1348
- let unsafeLayout = null;
1349
- if (autoSaveId) {
1350
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1351
- }
1352
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1353
- if (groupSizePixels <= 0) {
1354
- if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1355
- constraints
1356
- }) => constraints))) {
1357
- // Wait until the group has rendered a non-zero size before computing layout.
1358
- return;
1359
- }
1360
- }
1361
- if (unsafeLayout == null) {
1362
- unsafeLayout = calculateUnsafeDefaultLayout({
1363
- groupSizePixels,
1364
- panelDataArray
1365
- });
1366
- }
1367
-
1368
- // Validate even saved layouts in case something has changed since last render
1369
- // e.g. for pixel groups, this could be the size of the window
1370
- const validatedLayout = validatePanelGroupLayout({
1371
- groupSizePixels,
1372
- layout: unsafeLayout,
1373
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1374
- });
1375
- if (!areEqual(layout, validatedLayout)) {
1376
- setLayout(validatedLayout);
1377
- }
1378
- if (onLayout) {
1379
- onLayout(validatedLayout.map(sizePercentage => ({
1380
- sizePercentage,
1381
- sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1382
- })));
1383
- }
1384
- callPanelCallbacks(groupId, panelDataArray, validatedLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1385
- }, [autoSaveId, layout, panelDataArray, storage]);
1386
- useIsomorphicLayoutEffect(() => {
1356
+ layout: prevLayout,
1357
+ panelDataArray
1358
+ } = eagerValuesRef.current;
1387
1359
  const constraints = panelDataArray.map(({
1388
1360
  constraints
1389
1361
  }) => constraints);
@@ -1397,7 +1369,6 @@ function PanelGroupWithForwardedRef({
1397
1369
  const resizeObserver = new ResizeObserver(() => {
1398
1370
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1399
1371
  const {
1400
- layout: prevLayout,
1401
1372
  onLayout
1402
1373
  } = committedValuesRef.current;
1403
1374
  const nextLayout = validatePanelGroupLayout({
@@ -1407,6 +1378,7 @@ function PanelGroupWithForwardedRef({
1407
1378
  });
1408
1379
  if (!areEqual(prevLayout, nextLayout)) {
1409
1380
  setLayout(nextLayout);
1381
+ eagerValuesRef.current.layout = nextLayout;
1410
1382
  if (onLayout) {
1411
1383
  onLayout(nextLayout.map(sizePercentage => ({
1412
1384
  sizePercentage,
@@ -1421,7 +1393,7 @@ function PanelGroupWithForwardedRef({
1421
1393
  resizeObserver.disconnect();
1422
1394
  };
1423
1395
  }
1424
- }, [groupId, panelDataArray]);
1396
+ }, [groupId]);
1425
1397
 
1426
1398
  // DEV warnings
1427
1399
  useEffect(() => {
@@ -1429,22 +1401,13 @@ function PanelGroupWithForwardedRef({
1429
1401
 
1430
1402
  // External APIs are safe to memoize via committed values ref
1431
1403
  const collapsePanel = useCallback(panelData => {
1404
+ const {
1405
+ onLayout
1406
+ } = committedValuesRef.current;
1432
1407
  const {
1433
1408
  layout: prevLayout,
1434
- onLayout,
1435
1409
  panelDataArray
1436
- } = committedValuesRef.current;
1437
-
1438
- // See issues/211
1439
- if (panelDataArray.find(({
1440
- id
1441
- }) => id === panelData.id) == null) {
1442
- setImperativeApiQueue(prev => [...prev, {
1443
- panelData,
1444
- type: "collapse"
1445
- }]);
1446
- return;
1447
- }
1410
+ } = eagerValuesRef.current;
1448
1411
  if (panelData.constraints.collapsible) {
1449
1412
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1450
1413
  const {
@@ -1469,6 +1432,7 @@ function PanelGroupWithForwardedRef({
1469
1432
  });
1470
1433
  if (!compareLayouts(prevLayout, nextLayout)) {
1471
1434
  setLayout(nextLayout);
1435
+ eagerValuesRef.current.layout = nextLayout;
1472
1436
  if (onLayout) {
1473
1437
  onLayout(nextLayout.map(sizePercentage => ({
1474
1438
  sizePercentage,
@@ -1483,22 +1447,13 @@ function PanelGroupWithForwardedRef({
1483
1447
 
1484
1448
  // External APIs are safe to memoize via committed values ref
1485
1449
  const expandPanel = useCallback(panelData => {
1450
+ const {
1451
+ onLayout
1452
+ } = committedValuesRef.current;
1486
1453
  const {
1487
1454
  layout: prevLayout,
1488
- onLayout,
1489
1455
  panelDataArray
1490
- } = committedValuesRef.current;
1491
-
1492
- // See issues/211
1493
- if (panelDataArray.find(({
1494
- id
1495
- }) => id === panelData.id) == null) {
1496
- setImperativeApiQueue(prev => [...prev, {
1497
- panelData,
1498
- type: "expand"
1499
- }]);
1500
- return;
1501
- }
1456
+ } = eagerValuesRef.current;
1502
1457
  if (panelData.constraints.collapsible) {
1503
1458
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1504
1459
  const {
@@ -1524,6 +1479,7 @@ function PanelGroupWithForwardedRef({
1524
1479
  });
1525
1480
  if (!compareLayouts(prevLayout, nextLayout)) {
1526
1481
  setLayout(nextLayout);
1482
+ eagerValuesRef.current.layout = nextLayout;
1527
1483
  if (onLayout) {
1528
1484
  onLayout(nextLayout.map(sizePercentage => ({
1529
1485
  sizePercentage,
@@ -1541,7 +1497,7 @@ function PanelGroupWithForwardedRef({
1541
1497
  const {
1542
1498
  layout,
1543
1499
  panelDataArray
1544
- } = committedValuesRef.current;
1500
+ } = eagerValuesRef.current;
1545
1501
  const {
1546
1502
  panelSizePercentage,
1547
1503
  panelSizePixels
@@ -1554,6 +1510,9 @@ function PanelGroupWithForwardedRef({
1554
1510
 
1555
1511
  // This API should never read from committedValuesRef
1556
1512
  const getPanelStyle = useCallback(panelData => {
1513
+ const {
1514
+ panelDataArray
1515
+ } = eagerValuesRef.current;
1557
1516
  const panelIndex = panelDataArray.indexOf(panelData);
1558
1517
  return computePanelFlexBoxStyle({
1559
1518
  dragState,
@@ -1561,14 +1520,14 @@ function PanelGroupWithForwardedRef({
1561
1520
  panelData: panelDataArray,
1562
1521
  panelIndex
1563
1522
  });
1564
- }, [dragState, layout, panelDataArray]);
1523
+ }, [dragState, layout]);
1565
1524
 
1566
1525
  // External APIs are safe to memoize via committed values ref
1567
1526
  const isPanelCollapsed = useCallback(panelData => {
1568
1527
  const {
1569
1528
  layout,
1570
1529
  panelDataArray
1571
- } = committedValuesRef.current;
1530
+ } = eagerValuesRef.current;
1572
1531
  const {
1573
1532
  collapsedSizePercentage,
1574
1533
  collapsible,
@@ -1582,7 +1541,7 @@ function PanelGroupWithForwardedRef({
1582
1541
  const {
1583
1542
  layout,
1584
1543
  panelDataArray
1585
- } = committedValuesRef.current;
1544
+ } = eagerValuesRef.current;
1586
1545
  const {
1587
1546
  collapsedSizePercentage,
1588
1547
  collapsible,
@@ -1591,22 +1550,82 @@ function PanelGroupWithForwardedRef({
1591
1550
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1592
1551
  }, [groupId]);
1593
1552
  const registerPanel = useCallback(panelData => {
1594
- setPanelDataArray(prevPanelDataArray => {
1595
- const nextPanelDataArray = [...prevPanelDataArray, panelData];
1596
- return nextPanelDataArray.sort((panelA, panelB) => {
1597
- const orderA = panelA.order;
1598
- const orderB = panelB.order;
1599
- if (orderA == null && orderB == null) {
1600
- return 0;
1601
- } else if (orderA == null) {
1602
- return -1;
1603
- } else if (orderB == null) {
1604
- return 1;
1605
- } else {
1606
- return orderA - orderB;
1607
- }
1553
+ const {
1554
+ autoSaveId,
1555
+ id: groupId,
1556
+ onLayout,
1557
+ storage
1558
+ } = committedValuesRef.current;
1559
+ const {
1560
+ layout: prevLayout,
1561
+ panelDataArray
1562
+ } = eagerValuesRef.current;
1563
+ panelDataArray.push(panelData);
1564
+ panelDataArray.sort((panelA, panelB) => {
1565
+ const orderA = panelA.order;
1566
+ const orderB = panelB.order;
1567
+ if (orderA == null && orderB == null) {
1568
+ return 0;
1569
+ } else if (orderA == null) {
1570
+ return -1;
1571
+ } else if (orderB == null) {
1572
+ return 1;
1573
+ } else {
1574
+ return orderA - orderB;
1575
+ }
1576
+ });
1577
+
1578
+ // Wait until all panels have registered before we try to compute layout;
1579
+ // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1580
+ const panelElements = getPanelElementsForGroup(groupId);
1581
+ if (panelElements.length !== panelDataArray.length) {
1582
+ return;
1583
+ }
1584
+
1585
+ // If this panel has been configured to persist sizing information,
1586
+ // default size should be restored from local storage if possible.
1587
+ let unsafeLayout = null;
1588
+ if (autoSaveId) {
1589
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1590
+ }
1591
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1592
+ if (groupSizePixels <= 0) {
1593
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1594
+ constraints
1595
+ }) => constraints))) {
1596
+ // Wait until the group has rendered a non-zero size before computing layout.
1597
+ return;
1598
+ }
1599
+ }
1600
+ if (unsafeLayout == null) {
1601
+ unsafeLayout = calculateUnsafeDefaultLayout({
1602
+ groupSizePixels,
1603
+ panelDataArray
1608
1604
  });
1605
+ }
1606
+
1607
+ // Validate even saved layouts in case something has changed since last render
1608
+ // e.g. for pixel groups, this could be the size of the window
1609
+ const nextLayout = validatePanelGroupLayout({
1610
+ groupSizePixels,
1611
+ layout: unsafeLayout,
1612
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1609
1613
  });
1614
+
1615
+ // Offscreen mode makes this a bit weird;
1616
+ // Panels unregister when hidden and re-register when shown again,
1617
+ // but the overall layout doesn't change between these two cases.
1618
+ setLayout(nextLayout);
1619
+ eagerValuesRef.current.layout = nextLayout;
1620
+ if (!areEqual(prevLayout, nextLayout)) {
1621
+ if (onLayout) {
1622
+ onLayout(nextLayout.map(sizePercentage => ({
1623
+ sizePercentage,
1624
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1625
+ })));
1626
+ }
1627
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1628
+ }
1610
1629
  }, []);
1611
1630
  const registerResizeHandle = useCallback(dragHandleId => {
1612
1631
  return function resizeHandler(event) {
@@ -1617,10 +1636,12 @@ function PanelGroupWithForwardedRef({
1617
1636
  id: groupId,
1618
1637
  keyboardResizeByPercentage,
1619
1638
  keyboardResizeByPixels,
1620
- onLayout,
1621
- panelDataArray,
1622
- layout: prevLayout
1639
+ onLayout
1623
1640
  } = committedValuesRef.current;
1641
+ const {
1642
+ layout: prevLayout,
1643
+ panelDataArray
1644
+ } = eagerValuesRef.current;
1624
1645
  const {
1625
1646
  initialLayout
1626
1647
  } = dragState !== null && dragState !== void 0 ? dragState : {};
@@ -1676,6 +1697,7 @@ function PanelGroupWithForwardedRef({
1676
1697
  }
1677
1698
  if (layoutChanged) {
1678
1699
  setLayout(nextLayout);
1700
+ eagerValuesRef.current.layout = nextLayout;
1679
1701
  if (onLayout) {
1680
1702
  onLayout(nextLayout.map(sizePercentage => ({
1681
1703
  sizePercentage,
@@ -1689,23 +1711,13 @@ function PanelGroupWithForwardedRef({
1689
1711
 
1690
1712
  // External APIs are safe to memoize via committed values ref
1691
1713
  const resizePanel = useCallback((panelData, mixedSizes) => {
1714
+ const {
1715
+ onLayout
1716
+ } = committedValuesRef.current;
1692
1717
  const {
1693
1718
  layout: prevLayout,
1694
- onLayout,
1695
1719
  panelDataArray
1696
- } = committedValuesRef.current;
1697
-
1698
- // See issues/211
1699
- if (panelDataArray.find(({
1700
- id
1701
- }) => id === panelData.id) == null) {
1702
- setImperativeApiQueue(prev => [...prev, {
1703
- panelData,
1704
- mixedSizes,
1705
- type: "resize"
1706
- }]);
1707
- return;
1708
- }
1720
+ } = eagerValuesRef.current;
1709
1721
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1710
1722
  const {
1711
1723
  groupSizePixels,
@@ -1725,6 +1737,7 @@ function PanelGroupWithForwardedRef({
1725
1737
  });
1726
1738
  if (!compareLayouts(prevLayout, nextLayout)) {
1727
1739
  setLayout(nextLayout);
1740
+ eagerValuesRef.current.layout = nextLayout;
1728
1741
  if (onLayout) {
1729
1742
  onLayout(nextLayout.map(sizePercentage => ({
1730
1743
  sizePercentage,
@@ -1736,9 +1749,11 @@ function PanelGroupWithForwardedRef({
1736
1749
  }, [groupId]);
1737
1750
  const startDragging = useCallback((dragHandleId, event) => {
1738
1751
  const {
1739
- direction,
1740
- layout
1752
+ direction
1741
1753
  } = committedValuesRef.current;
1754
+ const {
1755
+ layout
1756
+ } = eagerValuesRef.current;
1742
1757
  const handleElement = getResizeHandleElement(dragHandleId);
1743
1758
  const initialCursorPosition = getResizeEventCursorPosition(direction, event);
1744
1759
  setDragState({
@@ -1752,42 +1767,87 @@ function PanelGroupWithForwardedRef({
1752
1767
  resetGlobalCursorStyle();
1753
1768
  setDragState(null);
1754
1769
  }, []);
1770
+ const unregisterPanelRef = useRef({
1771
+ pendingPanelIds: new Set(),
1772
+ timeout: null
1773
+ });
1755
1774
  const unregisterPanel = useCallback(panelData => {
1756
- delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1757
- setPanelDataArray(panelDataArray => {
1758
- const index = panelDataArray.indexOf(panelData);
1759
- if (index >= 0) {
1760
- panelDataArray = [...panelDataArray];
1761
- panelDataArray.splice(index, 1);
1775
+ const {
1776
+ id: groupId,
1777
+ onLayout
1778
+ } = committedValuesRef.current;
1779
+ const {
1780
+ layout: prevLayout,
1781
+ panelDataArray
1782
+ } = eagerValuesRef.current;
1783
+ const index = panelDataArray.indexOf(panelData);
1784
+ if (index >= 0) {
1785
+ panelDataArray.splice(index, 1);
1786
+ unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1787
+ }
1788
+ if (unregisterPanelRef.current.timeout != null) {
1789
+ clearTimeout(unregisterPanelRef.current.timeout);
1790
+ }
1791
+
1792
+ // Batch panel unmounts so that we only calculate layout once;
1793
+ // This is more efficient and avoids misleading warnings in development mode.
1794
+ // We can't check the DOM to detect this because Panel elements have not yet been removed.
1795
+ unregisterPanelRef.current.timeout = setTimeout(() => {
1796
+ const {
1797
+ pendingPanelIds
1798
+ } = unregisterPanelRef.current;
1799
+ const map = panelIdToLastNotifiedMixedSizesMapRef.current;
1800
+
1801
+ // TRICKY
1802
+ // Strict effects mode
1803
+ let unmountDueToStrictMode = false;
1804
+ pendingPanelIds.forEach(panelId => {
1805
+ pendingPanelIds.delete(panelId);
1806
+ if (panelDataArray.find(({
1807
+ id
1808
+ }) => id === panelId) == null) {
1809
+ unmountDueToStrictMode = true;
1810
+
1811
+ // TRICKY
1812
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1813
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1814
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1815
+ delete map[panelData.id];
1816
+ }
1817
+ });
1818
+ if (!unmountDueToStrictMode) {
1819
+ return;
1762
1820
  }
1763
- return panelDataArray;
1764
- });
1765
- }, []);
1821
+ if (panelDataArray.length === 0) {
1822
+ // The group is unmounting; skip layout calculation.
1823
+ return;
1824
+ }
1825
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1826
+ let unsafeLayout = calculateUnsafeDefaultLayout({
1827
+ groupSizePixels,
1828
+ panelDataArray
1829
+ });
1766
1830
 
1767
- // Handle imperative API calls that were made before panels were registered
1768
- useIsomorphicLayoutEffect(() => {
1769
- const queue = imperativeApiQueue;
1770
- while (queue.length > 0) {
1771
- const current = queue.shift();
1772
- switch (current.type) {
1773
- case "collapse":
1774
- {
1775
- collapsePanel(current.panelData);
1776
- break;
1777
- }
1778
- case "expand":
1779
- {
1780
- expandPanel(current.panelData);
1781
- break;
1782
- }
1783
- case "resize":
1784
- {
1785
- resizePanel(current.panelData, current.mixedSizes);
1786
- break;
1787
- }
1831
+ // Validate even saved layouts in case something has changed since last render
1832
+ // e.g. for pixel groups, this could be the size of the window
1833
+ const nextLayout = validatePanelGroupLayout({
1834
+ groupSizePixels,
1835
+ layout: unsafeLayout,
1836
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1837
+ });
1838
+ if (!areEqual(prevLayout, nextLayout)) {
1839
+ setLayout(nextLayout);
1840
+ eagerValuesRef.current.layout = nextLayout;
1841
+ if (onLayout) {
1842
+ onLayout(nextLayout.map(sizePercentage => ({
1843
+ sizePercentage,
1844
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1845
+ })));
1846
+ }
1847
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1788
1848
  }
1789
- }
1790
- }, [collapsePanel, expandPanel, imperativeApiQueue, layout, panelDataArray, resizePanel]);
1849
+ }, 0);
1850
+ }, []);
1791
1851
  const context = useMemo(() => ({
1792
1852
  collapsePanel,
1793
1853
  direction,