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