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.
@@ -929,10 +929,6 @@ function debounce(callback, durationMs = 10) {
929
929
  return callable;
930
930
  }
931
931
 
932
- function getPanelElementsForGroup(groupId) {
933
- return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
934
- }
935
-
936
932
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
937
933
  // or on a browser with cookies/storage disabled.
938
934
  // In either case, this function avoids accessing localStorage until needed,
@@ -1146,6 +1142,7 @@ function PanelGroupWithForwardedRef({
1146
1142
  const groupId = useUniqueId(idFromProps);
1147
1143
  const [dragState, setDragState] = useState(null);
1148
1144
  const [layout, setLayout] = useState([]);
1145
+ useState([]);
1149
1146
  const panelIdToLastNotifiedSizeMapRef = useRef({});
1150
1147
  const panelSizeBeforeCollapseRef = useRef(new Map());
1151
1148
  const prevDeltaRef = useRef(0);
@@ -1160,7 +1157,8 @@ function PanelGroupWithForwardedRef({
1160
1157
  });
1161
1158
  const eagerValuesRef = useRef({
1162
1159
  layout,
1163
- panelDataArray: []
1160
+ panelDataArray: [],
1161
+ panelDataArrayChanged: false
1164
1162
  });
1165
1163
  const devWarningsRef = useRef({
1166
1164
  didLogIdAndOrderWarning: false,
@@ -1421,28 +1419,8 @@ function PanelGroupWithForwardedRef({
1421
1419
  }, []);
1422
1420
  const registerPanel = useCallback(panelData => {
1423
1421
  const {
1424
- autoSaveId,
1425
- id: groupId,
1426
- onLayout,
1427
- storage
1428
- } = committedValuesRef.current;
1429
- const {
1430
- layout: prevLayout,
1431
1422
  panelDataArray
1432
1423
  } = eagerValuesRef.current;
1433
-
1434
- // HACK
1435
- // This appears to be triggered by some React Suspense+Offscreen+StrictMode bug;
1436
- // see app.replay.io/recording/17b6e11d-4500-4173-b23d-61dfd141fed1
1437
- const index = findPanelDataIndex(panelDataArray, panelData);
1438
- if (index >= 0) {
1439
- if (panelData.idIsFromProps) {
1440
- console.warn(`Panel with id "${panelData.id}" registered twice`);
1441
- } else {
1442
- console.warn(`Panel registered twice`);
1443
- }
1444
- return;
1445
- }
1446
1424
  panelDataArray.push(panelData);
1447
1425
  panelDataArray.sort((panelA, panelB) => {
1448
1426
  const orderA = panelA.order;
@@ -1457,45 +1435,52 @@ function PanelGroupWithForwardedRef({
1457
1435
  return orderA - orderB;
1458
1436
  }
1459
1437
  });
1438
+ eagerValuesRef.current.panelDataArrayChanged = true;
1439
+ }, []);
1460
1440
 
1461
- // Wait until all panels have registered before we try to compute layout;
1462
- // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1463
- const panelElements = getPanelElementsForGroup(groupId);
1464
- if (panelElements.length !== panelDataArray.length) {
1465
- return;
1466
- }
1467
-
1468
- // If this panel has been configured to persist sizing information,
1469
- // default size should be restored from local storage if possible.
1470
- let unsafeLayout = null;
1471
- if (autoSaveId) {
1472
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1473
- }
1474
- if (unsafeLayout == null) {
1475
- unsafeLayout = calculateUnsafeDefaultLayout({
1441
+ // (Re)calculate group layout whenever panels are registered or unregistered.
1442
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1443
+ useIsomorphicLayoutEffect(() => {
1444
+ if (eagerValuesRef.current.panelDataArrayChanged) {
1445
+ eagerValuesRef.current.panelDataArrayChanged = false;
1446
+ const {
1447
+ autoSaveId,
1448
+ onLayout,
1449
+ storage
1450
+ } = committedValuesRef.current;
1451
+ const {
1452
+ layout: prevLayout,
1476
1453
  panelDataArray
1477
- });
1478
- }
1454
+ } = eagerValuesRef.current;
1479
1455
 
1480
- // Validate even saved layouts in case something has changed since last render
1481
- // e.g. for pixel groups, this could be the size of the window
1482
- const nextLayout = validatePanelGroupLayout({
1483
- layout: unsafeLayout,
1484
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1485
- });
1456
+ // If this panel has been configured to persist sizing information,
1457
+ // default size should be restored from local storage if possible.
1458
+ let unsafeLayout = null;
1459
+ if (autoSaveId) {
1460
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1461
+ }
1462
+ if (unsafeLayout == null) {
1463
+ unsafeLayout = calculateUnsafeDefaultLayout({
1464
+ panelDataArray
1465
+ });
1466
+ }
1486
1467
 
1487
- // Offscreen mode makes this a bit weird;
1488
- // Panels unregister when hidden and re-register when shown again,
1489
- // but the overall layout doesn't change between these two cases.
1490
- setLayout(nextLayout);
1491
- eagerValuesRef.current.layout = nextLayout;
1492
- if (!areEqual(prevLayout, nextLayout)) {
1493
- if (onLayout) {
1494
- onLayout(nextLayout);
1468
+ // Validate even saved layouts in case something has changed since last render
1469
+ // e.g. for pixel groups, this could be the size of the window
1470
+ const nextLayout = validatePanelGroupLayout({
1471
+ layout: unsafeLayout,
1472
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1473
+ });
1474
+ if (!areEqual(prevLayout, nextLayout)) {
1475
+ setLayout(nextLayout);
1476
+ eagerValuesRef.current.layout = nextLayout;
1477
+ if (onLayout) {
1478
+ onLayout(nextLayout);
1479
+ }
1480
+ callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1495
1481
  }
1496
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1497
1482
  }
1498
- }, []);
1483
+ });
1499
1484
  const registerResizeHandle = useCallback(dragHandleId => {
1500
1485
  return function resizeHandler(event) {
1501
1486
  event.preventDefault();
@@ -1623,79 +1608,21 @@ function PanelGroupWithForwardedRef({
1623
1608
  resetGlobalCursorStyle();
1624
1609
  setDragState(null);
1625
1610
  }, []);
1626
- const unregisterPanelRef = useRef({
1627
- pendingPanelIds: new Set(),
1628
- timeout: null
1629
- });
1630
1611
  const unregisterPanel = useCallback(panelData => {
1631
1612
  const {
1632
- onLayout
1633
- } = committedValuesRef.current;
1634
- const {
1635
- layout: prevLayout,
1636
1613
  panelDataArray
1637
1614
  } = eagerValuesRef.current;
1638
1615
  const index = findPanelDataIndex(panelDataArray, panelData);
1639
1616
  if (index >= 0) {
1640
1617
  panelDataArray.splice(index, 1);
1641
- unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1642
- }
1643
- if (unregisterPanelRef.current.timeout != null) {
1644
- clearTimeout(unregisterPanelRef.current.timeout);
1645
- }
1646
-
1647
- // Batch panel unmounts so that we only calculate layout once;
1648
- // This is more efficient and avoids misleading warnings in development mode.
1649
- // We can't check the DOM to detect this because Panel elements have not yet been removed.
1650
- unregisterPanelRef.current.timeout = setTimeout(() => {
1651
- const {
1652
- pendingPanelIds
1653
- } = unregisterPanelRef.current;
1654
- const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1655
1618
 
1656
1619
  // TRICKY
1657
- // Strict effects mode
1658
- let unmountDueToStrictMode = false;
1659
- pendingPanelIds.forEach(panelId => {
1660
- pendingPanelIds.delete(panelId);
1661
- if (panelDataArray.find(({
1662
- id
1663
- }) => id === panelId) != null) {
1664
- unmountDueToStrictMode = true;
1665
- } else {
1666
- // TRICKY
1667
- // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1668
- // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1669
- // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1670
- delete panelIdToLastNotifiedSizeMap[panelId];
1671
- }
1672
- });
1673
- if (unmountDueToStrictMode) {
1674
- return;
1675
- }
1676
- if (panelDataArray.length === 0) {
1677
- // The group is unmounting; skip layout calculation.
1678
- return;
1679
- }
1680
- let unsafeLayout = calculateUnsafeDefaultLayout({
1681
- panelDataArray
1682
- });
1683
-
1684
- // Validate even saved layouts in case something has changed since last render
1685
- // e.g. for pixel groups, this could be the size of the window
1686
- const nextLayout = validatePanelGroupLayout({
1687
- layout: unsafeLayout,
1688
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1689
- });
1690
- if (!areEqual(prevLayout, nextLayout)) {
1691
- setLayout(nextLayout);
1692
- eagerValuesRef.current.layout = nextLayout;
1693
- if (onLayout) {
1694
- onLayout(nextLayout);
1695
- }
1696
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1697
- }
1698
- }, 0);
1620
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1621
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1622
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1623
+ delete panelIdToLastNotifiedSizeMapRef.current[panelData.id];
1624
+ eagerValuesRef.current.panelDataArrayChanged = true;
1625
+ }
1699
1626
  }, []);
1700
1627
  const context = useMemo(() => ({
1701
1628
  collapsePanel,
@@ -913,10 +913,6 @@ function debounce(callback, durationMs = 10) {
913
913
  return callable;
914
914
  }
915
915
 
916
- function getPanelElementsForGroup(groupId) {
917
- return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
918
- }
919
-
920
916
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
921
917
  // or on a browser with cookies/storage disabled.
922
918
  // In either case, this function avoids accessing localStorage until needed,
@@ -1083,6 +1079,7 @@ function PanelGroupWithForwardedRef({
1083
1079
  const groupId = useUniqueId(idFromProps);
1084
1080
  const [dragState, setDragState] = useState(null);
1085
1081
  const [layout, setLayout] = useState([]);
1082
+ useState([]);
1086
1083
  const panelIdToLastNotifiedSizeMapRef = useRef({});
1087
1084
  const panelSizeBeforeCollapseRef = useRef(new Map());
1088
1085
  const prevDeltaRef = useRef(0);
@@ -1097,7 +1094,8 @@ function PanelGroupWithForwardedRef({
1097
1094
  });
1098
1095
  const eagerValuesRef = useRef({
1099
1096
  layout,
1100
- panelDataArray: []
1097
+ panelDataArray: [],
1098
+ panelDataArrayChanged: false
1101
1099
  });
1102
1100
  useRef({
1103
1101
  didLogIdAndOrderWarning: false,
@@ -1316,28 +1314,8 @@ function PanelGroupWithForwardedRef({
1316
1314
  }, []);
1317
1315
  const registerPanel = useCallback(panelData => {
1318
1316
  const {
1319
- autoSaveId,
1320
- id: groupId,
1321
- onLayout,
1322
- storage
1323
- } = committedValuesRef.current;
1324
- const {
1325
- layout: prevLayout,
1326
1317
  panelDataArray
1327
1318
  } = eagerValuesRef.current;
1328
-
1329
- // HACK
1330
- // This appears to be triggered by some React Suspense+Offscreen+StrictMode bug;
1331
- // see app.replay.io/recording/17b6e11d-4500-4173-b23d-61dfd141fed1
1332
- const index = findPanelDataIndex(panelDataArray, panelData);
1333
- if (index >= 0) {
1334
- if (panelData.idIsFromProps) {
1335
- console.warn(`Panel with id "${panelData.id}" registered twice`);
1336
- } else {
1337
- console.warn(`Panel registered twice`);
1338
- }
1339
- return;
1340
- }
1341
1319
  panelDataArray.push(panelData);
1342
1320
  panelDataArray.sort((panelA, panelB) => {
1343
1321
  const orderA = panelA.order;
@@ -1352,45 +1330,52 @@ function PanelGroupWithForwardedRef({
1352
1330
  return orderA - orderB;
1353
1331
  }
1354
1332
  });
1333
+ eagerValuesRef.current.panelDataArrayChanged = true;
1334
+ }, []);
1355
1335
 
1356
- // Wait until all panels have registered before we try to compute layout;
1357
- // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1358
- const panelElements = getPanelElementsForGroup(groupId);
1359
- if (panelElements.length !== panelDataArray.length) {
1360
- return;
1361
- }
1362
-
1363
- // If this panel has been configured to persist sizing information,
1364
- // default size should be restored from local storage if possible.
1365
- let unsafeLayout = null;
1366
- if (autoSaveId) {
1367
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1368
- }
1369
- if (unsafeLayout == null) {
1370
- unsafeLayout = calculateUnsafeDefaultLayout({
1336
+ // (Re)calculate group layout whenever panels are registered or unregistered.
1337
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1338
+ useIsomorphicLayoutEffect(() => {
1339
+ if (eagerValuesRef.current.panelDataArrayChanged) {
1340
+ eagerValuesRef.current.panelDataArrayChanged = false;
1341
+ const {
1342
+ autoSaveId,
1343
+ onLayout,
1344
+ storage
1345
+ } = committedValuesRef.current;
1346
+ const {
1347
+ layout: prevLayout,
1371
1348
  panelDataArray
1372
- });
1373
- }
1349
+ } = eagerValuesRef.current;
1374
1350
 
1375
- // Validate even saved layouts in case something has changed since last render
1376
- // e.g. for pixel groups, this could be the size of the window
1377
- const nextLayout = validatePanelGroupLayout({
1378
- layout: unsafeLayout,
1379
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1380
- });
1351
+ // If this panel has been configured to persist sizing information,
1352
+ // default size should be restored from local storage if possible.
1353
+ let unsafeLayout = null;
1354
+ if (autoSaveId) {
1355
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1356
+ }
1357
+ if (unsafeLayout == null) {
1358
+ unsafeLayout = calculateUnsafeDefaultLayout({
1359
+ panelDataArray
1360
+ });
1361
+ }
1381
1362
 
1382
- // Offscreen mode makes this a bit weird;
1383
- // Panels unregister when hidden and re-register when shown again,
1384
- // but the overall layout doesn't change between these two cases.
1385
- setLayout(nextLayout);
1386
- eagerValuesRef.current.layout = nextLayout;
1387
- if (!areEqual(prevLayout, nextLayout)) {
1388
- if (onLayout) {
1389
- onLayout(nextLayout);
1363
+ // Validate even saved layouts in case something has changed since last render
1364
+ // e.g. for pixel groups, this could be the size of the window
1365
+ const nextLayout = validatePanelGroupLayout({
1366
+ layout: unsafeLayout,
1367
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1368
+ });
1369
+ if (!areEqual(prevLayout, nextLayout)) {
1370
+ setLayout(nextLayout);
1371
+ eagerValuesRef.current.layout = nextLayout;
1372
+ if (onLayout) {
1373
+ onLayout(nextLayout);
1374
+ }
1375
+ callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1390
1376
  }
1391
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1392
1377
  }
1393
- }, []);
1378
+ });
1394
1379
  const registerResizeHandle = useCallback(dragHandleId => {
1395
1380
  return function resizeHandler(event) {
1396
1381
  event.preventDefault();
@@ -1518,79 +1503,21 @@ function PanelGroupWithForwardedRef({
1518
1503
  resetGlobalCursorStyle();
1519
1504
  setDragState(null);
1520
1505
  }, []);
1521
- const unregisterPanelRef = useRef({
1522
- pendingPanelIds: new Set(),
1523
- timeout: null
1524
- });
1525
1506
  const unregisterPanel = useCallback(panelData => {
1526
1507
  const {
1527
- onLayout
1528
- } = committedValuesRef.current;
1529
- const {
1530
- layout: prevLayout,
1531
1508
  panelDataArray
1532
1509
  } = eagerValuesRef.current;
1533
1510
  const index = findPanelDataIndex(panelDataArray, panelData);
1534
1511
  if (index >= 0) {
1535
1512
  panelDataArray.splice(index, 1);
1536
- unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1537
- }
1538
- if (unregisterPanelRef.current.timeout != null) {
1539
- clearTimeout(unregisterPanelRef.current.timeout);
1540
- }
1541
-
1542
- // Batch panel unmounts so that we only calculate layout once;
1543
- // This is more efficient and avoids misleading warnings in development mode.
1544
- // We can't check the DOM to detect this because Panel elements have not yet been removed.
1545
- unregisterPanelRef.current.timeout = setTimeout(() => {
1546
- const {
1547
- pendingPanelIds
1548
- } = unregisterPanelRef.current;
1549
- const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1550
1513
 
1551
1514
  // TRICKY
1552
- // Strict effects mode
1553
- let unmountDueToStrictMode = false;
1554
- pendingPanelIds.forEach(panelId => {
1555
- pendingPanelIds.delete(panelId);
1556
- if (panelDataArray.find(({
1557
- id
1558
- }) => id === panelId) != null) {
1559
- unmountDueToStrictMode = true;
1560
- } else {
1561
- // TRICKY
1562
- // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1563
- // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1564
- // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1565
- delete panelIdToLastNotifiedSizeMap[panelId];
1566
- }
1567
- });
1568
- if (unmountDueToStrictMode) {
1569
- return;
1570
- }
1571
- if (panelDataArray.length === 0) {
1572
- // The group is unmounting; skip layout calculation.
1573
- return;
1574
- }
1575
- let unsafeLayout = calculateUnsafeDefaultLayout({
1576
- panelDataArray
1577
- });
1578
-
1579
- // Validate even saved layouts in case something has changed since last render
1580
- // e.g. for pixel groups, this could be the size of the window
1581
- const nextLayout = validatePanelGroupLayout({
1582
- layout: unsafeLayout,
1583
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1584
- });
1585
- if (!areEqual(prevLayout, nextLayout)) {
1586
- setLayout(nextLayout);
1587
- eagerValuesRef.current.layout = nextLayout;
1588
- if (onLayout) {
1589
- onLayout(nextLayout);
1590
- }
1591
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1592
- }
1593
- }, 0);
1515
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1516
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1517
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1518
+ delete panelIdToLastNotifiedSizeMapRef.current[panelData.id];
1519
+ eagerValuesRef.current.panelDataArrayChanged = true;
1520
+ }
1594
1521
  }, []);
1595
1522
  const context = useMemo(() => ({
1596
1523
  collapsePanel,