react-resizable-panels 0.0.56 → 0.0.58

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 (33) hide show
  1. package/CHANGELOG.md +11 -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 +186 -87
  7. package/dist/react-resizable-panels.browser.development.cjs.js +186 -87
  8. package/dist/react-resizable-panels.browser.development.esm.js +186 -87
  9. package/dist/react-resizable-panels.browser.esm.js +186 -87
  10. package/dist/react-resizable-panels.cjs.js +186 -87
  11. package/dist/react-resizable-panels.cjs.js.map +1 -1
  12. package/dist/react-resizable-panels.development.cjs.js +186 -87
  13. package/dist/react-resizable-panels.development.esm.js +186 -87
  14. package/dist/react-resizable-panels.development.node.cjs.js +175 -82
  15. package/dist/react-resizable-panels.development.node.esm.js +175 -82
  16. package/dist/react-resizable-panels.esm.js +186 -87
  17. package/dist/react-resizable-panels.esm.js.map +1 -1
  18. package/dist/react-resizable-panels.node.cjs.js +175 -82
  19. package/dist/react-resizable-panels.node.esm.js +175 -82
  20. package/package.json +3 -1
  21. package/src/Panel.ts +9 -3
  22. package/src/PanelGroup.ts +11 -5
  23. package/src/PanelResizeHandle.ts +17 -13
  24. package/src/types.ts +4 -0
  25. package/src/utils/adjustLayoutByDelta.test.ts +238 -8
  26. package/src/utils/adjustLayoutByDelta.ts +122 -72
  27. package/src/utils/computePercentagePanelConstraints.test.ts +27 -0
  28. package/src/utils/convertPixelConstraintsToPercentages.test.ts +47 -0
  29. package/src/utils/convertPixelConstraintsToPercentages.ts +17 -0
  30. package/src/utils/dom/getPanelGroupElement.ts +3 -1
  31. package/src/utils/resizePanel.test.ts +105 -0
  32. package/src/utils/resizePanel.ts +26 -1
  33. package/src/utils/validatePanelGroupLayout.test.ts +36 -6
@@ -33,7 +33,7 @@ function useUniqueId(idFromParams = null) {
33
33
  if (idRef.current === null) {
34
34
  idRef.current = "" + counter++;
35
35
  }
36
- return idFromParams ?? idRef.current;
36
+ return idFromParams !== null && idFromParams !== void 0 ? idFromParams : idRef.current;
37
37
  }
38
38
 
39
39
  function PanelWithForwardedRef({
@@ -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,
@@ -127,11 +128,12 @@ function PanelWithForwardedRef({
127
128
  ...style,
128
129
  ...styleFromProps
129
130
  },
131
+ ...dataAttributes,
130
132
  // CSS selectors
131
133
  "data-panel": "",
134
+ "data-panel-id": panelId,
132
135
  // e2e test attributes
133
136
  "data-panel-collapsible": undefined,
134
- "data-panel-id": undefined,
135
137
  "data-panel-size": undefined
136
138
  });
137
139
  }
@@ -159,6 +161,16 @@ function convertPixelConstraintsToPercentages(panelConstraints, groupSizePixels)
159
161
  minSizePercentage = 0,
160
162
  minSizePixels
161
163
  } = panelConstraints;
164
+ const hasPixelConstraints = collapsedSizePixels != null || defaultSizePixels != null || minSizePixels != null || maxSizePixels != null;
165
+ if (hasPixelConstraints && groupSizePixels <= 0) {
166
+ console.warn(`WARNING: Invalid group size: ${groupSizePixels}px`);
167
+ return {
168
+ collapsedSizePercentage: 0,
169
+ defaultSizePercentage,
170
+ maxSizePercentage: 0,
171
+ minSizePercentage: 0
172
+ };
173
+ }
162
174
  if (collapsedSizePixels != null) {
163
175
  collapsedSizePercentage = convertPixelsToPercentage(collapsedSizePixels, groupSizePixels);
164
176
  }
