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

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.
@@ -654,47 +654,6 @@ function calculateDeltaPercentage(event, dragHandleId, direction, initialDragSta
654
654
  }
655
655
  }
656
656
 
657
- function calculateUnsafeDefaultLayout({
658
- panelDataArray
659
- }) {
660
- const layout = Array(panelDataArray.length);
661
- const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
662
- let numPanelsWithSizes = 0;
663
- let remainingSize = 100;
664
-
665
- // Distribute default sizes first
666
- for (let index = 0; index < panelDataArray.length; index++) {
667
- const panelConstraints = panelConstraintsArray[index];
668
- assert(panelConstraints);
669
- const {
670
- defaultSize
671
- } = panelConstraints;
672
- if (defaultSize != null) {
673
- numPanelsWithSizes++;
674
- layout[index] = defaultSize;
675
- remainingSize -= defaultSize;
676
- }
677
- }
678
-
679
- // Remaining size should be distributed evenly between panels without default sizes
680
- for (let index = 0; index < panelDataArray.length; index++) {
681
- const panelConstraints = panelConstraintsArray[index];
682
- assert(panelConstraints);
683
- const {
684
- defaultSize
685
- } = panelConstraints;
686
- if (defaultSize != null) {
687
- continue;
688
- }
689
- const numRemainingPanels = panelDataArray.length - numPanelsWithSizes;
690
- const size = remainingSize / numRemainingPanels;
691
- numPanelsWithSizes++;
692
- layout[index] = size;
693
- remainingSize -= size;
694
- }
695
- return layout;
696
- }
697
-
698
657
  // Layout should be pre-converted into percentages
