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.
@@ -936,10 +936,6 @@ function debounce(callback, durationMs = 10) {
936
936
  return callable;
937
937
  }
938
938
 
939
- function getPanelElementsForGroup(groupId) {
940
- return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
941
- }
942
-
943
939
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
944
940
  // or on a browser with cookies/storage disabled.
945
941
  // In either case, this function avoids accessing localStorage until needed,
@@ -1153,6 +1149,7 @@ function PanelGroupWithForwardedRef({
1153
1149
  const groupId = useUniqueId(idFromProps);
1154
1150
  const [dragState, setDragState] = useState(null);
1155
1151
  const [layout, setLayout] = useState([]);
1152
+ useState([]);
1156
1153
  const panelIdToLastNotifiedSizeMapRef = useRef({});
1157
1154
  const panelSizeBeforeCollapseRef = useRef(new Map());
1158
1155
  const prevDeltaRef = useRef(0);
@@ -1167,7 +1164,8 @@ function PanelGroupWithForwardedRef({
1167
1164
  });
1168
1165
  const eagerValuesRef = useRef({
1169
1166
  layout,
1170
- panelDataArray: []
1167
+ panelDataArray: [],
1168
+ panelDataArrayChanged: false
1171
1169
  });
1172
1170
  const devWarningsRef = useRef({
1173
1171
  didLogIdAndOrderWarning: false,
@@ -1428,28 +1426,8 @@ function PanelGroupWithForwardedRef({
1428
1426
  }, []);
1429
1427
  const registerPanel = useCallback(panelData => {
1430
1428
  const {
1431
- autoSaveId,
1432
- id: groupId,
1433
- onLayout,
1434
- storage
1435
- } = committedValuesRef.current;
1436
- const {
1437
- layout: prevLayout,
1438
1429
  panelDataArray
1439
1430
  } = eagerValuesRef.current;
1440
-
1441
- // HACK
1442
- // This appears to be triggered by some React Suspense+Offscreen+StrictMode bug;
1443
- // see app.replay.io/recording/17b6e11d-4500-4173-b23d-61dfd141fed1
1444
- const index = findPanelDataIndex(panelDataArray, panelData);
1445
- if (index >= 0) {
1446
- if (panelData.idIsFromProps) {
1447
- console.warn(`Panel with id "${panelData.id}" registered twice`);
1448
- } else {
1449
- console.warn(`Panel registered twice`);
1450
- }
1451
- return;
1452
- }
1453
1431
  panelDataArray.push(panelData);
1454
1432
  panelDataArray.sort((panelA, panelB) => {
1455
1433
  const orderA = panelA.order;
@@ -1464,45 +1442,52 @@ function PanelGroupWithForwardedRef({
1464
1442
  return orderA - orderB;
1465
1443
  }
1466
1444
  });
1445
+ eagerValuesRef.current.panelDataArrayChanged = true;
1446
+ }, []);
1467
1447
 
1468
- // Wait until all panels have registered before we try to compute layout;
1469
- // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1470
- const panelElements = getPanelElementsForGroup(groupId);
1471
- if (panelElements.length !== panelDataArray.length) {
1472
- return;
1473
- }
1474
-
1475
- // If this panel has been configured to persist sizing information,
1476
- // default size should be restored from local storage if possible.
1477
- let unsafeLayout = null;
1478
- if (autoSaveId) {
1479
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1480
- }
1481
- if (unsafeLayout == null) {
1482
- unsafeLayout = calculateUnsafeDefaultLayout({
1448
+ // (Re)calculate group layout whenever panels are registered or unregistered.
1449
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1450
+ useIsomorphicLayoutEffect(() => {
1451
+ if (eagerValuesRef.current.panelDataArrayChanged) {
1452
+ eagerValuesRef.current.panelDataArrayChanged = false;
1453
+ const {
1454
+ autoSaveId,
1455
+ onLayout,
1456
+ storage
1457
+ } = committedValuesRef.current;
1458
+ const {
1459
+ layout: prevLayout,
1483
1460
  panelDataArray
1484
- });
1485
- }
1461
+ } = eagerValuesRef.current;
1486
1462
 
1487
- // Validate even saved layouts in case something has changed since last render
1488
- // e.g. for pixel groups, this could be the size of the window
1489
- const nextLayout = validatePanelGroupLayout({
1490
- layout: unsafeLayout,
1491
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1492
- });
1463
+ // If this panel has been configured to persist sizing information,
1464
+ // default size should be restored from local storage if possible.
1465
+ let unsafeLayout = null;
1466
+ if (autoSaveId) {
1467
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1468
+ }
1469
+ if (unsafeLayout == null) {
1470
+ unsafeLayout = calculateUnsafeDefaultLayout({
1471
+ panelDataArray
1472
+ });
1473
+ }
1493
1474
 
1494
- // Offscreen mode makes this a bit weird;
1495
- // Panels unregister when hidden and re-register when shown again,
1496
- // but the overall layout doesn't change between these two cases.
1497
- setLayout(nextLayout);
1498
- eagerValuesRef.current.layout = nextLayout;
1499
- if (!areEqual(prevLayout, nextLayout)) {
1500
- if (onLayout) {
1501
- onLayout(nextLayout);
1475
+ // Validate even saved layouts in case something has changed since last render
1476
+ // e.g. for pixel groups, this could be the size of the window
1477
+ const nextLayout = validatePanelGroupLayout({
1478
+ layout: unsafeLayout,
1479
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1480
+ });
1481
+ if (!areEqual(prevLayout, nextLayout)) {
1482
+ setLayout(nextLayout);
1483
+ eagerValuesRef.current.layout = nextLayout;
1484
+ if (onLayout) {
1485
+ onLayout(nextLayout);
1486
+ }
1487
+ callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1502
1488
  }
1503
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1504
1489
  }
1505
- }, []);
1490
+ });
1506
1491
  const registerResizeHandle = useCallback(dragHandleId => {
1507
1492
  return function resizeHandler(event) {
1508
1493
  event.preventDefault();
@@ -1630,79 +1615,21 @@ function PanelGroupWithForwardedRef({
1630
1615
  resetGlobalCursorStyle();
1631
1616
  setDragState(null);
1632
1617
  }, []);
1633
- const unregisterPanelRef = useRef({
1634
- pendingPanelIds: new Set(),
1635
- timeout: null
1636
- });
1637
1618
  const unregisterPanel = useCallback(panelData => {
1638
1619
  const {
1639
- onLayout
1640
- } = committedValuesRef.current;
1641
- const {
1642
- layout: prevLayout,
1643
1620
  panelDataArray
1644
1621
  } = eagerValuesRef.current;
1645
1622
  const index = findPanelDataIndex(panelDataArray, panelData);
1646
1623
  if (index >= 0) {
1647
1624
  panelDataArray.splice(index, 1);
1648
- unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1649
- }
1650
- if (unregisterPanelRef.current.timeout != null) {
1651
- clearTimeout(unregisterPanelRef.current.timeout);
1652
- }
1653
-
1654
- // Batch panel unmounts so that we only calculate layout once;
1655
- // This is more efficient and avoids misleading warnings in development mode.
1656
- // We can't check the DOM to detect this because Panel elements have not yet been removed.
1657
- unregisterPanelRef.current.timeout = setTimeout(() => {
1658
- const {
1659
- pendingPanelIds
1660
- } = unregisterPanelRef.current;
1661
- const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1662
1625
 
1663
1626
  // TRICKY
1664
- // Strict effects mode
1665
- let unmountDueToStrictMode = false;
1666
- pendingPanelIds.forEach(panelId => {
1667
- pendingPanelIds.delete(panelId);
1668
- if (panelDataArray.find(({
1669
- id
1670
- }) => id === panelId) != null) {
1671
- unmountDueToStrictMode = true;
1672
- } else {
1673
- // TRICKY
1674
- // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1675
- // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1676
- // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1677
- delete panelIdToLastNotifiedSizeMap[panelId];
1678
- }
1679
- });
1680
- if (unmountDueToStrictMode) {
1681
- return;
1682
- }
1683
- if (panelDataArray.length === 0) {
1684
- // The group is unmounting; skip layout calculation.
1685
- return;
1686
- }
1687
- let unsafeLayout = calculateUnsafeDefaultLayout({
1688
- panelDataArray
1689
- });
1690
-
1691
- // Validate even saved layouts in case something has changed since last render
1692
- // e.g. for pixel groups, this could be the size of the window
1693
- const nextLayout = validatePanelGroupLayout({
1694
- layout: unsafeLayout,
1695
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1696
- });
1697
- if (!areEqual(prevLayout, nextLayout)) {
1698
- setLayout(nextLayout);
1699
- eagerValuesRef.current.layout = nextLayout;
1700
- if (onLayout) {
1701
- onLayout(nextLayout);
1702
- }
1703
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1704
- }
1705
- }, 0);
1627
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1628
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1629
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1630
+ delete panelIdToLastNotifiedSizeMapRef.current[panelData.id];
1631
+ eagerValuesRef.current.panelDataArrayChanged = true;
1632
+ }
1706
1633
  }, []);
1707
1634
  const context = useMemo(() => ({
1708
1635
  collapsePanel,
@@ -678,47 +678,6 @@ function calculateDeltaPercentage(event, dragHandleId, direction, initialDragSta
678
678
  }
679
679
  }
680
680
 
681
- function calculateUnsafeDefaultLayout({
682
- panelDataArray
683
- }) {
684
- const layout = Array(panelDataArray.length);
685
- const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
686
- let numPanelsWithSizes = 0;
687
- let remainingSize = 100;
688
-
689
- // Distribute default sizes first
690
- for (let index = 0; index < panelDataArray.length; index++) {
691
- const panelConstraints = panelConstraintsArray[index];
692
- assert(panelConstraints);
693
- const {
694
- defaultSize
695
- } = panelConstraints;
696
- if (defaultSize != null) {
697
- numPanelsWithSizes++;
698
- layout[index] = defaultSize;
699
- remainingSize -= defaultSize;
700
- }
701
- }
702
-
703
- // Remaining size should be distributed evenly between panels without default sizes
704
- for (let index = 0; index < panelDataArray.length; index++) {
705
- const panelConstraints = panelConstraintsArray[index];
706
- assert(panelConstraints);
707
- const {
708
- defaultSize
709
- } = panelConstraints;
710
- if (defaultSize != null) {
711
- continue;
712
- }
713
- const numRemainingPanels = panelDataArray.length - numPanelsWithSizes;
714
- const size = remainingSize / numRemainingPanels;
715
- numPanelsWithSizes++;
716
- layout[index] = size;
717
- remainingSize -= size;
718
- }
719
- return layout;
720
- }
721
-
722
681
  // Layout should be pre-converted into percentages
723
682
  function callPanelCallbacks(panelsArray, layout, panelIdToLastNotifiedSizeMap) {
724
683
  layout.forEach((size, index) => {
@@ -852,10 +811,6 @@ function debounce(callback, durationMs = 10) {
852
811
  return callable;
853
812
  }
854
813
 
855
- function getPanelElementsForGroup(groupId) {
856
- return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
857
- }
858
-
859
814
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
860
815
  // or on a browser with cookies/storage disabled.
861
816
  // In either case, this function avoids accessing localStorage until needed,
@@ -911,15 +866,6 @@ function loadSerializedPanelGroupState(autoSaveId, storage) {
911
866
  } catch (error) {}
912
867
  return null;
913
868
  }
914
- function loadPanelLayout(autoSaveId, panels, storage) {
915
- const state = loadSerializedPanelGroupState(autoSaveId, storage);
916
- if (state) {
917
- var _state$key;
918
- const key = getSerializationKey(panels);
919
- return (_state$key = state[key]) !== null && _state$key !== void 0 ? _state$key : null;
920
- }
921
- return null;
922
- }
923
869
  function savePanelGroupLayout(autoSaveId, panels, sizes, storage) {
924
870
  const key = getSerializationKey(panels);
925
871
  const state = loadSerializedPanelGroupState(autoSaveId, storage) || {};
@@ -1069,6 +1015,7 @@ function PanelGroupWithForwardedRef({
1069
1015
  const groupId = useUniqueId(idFromProps);
1070
1016
  const [dragState, setDragState] = useState(null);
1071
1017
  const [layout, setLayout] = useState([]);
1018
+ useState([]);
1072
1019
  const panelIdToLastNotifiedSizeMapRef = useRef({});
1073
1020
  const panelSizeBeforeCollapseRef = useRef(new Map());
1074
1021
  const prevDeltaRef = useRef(0);
@@ -1083,7 +1030,8 @@ function PanelGroupWithForwardedRef({
1083
1030
  });
1084
1031
  const eagerValuesRef = useRef({
1085
1032
  layout,
1086
- panelDataArray: []
1033
+ panelDataArray: [],
1034
+ panelDataArrayChanged: false
1087
1035
  });
1088
1036
  const devWarningsRef = useRef({
1089
1037
  didLogIdAndOrderWarning: false,
@@ -1336,28 +1284,8 @@ function PanelGroupWithForwardedRef({
1336
1284
  }, []);
1337
1285
  const registerPanel = useCallback(panelData => {
1338
1286
  const {
1339
- autoSaveId,
1340
- id: groupId,
1341
- onLayout,
1342
- storage
1343
- } = committedValuesRef.current;
1344
- const {
1345
- layout: prevLayout,
1346
1287
  panelDataArray
1347
1288
  } = eagerValuesRef.current;
1348
-
1349
- // HACK
1350
- // This appears to be triggered by some React Suspense+Offscreen+StrictMode bug;
1351
- // see app.replay.io/recording/17b6e11d-4500-4173-b23d-61dfd141fed1
1352
- const index = findPanelDataIndex(panelDataArray, panelData);
1353
- if (index >= 0) {
1354
- if (panelData.idIsFromProps) {
1355
- console.warn(`Panel with id "${panelData.id}" registered twice`);
1356
- } else {
1357
- console.warn(`Panel registered twice`);
1358
- }
1359
- return;
1360
- }
1361
1289
  panelDataArray.push(panelData);
1362
1290
  panelDataArray.sort((panelA, panelB) => {
1363
1291
  const orderA = panelA.order;
@@ -1372,44 +1300,7 @@ function PanelGroupWithForwardedRef({
1372
1300
  return orderA - orderB;
1373
1301
  }
1374
1302
  });
1375
-
1376
- // Wait until all panels have registered before we try to compute layout;
1377
- // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1378
- const panelElements = getPanelElementsForGroup(groupId);
1379
- if (panelElements.length !== panelDataArray.length) {
1380
- return;
1381
- }
1382
-
1383
- // If this panel has been configured to persist sizing information,
1384
- // default size should be restored from local storage if possible.
1385
- let unsafeLayout = null;
1386
- if (autoSaveId) {
1387
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1388
- }
1389
- if (unsafeLayout == null) {
1390
- unsafeLayout = calculateUnsafeDefaultLayout({
1391
- panelDataArray
1392
- });
1393
- }
1394
-
1395
- // Validate even saved layouts in case something has changed since last render
1396
- // e.g. for pixel groups, this could be the size of the window
1397
- const nextLayout = validatePanelGroupLayout({
1398
- layout: unsafeLayout,
1399
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1400
- });
1401
-
1402
- // Offscreen mode makes this a bit weird;
1403
- // Panels unregister when hidden and re-register when shown again,
1404
- // but the overall layout doesn't change between these two cases.
1405
- setLayout(nextLayout);
1406
- eagerValuesRef.current.layout = nextLayout;
1407
- if (!areEqual(prevLayout, nextLayout)) {
1408
- if (onLayout) {
1409
- onLayout(nextLayout);
1410
- }
1411
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1412
- }
1303
+ eagerValuesRef.current.panelDataArrayChanged = true;
1413
1304
  }, []);
1414
1305
  const registerResizeHandle = useCallback(dragHandleId => {
1415
1306
  return function resizeHandler(event) {
@@ -1538,79 +1429,21 @@ function PanelGroupWithForwardedRef({
1538
1429
  resetGlobalCursorStyle();
1539
1430
  setDragState(null);
1540
1431
  }, []);
1541
- const unregisterPanelRef = useRef({
1542
- pendingPanelIds: new Set(),
1543
- timeout: null
1544
- });
1545
1432
  const unregisterPanel = useCallback(panelData => {
1546
1433
  const {
1547
- onLayout
1548
- } = committedValuesRef.current;
1549
- const {
1550
- layout: prevLayout,
1551
1434
  panelDataArray
1552
1435
  } = eagerValuesRef.current;
1553
1436
  const index = findPanelDataIndex(panelDataArray, panelData);
1554
1437
  if (index >= 0) {
1555
1438
  panelDataArray.splice(index, 1);
1556
- unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1557
- }
1558
- if (unregisterPanelRef.current.timeout != null) {
1559
- clearTimeout(unregisterPanelRef.current.timeout);
1560
- }
1561
-
1562
- // Batch panel unmounts so that we only calculate layout once;
1563
- // This is more efficient and avoids misleading warnings in development mode.
1564
- // We can't check the DOM to detect this because Panel elements have not yet been removed.
1565
- unregisterPanelRef.current.timeout = setTimeout(() => {
1566
- const {
1567
- pendingPanelIds
1568
- } = unregisterPanelRef.current;
1569
- const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1570
1439
 
1571
1440
  // TRICKY
1572
- // Strict effects mode
1573
- let unmountDueToStrictMode = false;
1574
- pendingPanelIds.forEach(panelId => {
1575
- pendingPanelIds.delete(panelId);
1576
- if (panelDataArray.find(({
1577
- id
1578
- }) => id === panelId) != null) {
1579
- unmountDueToStrictMode = true;
1580
- } else {
1581
- // TRICKY
1582
- // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1583
- // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1584
- // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1585
- delete panelIdToLastNotifiedSizeMap[panelId];
1586
- }
1587
- });
1588
- if (unmountDueToStrictMode) {
1589
- return;
1590
- }
1591
- if (panelDataArray.length === 0) {
1592
- // The group is unmounting; skip layout calculation.
1593
- return;
1594
- }
1595
- let unsafeLayout = calculateUnsafeDefaultLayout({
1596
- panelDataArray
1597
- });
1598
-
1599
- // Validate even saved layouts in case something has changed since last render
1600
- // e.g. for pixel groups, this could be the size of the window
1601
- const nextLayout = validatePanelGroupLayout({
1602
- layout: unsafeLayout,
1603
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1604
- });
1605
- if (!areEqual(prevLayout, nextLayout)) {
1606
- setLayout(nextLayout);
1607
- eagerValuesRef.current.layout = nextLayout;
1608
- if (onLayout) {
1609
- onLayout(nextLayout);
1610
- }
1611
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1612
- }
1613
- }, 0);
1441
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1442
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1443
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1444
+ delete panelIdToLastNotifiedSizeMapRef.current[panelData.id];
1445
+ eagerValuesRef.current.panelDataArrayChanged = true;
1446
+ }
1614
1447
  }, []);
1615
1448
  const context = useMemo(() => ({
1616
1449
  collapsePanel,