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
@@ -66,6 +66,7 @@ function PanelWithForwardedRef({
66
66
  collapsedSizePercentage,
67
67
  collapsedSizePixels,
68
68
  collapsible,
69
+ dataAttributes,
69
70
  defaultSizePercentage,
70
71
  defaultSizePixels,
71
72
  forwardedRef,
@@ -162,6 +163,7 @@ function PanelWithForwardedRef({
162
163
  ...style,
163
164
  ...styleFromProps
164
165
  },
166
+ ...dataAttributes,
165
167
  // CSS selectors
166
168
  "data-panel": "",
167
169
  "data-panel-id": panelId,
@@ -177,8 +179,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
177
179
  PanelWithForwardedRef.displayName = "Panel";
178
180
  Panel.displayName = "forwardRef(Panel)";
179
181
 
180
- const PRECISION = 10;
181
-
182
182
  function convertPixelsToPercentage(pixels, groupSizePixels) {
183
183
  return pixels / groupSizePixels * 100;
184
184
  }
@@ -256,6 +256,8 @@ function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, gr
256
256
  };
257
257
  }
258
258
 
259
+ const PRECISION = 10;
260
+
259
261
  function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
260
262
  actual = parseFloat(actual.toFixed(fractionDigits));
261
263
  expected = parseFloat(expected.toFixed(fractionDigits));
@@ -299,7 +301,13 @@ function resizePanel({
299
301
  if (minSizePercentage != null) {
300
302
  if (fuzzyCompareNumbers(size, minSizePercentage) < 0) {
301
303
  if (collapsible) {
302
- size = collapsedSizePercentage;
304
+ // Collapsible panels should snap closed or open only once they cross the halfway point between collapsed and min size.
305
+ const halfwayPoint = (collapsedSizePercentage + minSizePercentage) / 2;
306
+ if (fuzzyCompareNumbers(size, halfwayPoint) < 0) {
307
+ size = collapsedSizePercentage;
308
+ } else {
309
+ size = minSizePercentage;
310
+ }
303
311
  } else {
304
312
  size = minSizePercentage;
305
313
  }
@@ -326,60 +334,123 @@ function adjustLayoutByDelta({
326
334
  const nextLayout = [...prevLayout];
327
335
  let deltaApplied = 0;
328
336
 
337
+ //const DEBUG = [];
338
+ //DEBUG.push(`adjustLayoutByDelta() ${prevLayout.join(", ")}`);
339
+ //DEBUG.push(` delta: ${delta}`);
340
+ //DEBUG.push(` pivotIndices: ${pivotIndices.join(", ")}`);
341
+ //DEBUG.push(` trigger: ${trigger}`);
342
+ //DEBUG.push("");
343
+
329
344
  // A resizing panel affects the panels before or after it.
330
345
  //
331
- // A negative delta means the panel immediately after the resizer should grow/expand by decreasing its offset.
346
+ // A negative delta means the panel(s) immediately after the resize handle should grow/expand by decreasing its offset.
332
347
  // Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
333
348
  //
334
- // A positive delta means the panel immediately before the resizer should "expand".
335
- // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.
349
+ // A positive delta means the panel(s) immediately before the resize handle should "expand".
350
+ // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resize handle.
336
351
 
337
- // First, check the panel we're pivoting around;
338
- // We should only expand or contract by as much as its constraints allow
339
352
  {
340
- const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
341
- const initialSize = nextLayout[pivotIndex];
342
- const {
343
- collapsible
344
- } = panelConstraints[pivotIndex];
345
- const {
346
- collapsedSizePercentage,
347
- minSizePercentage
348
- } = computePercentagePanelConstraints(panelConstraints, pivotIndex, groupSizePixels);
349
- const isCollapsed = collapsible && fuzzyNumbersEqual(initialSize, collapsedSizePercentage);
350
- let unsafeSize = initialSize + Math.abs(delta);
351
- if (isCollapsed) {
352
- switch (trigger) {
353
- case "keyboard":
354
- if (minSizePercentage > unsafeSize) {
355
- unsafeSize = minSizePercentage;
353
+ // If this is a resize triggered by a keyboard event, our logic for expanding/collapsing is different.
354
+ // We no longer check the halfway threshold because this may prevent the panel from expanding at all.
355
+ if (trigger === "keyboard") {
356
+ {
357
+ // Check if we should expand a collapsed panel
358
+ const index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
359
+ const constraints = panelConstraints[index];
360
+ //DEBUG.push(`edge case check 1: ${index}`);
361
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
362
+ if (constraints.collapsible) {
363
+ const prevSize = prevLayout[index];
364
+ const {
365
+ collapsedSizePercentage,
366
+ minSizePercentage
367
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
368
+ if (fuzzyNumbersEqual(prevSize, collapsedSizePercentage)) {
369
+ const localDelta = minSizePercentage - prevSize;
370
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
371
+
372
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
373
+ delta = delta < 0 ? 0 - localDelta : localDelta;
374
+ //DEBUG.push(` -> delta: ${delta}`);
375
+ }
356
376
  }
377
+ }
378
+ }
379
+
380
+ {
381
+ // Check if we should collapse a panel at its minimum size
382
+ const index = delta < 0 ? pivotIndices[0] : pivotIndices[1];
383
+ const constraints = panelConstraints[index];
384
+ //DEBUG.push(`edge case check 2: ${index}`);
385
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
386
+ if (constraints.collapsible) {
387
+ const prevSize = prevLayout[index];
388
+ const {
389
+ collapsedSizePercentage,
390
+ minSizePercentage
391
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
392
+ if (fuzzyNumbersEqual(prevSize, minSizePercentage)) {
393
+ const localDelta = prevSize - collapsedSizePercentage;
394
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
395
+
396
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
397
+ delta = delta < 0 ? 0 - localDelta : localDelta;
398
+ //DEBUG.push(` -> delta: ${delta}`);
399
+ }
400
+ }
401
+ }
357
402
  }
358
403
  }
359
- const safeSize = resizePanel({
360
- groupSizePixels,
361
- panelConstraints,
362
- panelIndex: pivotIndex,
363
- size: unsafeSize
364
- });
365
- if (fuzzyNumbersEqual(initialSize, safeSize)) {
366
- // If there's no room for the pivot panel to grow, we should ignore this change
367
- return nextLayout;
368
- } else {
369
- delta = delta < 0 ? initialSize - safeSize : safeSize - initialSize;
404
+ //DEBUG.push("");
405
+ }
406
+
407
+ {
408
+ // Pre-calculate max available delta in the opposite direction of our pivot.
409
+ // This will be the maximum amount we're allowed to expand/contract the panels in the primary direction.
410
+ // If this amount is less than the requested delta, adjust the requested delta.
411
+ // If this amount is greater than the requested delta, that's useful information too–
412
+ // as an expanding panel might change from collapsed to min size.
413
+
414
+ const increment = delta < 0 ? 1 : -1;
415
+ let index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
416
+ let maxAvailableDelta = 0;
417
+
418
+ //DEBUG.push("pre calc...");
419
+ while (true) {
420
+ const prevSize = prevLayout[index];
421
+ const maxSafeSize = resizePanel({
422
+ groupSizePixels,
423
+ panelConstraints,
424
+ panelIndex: index,
425
+ size: 100
426
+ });
427
+ const delta = maxSafeSize - prevSize;
428
+ //DEBUG.push(` ${index}: ${prevSize} -> ${maxSafeSize}`);
429
+
430
+ maxAvailableDelta += delta;
431
+ index += increment;
432
+ if (index < 0 || index >= panelConstraints.length) {
433
+ break;
434
+ }
370
435
  }
436
+
437
+ //DEBUG.push(` -> max available delta: ${maxAvailableDelta}`);
438
+ const minAbsDelta = Math.min(Math.abs(delta), Math.abs(maxAvailableDelta));
439
+ delta = delta < 0 ? 0 - minAbsDelta : minAbsDelta;
440
+ //DEBUG.push(` -> adjusted delta: ${delta}`);
441
+ //DEBUG.push("");
371
442
  }
372
443
 
373
- // Delta added to a panel needs to be subtracted from other panels
374
- // within the constraints that those panels allow
375
444
  {
445
+ // Delta added to a panel needs to be subtracted from other panels (within the constraints that those panels allow).
446
+
376
447
  const pivotIndex = delta < 0 ? pivotIndices[0] : pivotIndices[1];
377
448
  let index = pivotIndex;
378
449
  while (index >= 0 && index < panelConstraints.length) {
379
450
  const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
380
451
  const prevSize = prevLayout[index];
381
452
  const unsafeSize = prevSize - deltaRemaining;
382
- let safeSize = resizePanel({
453
+ const safeSize = resizePanel({
383
454
  groupSizePixels,
384
455
  panelConstraints,
385
456
  panelIndex: index,
@@ -401,13 +472,18 @@ function adjustLayoutByDelta({
401
472
  }
402
473
  }
403
474
  }
475
+ //DEBUG.push(`after 1: ${nextLayout.join(", ")}`);
476
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
477
+ //DEBUG.push("");
404
478
 
405
479
  // If we were unable to resize any of the panels panels, return the previous state.
406
480
  // This will essentially bailout and ignore e.g. drags past a panel's boundaries
407
481
  if (fuzzyNumbersEqual(deltaApplied, 0)) {
482
+ //console.log(DEBUG.join("\n"));
408
483
  return prevLayout;
409
484
  }
410
485
  {
486
+ // Now distribute the applied delta to the panels in the other direction
411
487
  const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
412
488
  const unsafeSize = prevLayout[pivotIndex] + deltaApplied;
413
489
  const safeSize = resizePanel({
@@ -447,29 +523,21 @@ function adjustLayoutByDelta({
447
523
  index++;
448
524
  }
449
525
  }
450
-
451
- // If we can't redistribute, this layout is invalid;
452
- // There may be an incremental layout that is valid though
453
- if (!fuzzyNumbersEqual(deltaRemaining, 0)) {
454
- try {
455
- return adjustLayoutByDelta({
456
- delta: delta < 0 ? delta + 1 : delta - 1,
457
- groupSizePixels,
458
- layout: prevLayout,
459
- panelConstraints,
460
- pivotIndices,
461
- trigger
462
- });
463
- } catch (error) {
464
- if (error instanceof RangeError) {
465
- console.error(`Could not apply delta ${delta} to layout`);
466
- return prevLayout;
467
- }
468
- } finally {
469
- }
470
- }
471
526
  }
472
527
  }
528
+ //DEBUG.push(`after 2: ${nextLayout.join(", ")}`);
529
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
530
+ //DEBUG.push("");
531
+
532
+ const totalSize = nextLayout.reduce((total, size) => size + total, 0);
533
+ deltaApplied = 100 - totalSize;
534
+ //DEBUG.push(`total size: ${totalSize}`);
535
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
536
+ //console.log(DEBUG.join("\n"));
537
+
538
+ if (!fuzzyNumbersEqual(totalSize, 100)) {
539
+ return prevLayout;
540
+ }
473
541
  return nextLayout;
474
542
  }
475
543
 
@@ -583,15 +651,10 @@ function useWindowSplitterPanelGroupBehavior({
583
651
  });
584
652
  useEffect(() => {
585
653
  const {
586
- direction,
587
654
  panelDataArray
588
655
  } = committedValuesRef.current;
589
656
  const groupElement = getPanelGroupElement(groupId);
590
657
  assert(groupElement != null, `No group found for id "${groupId}"`);
591
- const {
592
- height,
593
- width
594
- } = groupElement.getBoundingClientRect();
595
658
  const handles = getResizeHandleElementsForGroup(groupId);
596
659
  const cleanupFunctions = handles.map(handle => {
597
660
  const handleId = handle.getAttribute("data-panel-resize-handle-id");
@@ -611,21 +674,19 @@ function useWindowSplitterPanelGroupBehavior({
611
674
  if (index >= 0) {
612
675
  const panelData = panelDataArray[index];
613
676
  const size = layout[index];
614
- if (size != null) {
615
- var _getPercentageSizeFro;
677
+ if (size != null && panelData.constraints.collapsible) {
678
+ var _getPercentageSizeFro, _getPercentageSizeFro2;
616
679
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
617
- const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
680
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
681
+ sizePercentage: panelData.constraints.collapsedSizePercentage,
682
+ sizePixels: panelData.constraints.collapsedSizePixels
683
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
684
+ const minSize = (_getPercentageSizeFro2 = getPercentageSizeFromMixedSizes({
618
685
  sizePercentage: panelData.constraints.minSizePercentage,
619
686
  sizePixels: panelData.constraints.minSizePixels
620
- }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
621
- let delta = 0;
622
- if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
623
- delta = direction === "horizontal" ? width : height;
624
- } else {
625
- delta = -(direction === "horizontal" ? width : height);
626
- }
687
+ }, groupSizePixels)) !== null && _getPercentageSizeFro2 !== void 0 ? _getPercentageSizeFro2 : 0;
627
688
  const nextLayout = adjustLayoutByDelta({
628
- delta,
689
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
629
690
  groupSizePixels,
630
691
  layout,
631
692
  panelConstraints: panelDataArray.map(panelData => panelData.constraints),
@@ -1110,6 +1171,7 @@ function PanelGroupWithForwardedRef({
1110
1171
  autoSaveId,
1111
1172
  children,
1112
1173
  className: classNameFromProps = "",
1174
+ dataAttributes,
1113
1175
  direction,
1114
1176
  forwardedRef,
1115
1177
  id: idFromProps,
@@ -1127,6 +1189,7 @@ function PanelGroupWithForwardedRef({
1127
1189
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1128
1190
  const panelSizeBeforeCollapseRef = useRef(new Map());
1129
1191
  const prevDeltaRef = useRef(0);
1192
+ const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1130
1193
  const committedValuesRef = useRef({
1131
1194
  direction,
1132
1195
  dragState,
@@ -1258,6 +1321,17 @@ function PanelGroupWithForwardedRef({
1258
1321
  onLayout,
1259
1322
  panelDataArray
1260
1323
  } = committedValuesRef.current;
1324
+
1325
+ // See issues/211
1326
+ if (panelDataArray.find(({
1327
+ id
1328
+ }) => id === panelData.id) == null) {
1329
+ setImperativeApiQueue(prev => [...prev, {
1330
+ panelData,
1331
+ type: "collapse"
1332
+ }]);
1333
+ return;
1334
+ }
1261
1335
  if (panelData.constraints.collapsible) {
1262
1336
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1263
1337
  const {
@@ -1301,6 +1375,17 @@ function PanelGroupWithForwardedRef({
1301
1375
  onLayout,
1302
1376
  panelDataArray
1303
1377
  } = committedValuesRef.current;
1378
+
1379
+ // See issues/211
1380
+ if (panelDataArray.find(({
1381
+ id
1382
+ }) => id === panelData.id) == null) {
1383
+ setImperativeApiQueue(prev => [...prev, {
1384
+ panelData,
1385
+ type: "expand"
1386
+ }]);
1387
+ return;
1388
+ }
1304
1389
  if (panelData.constraints.collapsible) {
1305
1390
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1306
1391
  const {
@@ -1496,6 +1581,18 @@ function PanelGroupWithForwardedRef({
1496
1581
  onLayout,
1497
1582
  panelDataArray
1498
1583
  } = committedValuesRef.current;
1584
+
1585
+ // See issues/211
1586
+ if (panelDataArray.find(({
1587
+ id
1588
+ }) => id === panelData.id) == null) {
1589
+ setImperativeApiQueue(prev => [...prev, {
1590
+ panelData,
1591
+ mixedSizes,
1592
+ type: "resize"
1593
+ }]);
1594
+ return;
1595
+ }
1499
1596
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1500
1597
  const {
1501
1598
  groupSizePixels,
@@ -1586,6 +1683,7 @@ function PanelGroupWithForwardedRef({
1586
1683
  ...style,
1587
1684
  ...styleFromProps
1588
1685
  },
1686
+ ...dataAttributes,
1589
1687
  // CSS selectors
1590
1688
  "data-panel-group": "",
1591
1689
  "data-panel-group-direction": direction,
@@ -1673,6 +1771,7 @@ function useWindowSplitterResizeHandlerBehavior({
1673
1771
  function PanelResizeHandle({
1674
1772
  children = null,
1675
1773
  className: classNameFromProps = "",
1774
+ dataAttributes,
1676
1775
  disabled = false,
1677
1776
  id: idFromProps = null,
1678
1777
  onDragging,
@@ -1795,6 +1894,7 @@ function PanelResizeHandle({
1795
1894
  ...styleFromProps
1796
1895
  },
1797
1896
  tabIndex: 0,
1897
+ ...dataAttributes,
1798
1898
  // CSS selectors
1799
1899
  "data-panel-group-direction": direction,
1800
1900
  "data-panel-group-id": groupId,