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