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,
@@ -163,6 +164,7 @@ function PanelWithForwardedRef({
163
164
  ...style,
164
165
  ...styleFromProps
165
166
  },
167
+ ...dataAttributes,
166
168
  // CSS selectors
167
169
  "data-panel": "",
168
170
  "data-panel-id": panelId,
@@ -178,8 +180,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
178
180
  PanelWithForwardedRef.displayName = "Panel";
179
181
  Panel.displayName = "forwardRef(Panel)";
180
182
 
181
- const PRECISION = 10;
182
-
183
183
  function convertPixelsToPercentage(pixels, groupSizePixels) {
184
184
  return pixels / groupSizePixels * 100;
185
185
  }
@@ -257,6 +257,8 @@ function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, gr
257
257
  };
258
258
  }
259
259
 
260
+ const PRECISION = 10;
261
+
260
262
  function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
261
263
  actual = parseFloat(actual.toFixed(fractionDigits));
262
264
  expected = parseFloat(expected.toFixed(fractionDigits));
@@ -300,7 +302,13 @@ function resizePanel({
300
302
  if (minSizePercentage != null) {
301
303
  if (fuzzyCompareNumbers(size, minSizePercentage) < 0) {
302
304
  if (collapsible) {
303
- size = collapsedSizePercentage;
305
+ // Collapsible panels should snap closed or open only once they cross the halfway point between collapsed and min size.
306
+ const halfwayPoint = (collapsedSizePercentage + minSizePercentage) / 2;
307
+ if (fuzzyCompareNumbers(size, halfwayPoint) < 0) {
308
+ size = collapsedSizePercentage;
309
+ } else {
310
+ size = minSizePercentage;
311
+ }
304
312
  } else {
305
313
  size = minSizePercentage;
306
314
  }
@@ -327,60 +335,123 @@ function adjustLayoutByDelta({
327
335
  const nextLayout = [...prevLayout];
328
336
  let deltaApplied = 0;
329
337
 
338
+ //const DEBUG = [];
339
+ //DEBUG.push(`adjustLayoutByDelta() ${prevLayout.join(", ")}`);
340
+ //DEBUG.push(` delta: ${delta}`);
341
+ //DEBUG.push(` pivotIndices: ${pivotIndices.join(", ")}`);
342
+ //DEBUG.push(` trigger: ${trigger}`);
343
+ //DEBUG.push("");
344
+
330
345
  // A resizing panel affects the panels before or after it.
331
346
  //
332
- // A negative delta means the panel immediately after the resizer should grow/expand by decreasing its offset.
347
+ // A negative delta means the panel(s) immediately after the resize handle should grow/expand by decreasing its offset.
333
348
  // Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
334
349
  //
335
- // A positive delta means the panel immediately before the resizer should "expand".
336
- // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.
350
+ // A positive delta means the panel(s) immediately before the resize handle should "expand".
351
+ // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resize handle.
337
352
 
338
- // First, check the panel we're pivoting around;
339
- // We should only expand or contract by as much as its constraints allow
340
353
  {
341
- const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
342
- const initialSize = nextLayout[pivotIndex];
343
- const {
344
- collapsible
345
- } = panelConstraints[pivotIndex];
346
- const {
347
- collapsedSizePercentage,
348
- minSizePercentage
349
- } = computePercentagePanelConstraints(panelConstraints, pivotIndex, groupSizePixels);
350
- const isCollapsed = collapsible && fuzzyNumbersEqual(initialSize, collapsedSizePercentage);
351
- let unsafeSize = initialSize + Math.abs(delta);
352
- if (isCollapsed) {
353
- switch (trigger) {
354
- case "keyboard":
355
- if (minSizePercentage > unsafeSize) {
356
- unsafeSize = minSizePercentage;
354
+ // If this is a resize triggered by a keyboard event, our logic for expanding/collapsing is different.
355
+ // We no longer check the halfway threshold because this may prevent the panel from expanding at all.
356
+ if (trigger === "keyboard") {
357
+ {
358
+ // Check if we should expand a collapsed panel
359
+ const index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
360
+ const constraints = panelConstraints[index];
361
+ //DEBUG.push(`edge case check 1: ${index}`);
362
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
363
+ if (constraints.collapsible) {
364
+ const prevSize = prevLayout[index];
365
+ const {
366
+ collapsedSizePercentage,
367
+ minSizePercentage
368
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
369
+ if (fuzzyNumbersEqual(prevSize, collapsedSizePercentage)) {
370
+ const localDelta = minSizePercentage - prevSize;
371
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
372
+
373
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
374
+ delta = delta < 0 ? 0 - localDelta : localDelta;
375
+ //DEBUG.push(` -> delta: ${delta}`);
376
+ }
377
+ }
378
+ }
379
+ }
380
+
381
+ {
382
+ // Check if we should collapse a panel at its minimum size
383
+ const index = delta < 0 ? pivotIndices[0] : pivotIndices[1];
384
+ const constraints = panelConstraints[index];
385
+ //DEBUG.push(`edge case check 2: ${index}`);
386
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
387
+ if (constraints.collapsible) {
388
+ const prevSize = prevLayout[index];
389
+ const {
390
+ collapsedSizePercentage,
391
+ minSizePercentage
392
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
393
+ if (fuzzyNumbersEqual(prevSize, minSizePercentage)) {
394
+ const localDelta = prevSize - collapsedSizePercentage;
395
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
396
+
397
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
398
+ delta = delta < 0 ? 0 - localDelta : localDelta;
399
+ //DEBUG.push(` -> delta: ${delta}`);
400
+ }
357
401
  }
402
+ }
358
403
  }
359
404
  }
360
- const safeSize = resizePanel({
361
- groupSizePixels,
362
- panelConstraints,
363
- panelIndex: pivotIndex,
364
- size: unsafeSize
365
- });
366
- if (fuzzyNumbersEqual(initialSize, safeSize)) {
367
- // If there's no room for the pivot panel to grow, we should ignore this change
368
- return nextLayout;
369
- } else {
370
- delta = delta < 0 ? initialSize - safeSize : safeSize - initialSize;
405
+ //DEBUG.push("");
406
+ }
407
+
408
+ {
409
+ // Pre-calculate max available delta in the opposite direction of our pivot.
410
+ // This will be the maximum amount we're allowed to expand/contract the panels in the primary direction.
411
+ // If this amount is less than the requested delta, adjust the requested delta.
412
+ // If this amount is greater than the requested delta, that's useful information too–
413
+ // as an expanding panel might change from collapsed to min size.
414
+
415
+ const increment = delta < 0 ? 1 : -1;
416
+ let index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
417
+ let maxAvailableDelta = 0;
418
+
419
+ //DEBUG.push("pre calc...");
420
+ while (true) {
421
+ const prevSize = prevLayout[index];
422
+ const maxSafeSize = resizePanel({
423
+ groupSizePixels,
424
+ panelConstraints,
425
+ panelIndex: index,
426
+ size: 100
427
+ });
428
+ const delta = maxSafeSize - prevSize;
429
+ //DEBUG.push(` ${index}: ${prevSize} -> ${maxSafeSize}`);
430
+
431
+ maxAvailableDelta += delta;
432
+ index += increment;
433
+ if (index < 0 || index >= panelConstraints.length) {
434
+ break;
435
+ }
371
436
  }
437
+
438
+ //DEBUG.push(` -> max available delta: ${maxAvailableDelta}`);
439
+ const minAbsDelta = Math.min(Math.abs(delta), Math.abs(maxAvailableDelta));
440
+ delta = delta < 0 ? 0 - minAbsDelta : minAbsDelta;
441
+ //DEBUG.push(` -> adjusted delta: ${delta}`);
442
+ //DEBUG.push("");
372
443
  }
373
444
 
374
- // Delta added to a panel needs to be subtracted from other panels
375
- // within the constraints that those panels allow
376
445
  {
446
+ // Delta added to a panel needs to be subtracted from other panels (within the constraints that those panels allow).
447
+
377
448
  const pivotIndex = delta < 0 ? pivotIndices[0] : pivotIndices[1];
378
449
  let index = pivotIndex;
379
450
  while (index >= 0 && index < panelConstraints.length) {
380
451
  const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
381
452
  const prevSize = prevLayout[index];
382
453
  const unsafeSize = prevSize - deltaRemaining;
383
- let safeSize = resizePanel({
454
+ const safeSize = resizePanel({
384
455
  groupSizePixels,
385
456
  panelConstraints,
386
457
  panelIndex: index,
@@ -402,13 +473,18 @@ function adjustLayoutByDelta({
402
473
  }
403
474
  }
404
475
  }
476
+ //DEBUG.push(`after 1: ${nextLayout.join(", ")}`);
477
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
478
+ //DEBUG.push("");
405
479
 
406
480
  // If we were unable to resize any of the panels panels, return the previous state.
407
481
  // This will essentially bailout and ignore e.g. drags past a panel's boundaries
408
482
  if (fuzzyNumbersEqual(deltaApplied, 0)) {
483
+ //console.log(DEBUG.join("\n"));
409
484
  return prevLayout;
410
485
  }
411
486
  {
487
+ // Now distribute the applied delta to the panels in the other direction
412
488
  const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
413
489
  const unsafeSize = prevLayout[pivotIndex] + deltaApplied;
414
490
  const safeSize = resizePanel({
@@ -448,29 +524,21 @@ function adjustLayoutByDelta({
448
524
  index++;
449
525
  }
450
526
  }
451
-
452
- // If we can't redistribute, this layout is invalid;
453
- // There may be an incremental layout that is valid though
454
- if (!fuzzyNumbersEqual(deltaRemaining, 0)) {
455
- try {
456
- return adjustLayoutByDelta({
457
- delta: delta < 0 ? delta + 1 : delta - 1,
458
- groupSizePixels,
459
- layout: prevLayout,
460
- panelConstraints,
461
- pivotIndices,
462
- trigger
463
- });
464
- } catch (error) {
465
- if (error instanceof RangeError) {
466
- console.error(`Could not apply delta ${delta} to layout`);
467
- return prevLayout;
468
- }
469
- } finally {
470
- }
471
- }
472
527
  }
473
528
  }
529
+ //DEBUG.push(`after 2: ${nextLayout.join(", ")}`);
530
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
531
+ //DEBUG.push("");
532
+
533
+ const totalSize = nextLayout.reduce((total, size) => size + total, 0);
534
+ deltaApplied = 100 - totalSize;
535
+ //DEBUG.push(`total size: ${totalSize}`);
536
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
537
+ //console.log(DEBUG.join("\n"));
538
+
539
+ if (!fuzzyNumbersEqual(totalSize, 100)) {
540
+ return prevLayout;
541
+ }
474
542
  return nextLayout;
475
543
  }
476
544
 
@@ -674,15 +742,10 @@ function useWindowSplitterPanelGroupBehavior({
674
742
  }, [groupId, layout, panelDataArray]);
675
743
  useEffect(() => {
676
744
  const {
677
- direction,
678
745
  panelDataArray
679
746
  } = committedValuesRef.current;
680
747
  const groupElement = getPanelGroupElement(groupId);
681
748
  assert(groupElement != null, `No group found for id "${groupId}"`);
682
- const {
683
- height,
684
- width
685
- } = groupElement.getBoundingClientRect();
686
749
  const handles = getResizeHandleElementsForGroup(groupId);
687
750
  const cleanupFunctions = handles.map(handle => {
688
751
  const handleId = handle.getAttribute("data-panel-resize-handle-id");
@@ -702,21 +765,19 @@ function useWindowSplitterPanelGroupBehavior({
702
765
  if (index >= 0) {
703
766
  const panelData = panelDataArray[index];
704
767
  const size = layout[index];
705
- if (size != null) {
706
- var _getPercentageSizeFro;
768
+ if (size != null && panelData.constraints.collapsible) {
769
+ var _getPercentageSizeFro, _getPercentageSizeFro2;
707
770
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
708
- const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
771
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
772
+ sizePercentage: panelData.constraints.collapsedSizePercentage,
773
+ sizePixels: panelData.constraints.collapsedSizePixels
774
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
775
+ const minSize = (_getPercentageSizeFro2 = getPercentageSizeFromMixedSizes({
709
776
  sizePercentage: panelData.constraints.minSizePercentage,
710
777
  sizePixels: panelData.constraints.minSizePixels
711
- }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
712
- let delta = 0;
713
- if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
714
- delta = direction === "horizontal" ? width : height;
715
- } else {
716
- delta = -(direction === "horizontal" ? width : height);
717
- }
778
+ }, groupSizePixels)) !== null && _getPercentageSizeFro2 !== void 0 ? _getPercentageSizeFro2 : 0;
718
779
  const nextLayout = adjustLayoutByDelta({
719
- delta,
780
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
720
781
  groupSizePixels,
721
782
  layout,
722
783
  panelConstraints: panelDataArray.map(panelData => panelData.constraints),
@@ -1254,6 +1315,7 @@ function PanelGroupWithForwardedRef({
1254
1315
  autoSaveId,
1255
1316
  children,
1256
1317
  className: classNameFromProps = "",
1318
+ dataAttributes,
1257
1319
  direction,
1258
1320
  forwardedRef,
1259
1321
  id: idFromProps,
@@ -1271,6 +1333,7 @@ function PanelGroupWithForwardedRef({
1271
1333
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1272
1334
  const panelSizeBeforeCollapseRef = useRef(new Map());
1273
1335
  const prevDeltaRef = useRef(0);
1336
+ const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1274
1337
  const committedValuesRef = useRef({
1275
1338
  direction,
1276
1339
  dragState,
@@ -1379,8 +1442,12 @@ function PanelGroupWithForwardedRef({
1379
1442
  }
1380
1443
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
1381
1444
  if (groupSizePixels <= 0) {
1382
- // Wait until the group has rendered a non-zero size before computing layout.
1383
- return;
1445
+ if (shouldMonitorPixelBasedConstraints(panelDataArray.map(({
1446
+ constraints
1447
+ }) => constraints))) {
1448
+ // Wait until the group has rendered a non-zero size before computing layout.
1449
+ return;
1450
+ }
1384
1451
  }
1385
1452
  if (unsafeLayout == null) {
1386
1453
  unsafeLayout = calculateUnsafeDefaultLayout({
@@ -1500,6 +1567,17 @@ function PanelGroupWithForwardedRef({
1500
1567
  onLayout,
1501
1568
  panelDataArray
1502
1569
  } = committedValuesRef.current;
1570
+
1571
+ // See issues/211
1572
+ if (panelDataArray.find(({
1573
+ id
1574
+ }) => id === panelData.id) == null) {
1575
+ setImperativeApiQueue(prev => [...prev, {
1576
+ panelData,
1577
+ type: "collapse"
1578
+ }]);
1579
+ return;
1580
+ }
1503
1581
  if (panelData.constraints.collapsible) {
1504
1582
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1505
1583
  const {
@@ -1543,6 +1621,17 @@ function PanelGroupWithForwardedRef({
1543
1621
  onLayout,
1544
1622
  panelDataArray
1545
1623
  } = committedValuesRef.current;
1624
+
1625
+ // See issues/211
1626
+ if (panelDataArray.find(({
1627
+ id
1628
+ }) => id === panelData.id) == null) {
1629
+ setImperativeApiQueue(prev => [...prev, {
1630
+ panelData,
1631
+ type: "expand"
1632
+ }]);
1633
+ return;
1634
+ }
1546
1635
  if (panelData.constraints.collapsible) {
1547
1636
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1548
1637
  const {
@@ -1738,6 +1827,18 @@ function PanelGroupWithForwardedRef({
1738
1827
  onLayout,
1739
1828
  panelDataArray
1740
1829
  } = committedValuesRef.current;
1830
+
1831
+ // See issues/211
1832
+ if (panelDataArray.find(({
1833
+ id
1834
+ }) => id === panelData.id) == null) {
1835
+ setImperativeApiQueue(prev => [...prev, {
1836
+ panelData,
1837
+ mixedSizes,
1838
+ type: "resize"
1839
+ }]);
1840
+ return;
1841
+ }
1741
1842
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1742
1843
  const {
1743
1844
  groupSizePixels,
@@ -1795,6 +1896,31 @@ function PanelGroupWithForwardedRef({
1795
1896
  return panelDataArray;
1796
1897
  });
1797
1898
  }, []);
1899
+
1900
+ // Handle imperative API calls that were made before panels were registered
1901
+ useIsomorphicLayoutEffect(() => {
1902
+ const queue = imperativeApiQueue;
1903
+ while (queue.length > 0) {
1904
+ const current = queue.shift();
1905
+ switch (current.type) {
1906
+ case "collapse":
1907
+ {
1908
+ collapsePanel(current.panelData);
1909
+ break;
1910
+ }
1911
+ case "expand":
1912
+ {
1913
+ expandPanel(current.panelData);
1914
+ break;
1915
+ }
1916
+ case "resize":
1917
+ {
1918
+ resizePanel(current.panelData, current.mixedSizes);
1919
+ break;
1920
+ }
1921
+ }
1922
+ }
1923
+ }, [collapsePanel, expandPanel, imperativeApiQueue, layout, panelDataArray, resizePanel]);
1798
1924
  const context = useMemo(() => ({
1799
1925
  collapsePanel,
1800
1926
  direction,
@@ -1828,6 +1954,7 @@ function PanelGroupWithForwardedRef({
1828
1954
  ...style,
1829
1955
  ...styleFromProps
1830
1956
  },
1957
+ ...dataAttributes,
1831
1958
  // CSS selectors
1832
1959
  "data-panel-group": "",
1833
1960
  "data-panel-group-direction": direction,
@@ -1915,6 +2042,7 @@ function useWindowSplitterResizeHandlerBehavior({
1915
2042
  function PanelResizeHandle({
1916
2043
  children = null,
1917
2044
  className: classNameFromProps = "",
2045
+ dataAttributes,
1918
2046
  disabled = false,
1919
2047
  id: idFromProps = null,
1920
2048
  onDragging,
@@ -2037,6 +2165,7 @@ function PanelResizeHandle({
2037
2165
  ...styleFromProps
2038
2166
  },
2039
2167
  tabIndex: 0,
2168
+ ...dataAttributes,
2040
2169
  // CSS selectors
2041
2170
  "data-panel-group-direction": direction,
2042
2171
  "data-panel-group-id": groupId,