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.
@@ -95,6 +95,7 @@ function PanelWithForwardedRef({
95
95
  expandPanel,
96
96
  getPanelSize,
97
97
  getPanelStyle,
98
+ groupId,
98
99
  isPanelCollapsed,
99
100
  registerPanel,
100
101
  resizePanel,
@@ -188,6 +189,7 @@ function PanelWithForwardedRef({
188
189
  // CSS selectors
189
190
  "data-panel": "",
190
191
  "data-panel-id": panelId,
192
+ "data-panel-group-id": groupId,
191
193
  // e2e test attributes
192
194
  "data-panel-collapsible": undefined,
193
195
  "data-panel-size": undefined
@@ -200,8 +202,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
200
202
  PanelWithForwardedRef.displayName = "Panel";
201
203
  Panel.displayName = "forwardRef(Panel)";
202
204
 
203
- const PRECISION = 10;
204
-
205
205
  function convertPixelsToPercentage(pixels, groupSizePixels) {
206
206
  return pixels / groupSizePixels * 100;
207
207
  }
@@ -279,6 +279,8 @@ function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, gr
279
279
  };
280
280
  }
281
281
 
282
+ const PRECISION = 10;
283
+
282
284
  function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
283
285
  actual = parseFloat(actual.toFixed(fractionDigits));
284
286
  expected = parseFloat(expected.toFixed(fractionDigits));
@@ -752,15 +754,10 @@ function useWindowSplitterPanelGroupBehavior({
752
754
  }, [groupId, layout, panelDataArray]);
753
755
  useEffect(() => {
754
756
  const {
755
- direction,
756
757
  panelDataArray
757
758
  } = committedValuesRef.current;
758
759
  const groupElement = getPanelGroupElement(groupId);
759
760
  assert(groupElement != null, `No group found for id "${groupId}"`);
760
- const {
761
- height,
762
- width
763
- } = groupElement.getBoundingClientRect();
764
761
  const handles = getResizeHandleElementsForGroup(groupId);
765
762
  const cleanupFunctions = handles.map(handle => {
766
763
  const handleId = handle.getAttribute("data-panel-resize-handle-id");
@@ -780,21 +777,19 @@ function useWindowSplitterPanelGroupBehavior({
780
777
  if (index >= 0) {
781
778
  const panelData = panelDataArray[index];
782
779
  const size = layout[index];
783
- if (size != null) {
784
- var _getPercentageSizeFro;
780
+ if (size != null && panelData.constraints.collapsible) {
781
+ var _getPercentageSizeFro, _getPercentageSizeFro2;
785
782
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
786
- const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
783
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
784
+ sizePercentage: panelData.constraints.collapsedSizePercentage,
785
+ sizePixels: panelData.constraints.collapsedSizePixels
786
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
787
+ const minSize = (_getPercentageSizeFro2 = getPercentageSizeFromMixedSizes({
787
788
  sizePercentage: panelData.constraints.minSizePercentage,
788
789
  sizePixels: panelData.constraints.minSizePixels
789
- }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
790
- let delta = 0;
791
- if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
792
- delta = direction === "horizontal" ? width : height;
793
- } else {
794
- delta = -(direction === "horizontal" ? width : height);
795
- }
790
+ }, groupSizePixels)) !== null && _getPercentageSizeFro2 !== void 0 ? _getPercentageSizeFro2 : 0;
796
791
  const nextLayout = adjustLayoutByDelta({
797
- delta,
792
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
798
793
  groupSizePixels,
799
794
  layout,
800
795
  panelConstraints: panelDataArray.map(panelData => panelData.constraints),
@@ -1103,6 +1098,10 @@ function debounce(callback, durationMs = 10) {
1103
1098
  return callable;
1104
1099
  }
1105
1100
 
1101
+ function getPanelElementsForGroup(groupId) {
1102
+ return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
1103
+ }
1104
+
1106
1105
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
1107
1106
  // or on a browser with cookies/storage disabled.
1108
1107
  // In either case, this function avoids accessing localStorage until needed,
@@ -1252,7 +1251,7 @@ const defaultStorage = {
1252
1251
  };
1253
1252
  const debounceMap = {};
1254
1253
  function PanelGroupWithForwardedRef({
1255
- autoSaveId,
1254
+ autoSaveId = null,
1256
1255
  children,
1257
1256
  className: classNameFromProps = "",
1258
1257
  dataAttributes,
@@ -1269,11 +1268,11 @@ function PanelGroupWithForwardedRef({
1269
1268
  const groupId = useUniqueId(idFromProps);
1270
1269
  const [dragState, setDragState] = useState(null);
1271
1270
  const [layout, setLayout] = useState([]);
1272
- const [panelDataArray, setPanelDataArray] = useState([]);
1273
1271
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1274
1272
  const panelSizeBeforeCollapseRef = useRef(new Map());
1275
1273
  const prevDeltaRef = useRef(0);
1276
1274
  const committedValuesRef = useRef({
1275
+ autoSaveId,
1277
1276
  direction,
1278
1277
  dragState,
1279
1278
  id: groupId,
@@ -1281,7 +1280,8 @@ function PanelGroupWithForwardedRef({
1281
1280
  keyboardResizeByPixels,
1282
1281
  layout,
1283
1282
  onLayout,
1284
- panelDataArray
1283
+ panelDataArray: [],
1284
+ storage
1285
1285
  });
1286
1286
  useRef({
1287
1287
  didLogIdAndOrderWarning: false,
@@ -1319,6 +1319,7 @@ function PanelGroupWithForwardedRef({
1319
1319
  });
1320
1320
  if (!areEqual(prevLayout, safeLayout)) {
1321
1321
  setLayout(safeLayout);
1322
+ committedValuesRef.current.layout = safeLayout;
1322
1323
  if (onLayout) {
1323
1324
  onLayout(safeLayout.map(sizePercentage => ({
1324
1325
  sizePercentage,
@@ -1330,21 +1331,29 @@ function PanelGroupWithForwardedRef({
1330
1331
  }
1331
1332
  }), []);
1332
1333
  useIsomorphicLayoutEffect(() => {
1334
+ committedValuesRef.current.autoSaveId = autoSaveId;
1333
1335
  committedValuesRef.current.direction = direction;
1334
1336
  committedValuesRef.current.dragState = dragState;
1335
1337
  committedValuesRef.current.id = groupId;
1336
- committedValuesRef.current.layout = layout;
1337
1338
  committedValuesRef.current.onLayout = onLayout;
1338
- committedValuesRef.current.panelDataArray = panelDataArray;
1339
+ committedValuesRef.current.storage = storage;
1340
+
1341
+ // panelDataArray and layout are updated in-sync with scheduled state updates.
1342
+ // TODO [217] Move these values into a separate ref
1339
1343
  });
1344
+
1340
1345
  useWindowSplitterPanelGroupBehavior({
1341
1346
  committedValuesRef,
1342
1347
  groupId,
1343
1348
  layout,
1344
- panelDataArray,
1349
+ panelDataArray: committedValuesRef.current.panelDataArray,
1345
1350
  setLayout
1346
1351
  });
1347
1352
  useEffect(() => {
1353
+ const {
1354
+ panelDataArray
1355
+ } = committedValuesRef.current;
1356
+
1348
1357
  // If this panel has been configured to persist sizing information, save sizes to local storage.
1349
1358
  if (autoSaveId) {
1350
1359
  if (layout.length === 0 || layout.length !== panelDataArray.length) {
@@ -1357,59 +1366,11 @@ function PanelGroupWithForwardedRef({
1357
1366
  }
1358
1367
  debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage);
1359
1368
  }
1360
- }, [autoSaveId, layout, panelDataArray, storage]);
1361
-
1362
- // Once all panels have registered themselves,
1363
- // Compute the initial sizes based on default weights.
1364
- // This assumes that panels register during initial mount (no conditional rendering)!
1369
+ }, [autoSaveId, layout, storage]);
1365
1370
  useIsomorphicLayoutEffect(() => {
1366
1371
  const {
1367
- id: groupId,
1368
- layout,
1369
- onLayout
1372
+ panelDataArray
1370
1373
  } = committedValuesRef.current;
1371
- if (layout.length === panelDataArray.length) {
1372
- // Only compute (or restore) default layout once per panel configuration.
1373
- return;
1374
- }
1375
-
1376
- // If this panel has been configured to persist sizing information,
1377
- // default size should be restored from local storage if possible.
1378
- let unsafeLayout = null;
1379
- if (autoSaveId) {
1380
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1381
- }
1382
- const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1383
- if (groupSizePixels <= 0) {
1384
- // Wait until the group has rendered a non-zero size before computing layout.
1385
- return;
1386
- }
1387
- if (unsafeLayout == null) {
1388
- unsafeLayout = calculateUnsafeDefaultLayout({
1389
- groupSizePixels,
1390
- panelDataArray
1391
- });
1392
- }
1393
-
1394
- // Validate even saved layouts in case something has changed since last render
1395
- // e.g. for pixel groups, this could be the size of the window
1396
- const validatedLayout = validatePanelGroupLayout({
1397
- groupSizePixels,
1398
- layout: unsafeLayout,
1399
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1400
- });
1401
- if (!areEqual(layout, validatedLayout)) {
1402
- setLayout(validatedLayout);
1403
- }
1404
- if (onLayout) {
1405
- onLayout(validatedLayout.map(sizePercentage => ({
1406
- sizePercentage,
1407
- sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1408
- })));
1409
- }
1410
- callPanelCallbacks(groupId, panelDataArray, validatedLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1411
- }, [autoSaveId, layout, panelDataArray, storage]);
1412
- useIsomorphicLayoutEffect(() => {
1413
1374
  const constraints = panelDataArray.map(({
1414
1375
  constraints
1415
1376
  }) => constraints);
@@ -1433,6 +1394,7 @@ function PanelGroupWithForwardedRef({
1433
1394
  });
1434
1395
  if (!areEqual(prevLayout, nextLayout)) {
1435
1396
  setLayout(nextLayout);
1397
+ committedValuesRef.current.layout = nextLayout;
1436
1398
  if (onLayout) {
1437
1399
  onLayout(nextLayout.map(sizePercentage => ({
1438
1400
  sizePercentage,
@@ -1447,7 +1409,7 @@ function PanelGroupWithForwardedRef({
1447
1409
  resizeObserver.disconnect();
1448
1410
  };
1449
1411
  }
1450
- }, [groupId, panelDataArray]);
1412
+ }, [groupId]);
1451
1413
 
1452
1414
  // DEV warnings
1453
1415
  useEffect(() => {
@@ -1484,6 +1446,7 @@ function PanelGroupWithForwardedRef({
1484
1446
  });
1485
1447
  if (!compareLayouts(prevLayout, nextLayout)) {
1486
1448
  setLayout(nextLayout);
1449
+ committedValuesRef.current.layout = nextLayout;
1487
1450
  if (onLayout) {
1488
1451
  onLayout(nextLayout.map(sizePercentage => ({
1489
1452
  sizePercentage,
@@ -1528,6 +1491,7 @@ function PanelGroupWithForwardedRef({
1528
1491
  });
1529
1492
  if (!compareLayouts(prevLayout, nextLayout)) {
1530
1493
  setLayout(nextLayout);
1494
+ committedValuesRef.current.layout = nextLayout;
1531
1495
  if (onLayout) {
1532
1496
  onLayout(nextLayout.map(sizePercentage => ({
1533
1497
  sizePercentage,
@@ -1558,6 +1522,9 @@ function PanelGroupWithForwardedRef({
1558
1522
 
1559
1523
  // This API should never read from committedValuesRef
1560
1524
  const getPanelStyle = useCallback(panelData => {
1525
+ const {
1526
+ panelDataArray
1527
+ } = committedValuesRef.current;
1561
1528
  const panelIndex = panelDataArray.indexOf(panelData);
1562
1529
  return computePanelFlexBoxStyle({
1563
1530
  dragState,
@@ -1565,7 +1532,7 @@ function PanelGroupWithForwardedRef({
1565
1532
  panelData: panelDataArray,
1566
1533
  panelIndex
1567
1534
  });
1568
- }, [dragState, layout, panelDataArray]);
1535
+ }, [dragState, layout]);
1569
1536
 
1570
1537
  // External APIs are safe to memoize via committed values ref
1571
1538
  const isPanelCollapsed = useCallback(panelData => {
@@ -1595,22 +1562,76 @@ function PanelGroupWithForwardedRef({
1595
1562
  return !collapsible || panelSizePercentage > collapsedSizePercentage;
1596
1563
  }, [groupId]);
1597
1564
  const registerPanel = useCallback(panelData => {
1598
- setPanelDataArray(prevPanelDataArray => {
1599
- const nextPanelDataArray = [...prevPanelDataArray, panelData];
1600
- return nextPanelDataArray.sort((panelA, panelB) => {
1601
- const orderA = panelA.order;
1602
- const orderB = panelB.order;
1603
- if (orderA == null && orderB == null) {
1604
- return 0;
1605
- } else if (orderA == null) {
1606
- return -1;
1607
- } else if (orderB == null) {
1608
- return 1;
1609
- } else {
1610
- return orderA - orderB;
1611
- }
1565
+ const {
1566
+ autoSaveId,
1567
+ id: groupId,
1568
+ layout: prevLayout,
1569
+ onLayout,
1570
+ panelDataArray,
1571
+ storage
1572
+ } = committedValuesRef.current;
1573
+ panelDataArray.push(panelData);
1574
+ panelDataArray.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
+ }
1586
+ });
1587
+
1588
+ // Wait until all panels have registered before we try to compute layout;
1589
+ // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1590
+ const panelElements = getPanelElementsForGroup(groupId);
1591
+ if (panelElements.length !== panelDataArray.length) {
1592
+ return;
1593
+ }
1594
+
1595
+ // If this panel has been configured to persist sizing information,
1596
+ // default size should be restored from local storage if possible.
1597
+ let unsafeLayout = null;
1598
+ if (autoSaveId) {
1599
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1600
+ }
1601
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1602
+ if (groupSizePixels <= 0) {
1603
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1604
+ constraints
1605
+ }) => constraints))) {
1606
+ // Wait until the group has rendered a non-zero size before computing layout.
1607
+ return;
1608
+ }
1609
+ }
1610
+ if (unsafeLayout == null) {
1611
+ unsafeLayout = calculateUnsafeDefaultLayout({
1612
+ groupSizePixels,
1613
+ panelDataArray
1612
1614
  });
1615
+ }
1616
+
1617
+ // Validate even saved layouts in case something has changed since last render
1618
+ // e.g. for pixel groups, this could be the size of the window
1619
+ const nextLayout = validatePanelGroupLayout({
1620
+ groupSizePixels,
1621
+ layout: unsafeLayout,
1622
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1613
1623
  });
1624
+ if (!areEqual(prevLayout, nextLayout)) {
1625
+ setLayout(nextLayout);
1626
+ committedValuesRef.current.layout = nextLayout;
1627
+ if (onLayout) {
1628
+ onLayout(nextLayout.map(sizePercentage => ({
1629
+ sizePercentage,
1630
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1631
+ })));
1632
+ }
1633
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1634
+ }
1614
1635
  }, []);
1615
1636
  const registerResizeHandle = useCallback(dragHandleId => {
1616
1637
  return function resizeHandler(event) {
@@ -1680,6 +1701,7 @@ function PanelGroupWithForwardedRef({
1680
1701
  }
1681
1702
  if (layoutChanged) {
1682
1703
  setLayout(nextLayout);
1704
+ committedValuesRef.current.layout = nextLayout;
1683
1705
  if (onLayout) {
1684
1706
  onLayout(nextLayout.map(sizePercentage => ({
1685
1707
  sizePercentage,
@@ -1717,6 +1739,7 @@ function PanelGroupWithForwardedRef({
1717
1739
  });
1718
1740
  if (!compareLayouts(prevLayout, nextLayout)) {
1719
1741
  setLayout(nextLayout);
1742
+ committedValuesRef.current.layout = nextLayout;
1720
1743
  if (onLayout) {
1721
1744
  onLayout(nextLayout.map(sizePercentage => ({
1722
1745
  sizePercentage,
@@ -1744,16 +1767,84 @@ function PanelGroupWithForwardedRef({
1744
1767
  resetGlobalCursorStyle();
1745
1768
  setDragState(null);
1746
1769
  }, []);
1770
+ const unregisterPanelRef = useRef({
1771
+ pendingPanelIds: new Set(),
1772
+ timeout: null
1773
+ });
1747
1774
  const unregisterPanel = useCallback(panelData => {
1748
- delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1749
- setPanelDataArray(panelDataArray => {
1750
- const index = panelDataArray.indexOf(panelData);
1751
- if (index >= 0) {
1752
- panelDataArray = [...panelDataArray];
1753
- panelDataArray.splice(index, 1);
1775
+ const {
1776
+ id: groupId,
1777
+ layout: prevLayout,
1778
+ onLayout,
1779
+ panelDataArray
1780
+ } = committedValuesRef.current;
1781
+ const index = panelDataArray.indexOf(panelData);
1782
+ if (index >= 0) {
1783
+ panelDataArray.splice(index, 1);
1784
+ unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1785
+ }
1786
+ if (unregisterPanelRef.current.timeout != null) {
1787
+ clearTimeout(unregisterPanelRef.current.timeout);
1788
+ }
1789
+
1790
+ // Batch panel unmounts so that we only calculate layout once;
1791
+ // This is more efficient and avoids misleading warnings in development mode.
1792
+ // We can't check the DOM to detect this because Panel elements have not yet been removed.
1793
+ unregisterPanelRef.current.timeout = setTimeout(() => {
1794
+ const {
1795
+ pendingPanelIds
1796
+ } = unregisterPanelRef.current;
1797
+ panelIdToLastNotifiedMixedSizesMapRef.current;
1798
+
1799
+ // TRICKY
1800
+ // Strict effects mode
1801
+ let unmountDueToStrictMode = false;
1802
+ pendingPanelIds.forEach(panelId => {
1803
+ pendingPanelIds.delete(panelId);
1804
+ if (panelDataArray.find(({
1805
+ id
1806
+ }) => id === panelId) == null) {
1807
+ unmountDueToStrictMode = true;
1808
+
1809
+ // TRICKY
1810
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1811
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1812
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1813
+ delete panelIdToLastNotifiedMixedSizesMapRef.current[panelData.id];
1814
+ }
1815
+ });
1816
+ if (!unmountDueToStrictMode) {
1817
+ return;
1754
1818
  }
1755
- return panelDataArray;
1756
- });
1819
+ if (panelDataArray.length === 0) {
1820
+ // The group is unmounting; skip layout calculation.
1821
+ return;
1822
+ }
1823
+ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1824
+ let unsafeLayout = calculateUnsafeDefaultLayout({
1825
+ groupSizePixels,
1826
+ panelDataArray
1827
+ });
1828
+
1829
+ // Validate even saved layouts in case something has changed since last render
1830
+ // e.g. for pixel groups, this could be the size of the window
1831
+ const nextLayout = validatePanelGroupLayout({
1832
+ groupSizePixels,
1833
+ layout: unsafeLayout,
1834
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1835
+ });
1836
+ if (!areEqual(prevLayout, nextLayout)) {
1837
+ setLayout(nextLayout);
1838
+ committedValuesRef.current.layout = nextLayout;
1839
+ if (onLayout) {
1840
+ onLayout(nextLayout.map(sizePercentage => ({
1841
+ sizePercentage,
1842
+ sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels)
1843
+ })));
1844
+ }
1845
+ callPanelCallbacks(groupId, panelDataArray, nextLayout, panelIdToLastNotifiedMixedSizesMapRef.current);
1846
+ }
1847
+ }, 0);
1757
1848
  }, []);
1758
1849
  const context = useMemo(() => ({
1759
1850
  collapsePanel,