@@ -233,6 +245,16 @@ function resizePanel({
233
245
  panelIndex,
234
246
  size
235
247
  }) {
248
+ const hasPixelConstraints = panelConstraints.some(({
249
+ collapsedSizePixels,
250
+ defaultSizePixels,
251
+ minSizePixels,
252
+ maxSizePixels
253
+ }) => collapsedSizePixels != null || defaultSizePixels != null || minSizePixels != null || maxSizePixels != null);
254
+ if (hasPixelConstraints && groupSizePixels <= 0) {
255
+ console.warn(`WARNING: Invalid group size: ${groupSizePixels}px`);
256
+ return 0;
257
+ }
236
258
  let {
237
259
  collapsible
238
260
  } = panelConstraints[panelIndex];
@@ -244,7 +266,13 @@ function resizePanel({
244
266
  if (minSizePercentage != null) {
245
267
  if (fuzzyCompareNumbers(size, minSizePercentage) < 0) {
246
268
  if (collapsible) {
247
- size = collapsedSizePercentage;
269
+ // Collapsible panels should snap closed or open only once they cross the halfway point between collapsed and min size.
270
+ const halfwayPoint = (collapsedSizePercentage + minSizePercentage) / 2;
271
+ if (fuzzyCompareNumbers(size, halfwayPoint) < 0) {
272
+ size = collapsedSizePercentage;
273
+ } else {
274
+ size = minSizePercentage;
275
+ }
248
276
  } else {
249
277
  size = minSizePercentage;
250
278
  }
@@ -271,61 +299,123 @@ function adjustLayoutByDelta({
271
299
  const nextLayout = [...prevLayout];
272
300
  let deltaApplied = 0;
273
301
 
302
+ //const DEBUG = [];
303
+ //DEBUG.push(`adjustLayoutByDelta() ${prevLayout.join(", ")}`);
304
+ //DEBUG.push(` delta: ${delta}`);
305
+ //DEBUG.push(` pivotIndices: ${pivotIndices.join(", ")}`);
306
+ //DEBUG.push(` trigger: ${trigger}`);
307
+ //DEBUG.push("");
308
+
274
309
  // A resizing panel affects the panels before or after it.
275
310
  //
276
- // A negative delta means the panel immediately after the resizer should grow/expand by decreasing its offset.
311
+ // A negative delta means the panel(s) immediately after the resize handle should grow/expand by decreasing its offset.
277
312
  // Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
278
313
  //
279
- // A positive delta means the panel immediately before the resizer should "expand".
280
- // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.
314
+ // A positive delta means the panel(s) immediately before the resize handle should "expand".
315
+ // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resize handle.
281
316
 
282
- // First, check the panel we're pivoting around;
283
- // We should only expand or contract by as much as its constraints allow
284
317
  {
285
- const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
286
- const initialSize = nextLayout[pivotIndex];
287
- const {
288
- collapsible
289
- } = panelConstraints[pivotIndex];
290
- const {
291
- collapsedSizePercentage,
292
- maxSizePercentage,
293
- minSizePercentage
294
- } = computePercentagePanelConstraints(panelConstraints, pivotIndex, groupSizePixels);
295
- const isCollapsed = collapsible && fuzzyNumbersEqual(initialSize, collapsedSizePercentage);
296
- let unsafeSize = initialSize + Math.abs(delta);
297
- if (isCollapsed) {
298
- switch (trigger) {
299
- case "keyboard":
300
- if (minSizePercentage > unsafeSize) {
301
- unsafeSize = minSizePercentage;
318
+ // If this is a resize triggered by a keyboard event, our logic for expanding/collapsing is different.
319
+ // We no longer check the halfway threshold because this may prevent the panel from expanding at all.
320
+ if (trigger === "keyboard") {
321
+ {
322
+ // Check if we should expand a collapsed panel
323
+ const index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
324
+ const constraints = panelConstraints[index];
325
+ //DEBUG.push(`edge case check 1: ${index}`);
326
+ //DEBUG.push(` -> collapsible? ${constraints.collapsible}`);
327
+ if (constraints.collapsible) {
328
+ const prevSize = prevLayout[index];
329
+ const {
330
+ collapsedSizePercentage,
331
+ minSizePercentage
332
+ } = computePercentagePanelConstraints(panelConstraints, index, groupSizePixels);
333
+ if (fuzzyNumbersEqual(prevSize, collapsedSizePercentage)) {
334
+ const localDelta = minSizePercentage - prevSize;
335
+ //DEBUG.push(` -> expand delta: ${localDelta}`);
336
+
337
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
338
+ delta = delta < 0 ? 0 - localDelta : localDelta;
339
+ //DEBUG.push(` -> delta: ${delta}`);
340
+ }
302
341
  }
342
+ }
343
+ }
344
+
345
+ {
346
+ // Check if we should collapse a panel at its minimum size
347
+ const index = delta < 0 ? pivotIndices[0] : pivotIndices[1];
348
+ const constraints = panelConstraints[index];
349
+ //DEBUG.push(`edge case check 2: ${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, minSizePercentage)) {
358
+ const localDelta = prevSize - collapsedSizePercentage;
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
+ }
365
+ }
366
+ }
303
367
  }
304
368
  }
305
- const safeSize = resizePanel({
306
- groupSizePixels,
307
- panelConstraints,
308
- panelIndex: pivotIndex,
309
- size: unsafeSize
310
- });
311
- if (fuzzyNumbersEqual(initialSize, safeSize)) {
312
- // If there's no room for the pivot panel to grow, we should ignore this change
313
- return nextLayout;
314
- } else {
315
- delta = delta < 0 ? initialSize - safeSize : safeSize - initialSize;
369
+ //DEBUG.push("");
370
+ }
371
+
372
+ {
373
+ // Pre-calculate max available delta in the opposite direction of our pivot.
374
+ // This will be the maximum amount we're allowed to expand/contract the panels in the primary direction.
375
+ // If this amount is less than the requested delta, adjust the requested delta.
376
+ // If this amount is greater than the requested delta, that's useful information too–
377
+ // as an expanding panel might change from collapsed to min size.
378
+
379
+ const increment = delta < 0 ? 1 : -1;
380
+ let index = delta < 0 ? pivotIndices[1] : pivotIndices[0];
381
+ let maxAvailableDelta = 0;
382
+
383
+ //DEBUG.push("pre calc...");
384
+ while (true) {
385
+ const prevSize = prevLayout[index];
386
+ const maxSafeSize = resizePanel({
387
+ groupSizePixels,
388
+ panelConstraints,
389
+ panelIndex: index,
390
+ size: 100
391
+ });
392
+ const delta = maxSafeSize - prevSize;
393
+ //DEBUG.push(` ${index}: ${prevSize} -> ${maxSafeSize}`);
394
+
395
+ maxAvailableDelta += delta;
396
+ index += increment;
397
+ if (index < 0 || index >= panelConstraints.length) {
398
+ break;
399
+ }
316
400
  }
401
+
402
+ //DEBUG.push(` -> max available delta: ${maxAvailableDelta}`);
403
+ const minAbsDelta = Math.min(Math.abs(delta), Math.abs(maxAvailableDelta));
404
+ delta = delta < 0 ? 0 - minAbsDelta : minAbsDelta;
405
+ //DEBUG.push(` -> adjusted delta: ${delta}`);
406
+ //DEBUG.push("");
317
407
  }
318
408
 
319
- // Delta added to a panel needs to be subtracted from other panels
320
- // within the constraints that those panels allow
321
409
  {
410
+ // Delta added to a panel needs to be subtracted from other panels (within the constraints that those panels allow).
411
+
322
412
  const pivotIndex = delta < 0 ? pivotIndices[0] : pivotIndices[1];
323
413
  let index = pivotIndex;
324
414
  while (index >= 0 && index < panelConstraints.length) {
325
415
  const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
326
416
  const prevSize = prevLayout[index];
327
417
  const unsafeSize = prevSize - deltaRemaining;
328
- let safeSize = resizePanel({
418
+ const safeSize = resizePanel({
329
419
  groupSizePixels,
330
420
  panelConstraints,
331
421
  panelIndex: index,
@@ -347,13 +437,18 @@ function adjustLayoutByDelta({
347
437
  }
348
438
  }
349
439
  }
440
+ //DEBUG.push(`after 1: ${nextLayout.join(", ")}`);
441
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
442
+ //DEBUG.push("");
350
443
 
351
444
  // If we were unable to resize any of the panels panels, return the previous state.
352
445
  // This will essentially bailout and ignore e.g. drags past a panel's boundaries
353
446
  if (fuzzyNumbersEqual(deltaApplied, 0)) {
447
+ //console.log(DEBUG.join("\n"));
354
448
  return prevLayout;
355
449
  }
356
450
  {
451
+ // Now distribute the applied delta to the panels in the other direction
357
452
  const pivotIndex = delta < 0 ? pivotIndices[1] : pivotIndices[0];
358
453
  const unsafeSize = prevLayout[pivotIndex] + deltaApplied;
359
454
  const safeSize = resizePanel({
@@ -393,29 +488,21 @@ function adjustLayoutByDelta({
393
488
  index++;
394
489
  }
395
490
  }
396
-
397
- // If we can't redistribute, this layout is invalid;
398
- // There may be an incremental layout that is valid though
399
- if (!fuzzyNumbersEqual(deltaRemaining, 0)) {
400
- try {
401
- return adjustLayoutByDelta({
402
- delta: delta < 0 ? delta + 1 : delta - 1,
403
- groupSizePixels,
404
- layout: prevLayout,
405
- panelConstraints,
406
- pivotIndices,
407
- trigger
408
- });
409
- } catch (error) {
410
- if (error instanceof RangeError) {
411
- console.error(`Could not apply delta ${delta} to layout`);
412
- return prevLayout;
413
- }
414
- } finally {
415
- }
416
- }
417
491
  }
418
492
  }
493
+ //DEBUG.push(`after 2: ${nextLayout.join(", ")}`);
494
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
495
+ //DEBUG.push("");
496
+
497
+ const totalSize = nextLayout.reduce((total, size) => size + total, 0);
498
+ deltaApplied = 100 - totalSize;
499
+ //DEBUG.push(`total size: ${totalSize}`);
500
+ //DEBUG.push(` deltaApplied: ${deltaApplied}`);
501
+ //console.log(DEBUG.join("\n"));
502
+
503
+ if (!fuzzyNumbersEqual(totalSize, 100)) {
504
+ return prevLayout;
505
+ }
419
506
  return nextLayout;
420
507
  }
421
508
 
@@ -445,7 +532,7 @@ function getResizeHandleElementsForGroup(groupId) {
445
532
  function getResizeHandleElementIndex(groupId, id) {
446
533
  const handles = getResizeHandleElementsForGroup(groupId);
447
534
  const index = handles.findIndex(handle => handle.getAttribute("data-panel-resize-handle-id") === id);
448
- return index ?? null;
535
+ return index !== null && index !== void 0 ? index : null;
449
536
  }
450
537
 
451
538
  function determinePivotIndices(groupId, dragHandleId) {
@@ -454,7 +541,7 @@ function determinePivotIndices(groupId, dragHandleId) {
454
541
  }
455
542
 
456
543
  function getPanelGroupElement(id) {
457
- const element = document.querySelector(`[data-panel-group-id="${id}"]`);
544
+ const element = document.querySelector(`[data-panel-group][data-panel-group-id="${id}"]`);
458
545
  if (element) {
459
546
  return element;
460
547
  }
@@ -506,11 +593,12 @@ function getResizeHandleElement(id) {
506
593
  }
507
594
 
508
595
  function getResizeHandlePanelIds(groupId, handleId, panelsArray) {
596
+ var _panelsArray$index$id, _panelsArray$index, _panelsArray$id, _panelsArray;
509
597
  const handle = getResizeHandleElement(handleId);
510
598
  const handles = getResizeHandleElementsForGroup(groupId);
511
599
  const index = handle ? handles.indexOf(handle) : -1;
512
- const idBefore = panelsArray[index]?.id ?? null;
513
- const idAfter = panelsArray[index + 1]?.id ?? null;
600
+ const idBefore = (_panelsArray$index$id = (_panelsArray$index = panelsArray[index]) === null || _panelsArray$index === void 0 ? void 0 : _panelsArray$index.id) !== null && _panelsArray$index$id !== void 0 ? _panelsArray$index$id : null;
601
+ const idAfter = (_panelsArray$id = (_panelsArray = panelsArray[index + 1]) === null || _panelsArray === void 0 ? void 0 : _panelsArray.id) !== null && _panelsArray$id !== void 0 ? _panelsArray$id : null;
514
602
  return [idBefore, idAfter];
515
603
  }
516
604
 
@@ -557,11 +645,12 @@ function useWindowSplitterPanelGroupBehavior({
557
645
  const panelData = panelDataArray[index];
558
646
  const size = layout[index];
559
647
  if (size != null) {
648
+ var _getPercentageSizeFro;
560
649
  const groupSizePixels = getAvailableGroupSizePixels(groupId);
561
- const minSize = getPercentageSizeFromMixedSizes({
650
+ const minSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
562
651
  sizePercentage: panelData.constraints.minSizePercentage,
563
652
  sizePixels: panelData.constraints.minSizePixels
564
- }, groupSizePixels) ?? 0;
653
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
565
654
  let delta = 0;
566
655
  if (size.toPrecision(PRECISION) <= minSize.toPrecision(PRECISION)) {
567
656
  delta = direction === "horizontal" ? width : height;
@@ -728,10 +817,11 @@ function callPanelCallbacks(groupId, panelsArray, layout, panelIdToLastNotifiedM
728
817
  onResize(mixedSizes, lastNotifiedMixedSizes);
729
818
  }
730
819
  if (collapsible && (onCollapse || onExpand)) {
731
- const collapsedSize = getPercentageSizeFromMixedSizes({
820
+ var _getPercentageSizeFro;
821
+ const collapsedSize = (_getPercentageSizeFro = getPercentageSizeFromMixedSizes({
732
822
  sizePercentage: constraints.collapsedSizePercentage,
733
823
  sizePixels: constraints.collapsedSizePixels
734
- }, groupSizePixels) ?? 0;
824
+ }, groupSizePixels)) !== null && _getPercentageSizeFro !== void 0 ? _getPercentageSizeFro : 0;
735
825
  const size = getPercentageSizeFromMixedSizes(mixedSizes, groupSizePixels);
736
826
  if (onExpand && (lastNotifiedMixedSizes == null || lastNotifiedMixedSizes.sizePercentage === collapsedSize) && size !== collapsedSize) {
737
827
  onExpand();
@@ -976,6 +1066,7 @@ function PanelGroupWithForwardedRef({
976
1066
  autoSaveId,
977
1067
  children,
978
1068
  className: classNameFromProps = "",
1069
+ dataAttributes,
979
1070
  direction,
980
1071
  forwardedRef,
981
1072
  id: idFromProps,
@@ -1249,7 +1340,7 @@ function PanelGroupWithForwardedRef({
1249
1340
  } = committedValuesRef.current;
1250
1341
  const {
1251
1342
  initialLayout
1252
- } = dragState ?? {};
1343
+ } = dragState !== null && dragState !== void 0 ? dragState : {};
1253
1344
  const pivotIndices = determinePivotIndices(groupId, dragHandleId);
1254
1345
  let delta = calculateDeltaPercentage(event, groupId, dragHandleId, direction, dragState, {
1255
1346
  percentage: keyboardResizeByPercentage,
@@ -1269,7 +1360,7 @@ function PanelGroupWithForwardedRef({
1269
1360
  const nextLayout = adjustLayoutByDelta({
1270
1361
  delta,
1271
1362
  groupSizePixels,
1272
- layout: initialLayout ?? prevLayout,
1363
+ layout: initialLayout !== null && initialLayout !== void 0 ? initialLayout : prevLayout,
1273
1364
  panelConstraints,
1274
1365
  pivotIndices,
1275
1366
  trigger: isKeyDown(event) ? "keyboard" : "mouse-or-touch"
@@ -1410,11 +1501,11 @@ function PanelGroupWithForwardedRef({
1410
1501
  ...style,
1411
1502
  ...styleFromProps
1412
1503
  },
1504
+ ...dataAttributes,
1413
1505
  // CSS selectors
1414
1506
  "data-panel-group": "",
1415
- // e2e test attributes
1416
- "data-panel-group-direction": undefined,
1417
- "data-panel-group-id": undefined
1507
+ "data-panel-group-direction": direction,
1508
+ "data-panel-group-id": groupId
1418
1509
  }));
1419
1510
  }
1420
1511
  const PanelGroup = forwardRef((props, ref) => createElement(PanelGroupWithForwardedRef, {
@@ -1498,6 +1589,7 @@ function useWindowSplitterResizeHandlerBehavior({
1498
1589
  function PanelResizeHandle({
1499
1590
  children = null,
1500
1591
  className: classNameFromProps = "",
1592
+ dataAttributes,
1501
1593
  disabled = false,
1502
1594
  id: idFromProps = null,
1503
1595
  onDragging,
@@ -1526,7 +1618,7 @@ function PanelResizeHandle({
1526
1618
  stopDragging
1527
1619
  } = panelGroupContext;
1528
1620
  const resizeHandleId = useUniqueId(idFromProps);
1529
- const isDragging = dragState?.dragHandleId === resizeHandleId;
1621
+ const isDragging = (dragState === null || dragState === void 0 ? void 0 : dragState.dragHandleId) === resizeHandleId;
1530
1622
  const [isFocused, setIsFocused] = useState(false);
1531
1623
  const [resizeHandler, setResizeHandler] = useState(null);
1532
1624
  const stopDraggingAndBlur = useCallback(() => {
@@ -1590,13 +1682,6 @@ function PanelResizeHandle({
1590
1682
  return createElement(Type, {
1591
1683
  children,
1592
1684
  className: classNameFromProps,
1593
- // CSS selectors
1594
- "data-resize-handle": "",
1595
- "data-resize-handle-active": isDragging ? "pointer" : isFocused ? "keyboard" : undefined,
1596
- "data-panel-group-direction": direction,
1597
- "data-panel-group-id": groupId,
1598
- "data-panel-resize-handle-enabled": !disabled,
1599
- "data-panel-resize-handle-id": resizeHandleId,
1600
1685
  onBlur: () => setIsFocused(false),
1601
1686
  onFocus: () => setIsFocused(true),
1602
1687
  onMouseDown: event => {
@@ -1626,7 +1711,15 @@ function PanelResizeHandle({
1626
1711
  ...style,
1627
1712
  ...styleFromProps
1628
1713
  },
1629
- tabIndex: 0
1714
+ tabIndex: 0,
1715
+ ...dataAttributes,
1716
+ // CSS selectors
1717
+ "data-panel-group-direction": direction,
1718
+ "data-panel-group-id": groupId,
1719
+ "data-resize-handle": "",
1720
+ "data-resize-handle-active": isDragging ? "pointer" : isFocused ? "keyboard" : undefined,
1721
+ "data-panel-resize-handle-enabled": !disabled,
1722
+ "data-panel-resize-handle-id": resizeHandleId
1630
1723
  });
1631
1724
  }
1632
1725
  PanelResizeHandle.displayName = "PanelResizeHandle";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-resizable-panels",
3
- "version": "0.0.56",
3
+ "version": "0.0.58",
4
4
  "description": "React components for resizable panel groups/layouts",
5
5
  "author": "Brian Vaughn <brian.david.vaughn@gmail.com>",
6
6
  "license": "MIT",
@@ -67,6 +67,8 @@
67
67
  "watch": "parcel watch --port=2345"
68
68
  },
69
69
  "devDependencies": {
70
+ "@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
71
+ "@babel/plugin-proposal-optional-chaining": "7.21.0",
70
72
  "eslint": "^8.37.0",
71
73
  "eslint-plugin-no-restricted-imports": "^0.0.0",
72
74
  "eslint-plugin-react-hooks": "^4.6.0",
package/src/Panel.ts CHANGED
@@ -3,7 +3,7 @@ import { isDevelopment } from "#is-development";
3
3
  import { PanelGroupContext } from "./PanelGroupContext";
4
4
  import useIsomorphicLayoutEffect from "./hooks/useIsomorphicEffect";
5
5
  import useUniqueId from "./hooks/useUniqueId";
6
- import { MixedSizes } from "./types";
6
+ import { DataAttributes, MixedSizes } from "./types";
7
7
  import {
8
8
  ElementType,
9
9
  ForwardedRef,
@@ -19,7 +19,7 @@ export type PanelOnCollapse = () => void;
19
19
  export type PanelOnExpand = () => void;
20
20
  export type PanelOnResize = (
21
21
  mixedSizes: MixedSizes,
22
- prevMixedSizes: MixedSizes
22
+ prevMixedSizes: MixedSizes | undefined
23
23
  ) => void;
24
24
 
25
25
  export type PanelCallbacks = {
@@ -53,6 +53,8 @@ export type ImperativePanelHandle = {
53
53
  expand: () => void;
54
54
  getId(): string;
55
55
  getSize(): MixedSizes;
56
+ isCollapsed: () => boolean;
57
+ isExpanded: () => boolean;
56
58
  resize: (size: Partial<MixedSizes>) => void;
57
59
  };
58
60
 
@@ -61,6 +63,7 @@ export type PanelProps = PropsWithChildren<{
61
63
  collapsedSizePercentage?: number | undefined;
62
64
  collapsedSizePixels?: number | undefined;
63
65
  collapsible?: boolean | undefined;
66
+ dataAttributes?: DataAttributes;
64
67
  defaultSizePercentage?: number | undefined;
65
68
  defaultSizePixels?: number | undefined;
66
69
  id?: string;
@@ -82,6 +85,7 @@ export function PanelWithForwardedRef({
82
85
  collapsedSizePercentage,
83
86
  collapsedSizePixels,
84
87
  collapsible,
88
+ dataAttributes,
85
89
  defaultSizePercentage,
86
90
  defaultSizePixels,
87
91
  forwardedRef,
@@ -241,14 +245,16 @@ export function PanelWithForwardedRef({
241
245
  ...styleFromProps,
242
246
  },
243
247
 
248
+ ...dataAttributes,
249
+
244
250
  // CSS selectors
245
251
  "data-panel": "",
252
+ "data-panel-id": panelId,
246
253
 
247
254
  // e2e test attributes
248
255
  "data-panel-collapsible": isDevelopment
249
256
  ? collapsible || undefined
250
257
  : undefined,
251
- "data-panel-id": isDevelopment ? panelId : undefined,
252
258
  "data-panel-size": isDevelopment
253
259
  ? parseFloat("" + style.flexGrow).toFixed(1)
254
260
  : undefined,
package/src/PanelGroup.ts CHANGED
@@ -4,7 +4,7 @@ import { DragState, PanelGroupContext, ResizeEvent } from "./PanelGroupContext";
4
4
  import useIsomorphicLayoutEffect from "./hooks/useIsomorphicEffect";
5
5
  import useUniqueId from "./hooks/useUniqueId";
6
6
  import { useWindowSplitterPanelGroupBehavior } from "./hooks/useWindowSplitterPanelGroupBehavior";
7
- import { Direction, MixedSizes } from "./types";
7
+ import { DataAttributes, Direction, MixedSizes } from "./types";
8
8
  import { adjustLayoutByDelta } from "./utils/adjustLayoutByDelta";
9
9
  import { areEqual } from "./utils/arrays";
10
10
  import { calculateDeltaPercentage } from "./utils/calculateDeltaPercentage";
@@ -72,6 +72,7 @@ const defaultStorage: PanelGroupStorage = {
72
72
  export type PanelGroupProps = PropsWithChildren<{
73
73
  autoSaveId?: string;
74
74
  className?: string;
75
+ dataAttributes?: DataAttributes;
75
76
  direction: Direction;
76
77
  id?: string | null;
77
78
  keyboardResizeByPercentage?: number | null;
@@ -90,6 +91,7 @@ function PanelGroupWithForwardedRef({
90
91
  autoSaveId,
91
92
  children,
92
93
  className: classNameFromProps = "",
94
+ dataAttributes,
93
95
  direction,
94
96
  forwardedRef,
95
97
  id: idFromProps,
@@ -266,6 +268,10 @@ function PanelGroupWithForwardedRef({
266
268
  }
267
269
 
268
270
  const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId);
271
+ if (groupSizePixels <= 0) {
272
+ // Wait until the group has rendered a non-zero size before computing layout.
273
+ return;
274
+ }
269
275
 
270
276
  if (unsafeLayout == null) {
271
277
  unsafeLayout = calculateUnsafeDefaultLayout({
@@ -923,12 +929,12 @@ function PanelGroupWithForwardedRef({
923
929
  ...styleFromProps,
924
930
  },
925
931
 
932
+ ...dataAttributes,
933
+
926
934
  // CSS selectors
927
935
  "data-panel-group": "",
928
-
929
- // e2e test attributes
930
- "data-panel-group-direction": isDevelopment ? direction : undefined,
931
- "data-panel-group-id": isDevelopment ? groupId : undefined,
936
+ "data-panel-group-direction": direction,
937
+ "data-panel-group-id": groupId,
932
938
  })
933
939
  );
934
940
  }
@@ -20,12 +20,14 @@ import {
20
20
  ResizeHandler,
21
21
  } from "./PanelGroupContext";
22
22
  import { getCursorStyle } from "./utils/cursor";
23
+ import { DataAttributes } from "./types";
23
24
 
24
25
  export type PanelResizeHandleOnDragging = (isDragging: boolean) => void;
25
26
 
26
27
  export type PanelResizeHandleProps = {
27
28
  children?: ReactNode;
28
29
  className?: string;
30
+ dataAttributes?: DataAttributes;
29
31
  disabled?: boolean;
30
32
  id?: string | null;
31
33
  onDragging?: PanelResizeHandleOnDragging;
@@ -36,6 +38,7 @@ export type PanelResizeHandleProps = {
36
38
  export function PanelResizeHandle({
37
39
  children = null,
38
40
  className: classNameFromProps = "",
41
+ dataAttributes,
39
42
  disabled = false,
40
43
  id: idFromProps = null,
41
44
  onDragging,
@@ -151,19 +154,6 @@ export function PanelResizeHandle({
151
154
  return createElement(Type, {
152
155
  children,
153
156
  className: classNameFromProps,
154
-
155
- // CSS selectors
156
- "data-resize-handle": "",
157
-
158
- "data-resize-handle-active": isDragging
159
- ? "pointer"
160
- : isFocused
161
- ? "keyboard"
162
- : undefined,
163
- "data-panel-group-direction": direction,
164
- "data-panel-group-id": groupId,
165
- "data-panel-resize-handle-enabled": !disabled,
166
- "data-panel-resize-handle-id": resizeHandleId,
167
157
  onBlur: () => setIsFocused(false),
168
158
  onFocus: () => setIsFocused(true),
169
159
  onMouseDown: (event: ReactMouseEvent) => {
@@ -192,6 +182,20 @@ export function PanelResizeHandle({
192
182
  ...styleFromProps,
193
183
  },
194
184
  tabIndex: 0,
185
+
186
+ ...dataAttributes,
187
+
188
+ // CSS selectors
189
+ "data-panel-group-direction": direction,
190
+ "data-panel-group-id": groupId,
191
+ "data-resize-handle": "",
192
+ "data-resize-handle-active": isDragging
193
+ ? "pointer"
194
+ : isFocused
195
+ ? "keyboard"
196
+ : undefined,
197
+ "data-panel-resize-handle-enabled": !disabled,
198
+ "data-panel-resize-handle-id": resizeHandleId,
195
199
  });
196
200
  }
197
201
 
package/src/types.ts CHANGED
@@ -7,3 +7,7 @@ export type MixedSizes = {
7
7
 
8
8
  export type ResizeEvent = KeyboardEvent | MouseEvent | TouchEvent;
9
9
  export type ResizeHandler = (event: ResizeEvent) => void;
10
+
11
+ export type DataAttributes = {
12
+ [attribute: string]: string | number | boolean | undefined;
13
+ };