react-resizable-panels 1.0.0-rc.3 → 1.0.0

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.
package/CHANGELOG.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Changelog
2
2
 
3
- ## 1.0.0-rc.1
3
+ ## 1.0.0
4
4
 
5
- - Remove support for pixel-based Panel constraints; props like `defaultSizePercentage` should now be `defaultSize`
5
+ - Remove support for pixel-based Panel constraints; (props like `defaultSizePercentage` should now be `defaultSize`)
6
6
  - Replaced `dataAttributes` prop with `...rest` prop that supports all HTML attributes
7
7
 
8
8
  ## 0.0.63
@@ -937,10 +937,6 @@ function debounce(callback, durationMs = 10) {
937
937
  return callable;
938
938
  }
939
939
 
940
- function getPanelElementsForGroup(groupId) {
941
- return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
942
- }
943
-
944
940
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
945
941
  // or on a browser with cookies/storage disabled.
946
942
  // In either case, this function avoids accessing localStorage until needed,
@@ -1107,6 +1103,7 @@ function PanelGroupWithForwardedRef({
1107
1103
  const groupId = useUniqueId(idFromProps);
1108
1104
  const [dragState, setDragState] = useState(null);
1109
1105
  const [layout, setLayout] = useState([]);
1106
+ useState([]);
1110
1107
  const panelIdToLastNotifiedSizeMapRef = useRef({});
1111
1108
  const panelSizeBeforeCollapseRef = useRef(new Map());
1112
1109
  const prevDeltaRef = useRef(0);
@@ -1121,7 +1118,8 @@ function PanelGroupWithForwardedRef({
1121
1118
  });
1122
1119
  const eagerValuesRef = useRef({
1123
1120
  layout,
1124
- panelDataArray: []
1121
+ panelDataArray: [],
1122
+ panelDataArrayChanged: false
1125
1123
  });
1126
1124
  useRef({
1127
1125
  didLogIdAndOrderWarning: false,
@@ -1340,28 +1338,8 @@ function PanelGroupWithForwardedRef({
1340
1338
  }, []);
1341
1339
  const registerPanel = useCallback(panelData => {
1342
1340
  const {
1343
- autoSaveId,
1344
- id: groupId,
1345
- onLayout,
1346
- storage
1347
- } = committedValuesRef.current;
1348
- const {
1349
- layout: prevLayout,
1350
1341
  panelDataArray
1351
1342
  } = eagerValuesRef.current;
1352
-
1353
- // HACK
1354
- // This appears to be triggered by some React Suspense+Offscreen+StrictMode bug;
1355
- // see app.replay.io/recording/17b6e11d-4500-4173-b23d-61dfd141fed1
1356
- const index = findPanelDataIndex(panelDataArray, panelData);
1357
- if (index >= 0) {
1358
- if (panelData.idIsFromProps) {
1359
- console.warn(`Panel with id "${panelData.id}" registered twice`);
1360
- } else {
1361
- console.warn(`Panel registered twice`);
1362
- }
1363
- return;
1364
- }
1365
1343
  panelDataArray.push(panelData);
1366
1344
  panelDataArray.sort((panelA, panelB) => {
1367
1345
  const orderA = panelA.order;
@@ -1376,45 +1354,52 @@ function PanelGroupWithForwardedRef({
1376
1354
  return orderA - orderB;
1377
1355
  }
1378
1356
  });
1357
+ eagerValuesRef.current.panelDataArrayChanged = true;
1358
+ }, []);
1379
1359
 
1380
- // Wait until all panels have registered before we try to compute layout;
1381
- // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1382
- const panelElements = getPanelElementsForGroup(groupId);
1383
- if (panelElements.length !== panelDataArray.length) {
1384
- return;
1385
- }
1386
-
1387
- // If this panel has been configured to persist sizing information,
1388
- // default size should be restored from local storage if possible.
1389
- let unsafeLayout = null;
1390
- if (autoSaveId) {
1391
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1392
- }
1393
- if (unsafeLayout == null) {
1394
- unsafeLayout = calculateUnsafeDefaultLayout({
1360
+ // (Re)calculate group layout whenever panels are registered or unregistered.
1361
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1362
+ useIsomorphicLayoutEffect(() => {
1363
+ if (eagerValuesRef.current.panelDataArrayChanged) {
1364
+ eagerValuesRef.current.panelDataArrayChanged = false;
1365
+ const {
1366
+ autoSaveId,
1367
+ onLayout,
1368
+ storage
1369
+ } = committedValuesRef.current;
1370
+ const {
1371
+ layout: prevLayout,
1395
1372
  panelDataArray
1396
- });
1397
- }
1373
+ } = eagerValuesRef.current;
1398
1374
 
1399
- // Validate even saved layouts in case something has changed since last render
1400
- // e.g. for pixel groups, this could be the size of the window
1401
- const nextLayout = validatePanelGroupLayout({
1402
- layout: unsafeLayout,
1403
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1404
- });
1375
+ // If this panel has been configured to persist sizing information,
1376
+ // default size should be restored from local storage if possible.
1377
+ let unsafeLayout = null;
1378
+ if (autoSaveId) {
1379
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1380
+ }
1381
+ if (unsafeLayout == null) {
1382
+ unsafeLayout = calculateUnsafeDefaultLayout({
1383
+ panelDataArray
1384
+ });
1385
+ }
1405
1386
 
1406
- // Offscreen mode makes this a bit weird;
1407
- // Panels unregister when hidden and re-register when shown again,
1408
- // but the overall layout doesn't change between these two cases.
1409
- setLayout(nextLayout);
1410
- eagerValuesRef.current.layout = nextLayout;
1411
- if (!areEqual(prevLayout, nextLayout)) {
1412
- if (onLayout) {
1413
- onLayout(nextLayout);
1387
+ // Validate even saved layouts in case something has changed since last render
1388
+ // e.g. for pixel groups, this could be the size of the window
1389
+ const nextLayout = validatePanelGroupLayout({
1390
+ layout: unsafeLayout,
1391
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1392
+ });
1393
+ if (!areEqual(prevLayout, nextLayout)) {
1394
+ setLayout(nextLayout);
1395
+ eagerValuesRef.current.layout = nextLayout;
1396
+ if (onLayout) {
1397
+ onLayout(nextLayout);
1398
+ }
1399
+ callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1414
1400
  }
1415
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1416
1401
  }
1417
- }, []);
1402
+ });
1418
1403
  const registerResizeHandle = useCallback(dragHandleId => {
1419
1404
  return function resizeHandler(event) {
1420
1405
  event.preventDefault();
@@ -1542,79 +1527,21 @@ function PanelGroupWithForwardedRef({
1542
1527
  resetGlobalCursorStyle();
1543
1528
  setDragState(null);
1544
1529
  }, []);
1545
- const unregisterPanelRef = useRef({
1546
- pendingPanelIds: new Set(),
1547
- timeout: null
1548
- });
1549
1530
  const unregisterPanel = useCallback(panelData => {
1550
1531
  const {
1551
- onLayout
1552
- } = committedValuesRef.current;
1553
- const {
1554
- layout: prevLayout,
1555
1532
  panelDataArray
1556
1533
  } = eagerValuesRef.current;
1557
1534
  const index = findPanelDataIndex(panelDataArray, panelData);
1558
1535
  if (index >= 0) {
1559
1536
  panelDataArray.splice(index, 1);
1560
- unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1561
- }
1562
- if (unregisterPanelRef.current.timeout != null) {
1563
- clearTimeout(unregisterPanelRef.current.timeout);
1564
- }
1565
-
1566
- // Batch panel unmounts so that we only calculate layout once;
1567
- // This is more efficient and avoids misleading warnings in development mode.
1568
- // We can't check the DOM to detect this because Panel elements have not yet been removed.
1569
- unregisterPanelRef.current.timeout = setTimeout(() => {
1570
- const {
1571
- pendingPanelIds
1572
- } = unregisterPanelRef.current;
1573
- const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1574
1537
 
1575
1538
  // TRICKY
1576
- // Strict effects mode
1577
- let unmountDueToStrictMode = false;
1578
- pendingPanelIds.forEach(panelId => {
1579
- pendingPanelIds.delete(panelId);
1580
- if (panelDataArray.find(({
1581
- id
1582
- }) => id === panelId) != null) {
1583
- unmountDueToStrictMode = true;
1584
- } else {
1585
- // TRICKY
1586
- // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1587
- // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1588
- // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1589
- delete panelIdToLastNotifiedSizeMap[panelId];
1590
- }
1591
- });
1592
- if (unmountDueToStrictMode) {
1593
- return;
1594
- }
1595
- if (panelDataArray.length === 0) {
1596
- // The group is unmounting; skip layout calculation.
1597
- return;
1598
- }
1599
- let unsafeLayout = calculateUnsafeDefaultLayout({
1600
- panelDataArray
1601
- });
1602
-
1603
- // Validate even saved layouts in case something has changed since last render
1604
- // e.g. for pixel groups, this could be the size of the window
1605
- const nextLayout = validatePanelGroupLayout({
1606
- layout: unsafeLayout,
1607
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1608
- });
1609
- if (!areEqual(prevLayout, nextLayout)) {
1610
- setLayout(nextLayout);
1611
- eagerValuesRef.current.layout = nextLayout;
1612
- if (onLayout) {
1613
- onLayout(nextLayout);
1614
- }
1615
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1616
- }
1617
- }, 0);
1539
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1540
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1541
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1542
+ delete panelIdToLastNotifiedSizeMapRef.current[panelData.id];
1543
+ eagerValuesRef.current.panelDataArrayChanged = true;
1544
+ }
1618
1545
  }, []);
1619
1546
  const context = useMemo(() => ({
1620
1547
  collapsePanel,
@@ -953,10 +953,6 @@ function debounce(callback, durationMs = 10) {
953
953
  return callable;
954
954
  }
955
955
 
956
- function getPanelElementsForGroup(groupId) {
957
- return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
958
- }
959
-
960
956
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
961
957
  // or on a browser with cookies/storage disabled.
962
958
  // In either case, this function avoids accessing localStorage until needed,
@@ -1170,6 +1166,7 @@ function PanelGroupWithForwardedRef({
1170
1166
  const groupId = useUniqueId(idFromProps);
1171
1167
  const [dragState, setDragState] = useState(null);
1172
1168
  const [layout, setLayout] = useState([]);
1169
+ useState([]);
1173
1170
  const panelIdToLastNotifiedSizeMapRef = useRef({});
1174
1171
  const panelSizeBeforeCollapseRef = useRef(new Map());
1175
1172
  const prevDeltaRef = useRef(0);
@@ -1184,7 +1181,8 @@ function PanelGroupWithForwardedRef({
1184
1181
  });
1185
1182
  const eagerValuesRef = useRef({
1186
1183
  layout,
1187
- panelDataArray: []
1184
+ panelDataArray: [],
1185
+ panelDataArrayChanged: false
1188
1186
  });
1189
1187
  const devWarningsRef = useRef({
1190
1188
  didLogIdAndOrderWarning: false,
@@ -1445,28 +1443,8 @@ function PanelGroupWithForwardedRef({
1445
1443
  }, []);
1446
1444
  const registerPanel = useCallback(panelData => {
1447
1445
  const {
1448
- autoSaveId,
1449
- id: groupId,
1450
- onLayout,
1451
- storage
1452
- } = committedValuesRef.current;
1453
- const {
1454
- layout: prevLayout,
1455
1446
  panelDataArray
1456
1447
  } = eagerValuesRef.current;
1457
-
1458
- // HACK
1459
- // This appears to be triggered by some React Suspense+Offscreen+StrictMode bug;
1460
- // see app.replay.io/recording/17b6e11d-4500-4173-b23d-61dfd141fed1
1461
- const index = findPanelDataIndex(panelDataArray, panelData);
1462
- if (index >= 0) {
1463
- if (panelData.idIsFromProps) {
1464
- console.warn(`Panel with id "${panelData.id}" registered twice`);
1465
- } else {
1466
- console.warn(`Panel registered twice`);
1467
- }
1468
- return;
1469
- }
1470
1448
  panelDataArray.push(panelData);
1471
1449
  panelDataArray.sort((panelA, panelB) => {
1472
1450
  const orderA = panelA.order;
@@ -1481,45 +1459,52 @@ function PanelGroupWithForwardedRef({
1481
1459
  return orderA - orderB;
1482
1460
  }
1483
1461
  });
1462
+ eagerValuesRef.current.panelDataArrayChanged = true;
1463
+ }, []);
1484
1464
 
1485
- // Wait until all panels have registered before we try to compute layout;
1486
- // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1487
- const panelElements = getPanelElementsForGroup(groupId);
1488
- if (panelElements.length !== panelDataArray.length) {
1489
- return;
1490
- }
1491
-
1492
- // If this panel has been configured to persist sizing information,
1493
- // default size should be restored from local storage if possible.
1494
- let unsafeLayout = null;
1495
- if (autoSaveId) {
1496
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1497
- }
1498
- if (unsafeLayout == null) {
1499
- unsafeLayout = calculateUnsafeDefaultLayout({
1465
+ // (Re)calculate group layout whenever panels are registered or unregistered.
1466
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1467
+ useIsomorphicLayoutEffect(() => {
1468
+ if (eagerValuesRef.current.panelDataArrayChanged) {
1469
+ eagerValuesRef.current.panelDataArrayChanged = false;
1470
+ const {
1471
+ autoSaveId,
1472
+ onLayout,
1473
+ storage
1474
+ } = committedValuesRef.current;
1475
+ const {
1476
+ layout: prevLayout,
1500
1477
  panelDataArray
1501
- });
1502
- }
1478
+ } = eagerValuesRef.current;
1503
1479
 
1504
- // Validate even saved layouts in case something has changed since last render
1505
- // e.g. for pixel groups, this could be the size of the window
1506
- const nextLayout = validatePanelGroupLayout({
1507
- layout: unsafeLayout,
1508
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1509
- });
1480
+ // If this panel has been configured to persist sizing information,
1481
+ // default size should be restored from local storage if possible.
1482
+ let unsafeLayout = null;
1483
+ if (autoSaveId) {
1484
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1485
+ }
1486
+ if (unsafeLayout == null) {
1487
+ unsafeLayout = calculateUnsafeDefaultLayout({
1488
+ panelDataArray
1489
+ });
1490
+ }
1510
1491
 
1511
- // Offscreen mode makes this a bit weird;
1512
- // Panels unregister when hidden and re-register when shown again,
1513
- // but the overall layout doesn't change between these two cases.
1514
- setLayout(nextLayout);
1515
- eagerValuesRef.current.layout = nextLayout;
1516
- if (!areEqual(prevLayout, nextLayout)) {
1517
- if (onLayout) {
1518
- onLayout(nextLayout);
1492
+ // Validate even saved layouts in case something has changed since last render
1493
+ // e.g. for pixel groups, this could be the size of the window
1494
+ const nextLayout = validatePanelGroupLayout({
1495
+ layout: unsafeLayout,
1496
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1497
+ });
1498
+ if (!areEqual(prevLayout, nextLayout)) {
1499
+ setLayout(nextLayout);
1500
+ eagerValuesRef.current.layout = nextLayout;
1501
+ if (onLayout) {
1502
+ onLayout(nextLayout);
1503
+ }
1504
+ callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1519
1505
  }
1520
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1521
1506
  }
1522
- }, []);
1507
+ });
1523
1508
  const registerResizeHandle = useCallback(dragHandleId => {
1524
1509
  return function resizeHandler(event) {
1525
1510
  event.preventDefault();
@@ -1647,79 +1632,21 @@ function PanelGroupWithForwardedRef({
1647
1632
  resetGlobalCursorStyle();
1648
1633
  setDragState(null);
1649
1634
  }, []);
1650
- const unregisterPanelRef = useRef({
1651
- pendingPanelIds: new Set(),
1652
- timeout: null
1653
- });
1654
1635
  const unregisterPanel = useCallback(panelData => {
1655
1636
  const {
1656
- onLayout
1657
- } = committedValuesRef.current;
1658
- const {
1659
- layout: prevLayout,
1660
1637
  panelDataArray
1661
1638
  } = eagerValuesRef.current;
1662
1639
  const index = findPanelDataIndex(panelDataArray, panelData);
1663
1640
  if (index >= 0) {
1664
1641
  panelDataArray.splice(index, 1);
1665
- unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1666
- }
1667
- if (unregisterPanelRef.current.timeout != null) {
1668
- clearTimeout(unregisterPanelRef.current.timeout);
1669
- }
1670
-
1671
- // Batch panel unmounts so that we only calculate layout once;
1672
- // This is more efficient and avoids misleading warnings in development mode.
1673
- // We can't check the DOM to detect this because Panel elements have not yet been removed.
1674
- unregisterPanelRef.current.timeout = setTimeout(() => {
1675
- const {
1676
- pendingPanelIds
1677
- } = unregisterPanelRef.current;
1678
- const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1679
1642
 
1680
1643
  // TRICKY
1681
- // Strict effects mode
1682
- let unmountDueToStrictMode = false;
1683
- pendingPanelIds.forEach(panelId => {
1684
- pendingPanelIds.delete(panelId);
1685
- if (panelDataArray.find(({
1686
- id
1687
- }) => id === panelId) != null) {
1688
- unmountDueToStrictMode = true;
1689
- } else {
1690
- // TRICKY
1691
- // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1692
- // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1693
- // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1694
- delete panelIdToLastNotifiedSizeMap[panelId];
1695
- }
1696
- });
1697
- if (unmountDueToStrictMode) {
1698
- return;
1699
- }
1700
- if (panelDataArray.length === 0) {
1701
- // The group is unmounting; skip layout calculation.
1702
- return;
1703
- }
1704
- let unsafeLayout = calculateUnsafeDefaultLayout({
1705
- panelDataArray
1706
- });
1707
-
1708
- // Validate even saved layouts in case something has changed since last render
1709
- // e.g. for pixel groups, this could be the size of the window
1710
- const nextLayout = validatePanelGroupLayout({
1711
- layout: unsafeLayout,
1712
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1713
- });
1714
- if (!areEqual(prevLayout, nextLayout)) {
1715
- setLayout(nextLayout);
1716
- eagerValuesRef.current.layout = nextLayout;
1717
- if (onLayout) {
1718
- onLayout(nextLayout);
1719
- }
1720
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1721
- }
1722
- }, 0);
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 panelIdToLastNotifiedSizeMapRef.current[panelData.id];
1648
+ eagerValuesRef.current.panelDataArrayChanged = true;
1649
+ }
1723
1650
  }, []);
1724
1651
  const context = useMemo(() => ({
1725
1652
  collapsePanel,