react-resizable-panels 0.0.58 → 0.0.60

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,
@@ -156,6 +157,7 @@ function PanelWithForwardedRef({
156
157
  // CSS selectors
157
158
  "data-panel": "",
158
159
  "data-panel-id": panelId,
160
+ "data-panel-group-id": groupId,
159
161
  // e2e test attributes
160
162
  "data-panel-collapsible": undefined,
161
163
  "data-panel-size": undefined
@@ -168,8 +170,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
168
170
  PanelWithForwardedRef.displayName = "Panel";
169
171
  Panel.displayName = "forwardRef(Panel)";
170
172
 
171
- const PRECISION = 10;
172
-
173
173
  function convertPixelsToPercentage(pixels, groupSizePixels) {
174
174
  return pixels / groupSizePixels * 100;
175
175
  }
@@ -247,6 +247,8 @@ function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, gr
247
247
  };
248
248
  }
249
249
 
250
+ const PRECISION = 10;
251
+
250
252
  function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
251
253
  actual = parseFloat(actual.toFixed(fractionDigits));
252
254
  expected = parseFloat(expected.toFixed(fractionDigits));
@@ -640,15 +642,10 @@ function useWindowSplitterPanelGroupBehavior({
640
642
  });
641
643
  useEffect(() => {
642
644
  const {
643
- direction,
644
645
  panelDataArray
645
646
  } = committedValuesRef.current;
646
647
  const groupElement = getPanelGroupElement(groupId);
647
648
  assert(groupElement != null, `No group found for id "${groupId}"`);
648
- const {
649
- height,
650
- width
651
- } = groupElement.getBoundingClientRect();
652
649
  const handles = getResizeHandleElementsForGroup(groupId);
653
650
  const cleanupFunctions = handles.map(handle => {
654
651
  const handleId = handle.getAttribute("data-panel-resize-handle-id");
@@ -668,21 +665,19 @@ function useWindowSplitterPanelGroupBehavior({
668
665
  if (index >= 0) {
669
666
  const panelData = panelDataArray[index];
670
667
  const size = layout[index];
671
- if (size != null) {
672
- var _getPercentageSizeFro;
668
+ if (size != null && panelData.constraints.collapsible) {
669
+ var _getPercentageSizeFro, _getPercentageSizeFro2;
673
670
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
674
- const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
671
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
672
+ sizePercentage: panelData.constraints.collapsedSizePercentage,
673
+ sizePixels: panelData.constraints.collapsedSizePixels
674
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
675
+ const minSize = (_getPercentageSizeFro2 = getPercentageSizeFromMixedSizes({
675
676
  sizePercentage: panelData.constraints.minSizePercentage,
676
677
  sizePixels: panelData.constraints.minSizePixels
677
- }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
678
- let delta = 0;
679
- if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
680
- delta = direction === "horizontal" ? width : height;
681
- } else {
682
- delta = -(direction === "horizontal" ? width : height);
683
- }
678
+ }, groupSizePixels)) !== null && _getPercentageSizeFro2 !== void 0 ? _getPercentageSizeFro2 : 0;
684
679
  const nextLayout = adjustLayoutByDelta({
685
- delta,
680
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
686
681
  groupSizePixels,
687
682
  layout,
688
683
  panelConstraints: panelDataArray.map(panelData => panelData.constraints),
@@ -803,6 +798,44 @@ function calculateDeltaPercentage(event, groupId, dragHandleId, direction, initi
803
798
  }
804
799
  }
805
800
 
801
+ function calculateUnsafeDefaultLayout({
802
+ groupSizePixels,
803
+ panelDataArray
804
+ }) {
805
+ const layout = Array(panelDataArray.length);
806
+ const panelDataConstraints = panelDataArray.map(panelData => panelData.constraints);
807
+ let numPanelsWithSizes = 0;
808
+ let remainingSize = 100;
809
+
810
+ // Distribute default sizes first
811
+ for (let index = 0; index < panelDataArray.length; index++) {
812
+ const {
813
+ defaultSizePercentage
814
+ } = computePercentagePanelConstraints(panelDataConstraints, index, groupSizePixels);
815
+ if (defaultSizePercentage != null) {
816
+ numPanelsWithSizes++;
817
+ layout[index] = defaultSizePercentage;
818
+ remainingSize -= defaultSizePercentage;
819
+ }
820
+ }
821
+
822
+ // Remaining size should be distributed evenly between panels without default sizes
823
+ for (let index = 0; index < panelDataArray.length; index++) {
824
+ const {
825
+ defaultSizePercentage
826
+ } = computePercentagePanelConstraints(panelDataConstraints, index, groupSizePixels);
827
+ if (defaultSizePercentage != null) {
828
+ continue;
829
+ }
830
+ const numRemainingPanels = panelDataArray.length - numPanelsWithSizes;
831
+ const size = remainingSize / numRemainingPanels;
832
+ numPanelsWithSizes++;
833
+ layout[index] = size;
834
+ remainingSize -= size;
835
+ }
836
+ return layout;
837
+ }
838
+
806
839
  function convertPercentageToPixels(percentage, groupSizePixels) {
807
840
  return percentage / 100 * groupSizePixels;
808
841
  }
@@ -953,6 +986,10 @@ function debounce(callback, durationMs = 10) {
953
986
  return callable;
954
987
  }
955
988
 
989
+ function getPanelElementsForGroup(groupId) {
990
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
991
+ }
992
+
956
993
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
957
994
  // or on a browser with cookies/storage disabled.
958
995
  // In either case, this function avoids accessing localStorage until needed,
@@ -1008,6 +1045,15 @@ function loadSerializedPanelGroupState(autoSaveId, storage) {
1008
1045
  } catch (error) {}
1009
1046
  return null;
1010
1047
  }
1048
+ function loadPanelLayout(autoSaveId, panels, storage) {
1049
+ const state = loadSerializedPanelGroupState(autoSaveId, storage);
1050
+ if (state) {
1051
+ var _state$key;
1052
+ const key = getSerializationKey(panels);
1053
+ return (_state$key = state[key]) !== null && _state$key !== void 0 ? _state$key : null;
1054
+ }
1055
+ return null;
1056
+ }
1011
1057
  function savePanelGroupLayout(autoSaveId, panels, sizes, storage) {
1012
1058
  const key = getSerializationKey(panels);
1013
1059
  const state = loadSerializedPanelGroupState(autoSaveId, storage) || {};
@@ -1019,6 +1065,12 @@ function savePanelGroupLayout(autoSaveId, panels, sizes, storage) {
1019
1065
  }
1020
1066
  }
1021
1067
 
1068
+ function shouldMonitorPixelBasedConstraints(constraints) {
1069
+ return constraints.some(constraints => {
1070
+ return constraints.collapsedSizePixels !== undefined || constraints.maxSizePixels !== undefined || constraints.minSizePixels !== undefined;
1071
+ });
1072
+ }
1073
+
1022
1074
  // All units must be in percentages; pixel values should be pre-converted
1023
1075
  function validatePanelGroupLayout({
1024
1076
  groupSizePixels,
@@ -1087,7 +1139,7 @@ const defaultStorage = {
1087
1139
  };
1088
1140
  const debounceMap = {};
1089
1141
  function PanelGroupWithForwardedRef({
1090
- autoSaveId,
1142
+ autoSaveId = null,
1091
1143
  children,
1092
1144
  className: classNameFromProps = "",
1093
1145
  dataAttributes,
@@ -1104,11 +1156,11 @@ function PanelGroupWithForwardedRef({
1104
1156
  const groupId = useUniqueId(idFromProps);
1105
1157
  const [dragState, setDragState] = useState(null);
1106
1158
  const [layout, setLayout] = useState([]);
1107
- const [panelDataArray, setPanelDataArray] = useState([]);
1108
1159
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1109
1160
  const panelSizeBeforeCollapseRef = useRef(new Map());
1110
1161
  const prevDeltaRef = useRef(0);
1111
1162
  const committedValuesRef = useRef({
1163
+ autoSaveId,
1112
1164
  direction,
1113
1165
  dragState,
1114
1166
  id: groupId,
@@ -1116,7 +1168,8 @@ function PanelGroupWithForwardedRef({
1116
1168
  keyboardResizeByPixels,
1117
1169
  layout,
1118
1170
  onLayout,
1119
- panelDataArray
1171
+ panelDataArray: [],
1172
+ storage
1120
1173
  });
1121
1174
  useRef({
1122
1175
  didLogIdAndOrderWarning: false,
@@ -1154,6 +1207,7 @@ function PanelGroupWithForwardedRef({
1154
1207
  });
1155
1208
  if (!areEqual(prevLayout, safeLayout)) {
1156
1209
  setLayout(safeLayout);
1210
+ committedValuesRef.current.layout = safeLayout;
1157
1211
  if (onLayout) {
1158
1212
  onLayout(safeLayout.map(sizePercentage => ({
1159
1213
  sizePercentage,
@@ -1164,14 +1218,19 @@ function PanelGroupWithForwardedRef({
1164
1218
  }
1165
1219
  }
1166
1220
  }), []);
1221
+
1167
1222
  useWindowSplitterPanelGroupBehavior({
1168
1223
  committedValuesRef,
1169
1224
  groupId,
1170
1225
  layout,
1171
- panelDataArray,
1226
+ panelDataArray: committedValuesRef.current.panelDataArray,
1172
1227
  setLayout
1173
1228
  });
1174
1229
  useEffect(() => {
1230
+ const {
1231
+ panelDataArray
1232
+ } = committedValuesRef.current;
1233
+
1175
1234
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1176
1235
  if (autoSaveId) {
1177
1236
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1184,7 +1243,7 @@ function PanelGroupWithForwardedRef({
1184
1243
  }
1185
1244
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1186
1245
  }
1187
- }, [autoSaveId, layout, panelDataArray, storage]);
1246
+ }, [autoSaveId, layout, storage]);
1188
1247
 
1189
1248
  // DEV warnings
1190
1249
  useEffect(() => {
@@ -1221,6 +1280,7 @@ function PanelGroupWithForwardedRef({
1221
1280
  });
1222
1281
  if (!compareLayouts(prevLayout, nextLayout)) {
1223
1282
  setLayout(nextLayout);
1283
+ committedValuesRef.current.layout = nextLayout;
1224
1284
  if (onLayout) {
1225
1285
  onLayout(nextLayout.map(sizePercentage => ({
1226
1286
  sizePercentage,
@@ -1265,6 +1325,7 @@ function PanelGroupWithForwardedRef({
1265
1325
  });
1266
1326
  if (!compareLayouts(prevLayout, nextLayout)) {
1267
1327
  setLayout(nextLayout);
1328
+ committedValuesRef.current.layout = nextLayout;
1268
1329
  if (onLayout) {
1269
1330
  onLayout(nextLayout.map(sizePercentage => ({
1270
1331
  sizePercentage,
@@ -1295,6 +1356,9 @@ function PanelGroupWithForwardedRef({
1295
1356
 
1296
1357
  // This API should never read from committedValuesRef
1297
1358
  const getPanelStyle = useCallback(panelData => {
1359
+ const {
1360
+ panelDataArray
1361
+ } = committedValuesRef.current;
1298
1362
  const panelIndex = panelDataArray.indexOf(panelData);
1299
1363
  return computePanelFlexBoxStyle({
1300
1364
  dragState,
@@ -1302,7 +1366,7 @@ function PanelGroupWithForwardedRef({
1302
1366
  panelData: panelDataArray,
1303
1367
  panelIndex
1304
1368
  });
1305
- }, [dragState, layout, panelDataArray]);
1369
+ }, [dragState, layout]);
1306
1370
 
1307
1371
  // External APIs are safe to memoize via committed values ref
1308
1372
  const isPanelCollapsed = useCallback(panelData => {
@@ -1332,22 +1396,76 @@ function PanelGroupWithForwardedRef({
1332
1396
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1333
1397
  }, [groupId]);
1334
1398
  const registerPanel = useCallback(panelData => {
1335
- setPanelDataArray(prevPanelDataArray => {
1336
- const nextPanelDataArray = [...prevPanelDataArray, panelData];
1337
- return nextPanelDataArray.sort((panelA, panelB) => {
1338
- const orderA = panelA.order;
1339
- const orderB = panelB.order;
1340
- if (orderA == null && orderB == null) {
1341
- return 0;
1342
- } else if (orderA == null) {
1343
- return -1;
1344
- } else if (orderB == null) {
1345
- return 1;
1346
- } else {
1347
- return orderA - orderB;
1348
- }
1399
+ const {
1400
+ autoSaveId,
1401
+ id: groupId,
1402
+ layout: prevLayout,
1403
+ onLayout,
1404
+ panelDataArray,
1405
+ storage
1406
+ } = committedValuesRef.current;
1407
+ panelDataArray.push(panelData);
1408
+ panelDataArray.sort((panelA, panelB) => {
1409
+ const orderA = panelA.order;
1410
+ const orderB = panelB.order;
1411
+ if (orderA == null && orderB == null) {
1412
+ return 0;
1413
+ } else if (orderA == null) {
1414
+ return -1;
1415
+ } else if (orderB == null) {
1416
+ return 1;
1417
+ } else {
1418
+ return orderA - orderB;
1419
+ }
1420
+ });
1421
+
1422
+ // Wait until all panels have registered before we try to compute layout;
1423
+ // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1424
+ const panelElements = getPanelElementsForGroup(groupId);
1425
+ if (panelElements.length !== panelDataArray.length) {
1426
+ return;
1427
+ }
1428
+
1429
+ // If this panel has been configured to persist sizing information,
1430
+ // default size should be restored from local storage if possible.
1431
+ let unsafeLayout = null;
1432
+ if (autoSaveId) {
1433
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1434
+ }
1435
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1436
+ if (groupSizePixels <= 0) {
1437
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1438
+ constraints
1439
+ }) => constraints))) {
1440
+ // Wait until the group has rendered a non-zero size before computing layout.
1441
+ return;
1442
+ }
1443
+ }
1444
+ if (unsafeLayout == null) {
1445
+ unsafeLayout = calculateUnsafeDefaultLayout({
1446
+ groupSizePixels,
1447
+ panelDataArray
1349
1448
  });
1449
+ }
1450
+
1451
+ // Validate even saved layouts in case something has changed since last render
1452
+ // e.g. for pixel groups, this could be the size of the window
1453
+ const nextLayout = validatePanelGroupLayout({
1454
+ groupSizePixels,
1455
+ layout: unsafeLayout,
1456
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1350
1457
  });
1458
+ if (!areEqual(prevLayout, nextLayout)) {
1459
+ setLayout(nextLayout);
1460
+ committedValuesRef.current.layout = nextLayout;
1461
+ if (onLayout) {
1462
+ onLayout(nextLayout.map(sizePercentage => ({
1463
+ sizePercentage,
1464
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1465
+ })));
1466
+ }
1467
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1468
+ }
1351
1469
  }, []);
1352
1470
  const registerResizeHandle = useCallback(dragHandleId => {
1353
1471
  return function resizeHandler(event) {
@@ -1417,6 +1535,7 @@ function PanelGroupWithForwardedRef({
1417
1535
  }
1418
1536
  if (layoutChanged) {
1419
1537
  setLayout(nextLayout);
1538
+ committedValuesRef.current.layout = nextLayout;
1420
1539
  if (onLayout) {
1421
1540
  onLayout(nextLayout.map(sizePercentage => ({
1422
1541
  sizePercentage,
@@ -1454,6 +1573,7 @@ function PanelGroupWithForwardedRef({
1454
1573
  });
1455
1574
  if (!compareLayouts(prevLayout, nextLayout)) {
1456
1575
  setLayout(nextLayout);
1576
+ committedValuesRef.current.layout = nextLayout;
1457
1577
  if (onLayout) {
1458
1578
  onLayout(nextLayout.map(sizePercentage => ({
1459
1579
  sizePercentage,
@@ -1481,16 +1601,84 @@ function PanelGroupWithForwardedRef({
1481
1601
  resetGlobalCursorStyle();
1482
1602
  setDragState(null);
1483
1603
  }, []);
1604
+ const unregisterPanelRef = useRef({
1605
+ pendingPanelIds: new Set(),
1606
+ timeout: null
1607
+ });
1484
1608
  const unregisterPanel = useCallback(panelData => {
1485
- delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1486
- setPanelDataArray(panelDataArray => {
1487
- const index = panelDataArray.indexOf(panelData);
1488
- if (index >= 0) {
1489
- panelDataArray = [...panelDataArray];
1490
- panelDataArray.splice(index, 1);
1609
+ const {
1610
+ id: groupId,
1611
+ layout: prevLayout,
1612
+ onLayout,
1613
+ panelDataArray
1614
+ } = committedValuesRef.current;
1615
+ const index = panelDataArray.indexOf(panelData);
1616
+ if (index >= 0) {
1617
+ panelDataArray.splice(index, 1);
1618
+ unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1619
+ }
1620
+ if (unregisterPanelRef.current.timeout != null) {
1621
+ clearTimeout(unregisterPanelRef.current.timeout);
1622
+ }
1623
+
1624
+ // Batch panel unmounts so that we only calculate layout once;
1625
+ // This is more efficient and avoids misleading warnings in development mode.
1626
+ // We can't check the DOM to detect this because Panel elements have not yet been removed.
1627
+ unregisterPanelRef.current.timeout = setTimeout(() => {
1628
+ const {
1629
+ pendingPanelIds
1630
+ } = unregisterPanelRef.current;
1631
+ panelIdToLastNotifiedMixedSizesMapRef.current;
1632
+
1633
+ // TRICKY
1634
+ // Strict effects mode
1635
+ let unmountDueToStrictMode = false;
1636
+ pendingPanelIds.forEach(panelId => {
1637
+ pendingPanelIds.delete(panelId);
1638
+ if (panelDataArray.find(({
1639
+ id
1640
+ }) => id === panelId) == null) {
1641
+ unmountDueToStrictMode = true;
1642
+
1643
+ // TRICKY
1644
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1645
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1646
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1647
+ delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1648
+ }
1649
+ });
1650
+ if (!unmountDueToStrictMode) {
1651
+ return;
1491
1652
  }
1492
- return panelDataArray;
1493
- });
1653
+ if (panelDataArray.length === 0) {
1654
+ // The group is unmounting; skip layout calculation.
1655
+ return;
1656
+ }
1657
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1658
+ let unsafeLayout = calculateUnsafeDefaultLayout({
1659
+ groupSizePixels,
1660
+ panelDataArray
1661
+ });
1662
+
1663
+ // Validate even saved layouts in case something has changed since last render
1664
+ // e.g. for pixel groups, this could be the size of the window
1665
+ const nextLayout = validatePanelGroupLayout({
1666
+ groupSizePixels,
1667
+ layout: unsafeLayout,
1668
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1669
+ });
1670
+ if (!areEqual(prevLayout, nextLayout)) {
1671
+ setLayout(nextLayout);
1672
+ committedValuesRef.current.layout = nextLayout;
1673
+ if (onLayout) {
1674
+ onLayout(nextLayout.map(sizePercentage => ({
1675
+ sizePercentage,
1676
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1677
+ })));
1678
+ }
1679
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1680
+ }
1681
+ }, 0);
1494
1682
  }, []);
1495
1683
  const context = useMemo(() => ({
1496
1684
  collapsePanel,