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
@@ -42,6 +42,7 @@ function PanelWithForwardedRef({
42
42
  collapsedSizePercentage,
43
43
  collapsedSizePixels,
44
44
  collapsible,
45
+ dataAttributes,
45
46
  defaultSizePercentage,
46
47
  defaultSizePixels,
47
48
  forwardedRef,
@@ -138,6 +139,7 @@ function PanelWithForwardedRef({
138
139
  ...style,
139
140
  ...styleFromProps
140
141
  },
142
+ ...dataAttributes,
141
143
  // CSS selectors
142
144
  "data-panel": "",
143
145
  "data-panel-id": panelId,
@@ -153,8 +155,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
153
155
  PanelWithForwardedRef.displayName = "Panel";
154
156
  Panel.displayName = "forwardRef(Panel)";
155
157
 
156
- const PRECISION = 10;
157
-
158
158
  function convertPixelsToPercentage(pixels, groupSizePixels) {
159
159
  return pixels / groupSizePixels * 100;
160
160
  }
@@ -232,6 +232,8 @@ function computePercentagePanelConstraints(panelConstraintsArray, panelIndex, gr
232
232
  };
233
233
  }
234
234
 
235
+ const PRECISION = 10;
236
+
235
237
  function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
236
238
  actual = parseFloat(actual.toFixed(fractionDigits));
237
239
  expected = parseFloat(expected.toFixed(fractionDigits));
@@ -275,7 +277,13 @@ function resizePanel({
275
277
  if (minSizePercentage != null) {
276
278
  if (fuzzyCompareNumbers(size, minSizePercentage) < 0) {
277
279
  if (collapsible) {
278
- size = collapsedSizePercentage;
280
+ // Collapsible panels should snap closed or open only once they cross the halfway point between collapsed and min size.
281
+ const halfwayPoint = (collapsedSizePercentage + minSizePercentage) / 2;
282
+ if (fuzzyCompareNumbers(size, halfwayPoint) < 0) {
283
+ size = collapsedSizePercentage;
284
+ } else {
285
+ size = minSizePercentage;
286
+ }
279
287
  } else {
280
288
  size = minSizePercentage;
281
289
  }
@@ -302,60 +310,123 @@ function adjustLayoutByDelta({
302
310
  const nextLayout = [...prevLayout];
303
311
  let deltaApplied = 0;
304
312
 
313
+ //const DEBUG = [];
314
+ //DEBUG.push(`adjustLayoutByDelta() ${prevLayout.join(", ")}`);
315
+ //DEBUG.push(` delta: ${delta}`);
316
+ //DEBUG.push(` pivotIndices: ${pivotIndices.join(", ")}`);
317
+ //DEBUG.push(` trigger: ${trigger}`);
318
+ //DEBUG.push("");
319
+
305
320
  // A resizing panel affects the panels before or after it.
306
321
  //
307
- // A negative delta means the panel immediately after the resizer should grow/expand by decreasing its offset.
322
+ // A negative delta means the panel(s) immediately after the resize handle should grow/expand by decreasing its offset.
308
323
  // Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
309
324
  //
310
- // A positive delta means the panel immediately before the resizer should "expand".
311
- // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.
325
+ // A positive delta means the panel(s) immediately before the resize handle should "expand".
326
+ // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resize handle.
312
327
 
313
- // First, check the panel we're pivoting around;
314
- // We should only expand or contract by as much as its constraints allow
315
328
  {
316
- const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
317
- const initialSize = nextLayout[pivotIndex];
318
- const {
319
- collapsible
320
- } = panelConstraints[pivotIndex];
321
- const {
322
- collapsedSizePercentage,
323
- minSizePercentage
324
- } = computePercentagePanelConstraints(panelConstraints, pivotIndex, groupSizePixels);
325
- const isCollapsed = collapsible && fuzzyNumbersEqual(initialSize, collapsedSizePercentage);
326
- let unsafeSize = initialSize + Math.abs(delta);
327
- if (isCollapsed) {
328
- switch (trigger) {
329
- case "keyboard":
330
- if (minSizePercentage > unsafeSize) {
331
- unsafeSize = minSizePercentage;
329
+ // If this is a resize triggered by a keyboard event, our logic for expanding/collapsing is different.
330
+ // We no longer check the halfway threshold because this may prevent the panel from expanding at all.
331
+ if (trigger === "keyboard") {
332
+ {
333
+ // Check if we should expand a collapsed panel
334
+ const index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
335
+ const constraints = panelConstraints[index];
336
+ //DEBUG.push(`edge case check 1: ${index}`);
337
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
338
+ if (constraints.collapsible) {
339
+ const prevSize = prevLayout[index];
340
+ const {
341
+ collapsedSizePercentage,
342
+ minSizePercentage
343
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
344
+ if (fuzzyNumbersEqual(prevSize, collapsedSizePercentage)) {
345
+ const localDelta = minSizePercentage - prevSize;
346
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
347
+
348
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
349
+ delta = delta < 0 ? 0 - localDelta : localDelta;
350
+ //DEBUG.push(` -> delta: ${delta}`);
351
+ }
332
352
  }
353
+ }
354
+ }
355
+
356
+ {
357
+ // Check if we should collapse a panel at its minimum size
358
+ const index = delta < 0 ? pivotIndices[0] : pivotIndices[1];
359
+ const constraints = panelConstraints[index];
360
+ //DEBUG.push(`edge case check 2: ${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, minSizePercentage)) {
369
+ const localDelta = prevSize - collapsedSizePercentage;
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
+ }
376
+ }
377
+ }
333
378
  }
334
379
  }
335
- const safeSize = resizePanel({
336
- groupSizePixels,
337
- panelConstraints,
338
- panelIndex: pivotIndex,
339
- size: unsafeSize
340
- });
341
- if (fuzzyNumbersEqual(initialSize, safeSize)) {
342
- // If there's no room for the pivot panel to grow, we should ignore this change
343
- return nextLayout;
344
- } else {
345
- delta = delta < 0 ? initialSize - safeSize : safeSize - initialSize;
380
+ //DEBUG.push("");
381
+ }
382
+
383
+ {
384
+ // Pre-calculate max available delta in the opposite direction of our pivot.
385
+ // This will be the maximum amount we're allowed to expand/contract the panels in the primary direction.
386
+ // If this amount is less than the requested delta, adjust the requested delta.
387
+ // If this amount is greater than the requested delta, that's useful information too–
388
+ // as an expanding panel might change from collapsed to min size.
389
+
390
+ const increment = delta < 0 ? 1 : -1;
391
+ let index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
392
+ let maxAvailableDelta = 0;
393
+
394
+ //DEBUG.push("pre calc...");
395
+ while (true) {
396
+ const prevSize = prevLayout[index];
397
+ const maxSafeSize = resizePanel({
398
+ groupSizePixels,
399
+ panelConstraints,
400
+ panelIndex: index,
401
+ size: 100
402
+ });
403
+ const delta = maxSafeSize - prevSize;
404
+ //DEBUG.push(` ${index}: ${prevSize} -> ${maxSafeSize}`);
405
+
406
+ maxAvailableDelta += delta;
407
+ index += increment;
408
+ if (index < 0 || index >= panelConstraints.length) {
409
+ break;
410
+ }
346
411
  }
412
+
413
+ //DEBUG.push(` -> max available delta: ${maxAvailableDelta}`);
414
+ const minAbsDelta = Math.min(Math.abs(delta), Math.abs(maxAvailableDelta));
415
+ delta = delta < 0 ? 0 - minAbsDelta : minAbsDelta;
416
+ //DEBUG.push(` -> adjusted delta: ${delta}`);
417
+ //DEBUG.push("");
347
418
  }
348
419
 
349
- // Delta added to a panel needs to be subtracted from other panels
350
- // within the constraints that those panels allow
351
420
  {
421
+ // Delta added to a panel needs to be subtracted from other panels (within the constraints that those panels allow).
422
+
352
423
  const pivotIndex = delta < 0 ? pivotIndices[0] : pivotIndices[1];
353
424
  let index = pivotIndex;
354
425
  while (index >= 0 && index < panelConstraints.length) {
355
426
  const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
356
427
  const prevSize = prevLayout[index];
357
428
  const unsafeSize = prevSize - deltaRemaining;
358
- let safeSize = resizePanel({
429
+ const safeSize = resizePanel({
359
430
  groupSizePixels,
360
431
  panelConstraints,
361
432
  panelIndex: index,
@@ -377,13 +448,18 @@ function adjustLayoutByDelta({
377
448
  }
378
449
  }
379
450
  }
451
+ //DEBUG.push(`after 1: ${nextLayout.join(", ")}`);
452
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
453
+ //DEBUG.push("");
380
454
 
381
455
  // If we were unable to resize any of the panels panels, return the previous state.
382
456
  // This will essentially bailout and ignore e.g. drags past a panel's boundaries
383
457
  if (fuzzyNumbersEqual(deltaApplied, 0)) {
458
+ //console.log(DEBUG.join("\n"));
384
459
  return prevLayout;
385
460
  }
386
461
  {
462
+ // Now distribute the applied delta to the panels in the other direction
387
463
  const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
388
464
  const unsafeSize = prevLayout[pivotIndex] + deltaApplied;
389
465
  const safeSize = resizePanel({
@@ -423,29 +499,21 @@ function adjustLayoutByDelta({
423
499
  index++;
424
500
  }
425
501
  }
426
-
427
- // If we can't redistribute, this layout is invalid;
428
- // There may be an incremental layout that is valid though
429
- if (!fuzzyNumbersEqual(deltaRemaining, 0)) {
430
- try {
431
- return adjustLayoutByDelta({
432
- delta: delta < 0 ? delta + 1 : delta - 1,
433
- groupSizePixels,
434
- layout: prevLayout,
435
- panelConstraints,
436
- pivotIndices,
437
- trigger
438
- });
439
- } catch (error) {
440
- if (error instanceof RangeError) {
441
- console.error(`Could not apply delta ${delta} to layout`);
442
- return prevLayout;
443
- }
444
- } finally {
445
- }
446
- }
447
502
  }
448
503
  }
504
+ //DEBUG.push(`after 2: ${nextLayout.join(", ")}`);
505
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
506
+ //DEBUG.push("");
507
+
508
+ const totalSize = nextLayout.reduce((total, size) => size + total, 0);
509
+ deltaApplied = 100 - totalSize;
510
+ //DEBUG.push(`total size: ${totalSize}`);
511
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
512
+ //console.log(DEBUG.join("\n"));
513
+
514
+ if (!fuzzyNumbersEqual(totalSize, 100)) {
515
+ return prevLayout;
516
+ }
449
517
  return nextLayout;
450
518
  }
451
519
 
@@ -559,15 +627,10 @@ function useWindowSplitterPanelGroupBehavior({
559
627
  });
560
628
  useEffect(() => {
561
629
  const {
562
- direction,
563
630
  panelDataArray
564
631
  } = committedValuesRef.current;
565
632
  const groupElement = getPanelGroupElement(groupId);
566
633
  assert(groupElement != null, `No group found for id "${groupId}"`);
567
- const {
568
- height,
569
- width
570
- } = groupElement.getBoundingClientRect();
571
634
  const handles = getResizeHandleElementsForGroup(groupId);
572
635
  const cleanupFunctions = handles.map(handle => {
573
636
  const handleId = handle.getAttribute("data-panel-resize-handle-id");
@@ -587,21 +650,19 @@ function useWindowSplitterPanelGroupBehavior({
587
650
  if (index >= 0) {
588
651
  const panelData = panelDataArray[index];
589
652
  const size = layout[index];
590
- if (size != null) {
591
- var _getPercentageSizeFro;
653
+ if (size != null && panelData.constraints.collapsible) {
654
+ var _getPercentageSizeFro, _getPercentageSizeFro2;
592
655
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
593
- const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
656
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
657
+ sizePercentage: panelData.constraints.collapsedSizePercentage,
658
+ sizePixels: panelData.constraints.collapsedSizePixels
659
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
660
+ const minSize = (_getPercentageSizeFro2 = getPercentageSizeFromMixedSizes({
594
661
  sizePercentage: panelData.constraints.minSizePercentage,
595
662
  sizePixels: panelData.constraints.minSizePixels
596
- }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
597
- let delta = 0;
598
- if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
599
- delta = direction === "horizontal" ? width : height;
600
- } else {
601
- delta = -(direction === "horizontal" ? width : height);
602
- }
663
+ }, groupSizePixels)) !== null && _getPercentageSizeFro2 !== void 0 ? _getPercentageSizeFro2 : 0;
603
664
  const nextLayout = adjustLayoutByDelta({
604
- delta,
665
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
605
666
  groupSizePixels,
606
667
  layout,
607
668
  panelConstraints: panelDataArray.map(panelData => panelData.constraints),
@@ -1086,6 +1147,7 @@ function PanelGroupWithForwardedRef({
1086
1147
  autoSaveId,
1087
1148
  children,
1088
1149
  className: classNameFromProps = "",
1150
+ dataAttributes,
1089
1151
  direction,
1090
1152
  forwardedRef,
1091
1153
  id: idFromProps,
@@ -1103,6 +1165,7 @@ function PanelGroupWithForwardedRef({
1103
1165
  const panelIdToLastNotifiedMixedSizesMapRef = useRef({});
1104
1166
  const panelSizeBeforeCollapseRef = useRef(new Map());
1105
1167
  const prevDeltaRef = useRef(0);
1168
+ const [imperativeApiQueue, setImperativeApiQueue] = useState([]);
1106
1169
  const committedValuesRef = useRef({
1107
1170
  direction,
1108
1171
  dragState,
@@ -1234,6 +1297,17 @@ function PanelGroupWithForwardedRef({
1234
1297
  onLayout,
1235
1298
  panelDataArray
1236
1299
  } = committedValuesRef.current;
1300
+
1301
+ // See issues/211
1302
+ if (panelDataArray.find(({
1303
+ id
1304
+ }) => id === panelData.id) == null) {
1305
+ setImperativeApiQueue(prev => [...prev, {
1306
+ panelData,
1307
+ type: "collapse"
1308
+ }]);
1309
+ return;
1310
+ }
1237
1311
  if (panelData.constraints.collapsible) {
1238
1312
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1239
1313
  const {
@@ -1277,6 +1351,17 @@ function PanelGroupWithForwardedRef({
1277
1351
  onLayout,
1278
1352
  panelDataArray
1279
1353
  } = committedValuesRef.current;
1354
+
1355
+ // See issues/211
1356
+ if (panelDataArray.find(({
1357
+ id
1358
+ }) => id === panelData.id) == null) {
1359
+ setImperativeApiQueue(prev => [...prev, {
1360
+ panelData,
1361
+ type: "expand"
1362
+ }]);
1363
+ return;
1364
+ }
1280
1365
  if (panelData.constraints.collapsible) {
1281
1366
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1282
1367
  const {
@@ -1472,6 +1557,18 @@ function PanelGroupWithForwardedRef({
1472
1557
  onLayout,
1473
1558
  panelDataArray
1474
1559
  } = committedValuesRef.current;
1560
+
1561
+ // See issues/211
1562
+ if (panelDataArray.find(({
1563
+ id
1564
+ }) => id === panelData.id) == null) {
1565
+ setImperativeApiQueue(prev => [...prev, {
1566
+ panelData,
1567
+ mixedSizes,
1568
+ type: "resize"
1569
+ }]);
1570
+ return;
1571
+ }
1475
1572
  const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
1476
1573
  const {
1477
1574
  groupSizePixels,
@@ -1562,6 +1659,7 @@ function PanelGroupWithForwardedRef({
1562
1659
  ...style,
1563
1660
  ...styleFromProps
1564
1661
  },
1662
+ ...dataAttributes,
1565
1663
  // CSS selectors
1566
1664
  "data-panel-group": "",
1567
1665
  "data-panel-group-direction": direction,
@@ -1649,6 +1747,7 @@ function useWindowSplitterResizeHandlerBehavior({
1649
1747
  function PanelResizeHandle({
1650
1748
  children = null,
1651
1749
  className: classNameFromProps = "",
1750
+ dataAttributes,
1652
1751
  disabled = false,
1653
1752
  id: idFromProps = null,
1654
1753
  onDragging,
@@ -1771,6 +1870,7 @@ function PanelResizeHandle({
1771
1870
  ...styleFromProps
1772
1871
  },
1773
1872
  tabIndex: 0,
1873
+ ...dataAttributes,
1774
1874
  // CSS selectors
1775
1875
  "data-panel-group-direction": direction,
1776
1876
  "data-panel-group-id": groupId,