react-resizable-panels 0.0.57 → 0.0.59

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/declarations/src/Panel.d.ts +7 -3
  3. package/dist/declarations/src/PanelGroup.d.ts +3 -1
  4. package/dist/declarations/src/PanelResizeHandle.d.ts +3 -1
  5. package/dist/declarations/src/types.d.ts +3 -0
  6. package/dist/react-resizable-panels.browser.cjs.js +206 -77
  7. package/dist/react-resizable-panels.browser.development.cjs.js +206 -77
  8. package/dist/react-resizable-panels.browser.development.esm.js +206 -77
  9. package/dist/react-resizable-panels.browser.esm.js +206 -77
  10. package/dist/react-resizable-panels.cjs.js +206 -77
  11. package/dist/react-resizable-panels.cjs.js.map +1 -1
  12. package/dist/react-resizable-panels.development.cjs.js +206 -77
  13. package/dist/react-resizable-panels.development.esm.js +206 -77
  14. package/dist/react-resizable-panels.development.node.cjs.js +175 -75
  15. package/dist/react-resizable-panels.development.node.esm.js +175 -75
  16. package/dist/react-resizable-panels.esm.js +206 -77
  17. package/dist/react-resizable-panels.esm.js.map +1 -1
  18. package/dist/react-resizable-panels.node.cjs.js +175 -75
  19. package/dist/react-resizable-panels.node.esm.js +175 -75
  20. package/package.json +1 -1
  21. package/src/Panel.ts +8 -2
  22. package/src/PanelGroup.ts +89 -3
  23. package/src/PanelResizeHandle.ts +5 -0
  24. package/src/hooks/useWindowSplitterPanelGroupBehavior.ts +15 -15
  25. package/src/types.ts +4 -0
  26. package/src/utils/adjustLayoutByDelta.test.ts +238 -8
  27. package/src/utils/adjustLayoutByDelta.ts +122 -72
  28. package/src/utils/resizePanel.test.ts +61 -1
  29. package/src/utils/resizePanel.ts +7 -1
  30. package/src/utils/validatePanelGroupLayout.test.ts +36 -6
