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
@@ -44,6 +44,7 @@ function PanelWithForwardedRef({
44
44
  collapsedSizePercentage,
45
45
  collapsedSizePixels,
46
46
  collapsible,
47
+ dataAttributes,
47
48
  defaultSizePercentage,
48
49
  defaultSizePixels,
49
50
  forwardedRef,
@@ -157,6 +158,7 @@ function PanelWithForwardedRef({
157
158
  ...style,
158
159
  ...styleFromProps
159
160
  },
161
+ ...dataAttributes,
160
162
  // CSS selectors
161
163
  "data-panel": "",
162
164
  "data-panel-id": panelId,
@@ -172,8 +174,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
172
174
  PanelWithForwardedRef.displayName = "Panel";
173
175
  Panel.displayName = "forwardRef(Panel)";
174
176
 
175
- const PRECISION = 10;
176
-
177
177
  function convertPixelsToPercentage(pixels, groupSizePixels) {
178
178
  return pixels / groupSizePixels * 100;
179
179
  }
@@ -251,6 +251,8 @@ function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, gr
251
251
  };
252
252
  }
253
253
 
254
+ const PRECISION = 10;
255
+
254
256
  function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
255
257
  actual = parseFloat(actual.toFixed(fractionDigits));
256
258
  expected = parseFloat(expected.toFixed(fractionDigits));
@@ -294,7 +296,13 @@ function resizePanel({
294
296
  if (minSizePercentage != null) {
295
297
  if (fuzzyCompareNumbers(size, minSizePercentage) < 0) {
296
298
  if (collapsible) {
297
- size = collapsedSizePercentage;
299
+ // Collapsible panels should snap closed or open only once they cross the halfway point between collapsed and min size.
300
+ const halfwayPoint = (collapsedSizePercentage + minSizePercentage) / 2;
301
+ if (fuzzyCompareNumbers(size, halfwayPoint) < 0) {
302
+ size = collapsedSizePercentage;
303
+ } else {
304
+ size = minSizePercentage;
305
+ }
298
306
  } else {
299
307
  size = minSizePercentage;
300
308
  }
@@ -321,60 +329,123 @@ function adjustLayoutByDelta({
321
329
  const nextLayout = [...prevLayout];
322
330
  let deltaApplied = 0;
323
331
 
332
+ //const DEBUG = [];
333
+ //DEBUG.push(`adjustLayoutByDelta() ${prevLayout.join(", ")}`);
334
+ //DEBUG.push(` delta: ${delta}`);
335
+ //DEBUG.push(` pivotIndices: ${pivotIndices.join(", ")}`);
336
+ //DEBUG.push(` trigger: ${trigger}`);
337
+ //DEBUG.push("");
338
+
324
339
  // A resizing panel affects the panels before or after it.
325
340
  //
326
- // A negative delta means the panel immediately after the resizer should grow/expand by decreasing its offset.
341
+ // A negative delta means the panel(s) immediately after the resize handle should grow/expand by decreasing its offset.
327
342
  // Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
328
343
  //
329
- // A positive delta means the panel immediately before the resizer should "expand".
330
- // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.
344
+ // A positive delta means the panel(s) immediately before the resize handle should "expand".
345
+ // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resize handle.
331
346
 
332
- // First, check the panel we're pivoting around;
333
- // We should only expand or contract by as much as its constraints allow
334
347
  {
335
- const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
336
- const initialSize = nextLayout[pivotIndex];
337
- const {
338
- collapsible
339
- } = panelConstraints[pivotIndex];
340
- const {
341
- collapsedSizePercentage,
342
- minSizePercentage
343
- } = computePercentagePanelConstraints(panelConstraints, pivotIndex, groupSizePixels);
344
- const isCollapsed = collapsible && fuzzyNumbersEqual(initialSize, collapsedSizePercentage);
345
- let unsafeSize = initialSize + Math.abs(delta);
346
- if (isCollapsed) {
347
- switch (trigger) {
348
- case "keyboard":
349
- if (minSizePercentage > unsafeSize) {
350
- unsafeSize = minSizePercentage;
348
+ // If this is a resize triggered by a keyboard event, our logic for expanding/collapsing is different.
349
+ // We no longer check the halfway threshold because this may prevent the panel from expanding at all.
350
+ if (trigger === "keyboard") {
351
+ {
352
+ // Check if we should expand a collapsed panel
353
+ const index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
354
+ const constraints = panelConstraints[index];
355
+ //DEBUG.push(`edge case check 1: ${index}`);
356
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
357
+ if (constraints.collapsible) {
358
+ const prevSize = prevLayout[index];
359
+ const {
360
+ collapsedSizePercentage,
361
+ minSizePercentage
362
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
363
+ if (fuzzyNumbersEqual(prevSize, collapsedSizePercentage)) {
364
+ const localDelta = minSizePercentage - prevSize;
365
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
366
+
367
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
368
+ delta = delta < 0 ? 0 - localDelta : localDelta;
369
+ //DEBUG.push(` -> delta: ${delta}`);
370
+ }
371
+ }
372
+ }
373
+ }
374
+
375
+ {
376
+ // Check if we should collapse a panel at its minimum size
377
+ const index = delta < 0 ? pivotIndices[0] : pivotIndices[1];
378
+ const constraints = panelConstraints[index];
379
+ //DEBUG.push(`edge case check 2: ${index}`);
380
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
381
+ if (constraints.collapsible) {
382
+ const prevSize = prevLayout[index];
383
+ const {
384
+ collapsedSizePercentage,
385
+ minSizePercentage
386
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
387
+ if (fuzzyNumbersEqual(prevSize, minSizePercentage)) {
388
+ const localDelta = prevSize - collapsedSizePercentage;
389
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
390
+
391
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
392
+ delta = delta < 0 ? 0 - localDelta : localDelta;
393
+ //DEBUG.push(` -> delta: ${delta}`);
394
+ }
351
395
  }
396
+ }
352
397
  }
353
398
  }
354
- const safeSize = resizePanel({
355
- groupSizePixels,
356
- panelConstraints,
357
- panelIndex: pivotIndex,
358
- size: unsafeSize
359
- });
360
- if (fuzzyNumbersEqual(initialSize, safeSize)) {
361
- // If there's no room for the pivot panel to grow, we should ignore this change
362
- return nextLayout;
363
- } else {
364
- delta = delta < 0 ? initialSize - safeSize : safeSize - initialSize;
399
+ //DEBUG.push("");
400
+ }
401
+
402
+ {
403
+ // Pre-calculate max available delta in the opposite direction of our pivot.
404
+ // This will be the maximum amount we're allowed to expand/contract the panels in the primary direction.
405
+ // If this amount is less than the requested delta, adjust the requested delta.
406
+ // If this amount is greater than the requested delta, that's useful information too–
407
+ // as an expanding panel might change from collapsed to min size.
408
+
409
+ const increment = delta < 0 ? 1 : -1;
410
+ let index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
411
+ let maxAvailableDelta = 0;
412
+
413
+ //DEBUG.push("pre calc...");
414
+ while (true) {
415
+ const prevSize = prevLayout[index];
416
+ const maxSafeSize = resizePanel({
417
+ groupSizePixels,
418
+ panelConstraints,
419
+ panelIndex: index,
420
+ size: 100
421
+ });
422
+ const delta = maxSafeSize - prevSize;
423
+ //DEBUG.push(` ${index}: ${prevSize} -> ${maxSafeSize}`);
424
+
425
+ maxAvailableDelta += delta;
426
+ index += increment;
427
+ if (index < 0 || index >= panelConstraints.length) {
428
+ break;
429
+ }
365
430
  }
431
+
432
+ //DEBUG.push(` -> max available delta: ${maxAvailableDelta}`);
433
+ const minAbsDelta = Math.min(Math.abs(delta), Math.abs(maxAvailableDelta));
434
+ delta = delta < 0 ? 0 - minAbsDelta : minAbsDelta;
435
+ //DEBUG.push(` -> adjusted delta: ${delta}`);
436
+ //DEBUG.push("");
366
437
  }
367
438
 
368
- // Delta added to a panel needs to be subtracted from other panels
369
- // within the constraints that those panels allow
370
439
  {
440
+ // Delta added to a panel needs to be subtracted from other panels (within the constraints that those panels allow).
441
+
371
442
  const pivotIndex = delta < 0 ? pivotIndices[0] : pivotIndices[1];
372
443
  let index = pivotIndex;
373
444
  while (index >= 0 && index < panelConstraints.length) {
374
445
  const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
375
446
  const prevSize = prevLayout[index];
376
447
  const unsafeSize = prevSize - deltaRemaining;
377
- let safeSize = resizePanel({
448
+ const safeSize = resizePanel({
378
449
  groupSizePixels,
379
450
  panelConstraints,
380
451
  panelIndex: index,
@@ -396,13 +467,18 @@ function adjustLayoutByDelta({
396
467
  }
397
468
  }
398
469
  }
470
+ //DEBUG.push(`after 1: ${nextLayout.join(", ")}`);
471
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
472
+ //DEBUG.push("");
399
473
 
400
474
  // If we were unable to resize any of the panels panels, return the previous state.
401
475
  // This will essentially bailout and ignore e.g. drags past a panel's boundaries
402
476
  if (fuzzyNumbersEqual(deltaApplied, 0)) {
477
+ //console.log(DEBUG.join("\n"));
403
478
  return prevLayout;
404
479
  }
405
480
  {
481
+ // Now distribute the applied delta to the panels in the other direction
406
482
  const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
407
483
  const unsafeSize = prevLayout[pivotIndex] + deltaApplied;
408
484
  const safeSize = resizePanel({
@@ -442,29 +518,21 @@ function adjustLayoutByDelta({
442
518
  index++;
443
519
  }
444
520
  }
445
-
446
- // If we can't redistribute, this layout is invalid;
447
- // There may be an incremental layout that is valid though
448
- if (!fuzzyNumbersEqual(deltaRemaining, 0)) {
449
- try {
450
- return adjustLayoutByDelta({
451
- delta: delta < 0 ? delta + 1 : delta - 1,
452
- groupSizePixels,
453
- layout: prevLayout,
454
- panelConstraints,
455
- pivotIndices,
456
- trigger
457
- });
458
- } catch (error) {
459
- if (error instanceof RangeError) {
460
- console.error(`Could not apply delta ${delta} to layout`);
461
- return prevLayout;
462
- }
463
- } finally {
464
- }
465
- }
466
521
  }
467
522
  }
523
+ //DEBUG.push(`after 2: ${nextLayout.join(", ")}`);
524
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
525
+ //DEBUG.push("");
526
+
527
+ const totalSize = nextLayout.reduce((total, size) => size + total, 0);
528
+ deltaApplied = 100 - totalSize;
529
+ //DEBUG.push(`total size: ${totalSize}`);
530
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
531
+ //console.log(DEBUG.join("\n"));
532
+
533
+ if (!fuzzyNumbersEqual(totalSize, 100)) {
534
+ return prevLayout;
535
+ }
468
536
  return nextLayout;
469
537
  }
470
538
 
@@ -658,15 +726,10 @@ function useWindowSplitterPanelGroupBehavior({
658
726
  }, [groupId, layout, panelDataArray]);
659
727
  useEffect(() => {
660
728
  const {
661
- direction,
662
729
  panelDataArray
663
730
  } = committedValuesRef.current;
664
731
  const groupElement = getPanelGroupElement(groupId);
665
732
  assert(groupElement != null, `No group found for id "${groupId}"`);
666
- const {
667
- height,
668
- width
669
- } = groupElement.getBoundingClientRect();
670
733
  const handles = getResizeHandleElementsForGroup(groupId);
671
734
  const cleanupFunctions = handles.map(handle => {
672
735
  const handleId = handle.getAttribute("data-panel-resize-handle-id");
@@ -686,21 +749,19 @@ function useWindowSplitterPanelGroupBehavior({
686
749
  if (index >= 0) {
687
750
  const panelData = panelDataArray[index];
688
751
  const size = layout[index];
689
- if (size != null) {
690
- var _getPercentageSizeFro;
752
+ if (size != null && panelData.constraints.collapsible) {
753
+ var _getPercentageSizeFro, _getPercentageSizeFro2;
691
754
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
692
- const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
755
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
756
+ sizePercentage: panelData.constraints.collapsedSizePercentage,
757
+ sizePixels: panelData.constraints.collapsedSizePixels
758
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
759
+ const minSize = (_getPercentageSizeFro2 = getPercentageSizeFromMixedSizes({
693
760
  sizePercentage: panelData.constraints.minSizePercentage,
694
761
  sizePixels: panelData.constraints.minSizePixels
695
- }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
696
- let delta = 0;
697
- if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
698
- delta = direction === "horizontal" ? width : height;
699
- } else {
700
- delta = -(direction === "horizontal" ? width : height);
701
- }
762
+ }, groupSizePixels)) !== null && _getPercentageSizeFro2 !== void 0 ? _getPercentageSizeFro2 : 0;
702
763
  const nextLayout = adjustLayoutByDelta({
703
- delta,
764
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
704
765
  groupSizePixels,
705
766
  layout,
706
767
  panelConstraints: panelDataArray.map(panelData => panelData.constraints),
@@ -1161,6 +1222,7 @@ function PanelGroupWithForwardedRef({
1161
1222
  autoSaveId,
1162
1223
  children,
1163
1224
  className: classNameFromProps = "",
1225
+ dataAttributes,
1164
1226
  direction,
1165
1227
  forwardedRef,
1166
1228
  id: idFromProps,
@@ -1178,6 +1240,7 @@ function PanelGroupWithForwardedRef({
1178
1240
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1179
1241
  const panelSizeBeforeCollapseRef = useRef(new Map());
1180
1242
  const prevDeltaRef = useRef(0);
1243
+ const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1181
1244
  const committedValuesRef = useRef({
1182
1245
  direction,
1183
1246
  dragState,
@@ -1286,8 +1349,12 @@ function PanelGroupWithForwardedRef({
1286
1349
  }
1287
1350
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1288
1351
  if (groupSizePixels <= 0) {
1289
- // Wait until the group has rendered a non-zero size before computing layout.
1290
- return;
1352
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1353
+ constraints
1354
+ }) => constraints))) {
1355
+ // Wait until the group has rendered a non-zero size before computing layout.
1356
+ return;
1357
+ }
1291
1358
  }
1292
1359
  if (unsafeLayout == null) {
1293
1360
  unsafeLayout = calculateUnsafeDefaultLayout({
@@ -1365,6 +1432,17 @@ function PanelGroupWithForwardedRef({
1365
1432
  onLayout,
1366
1433
  panelDataArray
1367
1434
  } = committedValuesRef.current;
1435
+
1436
+ // See issues/211
1437
+ if (panelDataArray.find(({
1438
+ id
1439
+ }) => id === panelData.id) == null) {
1440
+ setImperativeApiQueue(prev => [...prev, {
1441
+ panelData,
1442
+ type: "collapse"
1443
+ }]);
1444
+ return;
1445
+ }
1368
1446
  if (panelData.constraints.collapsible) {
1369
1447
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1370
1448
  const {
@@ -1408,6 +1486,17 @@ function PanelGroupWithForwardedRef({
1408
1486
  onLayout,
1409
1487
  panelDataArray
1410
1488
  } = committedValuesRef.current;
1489
+
1490
+ // See issues/211
1491
+ if (panelDataArray.find(({
1492
+ id
1493
+ }) => id === panelData.id) == null) {
1494
+ setImperativeApiQueue(prev => [...prev, {
1495
+ panelData,
1496
+ type: "expand"
1497
+ }]);
1498
+ return;
1499
+ }
1411
1500
  if (panelData.constraints.collapsible) {
1412
1501
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1413
1502
  const {
@@ -1603,6 +1692,18 @@ function PanelGroupWithForwardedRef({
1603
1692
  onLayout,
1604
1693
  panelDataArray
1605
1694
  } = committedValuesRef.current;
1695
+
1696
+ // See issues/211
1697
+ if (panelDataArray.find(({
1698
+ id
1699
+ }) => id === panelData.id) == null) {
1700
+ setImperativeApiQueue(prev => [...prev, {
1701
+ panelData,
1702
+ mixedSizes,
1703
+ type: "resize"
1704
+ }]);
1705
+ return;
1706
+ }
1606
1707
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1607
1708
  const {
1608
1709
  groupSizePixels,
@@ -1660,6 +1761,31 @@ function PanelGroupWithForwardedRef({
1660
1761
  return panelDataArray;
1661
1762
  });
1662
1763
  }, []);
1764
+
1765
+ // Handle imperative API calls that were made before panels were registered
1766
+ useIsomorphicLayoutEffect(() => {
1767
+ const queue = imperativeApiQueue;
1768
+ while (queue.length > 0) {
1769
+ const current = queue.shift();
1770
+ switch (current.type) {
1771
+ case "collapse":
1772
+ {
1773
+ collapsePanel(current.panelData);
1774
+ break;
1775
+ }
1776
+ case "expand":
1777
+ {
1778
+ expandPanel(current.panelData);
1779
+ break;
1780
+ }
1781
+ case "resize":
1782
+ {
1783
+ resizePanel(current.panelData, current.mixedSizes);
1784
+ break;
1785
+ }
1786
+ }
1787
+ }
1788
+ }, [collapsePanel, expandPanel, imperativeApiQueue, layout, panelDataArray, resizePanel]);
1663
1789
  const context = useMemo(() => ({
1664
1790
  collapsePanel,
1665
1791
  direction,
@@ -1693,6 +1819,7 @@ function PanelGroupWithForwardedRef({
1693
1819
  ...style,
1694
1820
  ...styleFromProps
1695
1821
  },
1822
+ ...dataAttributes,
1696
1823
  // CSS selectors
1697
1824
  "data-panel-group": "",
1698
1825
  "data-panel-group-direction": direction,
@@ -1780,6 +1907,7 @@ function useWindowSplitterResizeHandlerBehavior({
1780
1907
  function PanelResizeHandle({
1781
1908
  children = null,
1782
1909
  className: classNameFromProps = "",
1910
+ dataAttributes,
1783
1911
  disabled = false,
1784
1912
  id: idFromProps = null,
1785
1913
  onDragging,
@@ -1902,6 +2030,7 @@ function PanelResizeHandle({
1902
2030
  ...styleFromProps
1903
2031
  },
1904
2032
  tabIndex: 0,
2033
+ ...dataAttributes,
1905
2034
  // CSS selectors
1906
2035
  "data-panel-group-direction": direction,
1907
2036
  "data-panel-group-id": groupId,