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