@@ -70,6 +70,7 @@ function PanelWithForwardedRef({
70
70
  collapsedSizePercentage,
71
71
  collapsedSizePixels,
72
72
  collapsible,
73
+ dataAttributes,
73
74
  defaultSizePercentage,
74
75
  defaultSizePixels,
75
76
  forwardedRef,
@@ -183,6 +184,7 @@ function PanelWithForwardedRef({
183
184
  ...style,
184
185
  ...styleFromProps
185
186
  },
187
+ ...dataAttributes,
186
188
  // CSS selectors
187
189
  "data-panel": "",
188
190
  "data-panel-id": panelId,
@@ -198,8 +200,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
198
200
  PanelWithForwardedRef.displayName = "Panel";
199
201
  Panel.displayName = "forwardRef(Panel)";
200
202
 
201
- const PRECISION = 10;
202
-
203
203
  function convertPixelsToPercentage(pixels, groupSizePixels) {
204
204
  return pixels / groupSizePixels * 100;
205
205
  }
@@ -277,6 +277,8 @@ function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, gr
277
277
  };
278
278
  }
279
279
 
280
+ const PRECISION = 10;
281
+
280
282
  function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
281
283
  actual = parseFloat(actual.toFixed(fractionDigits));
282
284
  expected = parseFloat(expected.toFixed(fractionDigits));
@@ -320,7 +322,13 @@ function resizePanel({
320
322
  if (minSizePercentage != null) {
321
323
  if (fuzzyCompareNumbers(size, minSizePercentage) < 0) {
322
324
  if (collapsible) {
323
- size = collapsedSizePercentage;
325
+ // Collapsible panels should snap closed or open only once they cross the halfway point between collapsed and min size.
326
+ const halfwayPoint = (collapsedSizePercentage + minSizePercentage) / 2;
327
+ if (fuzzyCompareNumbers(size, halfwayPoint) < 0) {
328
+ size = collapsedSizePercentage;
329
+ } else {
330
+ size = minSizePercentage;
331
+ }
324
332
  } else {
325
333
  size = minSizePercentage;
326
334
  }
@@ -347,60 +355,123 @@ function adjustLayoutByDelta({
347
355
  const nextLayout = [...prevLayout];
348
356
  let deltaApplied = 0;
349
357
 
358
+ //const DEBUG = [];
359
+ //DEBUG.push(`adjustLayoutByDelta() ${prevLayout.join(", ")}`);
360
+ //DEBUG.push(` delta: ${delta}`);
361
+ //DEBUG.push(` pivotIndices: ${pivotIndices.join(", ")}`);
362
+ //DEBUG.push(` trigger: ${trigger}`);
363
+ //DEBUG.push("");
364
+
350
365
  // A resizing panel affects the panels before or after it.
351
366
  //
352
- // A negative delta means the panel immediately after the resizer should grow/expand by decreasing its offset.
367
+ // A negative delta means the panel(s) immediately after the resize handle should grow/expand by decreasing its offset.
353
368
  // Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
354
369
  //
355
- // A positive delta means the panel immediately before the resizer should "expand".
356
- // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.
370
+ // A positive delta means the panel(s) immediately before the resize handle should "expand".
371
+ // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resize handle.
357
372
 
358
- // First, check the panel we're pivoting around;
359
- // We should only expand or contract by as much as its constraints allow
360
373
  {
361
- const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
362
- const initialSize = nextLayout[pivotIndex];
363
- const {
364
- collapsible
365
- } = panelConstraints[pivotIndex];
366
- const {
367
- collapsedSizePercentage,
368
- minSizePercentage
369
- } = computePercentagePanelConstraints(panelConstraints, pivotIndex, groupSizePixels);
370
- const isCollapsed = collapsible && fuzzyNumbersEqual(initialSize, collapsedSizePercentage);
371
- let unsafeSize = initialSize + Math.abs(delta);
372
- if (isCollapsed) {
373
- switch (trigger) {
374
- case "keyboard":
375
- if (minSizePercentage > unsafeSize) {
376
- unsafeSize = minSizePercentage;
374
+ // If this is a resize triggered by a keyboard event, our logic for expanding/collapsing is different.
375
+ // We no longer check the halfway threshold because this may prevent the panel from expanding at all.
376
+ if (trigger === "keyboard") {
377
+ {
378
+ // Check if we should expand a collapsed panel
379
+ const index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
380
+ const constraints = panelConstraints[index];
381
+ //DEBUG.push(`edge case check 1: ${index}`);
382
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
383
+ if (constraints.collapsible) {
384
+ const prevSize = prevLayout[index];
385
+ const {
386
+ collapsedSizePercentage,
387
+ minSizePercentage
388
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
389
+ if (fuzzyNumbersEqual(prevSize, collapsedSizePercentage)) {
390
+ const localDelta = minSizePercentage - prevSize;
391
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
392
+
393
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
394
+ delta = delta < 0 ? 0 - localDelta : localDelta;
395
+ //DEBUG.push(` -> delta: ${delta}`);
396
+ }
397
+ }
398
+ }
399
+ }
400
+
401
+ {
402
+ // Check if we should collapse a panel at its minimum size
403
+ const index = delta < 0 ? pivotIndices[0] : pivotIndices[1];
404
+ const constraints = panelConstraints[index];
405
+ //DEBUG.push(`edge case check 2: ${index}`);
406
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
407
+ if (constraints.collapsible) {
408
+ const prevSize = prevLayout[index];
409
+ const {
410
+ collapsedSizePercentage,
411
+ minSizePercentage
412
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
413
+ if (fuzzyNumbersEqual(prevSize, minSizePercentage)) {
414
+ const localDelta = prevSize - collapsedSizePercentage;
415
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
416
+
417
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
418
+ delta = delta < 0 ? 0 - localDelta : localDelta;
419
+ //DEBUG.push(` -> delta: ${delta}`);
420
+ }
377
421
  }
422
+ }
378
423
  }
379
424
  }
380
- const safeSize = resizePanel({
381
- groupSizePixels,
382
- panelConstraints,
383
- panelIndex: pivotIndex,
384
- size: unsafeSize
385
- });
386
- if (fuzzyNumbersEqual(initialSize, safeSize)) {
387
- // If there's no room for the pivot panel to grow, we should ignore this change
388
- return nextLayout;
389
- } else {
390
- delta = delta < 0 ? initialSize - safeSize : safeSize - initialSize;
425
+ //DEBUG.push("");
426
+ }
427
+
428
+ {
429
+ // Pre-calculate max available delta in the opposite direction of our pivot.
430
+ // This will be the maximum amount we're allowed to expand/contract the panels in the primary direction.
431
+ // If this amount is less than the requested delta, adjust the requested delta.
432
+ // If this amount is greater than the requested delta, that's useful information too–
433
+ // as an expanding panel might change from collapsed to min size.
434
+
435
+ const increment = delta < 0 ? 1 : -1;
436
+ let index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
437
+ let maxAvailableDelta = 0;
438
+
439
+ //DEBUG.push("pre calc...");
440
+ while (true) {
441
+ const prevSize = prevLayout[index];
442
+ const maxSafeSize = resizePanel({
443
+ groupSizePixels,
444
+ panelConstraints,
445
+ panelIndex: index,
446
+ size: 100
447
+ });
448
+ const delta = maxSafeSize - prevSize;
449
+ //DEBUG.push(` ${index}: ${prevSize} -> ${maxSafeSize}`);
450
+
451
+ maxAvailableDelta += delta;
452
+ index += increment;
453
+ if (index < 0 || index >= panelConstraints.length) {
454
+ break;
455
+ }
391
456
  }
457
+
458
+ //DEBUG.push(` -> max available delta: ${maxAvailableDelta}`);
459
+ const minAbsDelta = Math.min(Math.abs(delta), Math.abs(maxAvailableDelta));
460
+ delta = delta < 0 ? 0 - minAbsDelta : minAbsDelta;
461
+ //DEBUG.push(` -> adjusted delta: ${delta}`);
462
+ //DEBUG.push("");
392
463
  }
393
464
 
394
- // Delta added to a panel needs to be subtracted from other panels
395
- // within the constraints that those panels allow
396
465
  {
466
+ // Delta added to a panel needs to be subtracted from other panels (within the constraints that those panels allow).
467
+
397
468
  const pivotIndex = delta < 0 ? pivotIndices[0] : pivotIndices[1];
398
469
  let index = pivotIndex;
399
470
  while (index >= 0 && index < panelConstraints.length) {
400
471
  const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
401
472
  const prevSize = prevLayout[index];
402
473
  const unsafeSize = prevSize - deltaRemaining;
403
- let safeSize = resizePanel({
474
+ const safeSize = resizePanel({
404
475
  groupSizePixels,
405
476
  panelConstraints,
406
477
  panelIndex: index,
@@ -422,13 +493,18 @@ function adjustLayoutByDelta({
422
493
  }
423
494
  }
424
495
  }
496
+ //DEBUG.push(`after 1: ${nextLayout.join(", ")}`);
497
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
498
+ //DEBUG.push("");
425
499
 
426
500
  // If we were unable to resize any of the panels panels, return the previous state.
427
501
  // This will essentially bailout and ignore e.g. drags past a panel's boundaries
428
502
  if (fuzzyNumbersEqual(deltaApplied, 0)) {
503
+ //console.log(DEBUG.join("\n"));
429
504
  return prevLayout;
430
505
  }
431
506
  {
507
+ // Now distribute the applied delta to the panels in the other direction
432
508
  const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
433
509
  const unsafeSize = prevLayout[pivotIndex] + deltaApplied;
434
510
  const safeSize = resizePanel({
@@ -468,29 +544,21 @@ function adjustLayoutByDelta({
468
544
  index++;
469
545
  }
470
546
  }
471
-
472
- // If we can't redistribute, this layout is invalid;
473
- // There may be an incremental layout that is valid though
474
- if (!fuzzyNumbersEqual(deltaRemaining, 0)) {
475
- try {
476
- return adjustLayoutByDelta({
477
- delta: delta < 0 ? delta + 1 : delta - 1,
478
- groupSizePixels,
479
- layout: prevLayout,
480
- panelConstraints,
481
- pivotIndices,
482
- trigger
483
- });
484
- } catch (error) {
485
- if (error instanceof RangeError) {
486
- console.error(`Could not apply delta ${delta} to layout`);
487
- return prevLayout;
488
- }
489
- } finally {
490
- }
491
- }
492
547
  }
493
548
  }
549
+ //DEBUG.push(`after 2: ${nextLayout.join(", ")}`);
550
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
551
+ //DEBUG.push("");
552
+
553
+ const totalSize = nextLayout.reduce((total, size) => size + total, 0);
554
+ deltaApplied = 100 - totalSize;
555
+ //DEBUG.push(`total size: ${totalSize}`);
556
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
557
+ //console.log(DEBUG.join("\n"));
558
+
559
+ if (!fuzzyNumbersEqual(totalSize, 100)) {
560
+ return prevLayout;
561
+ }
494
562
  return nextLayout;
495
563
  }
496
564
 
@@ -684,15 +752,10 @@ function useWindowSplitterPanelGroupBehavior({
684
752
  }, [groupId, layout, panelDataArray]);
685
753
  useEffect(() => {
686
754
  const {
687
- direction,
688
755
  panelDataArray
689
756
  } = committedValuesRef.current;
690
757
  const groupElement = getPanelGroupElement(groupId);
691
758
  assert(groupElement != null, `No group found for id "${groupId}"`);
692
- const {
693
- height,
694
- width
695
- } = groupElement.getBoundingClientRect();
696
759
  const handles = getResizeHandleElementsForGroup(groupId);
697
760
  const cleanupFunctions = handles.map(handle => {
698
761
  const handleId = handle.getAttribute("data-panel-resize-handle-id");
@@ -712,21 +775,19 @@ function useWindowSplitterPanelGroupBehavior({
712
775
  if (index >= 0) {
713
776
  const panelData = panelDataArray[index];
714
777
  const size = layout[index];
715
- if (size != null) {
716
- var _getPercentageSizeFro;
778
+ if (size != null && panelData.constraints.collapsible) {
779
+ var _getPercentageSizeFro, _getPercentageSizeFro2;
717
780
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
718
- const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
781
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
782
+ sizePercentage: panelData.constraints.collapsedSizePercentage,
783
+ sizePixels: panelData.constraints.collapsedSizePixels
784
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
785
+ const minSize = (_getPercentageSizeFro2 = getPercentageSizeFromMixedSizes({
719
786
  sizePercentage: panelData.constraints.minSizePercentage,
720
787
  sizePixels: panelData.constraints.minSizePixels
721
- }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
722
- let delta = 0;
723
- if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
724
- delta = direction === "horizontal" ? width : height;
725
- } else {
726
- delta = -(direction === "horizontal" ? width : height);
727
- }
788
+ }, groupSizePixels)) !== null && _getPercentageSizeFro2 !== void 0 ? _getPercentageSizeFro2 : 0;
728
789
  const nextLayout = adjustLayoutByDelta({
729
- delta,
790
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
730
791
  groupSizePixels,
731
792
  layout,
732
793
  panelConstraints: panelDataArray.map(panelData => panelData.constraints),
@@ -1187,6 +1248,7 @@ function PanelGroupWithForwardedRef({
1187
1248
  autoSaveId,
1188
1249
  children,
1189
1250
  className: classNameFromProps = "",
1251
+ dataAttributes,
1190
1252
  direction,
1191
1253
  forwardedRef,
1192
1254
  id: idFromProps,
@@ -1204,6 +1266,7 @@ function PanelGroupWithForwardedRef({
1204
1266
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1205
1267
  const panelSizeBeforeCollapseRef = useRef(new Map());
1206
1268
  const prevDeltaRef = useRef(0);
1269
+ const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1207
1270
  const committedValuesRef = useRef({
1208
1271
  direction,
1209
1272
  dragState,
@@ -1312,8 +1375,12 @@ function PanelGroupWithForwardedRef({
1312
1375
  }
1313
1376
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1314
1377
  if (groupSizePixels <= 0) {
1315
- // Wait until the group has rendered a non-zero size before computing layout.
1316
- return;
1378
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1379
+ constraints
1380
+ }) => constraints))) {
1381
+ // Wait until the group has rendered a non-zero size before computing layout.
1382
+ return;
1383
+ }
1317
1384
  }
1318
1385
  if (unsafeLayout == null) {
1319
1386
  unsafeLayout = calculateUnsafeDefaultLayout({
@@ -1391,6 +1458,17 @@ function PanelGroupWithForwardedRef({
1391
1458
  onLayout,
1392
1459
  panelDataArray
1393
1460
  } = committedValuesRef.current;
1461
+
1462
+ // See issues/211
1463
+ if (panelDataArray.find(({
1464
+ id
1465
+ }) => id === panelData.id) == null) {
1466
+ setImperativeApiQueue(prev => [...prev, {
1467
+ panelData,
1468
+ type: "collapse"
1469
+ }]);
1470
+ return;
1471
+ }
1394
1472
  if (panelData.constraints.collapsible) {
1395
1473
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1396
1474
  const {
@@ -1434,6 +1512,17 @@ function PanelGroupWithForwardedRef({
1434
1512
  onLayout,
1435
1513
  panelDataArray
1436
1514
  } = committedValuesRef.current;
1515
+
1516
+ // See issues/211
1517
+ if (panelDataArray.find(({
1518
+ id
1519
+ }) => id === panelData.id) == null) {
1520
+ setImperativeApiQueue(prev => [...prev, {
1521
+ panelData,
1522
+ type: "expand"
1523
+ }]);
1524
+ return;
1525
+ }
1437
1526
  if (panelData.constraints.collapsible) {
1438
1527
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1439
1528
  const {
@@ -1629,6 +1718,18 @@ function PanelGroupWithForwardedRef({
1629
1718
  onLayout,
1630
1719
  panelDataArray
1631
1720
  } = committedValuesRef.current;
1721
+
1722
+ // See issues/211
1723
+ if (panelDataArray.find(({
1724
+ id
1725
+ }) => id === panelData.id) == null) {
1726
+ setImperativeApiQueue(prev => [...prev, {
1727
+ panelData,
1728
+ mixedSizes,
1729
+ type: "resize"
1730
+ }]);
1731
+ return;
1732
+ }
1632
1733
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1633
1734
  const {
1634
1735
  groupSizePixels,
@@ -1686,6 +1787,31 @@ function PanelGroupWithForwardedRef({
1686
1787
  return panelDataArray;
1687
1788
  });
1688
1789
  }, []);
1790
+
1791
+ // Handle imperative API calls that were made before panels were registered
1792
+ useIsomorphicLayoutEffect(() => {
1793
+ const queue = imperativeApiQueue;
1794
+ while (queue.length > 0) {
1795
+ const current = queue.shift();
1796
+ switch (current.type) {
1797
+ case "collapse":
1798
+ {
1799
+ collapsePanel(current.panelData);
1800
+ break;
1801
+ }
1802
+ case "expand":
1803
+ {
1804
+ expandPanel(current.panelData);
1805
+ break;
1806
+ }
1807
+ case "resize":
1808
+ {
1809
+ resizePanel(current.panelData, current.mixedSizes);
1810
+ break;
1811
+ }
1812
+ }
1813
+ }
1814
+ }, [collapsePanel, expandPanel, imperativeApiQueue, layout, panelDataArray, resizePanel]);
1689
1815
  const context = useMemo(() => ({
1690
1816
  collapsePanel,
1691
1817
  direction,
@@ -1719,6 +1845,7 @@ function PanelGroupWithForwardedRef({
1719
1845
  ...style,
1720
1846
  ...styleFromProps
1721
1847
  },
1848
+ ...dataAttributes,
1722
1849
  // CSS selectors
1723
1850
  "data-panel-group": "",
1724
1851
  "data-panel-group-direction": direction,
@@ -1806,6 +1933,7 @@ function useWindowSplitterResizeHandlerBehavior({
1806
1933
  function PanelResizeHandle({
1807
1934
  children = null,
1808
1935
  className: classNameFromProps = "",
1936
+ dataAttributes,
1809
1937
  disabled = false,
1810
1938
  id: idFromProps = null,
1811
1939
  onDragging,
@@ -1928,6 +2056,7 @@ function PanelResizeHandle({
1928
2056
  ...styleFromProps
1929
2057
  },
1930
2058
  tabIndex: 0,
2059
+ ...dataAttributes,
1931
2060
  // CSS selectors
1932
2061
  "data-panel-group-direction": direction,
1933
2062
  "data-panel-group-id": groupId,