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.
@@ -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
@@ -176,8 +178,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
176
178
  PanelWithForwardedRef.displayName = "Panel";
177
179
  Panel.displayName = "forwardRef(Panel)";
178
180
 
179
- const PRECISION = 10;
180
-
181
181
  function convertPixelsToPercentage(pixels, groupSizePixels) {
182
182
  return pixels / groupSizePixels * 100;
183
183
  }
@@ -255,6 +255,8 @@ function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, gr
255
255
  };
256
256
  }
257
257
 
258
+ const PRECISION = 10;
259
+
258
260
  function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
259
261
  actual = parseFloat(actual.toFixed(fractionDigits));
260
262
  expected = parseFloat(expected.toFixed(fractionDigits));
@@ -728,15 +730,10 @@ function useWindowSplitterPanelGroupBehavior({
728
730
  }, [groupId, layout, panelDataArray]);
729
731
  useEffect(() => {
730
732
  const {
731
- direction,
732
733
  panelDataArray
733
734
  } = committedValuesRef.current;
734
735
  const groupElement = getPanelGroupElement(groupId);
735
736
  assert(groupElement != null, `No group found for id "${groupId}"`);
736
- const {
737
- height,
738
- width
739
- } = groupElement.getBoundingClientRect();
740
737
  const handles = getResizeHandleElementsForGroup(groupId);
741
738
  const cleanupFunctions = handles.map(handle => {
742
739
  const handleId = handle.getAttribute("data-panel-resize-handle-id");
@@ -756,21 +753,19 @@ function useWindowSplitterPanelGroupBehavior({
756
753
  if (index >= 0) {
757
754
  const panelData = panelDataArray[index];
758
755
  const size = layout[index];
759
- if (size != null) {
760
- var _getPercentageSizeFro;
756
+ if (size != null && panelData.constraints.collapsible) {
757
+ var _getPercentageSizeFro, _getPercentageSizeFro2;
761
758
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
762
- const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
759
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
760
+ sizePercentage: panelData.constraints.collapsedSizePercentage,
761
+ sizePixels: panelData.constraints.collapsedSizePixels
762
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
763
+ const minSize = (_getPercentageSizeFro2 = getPercentageSizeFromMixedSizes({
763
764
  sizePercentage: panelData.constraints.minSizePercentage,
764
765
  sizePixels: panelData.constraints.minSizePixels
765
- }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
766
- let delta = 0;
767
- if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
768
- delta = direction === "horizontal" ? width : height;
769
- } else {
770
- delta = -(direction === "horizontal" ? width : height);
771
- }
766
+ }, groupSizePixels)) !== null && _getPercentageSizeFro2 !== void 0 ? _getPercentageSizeFro2 : 0;
772
767
  const nextLayout = adjustLayoutByDelta({
773
- delta,
768
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
774
769
  groupSizePixels,
775
770
  layout,
776
771
  panelConstraints: panelDataArray.map(panelData => panelData.constraints),
@@ -1079,6 +1074,10 @@ function debounce(callback, durationMs = 10) {
1079
1074
  return callable;
1080
1075
  }
1081
1076
 
1077
+ function getPanelElementsForGroup(groupId) {
1078
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
1079
+ }
1080
+
1082
1081
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
1083
1082
  // or on a browser with cookies/storage disabled.
1084
1083
  // In either case, this function avoids accessing localStorage until needed,
@@ -1228,7 +1227,7 @@ const defaultStorage = {
1228
1227
  };
1229
1228
  const debounceMap = {};
1230
1229
  function PanelGroupWithForwardedRef({
1231
- autoSaveId,
1230
+ autoSaveId = null,
1232
1231
  children,
1233
1232
  className: classNameFromProps = "",
1234
1233
  dataAttributes,
@@ -1245,11 +1244,11 @@ function PanelGroupWithForwardedRef({
1245
1244
  const groupId = useUniqueId(idFromProps);
1246
1245
  const [dragState, setDragState] = useState(null);
1247
1246
  const [layout, setLayout] = useState([]);
1248
- const [panelDataArray, setPanelDataArray] = useState([]);
1249
1247
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1250
1248
  const panelSizeBeforeCollapseRef = useRef(new Map());
1251
1249
  const prevDeltaRef = useRef(0);
1252
1250
  const committedValuesRef = useRef({
1251
+ autoSaveId,
1253
1252
  direction,
1254
1253
  dragState,
1255
1254
  id: groupId,
@@ -1257,7 +1256,8 @@ function PanelGroupWithForwardedRef({
1257
1256
  keyboardResizeByPixels,
1258
1257
  layout,
1259
1258
  onLayout,
1260
- panelDataArray
1259
+ panelDataArray: [],
1260
+ storage
1261
1261
  });
1262
1262
  useRef({
1263
1263
  didLogIdAndOrderWarning: false,
@@ -1295,6 +1295,7 @@ function PanelGroupWithForwardedRef({
1295
1295
  });
1296
1296
  if (!areEqual(prevLayout, safeLayout)) {
1297
1297
  setLayout(safeLayout);
1298
+ committedValuesRef.current.layout = safeLayout;
1298
1299
  if (onLayout) {
1299
1300
  onLayout(safeLayout.map(sizePercentage => ({
1300
1301
  sizePercentage,
@@ -1306,21 +1307,29 @@ function PanelGroupWithForwardedRef({
1306
1307
  }
1307
1308
  }), []);
1308
1309
  useIsomorphicLayoutEffect(() => {
1310
+ committedValuesRef.current.autoSaveId = autoSaveId;
1309
1311
  committedValuesRef.current.direction = direction;
1310
1312
  committedValuesRef.current.dragState = dragState;
1311
1313
  committedValuesRef.current.id = groupId;
1312
- committedValuesRef.current.layout = layout;
1313
1314
  committedValuesRef.current.onLayout = onLayout;
1314
- committedValuesRef.current.panelDataArray = panelDataArray;
1315
+ committedValuesRef.current.storage = storage;
1316
+
1317
+ // panelDataArray and layout are updated in-sync with scheduled state updates.
1318
+ // TODO [217] Move these values into a separate ref
1315
1319
  });
1320
+
1316
1321
  useWindowSplitterPanelGroupBehavior({
1317
1322
  committedValuesRef,
1318
1323
  groupId,
1319
1324
  layout,
1320
- panelDataArray,
1325
+ panelDataArray: committedValuesRef.current.panelDataArray,
1321
1326
  setLayout
1322
1327
  });
1323
1328
  useEffect(() => {
1329
+ const {
1330
+ panelDataArray
1331
+ } = committedValuesRef.current;
1332
+
1324
1333
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1325
1334
  if (autoSaveId) {
1326
1335
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1333,59 +1342,11 @@ function PanelGroupWithForwardedRef({
1333
1342
  }
1334
1343
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1335
1344
  }
1336
- }, [autoSaveId, layout, panelDataArray, storage]);
1337
-
1338
- // Once all panels have registered themselves,
1339
- // Compute the initial sizes based on default weights.
1340
- // This assumes that panels register during initial mount (no conditional rendering)!
1345
+ }, [autoSaveId, layout, storage]);
1341
1346
  useIsomorphicLayoutEffect(() => {
1342
1347
  const {
1343
- id: groupId,
1344
- layout,
1345
- onLayout
1348
+ panelDataArray
1346
1349
  } = committedValuesRef.current;
1347
- if (layout.length === panelDataArray.length) {
1348
- // Only compute (or restore) default layout once per panel configuration.
1349
- return;
1350
- }
1351
-
1352
- // If this panel has been configured to persist sizing information,
1353
- // default size should be restored from local storage if possible.
1354
- let unsafeLayout = null;
1355
- if (autoSaveId) {
1356
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1357
- }
1358
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1359
- if (groupSizePixels <= 0) {
1360
- // Wait until the group has rendered a non-zero size before computing layout.
1361
- return;
1362
- }
1363
- if (unsafeLayout == null) {
1364
- unsafeLayout = calculateUnsafeDefaultLayout({
1365
- groupSizePixels,
1366
- panelDataArray
1367
- });
1368
- }
1369
-
1370
- // Validate even saved layouts in case something has changed since last render
1371
- // e.g. for pixel groups, this could be the size of the window
1372
- const validatedLayout = validatePanelGroupLayout({
1373
- groupSizePixels,
1374
- layout: unsafeLayout,
1375
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1376
- });
1377
- if (!areEqual(layout, validatedLayout)) {
1378
- setLayout(validatedLayout);
1379
- }
1380
- if (onLayout) {
1381
- onLayout(validatedLayout.map(sizePercentage => ({
1382
- sizePercentage,
1383
- sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1384
- })));
1385
- }
1386
- callPanelCallbacks(groupId, panelDataArray, validatedLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1387
- }, [autoSaveId, layout, panelDataArray, storage]);
1388
- useIsomorphicLayoutEffect(() => {
1389
1350
  const constraints = panelDataArray.map(({
1390
1351
  constraints
1391
1352
  }) => constraints);
@@ -1409,6 +1370,7 @@ function PanelGroupWithForwardedRef({
1409
1370
  });
1410
1371
  if (!areEqual(prevLayout, nextLayout)) {
1411
1372
  setLayout(nextLayout);
1373
+ committedValuesRef.current.layout = nextLayout;
1412
1374
  if (onLayout) {
1413
1375
  onLayout(nextLayout.map(sizePercentage => ({
1414
1376
  sizePercentage,
@@ -1423,7 +1385,7 @@ function PanelGroupWithForwardedRef({
1423
1385
  resizeObserver.disconnect();
1424
1386
  };
1425
1387
  }
1426
- }, [groupId, panelDataArray]);
1388
+ }, [groupId]);
1427
1389
 
1428
1390
  // DEV warnings
1429
1391
  useEffect(() => {
@@ -1460,6 +1422,7 @@ function PanelGroupWithForwardedRef({
1460
1422
  });
1461
1423
  if (!compareLayouts(prevLayout, nextLayout)) {
1462
1424
  setLayout(nextLayout);
1425
+ committedValuesRef.current.layout = nextLayout;
1463
1426
  if (onLayout) {
1464
1427
  onLayout(nextLayout.map(sizePercentage => ({
1465
1428
  sizePercentage,
@@ -1504,6 +1467,7 @@ function PanelGroupWithForwardedRef({
1504
1467
  });
1505
1468
  if (!compareLayouts(prevLayout, nextLayout)) {
1506
1469
  setLayout(nextLayout);
1470
+ committedValuesRef.current.layout = nextLayout;
1507
1471
  if (onLayout) {
1508
1472
  onLayout(nextLayout.map(sizePercentage => ({
1509
1473
  sizePercentage,
@@ -1534,6 +1498,9 @@ function PanelGroupWithForwardedRef({
1534
1498
 
1535
1499
  // This API should never read from committedValuesRef
1536
1500
  const getPanelStyle = useCallback(panelData => {
1501
+ const {
1502
+ panelDataArray
1503
+ } = committedValuesRef.current;
1537
1504
  const panelIndex = panelDataArray.indexOf(panelData);
1538
1505
  return computePanelFlexBoxStyle({
1539
1506
  dragState,
@@ -1541,7 +1508,7 @@ function PanelGroupWithForwardedRef({
1541
1508
  panelData: panelDataArray,
1542
1509
  panelIndex
1543
1510
  });
1544
- }, [dragState, layout, panelDataArray]);
1511
+ }, [dragState, layout]);
1545
1512
 
1546
1513
  // External APIs are safe to memoize via committed values ref
1547
1514
  const isPanelCollapsed = useCallback(panelData => {
@@ -1571,22 +1538,76 @@ function PanelGroupWithForwardedRef({
1571
1538
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1572
1539
  }, [groupId]);
1573
1540
  const registerPanel = useCallback(panelData => {
1574
- setPanelDataArray(prevPanelDataArray => {
1575
- const nextPanelDataArray = [...prevPanelDataArray, panelData];
1576
- return nextPanelDataArray.sort((panelA, panelB) => {
1577
- const orderA = panelA.order;
1578
- const orderB = panelB.order;
1579
- if (orderA == null && orderB == null) {
1580
- return 0;
1581
- } else if (orderA == null) {
1582
- return -1;
1583
- } else if (orderB == null) {
1584
- return 1;
1585
- } else {
1586
- return orderA - orderB;
1587
- }
1541
+ const {
1542
+ autoSaveId,
1543
+ id: groupId,
1544
+ layout: prevLayout,
1545
+ onLayout,
1546
+ panelDataArray,
1547
+ storage
1548
+ } = committedValuesRef.current;
1549
+ panelDataArray.push(panelData);
1550
+ panelDataArray.sort((panelA, panelB) => {
1551
+ const orderA = panelA.order;
1552
+ const orderB = panelB.order;
1553
+ if (orderA == null && orderB == null) {
1554
+ return 0;
1555
+ } else if (orderA == null) {
1556
+ return -1;
1557
+ } else if (orderB == null) {
1558
+ return 1;
1559
+ } else {
1560
+ return orderA - orderB;
1561
+ }
1562
+ });
1563
+
1564
+ // Wait until all panels have registered before we try to compute layout;
1565
+ // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1566
+ const panelElements = getPanelElementsForGroup(groupId);
1567
+ if (panelElements.length !== panelDataArray.length) {
1568
+ return;
1569
+ }
1570
+
1571
+ // If this panel has been configured to persist sizing information,
1572
+ // default size should be restored from local storage if possible.
1573
+ let unsafeLayout = null;
1574
+ if (autoSaveId) {
1575
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1576
+ }
1577
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1578
+ if (groupSizePixels <= 0) {
1579
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1580
+ constraints
1581
+ }) => constraints))) {
1582
+ // Wait until the group has rendered a non-zero size before computing layout.
1583
+ return;
1584
+ }
1585
+ }
1586
+ if (unsafeLayout == null) {
1587
+ unsafeLayout = calculateUnsafeDefaultLayout({
1588
+ groupSizePixels,
1589
+ panelDataArray
1588
1590
  });
1591
+ }
1592
+
1593
+ // Validate even saved layouts in case something has changed since last render
1594
+ // e.g. for pixel groups, this could be the size of the window
1595
+ const nextLayout = validatePanelGroupLayout({
1596
+ groupSizePixels,
1597
+ layout: unsafeLayout,
1598
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1589
1599
  });
1600
+ if (!areEqual(prevLayout, nextLayout)) {
1601
+ setLayout(nextLayout);
1602
+ committedValuesRef.current.layout = nextLayout;
1603
+ if (onLayout) {
1604
+ onLayout(nextLayout.map(sizePercentage => ({
1605
+ sizePercentage,
1606
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1607
+ })));
1608
+ }
1609
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1610
+ }
1590
1611
  }, []);
1591
1612
  const registerResizeHandle = useCallback(dragHandleId => {
1592
1613
  return function resizeHandler(event) {
@@ -1656,6 +1677,7 @@ function PanelGroupWithForwardedRef({
1656
1677
  }
1657
1678
  if (layoutChanged) {
1658
1679
  setLayout(nextLayout);
1680
+ committedValuesRef.current.layout = nextLayout;
1659
1681
  if (onLayout) {
1660
1682
  onLayout(nextLayout.map(sizePercentage => ({
1661
1683
  sizePercentage,
@@ -1693,6 +1715,7 @@ function PanelGroupWithForwardedRef({
1693
1715
  });
1694
1716
  if (!compareLayouts(prevLayout, nextLayout)) {
1695
1717
  setLayout(nextLayout);
1718
+ committedValuesRef.current.layout = nextLayout;
1696
1719
  if (onLayout) {
1697
1720
  onLayout(nextLayout.map(sizePercentage => ({
1698
1721
  sizePercentage,
@@ -1720,16 +1743,84 @@ function PanelGroupWithForwardedRef({
1720
1743
  resetGlobalCursorStyle();
1721
1744
  setDragState(null);
1722
1745
  }, []);
1746
+ const unregisterPanelRef = useRef({
1747
+ pendingPanelIds: new Set(),
1748
+ timeout: null
1749
+ });
1723
1750
  const unregisterPanel = useCallback(panelData => {
1724
- delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1725
- setPanelDataArray(panelDataArray => {
1726
- const index = panelDataArray.indexOf(panelData);
1727
- if (index >= 0) {
1728
- panelDataArray = [...panelDataArray];
1729
- panelDataArray.splice(index, 1);
1751
+ const {
1752
+ id: groupId,
1753
+ layout: prevLayout,
1754
+ onLayout,
1755
+ panelDataArray
1756
+ } = committedValuesRef.current;
1757
+ const index = panelDataArray.indexOf(panelData);
1758
+ if (index >= 0) {
1759
+ panelDataArray.splice(index, 1);
1760
+ unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1761
+ }
1762
+ if (unregisterPanelRef.current.timeout != null) {
1763
+ clearTimeout(unregisterPanelRef.current.timeout);
1764
+ }
1765
+
1766
+ // Batch panel unmounts so that we only calculate layout once;
1767
+ // This is more efficient and avoids misleading warnings in development mode.
1768
+ // We can't check the DOM to detect this because Panel elements have not yet been removed.
1769
+ unregisterPanelRef.current.timeout = setTimeout(() => {
1770
+ const {
1771
+ pendingPanelIds
1772
+ } = unregisterPanelRef.current;
1773
+ panelIdToLastNotifiedMixedSizesMapRef.current;
1774
+
1775
+ // TRICKY
1776
+ // Strict effects mode
1777
+ let unmountDueToStrictMode = false;
1778
+ pendingPanelIds.forEach(panelId => {
1779
+ pendingPanelIds.delete(panelId);
1780
+ if (panelDataArray.find(({
1781
+ id
1782
+ }) => id === panelId) == null) {
1783
+ unmountDueToStrictMode = true;
1784
+
1785
+ // TRICKY
1786
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1787
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1788
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1789
+ delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1790
+ }
1791
+ });
1792
+ if (!unmountDueToStrictMode) {
1793
+ return;
1730
1794
  }
1731
- return panelDataArray;
1732
- });
1795
+ if (panelDataArray.length === 0) {
1796
+ // The group is unmounting; skip layout calculation.
1797
+ return;
1798
+ }
1799
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1800
+ let unsafeLayout = calculateUnsafeDefaultLayout({
1801
+ groupSizePixels,
1802
+ panelDataArray
1803
+ });
1804
+
1805
+ // Validate even saved layouts in case something has changed since last render
1806
+ // e.g. for pixel groups, this could be the size of the window
1807
+ const nextLayout = validatePanelGroupLayout({
1808
+ groupSizePixels,
1809
+ layout: unsafeLayout,
1810
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1811
+ });
1812
+ if (!areEqual(prevLayout, nextLayout)) {
1813
+ setLayout(nextLayout);
1814
+ committedValuesRef.current.layout = nextLayout;
1815
+ if (onLayout) {
1816
+ onLayout(nextLayout.map(sizePercentage => ({
1817
+ sizePercentage,
1818
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1819
+ })));
1820
+ }
1821
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1822
+ }
1823
+ }, 0);
1733
1824
  }, []);
1734
1825
  const context = useMemo(() => ({
1735
1826
  collapsePanel,