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,
@@ -151,6 +152,7 @@ function PanelWithForwardedRef({
151
152
  ...style,
152
153
  ...styleFromProps
153
154
  },
155
+ ...dataAttributes,
154
156
  // CSS selectors
155
157
  "data-panel": "",
156
158
  "data-panel-id": panelId,
@@ -166,8 +168,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
166
168
  PanelWithForwardedRef.displayName = "Panel";
167
169
  Panel.displayName = "forwardRef(Panel)";
168
170
 
169
- const PRECISION = 10;
170
-
171
171
  function convertPixelsToPercentage(pixels, groupSizePixels) {
172
172
  return pixels / groupSizePixels * 100;
173
173
  }
@@ -245,6 +245,8 @@ function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, gr
245
245
  };
246
246
  }
247
247
 
248
+ const PRECISION = 10;
249
+
248
250
  function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
249
251
  actual = parseFloat(actual.toFixed(fractionDigits));
250
252
  expected = parseFloat(expected.toFixed(fractionDigits));
@@ -288,7 +290,13 @@ function resizePanel({
288
290
  if (minSizePercentage != null) {
289
291
  if (fuzzyCompareNumbers(size, minSizePercentage) < 0) {
290
292
  if (collapsible) {
291
- size = collapsedSizePercentage;
293
+ // Collapsible panels should snap closed or open only once they cross the halfway point between collapsed and min size.
294
+ const halfwayPoint = (collapsedSizePercentage + minSizePercentage) / 2;
295
+ if (fuzzyCompareNumbers(size, halfwayPoint) < 0) {
296
+ size = collapsedSizePercentage;
297
+ } else {
298
+ size = minSizePercentage;
299
+ }
292
300
  } else {
293
301
  size = minSizePercentage;
294
302
  }
@@ -315,60 +323,123 @@ function adjustLayoutByDelta({
315
323
  const nextLayout = [...prevLayout];
316
324
  let deltaApplied = 0;
317
325
 
326
+ //const DEBUG = [];
327
+ //DEBUG.push(`adjustLayoutByDelta() ${prevLayout.join(", ")}`);
328
+ //DEBUG.push(` delta: ${delta}`);
329
+ //DEBUG.push(` pivotIndices: ${pivotIndices.join(", ")}`);
330
+ //DEBUG.push(` trigger: ${trigger}`);
331
+ //DEBUG.push("");
332
+
318
333
  // A resizing panel affects the panels before or after it.
319
334
  //
320
- // A negative delta means the panel immediately after the resizer should grow/expand by decreasing its offset.
335
+ // A negative delta means the panel(s) immediately after the resize handle should grow/expand by decreasing its offset.
321
336
  // Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
322
337
  //
323
- // A positive delta means the panel immediately before the resizer should "expand".
324
- // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.
338
+ // A positive delta means the panel(s) immediately before the resize handle should "expand".
339
+ // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resize handle.
325
340
 
326
- // First, check the panel we're pivoting around;
327
- // We should only expand or contract by as much as its constraints allow
328
341
  {
329
- const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
330
- const initialSize = nextLayout[pivotIndex];
331
- const {
332
- collapsible
333
- } = panelConstraints[pivotIndex];
334
- const {
335
- collapsedSizePercentage,
336
- minSizePercentage
337
- } = computePercentagePanelConstraints(panelConstraints, pivotIndex, groupSizePixels);
338
- const isCollapsed = collapsible && fuzzyNumbersEqual(initialSize, collapsedSizePercentage);
339
- let unsafeSize = initialSize + Math.abs(delta);
340
- if (isCollapsed) {
341
- switch (trigger) {
342
- case "keyboard":
343
- if (minSizePercentage > unsafeSize) {
344
- unsafeSize = minSizePercentage;
342
+ // If this is a resize triggered by a keyboard event, our logic for expanding/collapsing is different.
343
+ // We no longer check the halfway threshold because this may prevent the panel from expanding at all.
344
+ if (trigger === "keyboard") {
345
+ {
346
+ // Check if we should expand a collapsed panel
347
+ const index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
348
+ const constraints = panelConstraints[index];
349
+ //DEBUG.push(`edge case check 1: ${index}`);
350
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
351
+ if (constraints.collapsible) {
352
+ const prevSize = prevLayout[index];
353
+ const {
354
+ collapsedSizePercentage,
355
+ minSizePercentage
356
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
357
+ if (fuzzyNumbersEqual(prevSize, collapsedSizePercentage)) {
358
+ const localDelta = minSizePercentage - prevSize;
359
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
360
+
361
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
362
+ delta = delta < 0 ? 0 - localDelta : localDelta;
363
+ //DEBUG.push(` -> delta: ${delta}`);
364
+ }
345
365
  }
366
+ }
367
+ }
368
+
369
+ {
370
+ // Check if we should collapse a panel at its minimum size
371
+ const index = delta < 0 ? pivotIndices[0] : pivotIndices[1];
372
+ const constraints = panelConstraints[index];
373
+ //DEBUG.push(`edge case check 2: ${index}`);
374
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
375
+ if (constraints.collapsible) {
376
+ const prevSize = prevLayout[index];
377
+ const {
378
+ collapsedSizePercentage,
379
+ minSizePercentage
380
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
381
+ if (fuzzyNumbersEqual(prevSize, minSizePercentage)) {
382
+ const localDelta = prevSize - collapsedSizePercentage;
383
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
384
+
385
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
386
+ delta = delta < 0 ? 0 - localDelta : localDelta;
387
+ //DEBUG.push(` -> delta: ${delta}`);
388
+ }
389
+ }
390
+ }
346
391
  }
347
392
  }
348
- const safeSize = resizePanel({
349
- groupSizePixels,
350
- panelConstraints,
351
- panelIndex: pivotIndex,
352
- size: unsafeSize
353
- });
354
- if (fuzzyNumbersEqual(initialSize, safeSize)) {
355
- // If there's no room for the pivot panel to grow, we should ignore this change
356
- return nextLayout;
357
- } else {
358
- delta = delta < 0 ? initialSize - safeSize : safeSize - initialSize;
393
+ //DEBUG.push("");
394
+ }
395
+
396
+ {
397
+ // Pre-calculate max available delta in the opposite direction of our pivot.
398
+ // This will be the maximum amount we're allowed to expand/contract the panels in the primary direction.
399
+ // If this amount is less than the requested delta, adjust the requested delta.
400
+ // If this amount is greater than the requested delta, that's useful information too–
401
+ // as an expanding panel might change from collapsed to min size.
402
+
403
+ const increment = delta < 0 ? 1 : -1;
404
+ let index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
405
+ let maxAvailableDelta = 0;
406
+
407
+ //DEBUG.push("pre calc...");
408
+ while (true) {
409
+ const prevSize = prevLayout[index];
410
+ const maxSafeSize = resizePanel({
411
+ groupSizePixels,
412
+ panelConstraints,
413
+ panelIndex: index,
414
+ size: 100
415
+ });
416
+ const delta = maxSafeSize - prevSize;
417
+ //DEBUG.push(` ${index}: ${prevSize} -> ${maxSafeSize}`);
418
+
419
+ maxAvailableDelta += delta;
420
+ index += increment;
421
+ if (index < 0 || index >= panelConstraints.length) {
422
+ break;
423
+ }
359
424
  }
425
+
426
+ //DEBUG.push(` -> max available delta: ${maxAvailableDelta}`);
427
+ const minAbsDelta = Math.min(Math.abs(delta), Math.abs(maxAvailableDelta));
428
+ delta = delta < 0 ? 0 - minAbsDelta : minAbsDelta;
429
+ //DEBUG.push(` -> adjusted delta: ${delta}`);
430
+ //DEBUG.push("");
360
431
  }
361
432
 
362
- // Delta added to a panel needs to be subtracted from other panels
363
- // within the constraints that those panels allow
364
433
  {
434
+ // Delta added to a panel needs to be subtracted from other panels (within the constraints that those panels allow).
435
+
365
436
  const pivotIndex = delta < 0 ? pivotIndices[0] : pivotIndices[1];
366
437
  let index = pivotIndex;
367
438
  while (index >= 0 && index < panelConstraints.length) {
368
439
  const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
369
440
  const prevSize = prevLayout[index];
370
441
  const unsafeSize = prevSize - deltaRemaining;
371
- let safeSize = resizePanel({
442
+ const safeSize = resizePanel({
372
443
  groupSizePixels,
373
444
  panelConstraints,
374
445
  panelIndex: index,
@@ -390,13 +461,18 @@ function adjustLayoutByDelta({
390
461
  }
391
462
  }
392
463
  }
464
+ //DEBUG.push(`after 1: ${nextLayout.join(", ")}`);
465
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
466
+ //DEBUG.push("");
393
467
 
394
468
  // If we were unable to resize any of the panels panels, return the previous state.
395
469
  // This will essentially bailout and ignore e.g. drags past a panel's boundaries
396
470
  if (fuzzyNumbersEqual(deltaApplied, 0)) {
471
+ //console.log(DEBUG.join("\n"));
397
472
  return prevLayout;
398
473
  }
399
474
  {
475
+ // Now distribute the applied delta to the panels in the other direction
400
476
  const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
401
477
  const unsafeSize = prevLayout[pivotIndex] + deltaApplied;
402
478
  const safeSize = resizePanel({
@@ -436,29 +512,21 @@ function adjustLayoutByDelta({
436
512
  index++;
437
513
  }
438
514
  }
439
-
440
- // If we can't redistribute, this layout is invalid;
441
- // There may be an incremental layout that is valid though
442
- if (!fuzzyNumbersEqual(deltaRemaining, 0)) {
443
- try {
444
- return adjustLayoutByDelta({
445
- delta: delta < 0 ? delta + 1 : delta - 1,
446
- groupSizePixels,
447
- layout: prevLayout,
448
- panelConstraints,
449
- pivotIndices,
450
- trigger
451
- });
452
- } catch (error) {
453
- if (error instanceof RangeError) {
454
- console.error(`Could not apply delta ${delta} to layout`);
455
- return prevLayout;
456
- }
457
- } finally {
458
- }
459
- }
460
515
  }
461
516
  }
517
+ //DEBUG.push(`after 2: ${nextLayout.join(", ")}`);
518
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
519
+ //DEBUG.push("");
520
+
521
+ const totalSize = nextLayout.reduce((total, size) => size + total, 0);
522
+ deltaApplied = 100 - totalSize;
523
+ //DEBUG.push(`total size: ${totalSize}`);
524
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
525
+ //console.log(DEBUG.join("\n"));
526
+
527
+ if (!fuzzyNumbersEqual(totalSize, 100)) {
528
+ return prevLayout;
529
+ }
462
530
  return nextLayout;
463
531
  }
464
532
 
@@ -572,15 +640,10 @@ function useWindowSplitterPanelGroupBehavior({
572
640
  });
573
641
  useEffect(() => {
574
642
  const {
575
- direction,
576
643
  panelDataArray
577
644
  } = committedValuesRef.current;
578
645
  const groupElement = getPanelGroupElement(groupId);
579
646
  assert(groupElement != null, `No group found for id "${groupId}"`);
580
- const {
581
- height,
582
- width
583
- } = groupElement.getBoundingClientRect();
584
647
  const handles = getResizeHandleElementsForGroup(groupId);
585
648
  const cleanupFunctions = handles.map(handle => {
586
649
  const handleId = handle.getAttribute("data-panel-resize-handle-id");
@@ -600,21 +663,19 @@ function useWindowSplitterPanelGroupBehavior({
600
663
  if (index >= 0) {
601
664
  const panelData = panelDataArray[index];
602
665
  const size = layout[index];
603
- if (size != null) {
604
- var _getPercentageSizeFro;
666
+ if (size != null && panelData.constraints.collapsible) {
667
+ var _getPercentageSizeFro, _getPercentageSizeFro2;
605
668
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
606
- const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
669
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
670
+ sizePercentage: panelData.constraints.collapsedSizePercentage,
671
+ sizePixels: panelData.constraints.collapsedSizePixels
672
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
673
+ const minSize = (_getPercentageSizeFro2 = getPercentageSizeFromMixedSizes({
607
674
  sizePercentage: panelData.constraints.minSizePercentage,
608
675
  sizePixels: panelData.constraints.minSizePixels
609
- }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
610
- let delta = 0;
611
- if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
612
- delta = direction === "horizontal" ? width : height;
613
- } else {
614
- delta = -(direction === "horizontal" ? width : height);
615
- }
676
+ }, groupSizePixels)) !== null && _getPercentageSizeFro2 !== void 0 ? _getPercentageSizeFro2 : 0;
616
677
  const nextLayout = adjustLayoutByDelta({
617
- delta,
678
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
618
679
  groupSizePixels,
619
680
  layout,
620
681
  panelConstraints: panelDataArray.map(panelData => panelData.constraints),
@@ -1022,6 +1083,7 @@ function PanelGroupWithForwardedRef({
1022
1083
  autoSaveId,
1023
1084
  children,
1024
1085
  className: classNameFromProps = "",
1086
+ dataAttributes,
1025
1087
  direction,
1026
1088
  forwardedRef,
1027
1089
  id: idFromProps,
@@ -1039,6 +1101,7 @@ function PanelGroupWithForwardedRef({
1039
1101
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1040
1102
  const panelSizeBeforeCollapseRef = useRef(new Map());
1041
1103
  const prevDeltaRef = useRef(0);
1104
+ const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1042
1105
  const committedValuesRef = useRef({
1043
1106
  direction,
1044
1107
  dragState,
@@ -1128,6 +1191,17 @@ function PanelGroupWithForwardedRef({
1128
1191
  onLayout,
1129
1192
  panelDataArray
1130
1193
  } = committedValuesRef.current;
1194
+
1195
+ // See issues/211
1196
+ if (panelDataArray.find(({
1197
+ id
1198
+ }) => id === panelData.id) == null) {
1199
+ setImperativeApiQueue(prev => [...prev, {
1200
+ panelData,
1201
+ type: "collapse"
1202
+ }]);
1203
+ return;
1204
+ }
1131
1205
  if (panelData.constraints.collapsible) {
1132
1206
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1133
1207
  const {
@@ -1171,6 +1245,17 @@ function PanelGroupWithForwardedRef({
1171
1245
  onLayout,
1172
1246
  panelDataArray
1173
1247
  } = committedValuesRef.current;
1248
+
1249
+ // See issues/211
1250
+ if (panelDataArray.find(({
1251
+ id
1252
+ }) => id === panelData.id) == null) {
1253
+ setImperativeApiQueue(prev => [...prev, {
1254
+ panelData,
1255
+ type: "expand"
1256
+ }]);
1257
+ return;
1258
+ }
1174
1259
  if (panelData.constraints.collapsible) {
1175
1260
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1176
1261
  const {
@@ -1366,6 +1451,18 @@ function PanelGroupWithForwardedRef({
1366
1451
  onLayout,
1367
1452
  panelDataArray
1368
1453
  } = committedValuesRef.current;
1454
+
1455
+ // See issues/211
1456
+ if (panelDataArray.find(({
1457
+ id
1458
+ }) => id === panelData.id) == null) {
1459
+ setImperativeApiQueue(prev => [...prev, {
1460
+ panelData,
1461
+ mixedSizes,
1462
+ type: "resize"
1463
+ }]);
1464
+ return;
1465
+ }
1369
1466
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1370
1467
  const {
1371
1468
  groupSizePixels,
@@ -1456,6 +1553,7 @@ function PanelGroupWithForwardedRef({
1456
1553
  ...style,
1457
1554
  ...styleFromProps
1458
1555
  },
1556
+ ...dataAttributes,
1459
1557
  // CSS selectors
1460
1558
  "data-panel-group": "",
1461
1559
  "data-panel-group-direction": direction,
@@ -1543,6 +1641,7 @@ function useWindowSplitterResizeHandlerBehavior({
1543
1641
  function PanelResizeHandle({
1544
1642
  children = null,
1545
1643
  className: classNameFromProps = "",
1644
+ dataAttributes,
1546
1645
  disabled = false,
1547
1646
  id: idFromProps = null,
1548
1647
  onDragging,
@@ -1665,6 +1764,7 @@ function PanelResizeHandle({
1665
1764
  ...styleFromProps
1666
1765
  },
1667
1766
  tabIndex: 0,
1767
+ ...dataAttributes,
1668
1768
  // CSS selectors
1669
1769
  "data-panel-group-direction": direction,
1670
1770
  "data-panel-group-id": groupId,