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
@@ -46,6 +46,7 @@ function PanelWithForwardedRef({
46
46
  collapsedSizePercentage,
47
47
  collapsedSizePixels,
48
48
  collapsible,
49
+ dataAttributes,
49
50
  defaultSizePercentage,
50
51
  defaultSizePixels,
51
52
  forwardedRef,
@@ -170,6 +171,7 @@ function PanelWithForwardedRef({
170
171
  ...style,
171
172
  ...styleFromProps
172
173
  },
174
+ ...dataAttributes,
173
175
  // CSS selectors
174
176
  "data-panel": "",
175
177
  "data-panel-id": panelId,
@@ -185,8 +187,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
185
187
  PanelWithForwardedRef.displayName = "Panel";
186
188
  Panel.displayName = "forwardRef(Panel)";
187
189
 
188
- const PRECISION = 10;
189
-
190
190
  function convertPixelsToPercentage(pixels, groupSizePixels) {
191
191
  return pixels / groupSizePixels * 100;
192
192
  }
@@ -264,6 +264,8 @@ function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, gr
264
264
  };
265
265
  }
266
266
 
267
+ const PRECISION = 10;
268
+
267
269
  function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
268
270
  actual = parseFloat(actual.toFixed(fractionDigits));
269
271
  expected = parseFloat(expected.toFixed(fractionDigits));
@@ -307,7 +309,13 @@ function resizePanel({
307
309
  if (minSizePercentage != null) {
308
310
  if (fuzzyCompareNumbers(size, minSizePercentage) < 0) {
309
311
  if (collapsible) {
310
- size = collapsedSizePercentage;
312
+ // Collapsible panels should snap closed or open only once they cross the halfway point between collapsed and min size.
313
+ const halfwayPoint = (collapsedSizePercentage + minSizePercentage) / 2;
314
+ if (fuzzyCompareNumbers(size, halfwayPoint) < 0) {
315
+ size = collapsedSizePercentage;
316
+ } else {
317
+ size = minSizePercentage;
318
+ }
311
319
  } else {
312
320
  size = minSizePercentage;
313
321
  }
@@ -334,60 +342,123 @@ function adjustLayoutByDelta({
334
342
  const nextLayout = [...prevLayout];
335
343
  let deltaApplied = 0;
336
344
 
345
+ //const DEBUG = [];
346
+ //DEBUG.push(`adjustLayoutByDelta() ${prevLayout.join(", ")}`);
347
+ //DEBUG.push(` delta: ${delta}`);
348
+ //DEBUG.push(` pivotIndices: ${pivotIndices.join(", ")}`);
349
+ //DEBUG.push(` trigger: ${trigger}`);
350
+ //DEBUG.push("");
351
+
337
352
  // A resizing panel affects the panels before or after it.
338
353
  //
339
- // A negative delta means the panel immediately after the resizer should grow/expand by decreasing its offset.
354
+ // A negative delta means the panel(s) immediately after the resize handle should grow/expand by decreasing its offset.
340
355
  // Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
341
356
  //
342
- // A positive delta means the panel immediately before the resizer should "expand".
343
- // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.
357
+ // A positive delta means the panel(s) immediately before the resize handle should "expand".
358
+ // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resize handle.
344
359
 
345
- // First, check the panel we're pivoting around;
346
- // We should only expand or contract by as much as its constraints allow
347
360
  {
348
- const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
349
- const initialSize = nextLayout[pivotIndex];
350
- const {
351
- collapsible
352
- } = panelConstraints[pivotIndex];
353
- const {
354
- collapsedSizePercentage,
355
- minSizePercentage
356
- } = computePercentagePanelConstraints(panelConstraints, pivotIndex, groupSizePixels);
357
- const isCollapsed = collapsible && fuzzyNumbersEqual(initialSize, collapsedSizePercentage);
358
- let unsafeSize = initialSize + Math.abs(delta);
359
- if (isCollapsed) {
360
- switch (trigger) {
361
- case "keyboard":
362
- if (minSizePercentage > unsafeSize) {
363
- unsafeSize = minSizePercentage;
361
+ // If this is a resize triggered by a keyboard event, our logic for expanding/collapsing is different.
362
+ // We no longer check the halfway threshold because this may prevent the panel from expanding at all.
363
+ if (trigger === "keyboard") {
364
+ {
365
+ // Check if we should expand a collapsed panel
366
+ const index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
367
+ const constraints = panelConstraints[index];
368
+ //DEBUG.push(`edge case check 1: ${index}`);
369
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
370
+ if (constraints.collapsible) {
371
+ const prevSize = prevLayout[index];
372
+ const {
373
+ collapsedSizePercentage,
374
+ minSizePercentage
375
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
376
+ if (fuzzyNumbersEqual(prevSize, collapsedSizePercentage)) {
377
+ const localDelta = minSizePercentage - prevSize;
378
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
379
+
380
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
381
+ delta = delta < 0 ? 0 - localDelta : localDelta;
382
+ //DEBUG.push(` -> delta: ${delta}`);
383
+ }
384
+ }
385
+ }
386
+ }
387
+
388
+ {
389
+ // Check if we should collapse a panel at its minimum size
390
+ const index = delta < 0 ? pivotIndices[0] : pivotIndices[1];
391
+ const constraints = panelConstraints[index];
392
+ //DEBUG.push(`edge case check 2: ${index}`);
393
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
394
+ if (constraints.collapsible) {
395
+ const prevSize = prevLayout[index];
396
+ const {
397
+ collapsedSizePercentage,
398
+ minSizePercentage
399
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
400
+ if (fuzzyNumbersEqual(prevSize, minSizePercentage)) {
401
+ const localDelta = prevSize - collapsedSizePercentage;
402
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
403
+
404
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
405
+ delta = delta < 0 ? 0 - localDelta : localDelta;
406
+ //DEBUG.push(` -> delta: ${delta}`);
407
+ }
364
408
  }
409
+ }
365
410
  }
366
411
  }
367
- const safeSize = resizePanel({
368
- groupSizePixels,
369
- panelConstraints,
370
- panelIndex: pivotIndex,
371
- size: unsafeSize
372
- });
373
- if (fuzzyNumbersEqual(initialSize, safeSize)) {
374
- // If there's no room for the pivot panel to grow, we should ignore this change
375
- return nextLayout;
376
- } else {
377
- delta = delta < 0 ? initialSize - safeSize : safeSize - initialSize;
412
+ //DEBUG.push("");
413
+ }
414
+
415
+ {
416
+ // Pre-calculate max available delta in the opposite direction of our pivot.
417
+ // This will be the maximum amount we're allowed to expand/contract the panels in the primary direction.
418
+ // If this amount is less than the requested delta, adjust the requested delta.
419
+ // If this amount is greater than the requested delta, that's useful information too–
420
+ // as an expanding panel might change from collapsed to min size.
421
+
422
+ const increment = delta < 0 ? 1 : -1;
423
+ let index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
424
+ let maxAvailableDelta = 0;
425
+
426
+ //DEBUG.push("pre calc...");
427
+ while (true) {
428
+ const prevSize = prevLayout[index];
429
+ const maxSafeSize = resizePanel({
430
+ groupSizePixels,
431
+ panelConstraints,
432
+ panelIndex: index,
433
+ size: 100
434
+ });
435
+ const delta = maxSafeSize - prevSize;
436
+ //DEBUG.push(` ${index}: ${prevSize} -> ${maxSafeSize}`);
437
+
438
+ maxAvailableDelta += delta;
439
+ index += increment;
440
+ if (index < 0 || index >= panelConstraints.length) {
441
+ break;
442
+ }
378
443
  }
444
+
445
+ //DEBUG.push(` -> max available delta: ${maxAvailableDelta}`);
446
+ const minAbsDelta = Math.min(Math.abs(delta), Math.abs(maxAvailableDelta));
447
+ delta = delta < 0 ? 0 - minAbsDelta : minAbsDelta;
448
+ //DEBUG.push(` -> adjusted delta: ${delta}`);
449
+ //DEBUG.push("");
379
450
  }
380
451
 
381
- // Delta added to a panel needs to be subtracted from other panels
382
- // within the constraints that those panels allow
383
452
  {
453
+ // Delta added to a panel needs to be subtracted from other panels (within the constraints that those panels allow).
454
+
384
455
  const pivotIndex = delta < 0 ? pivotIndices[0] : pivotIndices[1];
385
456
  let index = pivotIndex;
386
457
  while (index >= 0 && index < panelConstraints.length) {
387
458
  const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
388
459
  const prevSize = prevLayout[index];
389
460
  const unsafeSize = prevSize - deltaRemaining;
390
- let safeSize = resizePanel({
461
+ const safeSize = resizePanel({
391
462
  groupSizePixels,
392
463
  panelConstraints,
393
464
  panelIndex: index,
@@ -409,13 +480,18 @@ function adjustLayoutByDelta({
409
480
  }
410
481
  }
411
482
  }
483
+ //DEBUG.push(`after 1: ${nextLayout.join(", ")}`);
484
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
485
+ //DEBUG.push("");
412
486
 
413
487
  // If we were unable to resize any of the panels panels, return the previous state.
414
488
  // This will essentially bailout and ignore e.g. drags past a panel's boundaries
415
489
  if (fuzzyNumbersEqual(deltaApplied, 0)) {
490
+ //console.log(DEBUG.join("\n"));
416
491
  return prevLayout;
417
492
  }
418
493
  {
494
+ // Now distribute the applied delta to the panels in the other direction
419
495
  const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
420
496
  const unsafeSize = prevLayout[pivotIndex] + deltaApplied;
421
497
  const safeSize = resizePanel({
@@ -455,29 +531,21 @@ function adjustLayoutByDelta({
455
531
  index++;
456
532
  }
457
533
  }
458
-
459
- // If we can't redistribute, this layout is invalid;
460
- // There may be an incremental layout that is valid though
461
- if (!fuzzyNumbersEqual(deltaRemaining, 0)) {
462
- try {
463
- return adjustLayoutByDelta({
464
- delta: delta < 0 ? delta + 1 : delta - 1,
465
- groupSizePixels,
466
- layout: prevLayout,
467
- panelConstraints,
468
- pivotIndices,
469
- trigger
470
- });
471
- } catch (error) {
472
- if (error instanceof RangeError) {
473
- console.error(`Could not apply delta ${delta} to layout`);
474
- return prevLayout;
475
- }
476
- } finally {
477
- }
478
- }
479
534
  }
480
535
  }
536
+ //DEBUG.push(`after 2: ${nextLayout.join(", ")}`);
537
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
538
+ //DEBUG.push("");
539
+
540
+ const totalSize = nextLayout.reduce((total, size) => size + total, 0);
541
+ deltaApplied = 100 - totalSize;
542
+ //DEBUG.push(`total size: ${totalSize}`);
543
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
544
+ //console.log(DEBUG.join("\n"));
545
+
546
+ if (!fuzzyNumbersEqual(totalSize, 100)) {
547
+ return prevLayout;
548
+ }
481
549
  return nextLayout;
482
550
  }
483
551
 
@@ -681,15 +749,10 @@ function useWindowSplitterPanelGroupBehavior({
681
749
  }, [groupId, layout, panelDataArray]);
682
750
  useEffect(() => {
683
751
  const {
684
- direction,
685
752
  panelDataArray
686
753
  } = committedValuesRef.current;
687
754
  const groupElement = getPanelGroupElement(groupId);
688
755
  assert(groupElement != null, `No group found for id "${groupId}"`);
689
- const {
690
- height,
691
- width
692
- } = groupElement.getBoundingClientRect();
693
756
  const handles = getResizeHandleElementsForGroup(groupId);
694
757
  const cleanupFunctions = handles.map(handle => {
695
758
  const handleId = handle.getAttribute("data-panel-resize-handle-id");
@@ -709,21 +772,19 @@ function useWindowSplitterPanelGroupBehavior({
709
772
  if (index >= 0) {
710
773
  const panelData = panelDataArray[index];
711
774
  const size = layout[index];
712
- if (size != null) {
713
- var _getPercentageSizeFro;
775
+ if (size != null && panelData.constraints.collapsible) {
776
+ var _getPercentageSizeFro, _getPercentageSizeFro2;
714
777
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
715
- const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
778
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
779
+ sizePercentage: panelData.constraints.collapsedSizePercentage,
780
+ sizePixels: panelData.constraints.collapsedSizePixels
781
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
782
+ const minSize = (_getPercentageSizeFro2 = getPercentageSizeFromMixedSizes({
716
783
  sizePercentage: panelData.constraints.minSizePercentage,
717
784
  sizePixels: panelData.constraints.minSizePixels
718
- }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
719
- let delta = 0;
720
- if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
721
- delta = direction === "horizontal" ? width : height;
722
- } else {
723
- delta = -(direction === "horizontal" ? width : height);
724
- }
785
+ }, groupSizePixels)) !== null && _getPercentageSizeFro2 !== void 0 ? _getPercentageSizeFro2 : 0;
725
786
  const nextLayout = adjustLayoutByDelta({
726
- delta,
787
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
727
788
  groupSizePixels,
728
789
  layout,
729
790
  panelConstraints: panelDataArray.map(panelData => panelData.constraints),
@@ -1261,6 +1322,7 @@ function PanelGroupWithForwardedRef({
1261
1322
  autoSaveId,
1262
1323
  children,
1263
1324
  className: classNameFromProps = "",
1325
+ dataAttributes,
1264
1326
  direction,
1265
1327
  forwardedRef,
1266
1328
  id: idFromProps,
@@ -1278,6 +1340,7 @@ function PanelGroupWithForwardedRef({
1278
1340
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1279
1341
  const panelSizeBeforeCollapseRef = useRef(new Map());
1280
1342
  const prevDeltaRef = useRef(0);
1343
+ const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1281
1344
  const committedValuesRef = useRef({
1282
1345
  direction,
1283
1346
  dragState,
@@ -1386,8 +1449,12 @@ function PanelGroupWithForwardedRef({
1386
1449
  }
1387
1450
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1388
1451
  if (groupSizePixels <= 0) {
1389
- // Wait until the group has rendered a non-zero size before computing layout.
1390
- return;
1452
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1453
+ constraints
1454
+ }) => constraints))) {
1455
+ // Wait until the group has rendered a non-zero size before computing layout.
1456
+ return;
1457
+ }
1391
1458
  }
1392
1459
  if (unsafeLayout == null) {
1393
1460
  unsafeLayout = calculateUnsafeDefaultLayout({
@@ -1507,6 +1574,17 @@ function PanelGroupWithForwardedRef({
1507
1574
  onLayout,
1508
1575
  panelDataArray
1509
1576
  } = committedValuesRef.current;
1577
+
1578
+ // See issues/211
1579
+ if (panelDataArray.find(({
1580
+ id
1581
+ }) => id === panelData.id) == null) {
1582
+ setImperativeApiQueue(prev => [...prev, {
1583
+ panelData,
1584
+ type: "collapse"
1585
+ }]);
1586
+ return;
1587
+ }
1510
1588
  if (panelData.constraints.collapsible) {
1511
1589
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1512
1590
  const {
@@ -1550,6 +1628,17 @@ function PanelGroupWithForwardedRef({
1550
1628
  onLayout,
1551
1629
  panelDataArray
1552
1630
  } = committedValuesRef.current;
1631
+
1632
+ // See issues/211
1633
+ if (panelDataArray.find(({
1634
+ id
1635
+ }) => id === panelData.id) == null) {
1636
+ setImperativeApiQueue(prev => [...prev, {
1637
+ panelData,
1638
+ type: "expand"
1639
+ }]);
1640
+ return;
1641
+ }
1553
1642
  if (panelData.constraints.collapsible) {
1554
1643
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1555
1644
  const {
@@ -1745,6 +1834,18 @@ function PanelGroupWithForwardedRef({
1745
1834
  onLayout,
1746
1835
  panelDataArray
1747
1836
  } = committedValuesRef.current;
1837
+
1838
+ // See issues/211
1839
+ if (panelDataArray.find(({
1840
+ id
1841
+ }) => id === panelData.id) == null) {
1842
+ setImperativeApiQueue(prev => [...prev, {
1843
+ panelData,
1844
+ mixedSizes,
1845
+ type: "resize"
1846
+ }]);
1847
+ return;
1848
+ }
1748
1849
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1749
1850
  const {
1750
1851
  groupSizePixels,
@@ -1802,6 +1903,31 @@ function PanelGroupWithForwardedRef({
1802
1903
  return panelDataArray;
1803
1904
  });
1804
1905
  }, []);
1906
+
1907
+ // Handle imperative API calls that were made before panels were registered
1908
+ useIsomorphicLayoutEffect(() => {
1909
+ const queue = imperativeApiQueue;
1910
+ while (queue.length > 0) {
1911
+ const current = queue.shift();
1912
+ switch (current.type) {
1913
+ case "collapse":
1914
+ {
1915
+ collapsePanel(current.panelData);
1916
+ break;
1917
+ }
1918
+ case "expand":
1919
+ {
1920
+ expandPanel(current.panelData);
1921
+ break;
1922
+ }
1923
+ case "resize":
1924
+ {
1925
+ resizePanel(current.panelData, current.mixedSizes);
1926
+ break;
1927
+ }
1928
+ }
1929
+ }
1930
+ }, [collapsePanel, expandPanel, imperativeApiQueue, layout, panelDataArray, resizePanel]);
1805
1931
  const context = useMemo(() => ({
1806
1932
  collapsePanel,
1807
1933
  direction,
@@ -1835,6 +1961,7 @@ function PanelGroupWithForwardedRef({
1835
1961
  ...style,
1836
1962
  ...styleFromProps
1837
1963
  },
1964
+ ...dataAttributes,
1838
1965
  // CSS selectors
1839
1966
  "data-panel-group": "",
1840
1967
  "data-panel-group-direction": direction,
@@ -1922,6 +2049,7 @@ function useWindowSplitterResizeHandlerBehavior({
1922
2049
  function PanelResizeHandle({
1923
2050
  children = null,
1924
2051
  className: classNameFromProps = "",
2052
+ dataAttributes,
1925
2053
  disabled = false,
1926
2054
  id: idFromProps = null,
1927
2055
  onDragging,
@@ -2044,6 +2172,7 @@ function PanelResizeHandle({
2044
2172
  ...styleFromProps
2045
2173
  },
2046
2174
  tabIndex: 0,
2175
+ ...dataAttributes,
2047
2176
  // CSS selectors
2048
2177
  "data-panel-group-direction": direction,
2049
2178
  "data-panel-group-id": groupId,