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.
@@ -67,6 +67,7 @@ function PanelWithForwardedRef({
67
67
  expandPanel,
68
68
  getPanelSize,
69
69
  getPanelStyle,
70
+ groupId,
70
71
  isPanelCollapsed,
71
72
  registerPanel,
72
73
  resizePanel,
@@ -143,6 +144,7 @@ function PanelWithForwardedRef({
143
144
  // CSS selectors
144
145
  "data-panel": "",
145
146
  "data-panel-id": panelId,
147
+ "data-panel-group-id": groupId,
146
148
  // e2e test attributes
147
149
  "data-panel-collapsible": collapsible || undefined ,
148
150
  "data-panel-size": parseFloat("" + style.flexGrow).toFixed(1)
@@ -617,6 +619,7 @@ function getResizeHandlePanelIds(groupId, handleId, panelsArray) {
617
619
 
618
620
  function useWindowSplitterPanelGroupBehavior({
619
621
  committedValuesRef,
622
+ eagerValuesRef,
620
623
  groupId,
621
624
  layout,
622
625
  panelDataArray,
@@ -628,7 +631,7 @@ function useWindowSplitterPanelGroupBehavior({
628
631
  useEffect(() => {
629
632
  const {
630
633
  panelDataArray
631
- } = committedValuesRef.current;
634
+ } = eagerValuesRef.current;
632
635
  const groupElement = getPanelGroupElement(groupId);
633
636
  assert(groupElement != null, `No group found for id "${groupId}"`);
634
637
  const handles = getResizeHandleElementsForGroup(groupId);
@@ -686,7 +689,7 @@ function useWindowSplitterPanelGroupBehavior({
686
689
  return () => {
687
690
  cleanupFunctions.forEach(cleanupFunction => cleanupFunction());
688
691
  };
689
- }, [committedValuesRef, groupId, layout, panelDataArray, setLayout]);
692
+ }, [committedValuesRef, eagerValuesRef, groupId, layout, panelDataArray, setLayout]);
690
693
  }
691
694
 
692
695
  function areEqual(arrayA, arrayB) {
@@ -783,6 +786,44 @@ function calculateDeltaPercentage(event, groupId, dragHandleId, direction, initi
783
786
  }
784
787
  }
785
788
 
789
+ function calculateUnsafeDefaultLayout({
790
+ groupSizePixels,
791
+ panelDataArray
792
+ }) {
793
+ const layout = Array(panelDataArray.length);
794
+ const panelDataConstraints = panelDataArray.map(panelData => panelData.constraints);
795
+ let numPanelsWithSizes = 0;
796
+ let remainingSize = 100;
797
+
798
+ // Distribute default sizes first
799
+ for (let index = 0; index < panelDataArray.length; index++) {
800
+ const {
801
+ defaultSizePercentage
802
+ } = computePercentagePanelConstraints(panelDataConstraints, index, groupSizePixels);
803
+ if (defaultSizePercentage != null) {
804
+ numPanelsWithSizes++;
805
+ layout[index] = defaultSizePercentage;
806
+ remainingSize -= defaultSizePercentage;
807
+ }
808
+ }
809
+
810
+ // Remaining size should be distributed evenly between panels without default sizes
811
+ for (let index = 0; index < panelDataArray.length; index++) {
812
+ const {
813
+ defaultSizePercentage
814
+ } = computePercentagePanelConstraints(panelDataConstraints, index, groupSizePixels);
815
+ if (defaultSizePercentage != null) {
816
+ continue;
817
+ }
818
+ const numRemainingPanels = panelDataArray.length - numPanelsWithSizes;
819
+ const size = remainingSize / numRemainingPanels;
820
+ numPanelsWithSizes++;
821
+ layout[index] = size;
822
+ remainingSize -= size;
823
+ }
824
+ return layout;
825
+ }
826
+
786
827
  function convertPercentageToPixels(percentage, groupSizePixels) {
787
828
  return percentage / 100 * groupSizePixels;
788
829
  }
@@ -933,6 +974,10 @@ function debounce(callback, durationMs = 10) {
933
974
  return callable;
934
975
  }
935
976
 
977
+ function getPanelElementsForGroup(groupId) {
978
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
979
+ }
980
+
936
981
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
937
982
  // or on a browser with cookies/storage disabled.
938
983
  // In either case, this function avoids accessing localStorage until needed,
@@ -988,6 +1033,15 @@ function loadSerializedPanelGroupState(autoSaveId, storage) {
988
1033
  } catch (error) {}
989
1034
  return null;
990
1035
  }
1036
+ function loadPanelLayout(autoSaveId, panels, storage) {
1037
+ const state = loadSerializedPanelGroupState(autoSaveId, storage);
1038
+ if (state) {
1039
+ var _state$key;
1040
+ const key = getSerializationKey(panels);
1041
+ return (_state$key = state[key]) !== null && _state$key !== void 0 ? _state$key : null;
1042
+ }
1043
+ return null;
1044
+ }
991
1045
  function savePanelGroupLayout(autoSaveId, panels, sizes, storage) {
992
1046
  const key = getSerializationKey(panels);
993
1047
  const state = loadSerializedPanelGroupState(autoSaveId, storage) || {};
@@ -999,6 +1053,12 @@ function savePanelGroupLayout(autoSaveId, panels, sizes, storage) {
999
1053
  }
1000
1054
  }
1001
1055
 
1056
+ function shouldMonitorPixelBasedConstraints(constraints) {
1057
+ return constraints.some(constraints => {
1058
+ return constraints.collapsedSizePixels !== undefined || constraints.maxSizePixels !== undefined || constraints.minSizePixels !== undefined;
1059
+ });
1060
+ }
1061
+
1002
1062
  function validatePanelConstraints({
1003
1063
  groupSizePixels,
1004
1064
  panelConstraints,
@@ -1144,7 +1204,7 @@ const defaultStorage = {
1144
1204
  };
1145
1205
  const debounceMap = {};
1146
1206
  function PanelGroupWithForwardedRef({
1147
- autoSaveId,
1207
+ autoSaveId = null,
1148
1208
  children,
1149
1209
  className: classNameFromProps = "",
1150
1210
  dataAttributes,
@@ -1161,20 +1221,22 @@ function PanelGroupWithForwardedRef({
1161
1221
  const groupId = useUniqueId(idFromProps);
1162
1222
  const [dragState, setDragState] = useState(null);
1163
1223
  const [layout, setLayout] = useState([]);
1164
- const [panelDataArray, setPanelDataArray] = useState([]);
1165
1224
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1166
1225
  const panelSizeBeforeCollapseRef = useRef(new Map());
1167
1226
  const prevDeltaRef = useRef(0);
1168
- const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1169
1227
  const committedValuesRef = useRef({
1228
+ autoSaveId,
1170
1229
  direction,
1171
1230
  dragState,
1172
1231
  id: groupId,
1173
1232
  keyboardResizeByPercentage,
1174
1233
  keyboardResizeByPixels,
1175
- layout,
1176
1234
  onLayout,
1177
- panelDataArray
1235
+ storage
1236
+ });
1237
+ const eagerValuesRef = useRef({
1238
+ layout,
1239
+ panelDataArray: []
1178
1240
  });
1179
1241
  const devWarningsRef = useRef({
1180
1242
  didLogIdAndOrderWarning: false,
@@ -1185,9 +1247,11 @@ function PanelGroupWithForwardedRef({
1185
1247
  getId: () => committedValuesRef.current.id,
1186
1248
  getLayout: () => {
1187
1249
  const {
1188
- id: groupId,
1189
- layout
1250
+ id: groupId
1190
1251
  } = committedValuesRef.current;
1252
+ const {
1253
+ layout
1254
+ } = eagerValuesRef.current;
1191
1255
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1192
1256
  return layout.map(sizePercentage => {
1193
1257
  return {
@@ -1199,10 +1263,12 @@ function PanelGroupWithForwardedRef({
1199
1263
  setLayout: mixedSizes => {
1200
1264
  const {
1201
1265
  id: groupId,
1266
+ onLayout
1267
+ } = committedValuesRef.current;
1268
+ const {
1202
1269
  layout: prevLayout,
1203
- onLayout,
1204
1270
  panelDataArray
1205
- } = committedValuesRef.current;
1271
+ } = eagerValuesRef.current;
1206
1272
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1207
1273
  const unsafeLayout = mixedSizes.map(mixedSize => getPercentageSizeFromMixedSizes(mixedSize, groupSizePixels));
1208
1274
  const safeLayout = validatePanelGroupLayout({
@@ -1212,6 +1278,7 @@ function PanelGroupWithForwardedRef({
1212
1278
  });
1213
1279
  if (!areEqual(prevLayout, safeLayout)) {
1214
1280
  setLayout(safeLayout);
1281
+ eagerValuesRef.current.layout = safeLayout;
1215
1282
  if (onLayout) {
1216
1283
  onLayout(safeLayout.map(sizePercentage => ({
1217
1284
  sizePercentage,
@@ -1222,14 +1289,20 @@ function PanelGroupWithForwardedRef({
1222
1289
  }
1223
1290
  }
1224
1291
  }), []);
1292
+
1225
1293
  useWindowSplitterPanelGroupBehavior({
1226
1294
  committedValuesRef,
1295
+ eagerValuesRef,
1227
1296
  groupId,
1228
1297
  layout,
1229
- panelDataArray,
1298
+ panelDataArray: eagerValuesRef.current.panelDataArray,
1230
1299
  setLayout
1231
1300
  });
1232
1301
  useEffect(() => {
1302
+ const {
1303
+ panelDataArray
1304
+ } = eagerValuesRef.current;
1305
+
1233
1306
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1234
1307
  if (autoSaveId) {
1235
1308
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1242,20 +1315,20 @@ function PanelGroupWithForwardedRef({
1242
1315
  }
1243
1316
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1244
1317
  }
1245
- }, [autoSaveId, layout, panelDataArray, storage]);
1318
+ }, [autoSaveId, layout, storage]);
1246
1319
 
1247
1320
  // DEV warnings
1248
1321
  useEffect(() => {
1249
1322
  {
1323
+ const {
1324
+ panelDataArray
1325
+ } = eagerValuesRef.current;
1250
1326
  const {
1251
1327
  didLogIdAndOrderWarning,
1252
1328
  didLogPanelConstraintsWarning,
1253
1329
  prevPanelIds
1254
1330
  } = devWarningsRef.current;
1255
1331
  if (!didLogIdAndOrderWarning) {
1256
- const {
1257
- panelDataArray
1258
- } = committedValuesRef.current;
1259
1332
  const panelIds = panelDataArray.map(({
1260
1333
  id
1261
1334
  }) => id);
@@ -1292,22 +1365,13 @@ function PanelGroupWithForwardedRef({
1292
1365
 
1293
1366
  // External APIs are safe to memoize via committed values ref
1294
1367
  const collapsePanel = useCallback(panelData => {
1368
+ const {
1369
+ onLayout
1370
+ } = committedValuesRef.current;
1295
1371
  const {
1296
1372
  layout: prevLayout,
1297
- onLayout,
1298
1373
  panelDataArray
1299
- } = committedValuesRef.current;
1300
-
1301
- // See issues/211
1302
- if (panelDataArray.find(({
1303
- id
1304
- }) => id === panelData.id) == null) {
1305
- setImperativeApiQueue(prev => [...prev, {
1306
- panelData,
1307
- type: "collapse"
1308
- }]);
1309
- return;
1310
- }
1374
+ } = eagerValuesRef.current;
1311
1375
  if (panelData.constraints.collapsible) {
1312
1376
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1313
1377
  const {
@@ -1332,6 +1396,7 @@ function PanelGroupWithForwardedRef({
1332
1396
  });
1333
1397
  if (!compareLayouts(prevLayout, nextLayout)) {
1334
1398
  setLayout(nextLayout);
1399
+ eagerValuesRef.current.layout = nextLayout;
1335
1400
  if (onLayout) {
1336
1401
  onLayout(nextLayout.map(sizePercentage => ({
1337
1402
  sizePercentage,
@@ -1346,22 +1411,13 @@ function PanelGroupWithForwardedRef({
1346
1411
 
1347
1412
  // External APIs are safe to memoize via committed values ref
1348
1413
  const expandPanel = useCallback(panelData => {
1414
+ const {
1415
+ onLayout
1416
+ } = committedValuesRef.current;
1349
1417
  const {
1350
1418
  layout: prevLayout,
1351
- onLayout,
1352
1419
  panelDataArray
1353
- } = committedValuesRef.current;
1354
-
1355
- // See issues/211
1356
- if (panelDataArray.find(({
1357
- id
1358
- }) => id === panelData.id) == null) {
1359
- setImperativeApiQueue(prev => [...prev, {
1360
- panelData,
1361
- type: "expand"
1362
- }]);
1363
- return;
1364
- }
1420
+ } = eagerValuesRef.current;
1365
1421
  if (panelData.constraints.collapsible) {
1366
1422
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1367
1423
  const {
@@ -1387,6 +1443,7 @@ function PanelGroupWithForwardedRef({
1387
1443
  });
1388
1444
  if (!compareLayouts(prevLayout, nextLayout)) {
1389
1445
  setLayout(nextLayout);
1446
+ eagerValuesRef.current.layout = nextLayout;
1390
1447
  if (onLayout) {
1391
1448
  onLayout(nextLayout.map(sizePercentage => ({
1392
1449
  sizePercentage,
@@ -1404,7 +1461,7 @@ function PanelGroupWithForwardedRef({
1404
1461
  const {
1405
1462
  layout,
1406
1463
  panelDataArray
1407
- } = committedValuesRef.current;
1464
+ } = eagerValuesRef.current;
1408
1465
  const {
1409
1466
  panelSizePercentage,
1410
1467
  panelSizePixels
@@ -1417,6 +1474,9 @@ function PanelGroupWithForwardedRef({
1417
1474
 
1418
1475
  // This API should never read from committedValuesRef
1419
1476
  const getPanelStyle = useCallback(panelData => {
1477
+ const {
1478
+ panelDataArray
1479
+ } = eagerValuesRef.current;
1420
1480
  const panelIndex = panelDataArray.indexOf(panelData);
1421
1481
  return computePanelFlexBoxStyle({
1422
1482
  dragState,
@@ -1424,14 +1484,14 @@ function PanelGroupWithForwardedRef({
1424
1484
  panelData: panelDataArray,
1425
1485
  panelIndex
1426
1486
  });
1427
- }, [dragState, layout, panelDataArray]);
1487
+ }, [dragState, layout]);
1428
1488
 
1429
1489
  // External APIs are safe to memoize via committed values ref
1430
1490
  const isPanelCollapsed = useCallback(panelData => {
1431
1491
  const {
1432
1492
  layout,
1433
1493
  panelDataArray
1434
- } = committedValuesRef.current;
1494
+ } = eagerValuesRef.current;
1435
1495
  const {
1436
1496
  collapsedSizePercentage,
1437
1497
  collapsible,
@@ -1445,7 +1505,7 @@ function PanelGroupWithForwardedRef({
1445
1505
  const {
1446
1506
  layout,
1447
1507
  panelDataArray
1448
- } = committedValuesRef.current;
1508
+ } = eagerValuesRef.current;
1449
1509
  const {
1450
1510
  collapsedSizePercentage,
1451
1511
  collapsible,
@@ -1454,22 +1514,82 @@ function PanelGroupWithForwardedRef({
1454
1514
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1455
1515
  }, [groupId]);
1456
1516
  const registerPanel = useCallback(panelData => {
1457
- setPanelDataArray(prevPanelDataArray => {
1458
- const nextPanelDataArray = [...prevPanelDataArray, panelData];
1459
- return nextPanelDataArray.sort((panelA, panelB) => {
1460
- const orderA = panelA.order;
1461
- const orderB = panelB.order;
1462
- if (orderA == null && orderB == null) {
1463
- return 0;
1464
- } else if (orderA == null) {
1465
- return -1;
1466
- } else if (orderB == null) {
1467
- return 1;
1468
- } else {
1469
- return orderA - orderB;
1470
- }
1517
+ const {
1518
+ autoSaveId,
1519
+ id: groupId,
1520
+ onLayout,
1521
+ storage
1522
+ } = committedValuesRef.current;
1523
+ const {
1524
+ layout: prevLayout,
1525
+ panelDataArray
1526
+ } = eagerValuesRef.current;
1527
+ panelDataArray.push(panelData);
1528
+ panelDataArray.sort((panelA, panelB) => {
1529
+ const orderA = panelA.order;
1530
+ const orderB = panelB.order;
1531
+ if (orderA == null && orderB == null) {
1532
+ return 0;
1533
+ } else if (orderA == null) {
1534
+ return -1;
1535
+ } else if (orderB == null) {
1536
+ return 1;
1537
+ } else {
1538
+ return orderA - orderB;
1539
+ }
1540
+ });
1541
+
1542
+ // Wait until all panels have registered before we try to compute layout;
1543
+ // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1544
+ const panelElements = getPanelElementsForGroup(groupId);
1545
+ if (panelElements.length !== panelDataArray.length) {
1546
+ return;
1547
+ }
1548
+
1549
+ // If this panel has been configured to persist sizing information,
1550
+ // default size should be restored from local storage if possible.
1551
+ let unsafeLayout = null;
1552
+ if (autoSaveId) {
1553
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1554
+ }
1555
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1556
+ if (groupSizePixels <= 0) {
1557
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1558
+ constraints
1559
+ }) => constraints))) {
1560
+ // Wait until the group has rendered a non-zero size before computing layout.
1561
+ return;
1562
+ }
1563
+ }
1564
+ if (unsafeLayout == null) {
1565
+ unsafeLayout = calculateUnsafeDefaultLayout({
1566
+ groupSizePixels,
1567
+ panelDataArray
1471
1568
  });
1569
+ }
1570
+
1571
+ // Validate even saved layouts in case something has changed since last render
1572
+ // e.g. for pixel groups, this could be the size of the window
1573
+ const nextLayout = validatePanelGroupLayout({
1574
+ groupSizePixels,
1575
+ layout: unsafeLayout,
1576
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1472
1577
  });
1578
+
1579
+ // Offscreen mode makes this a bit weird;
1580
+ // Panels unregister when hidden and re-register when shown again,
1581
+ // but the overall layout doesn't change between these two cases.
1582
+ setLayout(nextLayout);
1583
+ eagerValuesRef.current.layout = nextLayout;
1584
+ if (!areEqual(prevLayout, nextLayout)) {
1585
+ if (onLayout) {
1586
+ onLayout(nextLayout.map(sizePercentage => ({
1587
+ sizePercentage,
1588
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1589
+ })));
1590
+ }
1591
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1592
+ }
1473
1593
  }, []);
1474
1594
  const registerResizeHandle = useCallback(dragHandleId => {
1475
1595
  return function resizeHandler(event) {
@@ -1480,10 +1600,12 @@ function PanelGroupWithForwardedRef({
1480
1600
  id: groupId,
1481
1601
  keyboardResizeByPercentage,
1482
1602
  keyboardResizeByPixels,
1483
- onLayout,
1484
- panelDataArray,
1485
- layout: prevLayout
1603
+ onLayout
1486
1604
  } = committedValuesRef.current;
1605
+ const {
1606
+ layout: prevLayout,
1607
+ panelDataArray
1608
+ } = eagerValuesRef.current;
1487
1609
  const {
1488
1610
  initialLayout
1489
1611
  } = dragState !== null && dragState !== void 0 ? dragState : {};
@@ -1539,6 +1661,7 @@ function PanelGroupWithForwardedRef({
1539
1661
  }
1540
1662
  if (layoutChanged) {
1541
1663
  setLayout(nextLayout);
1664
+ eagerValuesRef.current.layout = nextLayout;
1542
1665
  if (onLayout) {
1543
1666
  onLayout(nextLayout.map(sizePercentage => ({
1544
1667
  sizePercentage,
@@ -1552,23 +1675,13 @@ function PanelGroupWithForwardedRef({
1552
1675
 
1553
1676
  // External APIs are safe to memoize via committed values ref
1554
1677
  const resizePanel = useCallback((panelData, mixedSizes) => {
1678
+ const {
1679
+ onLayout
1680
+ } = committedValuesRef.current;
1555
1681
  const {
1556
1682
  layout: prevLayout,
1557
- onLayout,
1558
1683
  panelDataArray
1559
- } = committedValuesRef.current;
1560
-
1561
- // See issues/211
1562
- if (panelDataArray.find(({
1563
- id
1564
- }) => id === panelData.id) == null) {
1565
- setImperativeApiQueue(prev => [...prev, {
1566
- panelData,
1567
- mixedSizes,
1568
- type: "resize"
1569
- }]);
1570
- return;
1571
- }
1684
+ } = eagerValuesRef.current;
1572
1685
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1573
1686
  const {
1574
1687
  groupSizePixels,
@@ -1588,6 +1701,7 @@ function PanelGroupWithForwardedRef({
1588
1701
  });
1589
1702
  if (!compareLayouts(prevLayout, nextLayout)) {
1590
1703
  setLayout(nextLayout);
1704
+ eagerValuesRef.current.layout = nextLayout;
1591
1705
  if (onLayout) {
1592
1706
  onLayout(nextLayout.map(sizePercentage => ({
1593
1707
  sizePercentage,
@@ -1599,9 +1713,11 @@ function PanelGroupWithForwardedRef({
1599
1713
  }, [groupId]);
1600
1714
  const startDragging = useCallback((dragHandleId, event) => {
1601
1715
  const {
1602
- direction,
1603
- layout
1716
+ direction
1604
1717
  } = committedValuesRef.current;
1718
+ const {
1719
+ layout
1720
+ } = eagerValuesRef.current;
1605
1721
  const handleElement = getResizeHandleElement(dragHandleId);
1606
1722
  const initialCursorPosition = getResizeEventCursorPosition(direction, event);
1607
1723
  setDragState({
@@ -1615,16 +1731,86 @@ function PanelGroupWithForwardedRef({
1615
1731
  resetGlobalCursorStyle();
1616
1732
  setDragState(null);
1617
1733
  }, []);
1734
+ const unregisterPanelRef = useRef({
1735
+ pendingPanelIds: new Set(),
1736
+ timeout: null
1737
+ });
1618
1738
  const unregisterPanel = useCallback(panelData => {
1619
- delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1620
- setPanelDataArray(panelDataArray => {
1621
- const index = panelDataArray.indexOf(panelData);
1622
- if (index >= 0) {
1623
- panelDataArray = [...panelDataArray];
1624
- panelDataArray.splice(index, 1);
1739
+ const {
1740
+ id: groupId,
1741
+ onLayout
1742
+ } = committedValuesRef.current;
1743
+ const {
1744
+ layout: prevLayout,
1745
+ panelDataArray
1746
+ } = eagerValuesRef.current;
1747
+ const index = panelDataArray.indexOf(panelData);
1748
+ if (index >= 0) {
1749
+ panelDataArray.splice(index, 1);
1750
+ unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1751
+ }
1752
+ if (unregisterPanelRef.current.timeout != null) {
1753
+ clearTimeout(unregisterPanelRef.current.timeout);
1754
+ }
1755
+
1756
+ // Batch panel unmounts so that we only calculate layout once;
1757
+ // This is more efficient and avoids misleading warnings in development mode.
1758
+ // We can't check the DOM to detect this because Panel elements have not yet been removed.
1759
+ unregisterPanelRef.current.timeout = setTimeout(() => {
1760
+ const {
1761
+ pendingPanelIds
1762
+ } = unregisterPanelRef.current;
1763
+ const map = panelIdToLastNotifiedMixedSizesMapRef.current;
1764
+
1765
+ // TRICKY
1766
+ // Strict effects mode
1767
+ let unmountDueToStrictMode = false;
1768
+ pendingPanelIds.forEach(panelId => {
1769
+ pendingPanelIds.delete(panelId);
1770
+ if (panelDataArray.find(({
1771
+ id
1772
+ }) => id === panelId) == null) {
1773
+ unmountDueToStrictMode = true;
1774
+
1775
+ // TRICKY
1776
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1777
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1778
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1779
+ delete map[panelData.id];
1780
+ }
1781
+ });
1782
+ if (!unmountDueToStrictMode) {
1783
+ return;
1625
1784
  }
1626
- return panelDataArray;
1627
- });
1785
+ if (panelDataArray.length === 0) {
1786
+ // The group is unmounting; skip layout calculation.
1787
+ return;
1788
+ }
1789
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1790
+ let unsafeLayout = calculateUnsafeDefaultLayout({
1791
+ groupSizePixels,
1792
+ panelDataArray
1793
+ });
1794
+
1795
+ // Validate even saved layouts in case something has changed since last render
1796
+ // e.g. for pixel groups, this could be the size of the window
1797
+ const nextLayout = validatePanelGroupLayout({
1798
+ groupSizePixels,
1799
+ layout: unsafeLayout,
1800
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1801
+ });
1802
+ if (!areEqual(prevLayout, nextLayout)) {
1803
+ setLayout(nextLayout);
1804
+ eagerValuesRef.current.layout = nextLayout;
1805
+ if (onLayout) {
1806
+ onLayout(nextLayout.map(sizePercentage => ({
1807
+ sizePercentage,
1808
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1809
+ })));
1810
+ }
1811
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1812
+ }
1813
+ }, 0);
1628
1814
  }, []);
1629
1815
  const context = useMemo(() => ({
1630
1816
  collapsePanel,