699
658
  function callPanelCallbacks(panelsArray, layout, panelIdToLastNotifiedSizeMap) {
700
659
  layout.forEach((size, index) => {
@@ -828,10 +787,6 @@ function debounce(callback, durationMs = 10) {
828
787
  return callable;
829
788
  }
830
789
 
831
- function getPanelElementsForGroup(groupId) {
832
- return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
833
- }
834
-
835
790
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
836
791
  // or on a browser with cookies/storage disabled.
837
792
  // In either case, this function avoids accessing localStorage until needed,
@@ -887,15 +842,6 @@ function loadSerializedPanelGroupState(autoSaveId, storage) {
887
842
  } catch (error) {}
888
843
  return null;
889
844
  }
890
- function loadPanelLayout(autoSaveId, panels, storage) {
891
- const state = loadSerializedPanelGroupState(autoSaveId, storage);
892
- if (state) {
893
- var _state$key;
894
- const key = getSerializationKey(panels);
895
- return (_state$key = state[key]) !== null && _state$key !== void 0 ? _state$key : null;
896
- }
897
- return null;
898
- }
899
845
  function savePanelGroupLayout(autoSaveId, panels, sizes, storage) {
900
846
  const key = getSerializationKey(panels);
901
847
  const state = loadSerializedPanelGroupState(autoSaveId, storage) || {};
@@ -1045,6 +991,7 @@ function PanelGroupWithForwardedRef({
1045
991
  const groupId = useUniqueId(idFromProps);
1046
992
  const [dragState, setDragState] = useState(null);
1047
993
  const [layout, setLayout] = useState([]);
994
+ useState([]);
1048
995
  const panelIdToLastNotifiedSizeMapRef = useRef({});
1049
996
  const panelSizeBeforeCollapseRef = useRef(new Map());
1050
997
  const prevDeltaRef = useRef(0);
@@ -1059,7 +1006,8 @@ function PanelGroupWithForwardedRef({
1059
1006
  });
1060
1007
  const eagerValuesRef = useRef({
1061
1008
  layout,
1062
- panelDataArray: []
1009
+ panelDataArray: [],
1010
+ panelDataArrayChanged: false
1063
1011
  });
1064
1012
  const devWarningsRef = useRef({
1065
1013
  didLogIdAndOrderWarning: false,
@@ -1312,28 +1260,8 @@ function PanelGroupWithForwardedRef({
1312
1260
  }, []);
1313
1261
  const registerPanel = useCallback(panelData => {
1314
1262
  const {
1315
- autoSaveId,
1316
- id: groupId,
1317
- onLayout,
1318
- storage
1319
- } = committedValuesRef.current;
1320
- const {
1321
- layout: prevLayout,
1322
1263
  panelDataArray
1323
1264
  } = eagerValuesRef.current;
1324
-
1325
- // HACK
1326
- // This appears to be triggered by some React Suspense+Offscreen+StrictMode bug;
1327
- // see app.replay.io/recording/17b6e11d-4500-4173-b23d-61dfd141fed1
1328
- const index = findPanelDataIndex(panelDataArray, panelData);
1329
- if (index >= 0) {
1330
- if (panelData.idIsFromProps) {
1331
- console.warn(`Panel with id "${panelData.id}" registered twice`);
1332
- } else {
1333
- console.warn(`Panel registered twice`);
1334
- }
1335
- return;
1336
- }
1337
1265
  panelDataArray.push(panelData);
1338
1266
  panelDataArray.sort((panelA, panelB) => {
1339
1267
  const orderA = panelA.order;
@@ -1348,44 +1276,7 @@ function PanelGroupWithForwardedRef({
1348
1276
  return orderA - orderB;
1349
1277
  }
1350
1278
  });
1351
-
1352
- // Wait until all panels have registered before we try to compute layout;
1353
- // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1354
- const panelElements = getPanelElementsForGroup(groupId);
1355
- if (panelElements.length !== panelDataArray.length) {
1356
- return;
1357
- }
1358
-
1359
- // If this panel has been configured to persist sizing information,
1360
- // default size should be restored from local storage if possible.
1361
- let unsafeLayout = null;
1362
- if (autoSaveId) {
1363
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1364
- }
1365
- if (unsafeLayout == null) {
1366
- unsafeLayout = calculateUnsafeDefaultLayout({
1367
- panelDataArray
1368
- });
1369
- }
1370
-
1371
- // Validate even saved layouts in case something has changed since last render
1372
- // e.g. for pixel groups, this could be the size of the window
1373
- const nextLayout = validatePanelGroupLayout({
1374
- layout: unsafeLayout,
1375
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1376
- });
1377
-
1378
- // Offscreen mode makes this a bit weird;
1379
- // Panels unregister when hidden and re-register when shown again,
1380
- // but the overall layout doesn't change between these two cases.
1381
- setLayout(nextLayout);
1382
- eagerValuesRef.current.layout = nextLayout;
1383
- if (!areEqual(prevLayout, nextLayout)) {
1384
- if (onLayout) {
1385
- onLayout(nextLayout);
1386
- }
1387
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1388
- }
1279
+ eagerValuesRef.current.panelDataArrayChanged = true;
1389
1280
  }, []);
1390
1281
  const registerResizeHandle = useCallback(dragHandleId => {
1391
1282
  return function resizeHandler(event) {
@@ -1514,79 +1405,21 @@ function PanelGroupWithForwardedRef({
1514
1405
  resetGlobalCursorStyle();
1515
1406
  setDragState(null);
1516
1407
  }, []);
1517
- const unregisterPanelRef = useRef({
1518
- pendingPanelIds: new Set(),
1519
- timeout: null
1520
- });
1521
1408
  const unregisterPanel = useCallback(panelData => {
1522
1409
  const {
1523
- onLayout
1524
- } = committedValuesRef.current;
1525
- const {
1526
- layout: prevLayout,
1527
1410
  panelDataArray
1528
1411
  } = eagerValuesRef.current;
1529
1412
  const index = findPanelDataIndex(panelDataArray, panelData);
1530
1413
  if (index >= 0) {
1531
1414
  panelDataArray.splice(index, 1);
1532
- unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1533
- }
1534
- if (unregisterPanelRef.current.timeout != null) {
1535
- clearTimeout(unregisterPanelRef.current.timeout);
1536
- }
1537
-
1538
- // Batch panel unmounts so that we only calculate layout once;
1539
- // This is more efficient and avoids misleading warnings in development mode.
1540
- // We can't check the DOM to detect this because Panel elements have not yet been removed.
1541
- unregisterPanelRef.current.timeout = setTimeout(() => {
1542
- const {
1543
- pendingPanelIds
1544
- } = unregisterPanelRef.current;
1545
- const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1546
1415
 
1547
1416
  // TRICKY
1548
- // Strict effects mode
1549
- let unmountDueToStrictMode = false;
1550
- pendingPanelIds.forEach(panelId => {
1551
- pendingPanelIds.delete(panelId);
1552
- if (panelDataArray.find(({
1553
- id
1554
- }) => id === panelId) != null) {
1555
- unmountDueToStrictMode = true;
1556
- } else {
1557
- // TRICKY
1558
- // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1559
- // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1560
- // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1561
- delete panelIdToLastNotifiedSizeMap[panelId];
1562
- }
1563
- });
1564
- if (unmountDueToStrictMode) {
1565
- return;
1566
- }
1567
- if (panelDataArray.length === 0) {
1568
- // The group is unmounting; skip layout calculation.
1569
- return;
1570
- }
1571
- let unsafeLayout = calculateUnsafeDefaultLayout({
1572
- panelDataArray
1573
- });
1574
-
1575
- // Validate even saved layouts in case something has changed since last render
1576
- // e.g. for pixel groups, this could be the size of the window
1577
- const nextLayout = validatePanelGroupLayout({
1578
- layout: unsafeLayout,
1579
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1580
- });
1581
- if (!areEqual(prevLayout, nextLayout)) {
1582
- setLayout(nextLayout);
1583
- eagerValuesRef.current.layout = nextLayout;
1584
- if (onLayout) {
1585
- onLayout(nextLayout);
1586
- }
1587
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1588
- }
1589
- }, 0);
1417
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1418
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1419
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1420
+ delete panelIdToLastNotifiedSizeMapRef.current[panelData.id];
1421
+ eagerValuesRef.current.panelDataArrayChanged = true;
1422
+ }
1590
1423
  }, []);
1591
1424
  const context = useMemo(() => ({
1592
1425
  collapsePanel,
@@ -915,10 +915,6 @@ function debounce(callback, durationMs = 10) {
915
915
  return callable;
916
916
  }
917
917
 
918
- function getPanelElementsForGroup(groupId) {
919
- return Array.from(document.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
920
- }
921
-
922
918
  // PanelGroup might be rendering in a server-side environment where localStorage is not available
923
919
  // or on a browser with cookies/storage disabled.
924
920
  // In either case, this function avoids accessing localStorage until needed,
@@ -1085,6 +1081,7 @@ function PanelGroupWithForwardedRef({
1085
1081
  const groupId = useUniqueId(idFromProps);
1086
1082
  const [dragState, setDragState] = useState(null);
1087
1083
  const [layout, setLayout] = useState([]);
1084
+ useState([]);
1088
1085
  const panelIdToLastNotifiedSizeMapRef = useRef({});
1089
1086
  const panelSizeBeforeCollapseRef = useRef(new Map());
1090
1087
  const prevDeltaRef = useRef(0);
@@ -1099,7 +1096,8 @@ function PanelGroupWithForwardedRef({
1099
1096
  });
1100
1097
  const eagerValuesRef = useRef({
1101
1098
  layout,
1102
- panelDataArray: []
1099
+ panelDataArray: [],
1100
+ panelDataArrayChanged: false
1103
1101
  });
1104
1102
  useRef({
1105
1103
  didLogIdAndOrderWarning: false,
@@ -1318,28 +1316,8 @@ function PanelGroupWithForwardedRef({
1318
1316
  }, []);
1319
1317
  const registerPanel = useCallback(panelData => {
1320
1318
  const {
1321
- autoSaveId,
1322
- id: groupId,
1323
- onLayout,
1324
- storage
1325
- } = committedValuesRef.current;
1326
- const {
1327
- layout: prevLayout,
1328
1319
  panelDataArray
1329
1320
  } = eagerValuesRef.current;
1330
-
1331
- // HACK
1332
- // This appears to be triggered by some React Suspense+Offscreen+StrictMode bug;
1333
- // see app.replay.io/recording/17b6e11d-4500-4173-b23d-61dfd141fed1
1334
- const index = findPanelDataIndex(panelDataArray, panelData);
1335
- if (index >= 0) {
1336
- if (panelData.idIsFromProps) {
1337
- console.warn(`Panel with id "${panelData.id}" registered twice`);
1338
- } else {
1339
- console.warn(`Panel registered twice`);
1340
- }
1341
- return;
1342
- }
1343
1321
  panelDataArray.push(panelData);
1344
1322
  panelDataArray.sort((panelA, panelB) => {
1345
1323
  const orderA = panelA.order;
@@ -1354,45 +1332,52 @@ function PanelGroupWithForwardedRef({
1354
1332
  return orderA - orderB;
1355
1333
  }
1356
1334
  });
1335
+ eagerValuesRef.current.panelDataArrayChanged = true;
1336
+ }, []);
1357
1337
 
1358
- // Wait until all panels have registered before we try to compute layout;
1359
- // doing it earlier is both wasteful and may trigger misleading warnings in development mode.
1360
- const panelElements = getPanelElementsForGroup(groupId);
1361
- if (panelElements.length !== panelDataArray.length) {
1362
- return;
1363
- }
1364
-
1365
- // If this panel has been configured to persist sizing information,
1366
- // default size should be restored from local storage if possible.
1367
- let unsafeLayout = null;
1368
- if (autoSaveId) {
1369
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1370
- }
1371
- if (unsafeLayout == null) {
1372
- unsafeLayout = calculateUnsafeDefaultLayout({
1338
+ // (Re)calculate group layout whenever panels are registered or unregistered.
1339
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1340
+ useIsomorphicLayoutEffect(() => {
1341
+ if (eagerValuesRef.current.panelDataArrayChanged) {
1342
+ eagerValuesRef.current.panelDataArrayChanged = false;
1343
+ const {
1344
+ autoSaveId,
1345
+ onLayout,
1346
+ storage
1347
+ } = committedValuesRef.current;
1348
+ const {
1349
+ layout: prevLayout,
1373
1350
  panelDataArray
1374
- });
1375
- }
1351
+ } = eagerValuesRef.current;
1376
1352
 
1377
- // Validate even saved layouts in case something has changed since last render
1378
- // e.g. for pixel groups, this could be the size of the window
1379
- const nextLayout = validatePanelGroupLayout({
1380
- layout: unsafeLayout,
1381
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1382
- });
1353
+ // If this panel has been configured to persist sizing information,
1354
+ // default size should be restored from local storage if possible.
1355
+ let unsafeLayout = null;
1356
+ if (autoSaveId) {
1357
+ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
1358
+ }
1359
+ if (unsafeLayout == null) {
1360
+ unsafeLayout = calculateUnsafeDefaultLayout({
1361
+ panelDataArray
1362
+ });
1363
+ }
1383
1364
 
1384
- // Offscreen mode makes this a bit weird;
1385
- // Panels unregister when hidden and re-register when shown again,
1386
- // but the overall layout doesn't change between these two cases.
1387
- setLayout(nextLayout);
1388
- eagerValuesRef.current.layout = nextLayout;
1389
- if (!areEqual(prevLayout, nextLayout)) {
1390
- if (onLayout) {
1391
- onLayout(nextLayout);
1365
+ // Validate even saved layouts in case something has changed since last render
1366
+ // e.g. for pixel groups, this could be the size of the window
1367
+ const nextLayout = validatePanelGroupLayout({
1368
+ layout: unsafeLayout,
1369
+ panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1370
+ });
1371
+ if (!areEqual(prevLayout, nextLayout)) {
1372
+ setLayout(nextLayout);
1373
+ eagerValuesRef.current.layout = nextLayout;
1374
+ if (onLayout) {
1375
+ onLayout(nextLayout);
1376
+ }
1377
+ callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1392
1378
  }
1393
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1394
1379
  }
1395
- }, []);
1380
+ });
1396
1381
  const registerResizeHandle = useCallback(dragHandleId => {
1397
1382
  return function resizeHandler(event) {
1398
1383
  event.preventDefault();
@@ -1520,79 +1505,21 @@ function PanelGroupWithForwardedRef({
1520
1505
  resetGlobalCursorStyle();
1521
1506
  setDragState(null);
1522
1507
  }, []);
1523
- const unregisterPanelRef = useRef({
1524
- pendingPanelIds: new Set(),
1525
- timeout: null
1526
- });
1527
1508
  const unregisterPanel = useCallback(panelData => {
1528
1509
  const {
1529
- onLayout
1530
- } = committedValuesRef.current;
1531
- const {
1532
- layout: prevLayout,
1533
1510
  panelDataArray
1534
1511
  } = eagerValuesRef.current;
1535
1512
  const index = findPanelDataIndex(panelDataArray, panelData);
1536
1513
  if (index >= 0) {
1537
1514
  panelDataArray.splice(index, 1);
1538
- unregisterPanelRef.current.pendingPanelIds.add(panelData.id);
1539
- }
1540
- if (unregisterPanelRef.current.timeout != null) {
1541
- clearTimeout(unregisterPanelRef.current.timeout);
1542
- }
1543
-
1544
- // Batch panel unmounts so that we only calculate layout once;
1545
- // This is more efficient and avoids misleading warnings in development mode.
1546
- // We can't check the DOM to detect this because Panel elements have not yet been removed.
1547
- unregisterPanelRef.current.timeout = setTimeout(() => {
1548
- const {
1549
- pendingPanelIds
1550
- } = unregisterPanelRef.current;
1551
- const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1552
1515
 
1553
1516
  // TRICKY
1554
- // Strict effects mode
1555
- let unmountDueToStrictMode = false;
1556
- pendingPanelIds.forEach(panelId => {
1557
- pendingPanelIds.delete(panelId);
1558
- if (panelDataArray.find(({
1559
- id
1560
- }) => id === panelId) != null) {
1561
- unmountDueToStrictMode = true;
1562
- } else {
1563
- // TRICKY
1564
- // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1565
- // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1566
- // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1567
- delete panelIdToLastNotifiedSizeMap[panelId];
1568
- }
1569
- });
1570
- if (unmountDueToStrictMode) {
1571
- return;
1572
- }
1573
- if (panelDataArray.length === 0) {
1574
- // The group is unmounting; skip layout calculation.
1575
- return;
1576
- }
1577
- let unsafeLayout = calculateUnsafeDefaultLayout({
1578
- panelDataArray
1579
- });
1580
-
1581
- // Validate even saved layouts in case something has changed since last render
1582
- // e.g. for pixel groups, this could be the size of the window
1583
- const nextLayout = validatePanelGroupLayout({
1584
- layout: unsafeLayout,
1585
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
1586
- });
1587
- if (!areEqual(prevLayout, nextLayout)) {
1588
- setLayout(nextLayout);
1589
- eagerValuesRef.current.layout = nextLayout;
1590
- if (onLayout) {
1591
- onLayout(nextLayout);
1592
- }
1593
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
1594
- }
1595
- }, 0);
1517
+ // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
1518
+ // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
1519
+ // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
1520
+ delete panelIdToLastNotifiedSizeMapRef.current[panelData.id];
1521
+ eagerValuesRef.current.panelDataArrayChanged = true;
1522
+ }
1596
1523
  }, []);
1597
1524
  const context = useMemo(() => ({
1598
1525
  collapsePanel,