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.
@@ -91,6 +91,7 @@ function PanelWithForwardedRef({
91
91
  expandPanel,
92
92
  getPanelSize,
93
93
  getPanelStyle,
94
+ groupId,
94
95
  isPanelCollapsed,
95
96
  registerPanel,
96
97
  resizePanel,
@@ -167,6 +168,7 @@ function PanelWithForwardedRef({
167
168
  // CSS selectors
168
169
  "data-panel": "",
169
170
  "data-panel-id": panelId,
171
+ "data-panel-group-id": groupId,
170
172
  // e2e test attributes
171
173
  "data-panel-collapsible": collapsible || undefined ,
172
174
  "data-panel-size": parseFloat("" + style.flexGrow).toFixed(1)
@@ -641,6 +643,7 @@ function getResizeHandlePanelIds(groupId, handleId, panelsArray) {
641
643
 
642
644
  function useWindowSplitterPanelGroupBehavior({
643
645
  committedValuesRef,
646
+ eagerValuesRef,
644
647
  groupId,
645
648
  layout,
646
649
  panelDataArray,
@@ -652,7 +655,7 @@ function useWindowSplitterPanelGroupBehavior({
652
655
  useEffect(() => {
653
656
  const {
654
657
  panelDataArray
655
- } = committedValuesRef.current;
658
+ } = eagerValuesRef.current;
656
659
  const groupElement = getPanelGroupElement(groupId);
657
660
  assert(groupElement != null, `No group found for id "${groupId}"`);
658
661
  const handles = getResizeHandleElementsForGroup(groupId);
@@ -710,7 +713,7 @@ function useWindowSplitterPanelGroupBehavior({
710
713
  return () => {
711
714
  cleanupFunctions.forEach(cleanupFunction => cleanupFunction());
712
715
  };
713
- }, [committedValuesRef, groupId, layout, panelDataArray, setLayout]);
716
+ }, [committedValuesRef, eagerValuesRef, groupId, layout, panelDataArray, setLayout]);
714
717
  }
715
718
 
716
719
  function areEqual(arrayA, arrayB) {
@@ -807,6 +810,44 @@ function calculateDeltaPercentage(event, groupId, dragHandleId, direction, initi
807
810
  }
808
811
  }
809
812
 
813
+ function calculateUnsafeDefaultLayout({
814
+ groupSizePixels,
815
+ panelDataArray
816
+ }) {
817
+ const layout = Array(panelDataArray.length);
818
+ const panelDataConstraints = panelDataArray.map(panelData => panelData.constraints);
819
+ let numPanelsWithSizes = 0;
820
+ let remainingSize = 100;
821
+
822
+ // Distribute default sizes first
823
+ for (let index = 0; index < panelDataArray.length; index++) {
824
+ const {
825
+ defaultSizePercentage
826
+ } = computePercentagePanelConstraints(panelDataConstraints, index, groupSizePixels);
827
+ if (defaultSizePercentage != null) {
828
+ numPanelsWithSizes++;
829
+ layout[index] = defaultSizePercentage;
830
+ remainingSize -= defaultSizePercentage;
831
+ }
832
+ }
833
+
834
+ // Remaining size should be distributed evenly between panels without default sizes
835
+ for (let index = 0; index < panelDataArray.length; index++) {
836
+ const {
837
+ defaultSizePercentage
838
+ } = computePercentagePanelConstraints(panelDataConstraints, index, groupSizePixels);
839
+ if (defaultSizePercentage != null) {
840
+ continue;
841
+ }
842
+ const numRemainingPanels = panelDataArray.length - numPanelsWithSizes;
843
+ const size = remainingSize / numRemainingPanels;
844
+ numPanelsWithSizes++;
845
+ layout[index] = size;
846
+ remainingSize -= size;
847
+ }
848
+ return layout;
849
+ }
850
+
810
851
  function convertPercentageToPixels(percentage, groupSizePixels) {
811
852
  return percentage / 100 * groupSizePixels;
812
853
  }
@@ -957,6 +998,10 @@ function debounce(callback, durationMs = 10) {
957
998
  return callable;
958
999
  }
959
1000
 
1001
+ function getPanelElementsForGroup(groupId) {
1002
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
1003
+ }
1004
+
960
1005
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
961
1006
  // or on a browser with cookies/storage disabled.
962
1007
  // In either case, this function avoids accessing localStorage until needed,
@@ -1012,6 +1057,15 @@ function loadSerializedPanelGroupState(autoSaveId, storage) {
1012
1057
  } catch (error) {}
1013
1058
  return null;
1014
1059
  }
1060
+ function loadPanelLayout(autoSaveId, panels, storage) {
1061
+ const state = loadSerializedPanelGroupState(autoSaveId, storage);
1062
+ if (state) {
1063
+ var _state$key;
1064
+ const key = getSerializationKey(panels);
1065
+ return (_state$key = state[key]) !== null && _state$key !== void 0 ? _state$key : null;
1066
+ }
1067
+ return null;
1068
+ }
1015
1069
  function savePanelGroupLayout(autoSaveId, panels, sizes, storage) {
1016
1070
  const key = getSerializationKey(panels);
1017
1071
  const state = loadSerializedPanelGroupState(autoSaveId, storage) || {};
@@ -1023,6 +1077,12 @@ function savePanelGroupLayout(autoSaveId, panels, sizes, storage) {
1023
1077
  }
1024
1078
  }
1025
1079
 
1080
+ function shouldMonitorPixelBasedConstraints(constraints) {
1081
+ return constraints.some(constraints => {
1082
+ return constraints.collapsedSizePixels !== undefined || constraints.maxSizePixels !== undefined || constraints.minSizePixels !== undefined;
1083
+ });
1084
+ }
1085
+
1026
1086
  function validatePanelConstraints({
1027
1087
  groupSizePixels,
1028
1088
  panelConstraints,
@@ -1168,7 +1228,7 @@ const defaultStorage = {
1168
1228
  };
1169
1229
  const debounceMap = {};
1170
1230
  function PanelGroupWithForwardedRef({
1171
- autoSaveId,
1231
+ autoSaveId = null,
1172
1232
  children,
1173
1233
  className: classNameFromProps = "",
1174
1234
  dataAttributes,
@@ -1185,20 +1245,22 @@ function PanelGroupWithForwardedRef({
1185
1245
  const groupId = useUniqueId(idFromProps);
1186
1246
  const [dragState, setDragState] = useState(null);
1187
1247
  const [layout, setLayout] = useState([]);
1188
- const [panelDataArray, setPanelDataArray] = useState([]);
1189
1248
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1190
1249
  const panelSizeBeforeCollapseRef = useRef(new Map());
1191
1250
  const prevDeltaRef = useRef(0);
1192
- const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1193
1251
  const committedValuesRef = useRef({
1252
+ autoSaveId,
1194
1253
  direction,
1195
1254
  dragState,
1196
1255
  id: groupId,
1197
1256
  keyboardResizeByPercentage,
1198
1257
  keyboardResizeByPixels,
1199
- layout,
1200
1258
  onLayout,
1201
- panelDataArray
1259
+ storage
1260
+ });
1261
+ const eagerValuesRef = useRef({
1262
+ layout,
1263
+ panelDataArray: []
1202
1264
  });
1203
1265
  const devWarningsRef = useRef({
1204
1266
  didLogIdAndOrderWarning: false,
@@ -1209,9 +1271,11 @@ function PanelGroupWithForwardedRef({
1209
1271
  getId: () => committedValuesRef.current.id,
1210
1272
  getLayout: () => {
1211
1273
  const {
1212
- id: groupId,
1213
- layout
1274
+ id: groupId
1214
1275
  } = committedValuesRef.current;
1276
+ const {
1277
+ layout
1278
+ } = eagerValuesRef.current;
1215
1279
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1216
1280
  return layout.map(sizePercentage => {
1217
1281
  return {
@@ -1223,10 +1287,12 @@ function PanelGroupWithForwardedRef({
1223
1287
  setLayout: mixedSizes => {
1224
1288
  const {
1225
1289
  id: groupId,
1290
+ onLayout
1291
+ } = committedValuesRef.current;
1292
+ const {
1226
1293
  layout: prevLayout,
1227
- onLayout,
1228
1294
  panelDataArray
1229
- } = committedValuesRef.current;
1295
+ } = eagerValuesRef.current;
1230
1296
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1231
1297
  const unsafeLayout = mixedSizes.map(mixedSize => getPercentageSizeFromMixedSizes(mixedSize, groupSizePixels));
1232
1298
  const safeLayout = validatePanelGroupLayout({
@@ -1236,6 +1302,7 @@ function PanelGroupWithForwardedRef({
1236
1302
  });
1237
1303
  if (!areEqual(prevLayout, safeLayout)) {
1238
1304
  setLayout(safeLayout);
1305
+ eagerValuesRef.current.layout = safeLayout;
1239
1306
  if (onLayout) {
1240
1307
  onLayout(safeLayout.map(sizePercentage => ({
1241
1308
  sizePercentage,
@@ -1246,14 +1313,20 @@ function PanelGroupWithForwardedRef({
1246
1313
  }
1247
1314
  }
1248
1315
  }), []);
1316
+
1249
1317
  useWindowSplitterPanelGroupBehavior({
1250
1318
  committedValuesRef,
1319
+ eagerValuesRef,
1251
1320
  groupId,
1252
1321
  layout,
1253
- panelDataArray,
1322
+ panelDataArray: eagerValuesRef.current.panelDataArray,
1254
1323
  setLayout
1255
1324
  });
1256
1325
  useEffect(() => {
1326
+ const {
1327
+ panelDataArray
1328
+ } = eagerValuesRef.current;
1329
+
1257
1330
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1258
1331
  if (autoSaveId) {
1259
1332
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1266,20 +1339,20 @@ function PanelGroupWithForwardedRef({
1266
1339
  }
1267
1340
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1268
1341
  }
1269
- }, [autoSaveId, layout, panelDataArray, storage]);
1342
+ }, [autoSaveId, layout, storage]);
1270
1343
 
1271
1344
  // DEV warnings
1272
1345
  useEffect(() => {
1273
1346
  {
1347
+ const {
1348
+ panelDataArray
1349
+ } = eagerValuesRef.current;
1274
1350
  const {
1275
1351
  didLogIdAndOrderWarning,
1276
1352
  didLogPanelConstraintsWarning,
1277
1353
  prevPanelIds
1278
1354
  } = devWarningsRef.current;
1279
1355
  if (!didLogIdAndOrderWarning) {
1280
- const {
1281
- panelDataArray
1282
- } = committedValuesRef.current;
1283
1356
  const panelIds = panelDataArray.map(({
1284
1357
  id
1285
1358
  }) => id);
@@ -1316,22 +1389,13 @@ function PanelGroupWithForwardedRef({
1316
1389
 
1317
1390
  // External APIs are safe to memoize via committed values ref
1318
1391
  const collapsePanel = useCallback(panelData => {
1392
+ const {
1393
+ onLayout
1394
+ } = committedValuesRef.current;
1319
1395
  const {
1320
1396
  layout: prevLayout,
1321
- onLayout,
1322
1397
  panelDataArray
1323
- } = committedValuesRef.current;
1324
-
1325
- // See issues/211
1326
- if (panelDataArray.find(({
1327
- id
1328
- }) => id === panelData.id) == null) {
1329
- setImperativeApiQueue(prev => [...prev, {
1330
- panelData,
1331
- type: "collapse"
1332
- }]);
1333
- return;
1334
- }
1398
+ } = eagerValuesRef.current;
1335
1399
  if (panelData.constraints.collapsible) {
1336
1400
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1337
1401
  const {
@@ -1356,6 +1420,7 @@ function PanelGroupWithForwardedRef({
1356
1420
  });
1357
1421
  if (!compareLayouts(prevLayout, nextLayout)) {
1358
1422
  setLayout(nextLayout);
1423
+ eagerValuesRef.current.layout = nextLayout;
1359
1424
  if (onLayout) {
1360
1425
  onLayout(nextLayout.map(sizePercentage => ({
1361
1426
  sizePercentage,
@@ -1370,22 +1435,13 @@ function PanelGroupWithForwardedRef({
1370
1435
 
1371
1436
  // External APIs are safe to memoize via committed values ref
1372
1437
  const expandPanel = useCallback(panelData => {
1438
+ const {
1439
+ onLayout
1440
+ } = committedValuesRef.current;
1373
1441
  const {
1374
1442
  layout: prevLayout,
1375
- onLayout,
1376
1443
  panelDataArray
1377
- } = committedValuesRef.current;
1378
-
1379
- // See issues/211
1380
- if (panelDataArray.find(({
1381
- id
1382
- }) => id === panelData.id) == null) {
1383
- setImperativeApiQueue(prev => [...prev, {
1384
- panelData,
1385
- type: "expand"
1386
- }]);
1387
- return;
1388
- }
1444
+ } = eagerValuesRef.current;
1389
1445
  if (panelData.constraints.collapsible) {
1390
1446
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1391
1447
  const {
@@ -1411,6 +1467,7 @@ function PanelGroupWithForwardedRef({
1411
1467
  });
1412
1468
  if (!compareLayouts(prevLayout, nextLayout)) {
1413
1469
  setLayout(nextLayout);
1470
+ eagerValuesRef.current.layout = nextLayout;
1414
1471
  if (onLayout) {
1415
1472
  onLayout(nextLayout.map(sizePercentage => ({
1416
1473
  sizePercentage,
@@ -1428,7 +1485,7 @@ function PanelGroupWithForwardedRef({
1428
1485
  const {
1429
1486
  layout,
1430
1487
  panelDataArray
1431
- } = committedValuesRef.current;
1488
+ } = eagerValuesRef.current;
1432
1489
  const {
1433
1490
  panelSizePercentage,
1434
1491
  panelSizePixels
@@ -1441,6 +1498,9 @@ function PanelGroupWithForwardedRef({
1441
1498
 
1442
1499
  // This API should never read from committedValuesRef
1443
1500
  const getPanelStyle = useCallback(panelData => {
1501
+ const {
1502
+ panelDataArray
1503
+ } = eagerValuesRef.current;
1444
1504
  const panelIndex = panelDataArray.indexOf(panelData);
1445
1505
  return computePanelFlexBoxStyle({
1446
1506
  dragState,
@@ -1448,14 +1508,14 @@ function PanelGroupWithForwardedRef({
1448
1508
  panelData: panelDataArray,
1449
1509
  panelIndex
1450
1510
  });
1451
- }, [dragState, layout, panelDataArray]);
1511
+ }, [dragState, layout]);
1452
1512
 
1453
1513
  // External APIs are safe to memoize via committed values ref
1454
1514
  const isPanelCollapsed = useCallback(panelData => {
1455
1515
  const {
1456
1516
  layout,
1457
1517
  panelDataArray
1458
- } = committedValuesRef.current;
1518
+ } = eagerValuesRef.current;
1459
1519
  const {
1460
1520
  collapsedSizePercentage,
1461
1521
  collapsible,
@@ -1469,7 +1529,7 @@ function PanelGroupWithForwardedRef({
1469
1529
  const {
1470
1530
  layout,
1471
1531
  panelDataArray
1472
- } = committedValuesRef.current;
1532
+ } = eagerValuesRef.current;
1473
1533
  const {
1474
1534
  collapsedSizePercentage,
1475
1535
  collapsible,
@@ -1478,22 +1538,82 @@ function PanelGroupWithForwardedRef({
1478
1538
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1479
1539
  }, [groupId]);
1480
1540
  const registerPanel = useCallback(panelData => {
1481
- setPanelDataArray(prevPanelDataArray => {
1482
- const nextPanelDataArray = [...prevPanelDataArray, panelData];
1483
- return nextPanelDataArray.sort((panelA, panelB) => {
1484
- const orderA = panelA.order;
1485
- const orderB = panelB.order;
1486
- if (orderA == null && orderB == null) {
1487
- return 0;
1488
- } else if (orderA == null) {
1489
- return -1;
1490
- } else if (orderB == null) {
1491
- return 1;
1492
- } else {
1493
- return orderA - orderB;
1494
- }
1541
+ const {
1542
+ autoSaveId,
1543
+ id: groupId,
1544
+ onLayout,
1545
+ storage
1546
+ } = committedValuesRef.current;
1547
+ const {
1548
+ layout: prevLayout,
1549
+ panelDataArray
1550
+ } = eagerValuesRef.current;
1551
+ panelDataArray.push(panelData);
1552
+ panelDataArray.sort((panelA, panelB) => {
1553
+ const orderA = panelA.order;
1554
+ const orderB = panelB.order;
1555
+ if (orderA == null && orderB == null) {
1556
+ return 0;
1557
+ } else if (orderA == null) {
1558
+ return -1;
1559
+ } else if (orderB == null) {
1560
+ return 1;
1561
+ } else {
1562
+ return orderA - orderB;
1563
+ }
1564
+ });
1565
+
1566
+ // Wait until all panels have registered before we try to compute layout;
1567
+ // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1568
+ const panelElements = getPanelElementsForGroup(groupId);
1569
+ if (panelElements.length !== panelDataArray.length) {
1570
+ return;
1571
+ }
1572
+
1573
+ // If this panel has been configured to persist sizing information,
1574
+ // default size should be restored from local storage if possible.
1575
+ let unsafeLayout = null;
1576
+ if (autoSaveId) {
1577
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1578
+ }
1579
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1580
+ if (groupSizePixels <= 0) {
1581
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1582
+ constraints
1583
+ }) => constraints))) {
1584
+ // Wait until the group has rendered a non-zero size before computing layout.
1585
+ return;
1586
+ }
1587
+ }
1588
+ if (unsafeLayout == null) {
1589
+ unsafeLayout = calculateUnsafeDefaultLayout({
1590
+ groupSizePixels,
1591
+ panelDataArray
1495
1592
  });
1593
+ }
1594
+
1595
+ // Validate even saved layouts in case something has changed since last render
1596
+ // e.g. for pixel groups, this could be the size of the window
1597
+ const nextLayout = validatePanelGroupLayout({
1598
+ groupSizePixels,
1599
+ layout: unsafeLayout,
1600
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1496
1601
  });
1602
+
1603
+ // Offscreen mode makes this a bit weird;
1604
+ // Panels unregister when hidden and re-register when shown again,
1605
+ // but the overall layout doesn't change between these two cases.
1606
+ setLayout(nextLayout);
1607
+ eagerValuesRef.current.layout = nextLayout;
1608
+ if (!areEqual(prevLayout, nextLayout)) {
1609
+ if (onLayout) {
1610
+ onLayout(nextLayout.map(sizePercentage => ({
1611
+ sizePercentage,
1612
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1613
+ })));
1614
+ }
1615
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1616
+ }
1497
1617
  }, []);
1498
1618
  const registerResizeHandle = useCallback(dragHandleId => {
1499
1619
  return function resizeHandler(event) {
@@ -1504,10 +1624,12 @@ function PanelGroupWithForwardedRef({
1504
1624
  id: groupId,
1505
1625
  keyboardResizeByPercentage,
1506
1626
  keyboardResizeByPixels,
1507
- onLayout,
1508
- panelDataArray,
1509
- layout: prevLayout
1627
+ onLayout
1510
1628
  } = committedValuesRef.current;
1629
+ const {
1630
+ layout: prevLayout,
1631
+ panelDataArray
1632
+ } = eagerValuesRef.current;
1511
1633
  const {
1512
1634
  initialLayout
1513
1635
  } = dragState !== null && dragState !== void 0 ? dragState : {};
@@ -1563,6 +1685,7 @@ function PanelGroupWithForwardedRef({
1563
1685
  }
1564
1686
  if (layoutChanged) {
1565
1687
  setLayout(nextLayout);
1688
+ eagerValuesRef.current.layout = nextLayout;
1566
1689
  if (onLayout) {
1567
1690
  onLayout(nextLayout.map(sizePercentage => ({
1568
1691
  sizePercentage,
@@ -1576,23 +1699,13 @@ function PanelGroupWithForwardedRef({
1576
1699
 
1577
1700
  // External APIs are safe to memoize via committed values ref
1578
1701
  const resizePanel = useCallback((panelData, mixedSizes) => {
1702
+ const {
1703
+ onLayout
1704
+ } = committedValuesRef.current;
1579
1705
  const {
1580
1706
  layout: prevLayout,
1581
- onLayout,
1582
1707
  panelDataArray
1583
- } = committedValuesRef.current;
1584
-
1585
- // See issues/211
1586
- if (panelDataArray.find(({
1587
- id
1588
- }) => id === panelData.id) == null) {
1589
- setImperativeApiQueue(prev => [...prev, {
1590
- panelData,
1591
- mixedSizes,
1592
- type: "resize"
1593
- }]);
1594
- return;
1595
- }
1708
+ } = eagerValuesRef.current;
1596
1709
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1597
1710
  const {
1598
1711
  groupSizePixels,
@@ -1612,6 +1725,7 @@ function PanelGroupWithForwardedRef({
1612
1725
  });
1613
1726
  if (!compareLayouts(prevLayout, nextLayout)) {
1614
1727
  setLayout(nextLayout);
1728
+ eagerValuesRef.current.layout = nextLayout;
1615
1729
  if (onLayout) {
1616
1730
  onLayout(nextLayout.map(sizePercentage => ({
1617
1731
  sizePercentage,
@@ -1623,9 +1737,11 @@ function PanelGroupWithForwardedRef({
1623
1737
  }, [groupId]);
1624
1738
  const startDragging = useCallback((dragHandleId, event) => {
1625
1739
  const {
1626
- direction,
1627
- layout
1740
+ direction
1628
1741
  } = committedValuesRef.current;
1742
+ const {
1743
+ layout
1744
+ } = eagerValuesRef.current;
1629
1745
  const handleElement = getResizeHandleElement(dragHandleId);
1630
1746
  const initialCursorPosition = getResizeEventCursorPosition(direction, event);
1631
1747
  setDragState({
@@ -1639,16 +1755,86 @@ function PanelGroupWithForwardedRef({
1639
1755
  resetGlobalCursorStyle();
1640
1756
  setDragState(null);
1641
1757
  }, []);
1758
+ const unregisterPanelRef = useRef({
1759
+ pendingPanelIds: new Set(),
1760
+ timeout: null
1761
+ });
1642
1762
  const unregisterPanel = useCallback(panelData => {
1643
- delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1644
- setPanelDataArray(panelDataArray => {
1645
- const index = panelDataArray.indexOf(panelData);
1646
- if (index >= 0) {
1647
- panelDataArray = [...panelDataArray];
1648
- panelDataArray.splice(index, 1);
1763
+ const {
1764
+ id: groupId,
1765
+ onLayout
1766
+ } = committedValuesRef.current;
1767
+ const {
1768
+ layout: prevLayout,
1769
+ panelDataArray
1770
+ } = eagerValuesRef.current;
1771
+ const index = panelDataArray.indexOf(panelData);
1772
+ if (index >= 0) {
1773
+ panelDataArray.splice(index, 1);
1774
+ unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1775
+ }
1776
+ if (unregisterPanelRef.current.timeout != null) {
1777
+ clearTimeout(unregisterPanelRef.current.timeout);
1778
+ }
1779
+
1780
+ // Batch panel unmounts so that we only calculate layout once;
1781
+ // This is more efficient and avoids misleading warnings in development mode.
1782
+ // We can't check the DOM to detect this because Panel elements have not yet been removed.
1783
+ unregisterPanelRef.current.timeout = setTimeout(() => {
1784
+ const {
1785
+ pendingPanelIds
1786
+ } = unregisterPanelRef.current;
1787
+ const map = panelIdToLastNotifiedMixedSizesMapRef.current;
1788
+
1789
+ // TRICKY
1790
+ // Strict effects mode
1791
+ let unmountDueToStrictMode = false;
1792
+ pendingPanelIds.forEach(panelId => {
1793
+ pendingPanelIds.delete(panelId);
1794
+ if (panelDataArray.find(({
1795
+ id
1796
+ }) => id === panelId) == null) {
1797
+ unmountDueToStrictMode = true;
1798
+
1799
+ // TRICKY
1800
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1801
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1802
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1803
+ delete map[panelData.id];
1804
+ }
1805
+ });
1806
+ if (!unmountDueToStrictMode) {
1807
+ return;
1649
1808
  }
1650
- return panelDataArray;
1651
- });
1809
+ if (panelDataArray.length === 0) {
1810
+ // The group is unmounting; skip layout calculation.
1811
+ return;
1812
+ }
1813
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1814
+ let unsafeLayout = calculateUnsafeDefaultLayout({
1815
+ groupSizePixels,
1816
+ panelDataArray
1817
+ });
1818
+
1819
+ // Validate even saved layouts in case something has changed since last render
1820
+ // e.g. for pixel groups, this could be the size of the window
1821
+ const nextLayout = validatePanelGroupLayout({
1822
+ groupSizePixels,
1823
+ layout: unsafeLayout,
1824
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1825
+ });
1826
+ if (!areEqual(prevLayout, nextLayout)) {
1827
+ setLayout(nextLayout);
1828
+ eagerValuesRef.current.layout = nextLayout;
1829
+ if (onLayout) {
1830
+ onLayout(nextLayout.map(sizePercentage => ({
1831
+ sizePercentage,
1832
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1833
+ })));
1834
+ }
1835
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1836
+ }
1837
+ }, 0);
1652
1838
  }, []);
1653
1839
  const context = useMemo(() => ({
1654
1840
  collapsePanel,