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