react-resizable-panels 0.0.54 → 0.0.55

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 (34) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/declarations/src/Panel.d.ts +5 -4
  3. package/dist/declarations/src/PanelGroup.d.ts +7 -3
  4. package/dist/declarations/src/index.d.ts +3 -2
  5. package/dist/declarations/src/types.d.ts +2 -1
  6. package/dist/declarations/src/utils/group.d.ts +29 -0
  7. package/dist/react-resizable-panels.browser.cjs.js +385 -108
  8. package/dist/react-resizable-panels.browser.cjs.mjs +2 -1
  9. package/dist/react-resizable-panels.browser.development.cjs.js +417 -108
  10. package/dist/react-resizable-panels.browser.development.cjs.mjs +2 -1
  11. package/dist/react-resizable-panels.browser.development.esm.js +417 -109
  12. package/dist/react-resizable-panels.browser.esm.js +385 -109
  13. package/dist/react-resizable-panels.cjs.js +385 -108
  14. package/dist/react-resizable-panels.cjs.js.map +1 -0
  15. package/dist/react-resizable-panels.cjs.mjs +2 -1
  16. package/dist/react-resizable-panels.development.cjs.js +417 -108
  17. package/dist/react-resizable-panels.development.cjs.mjs +2 -1
  18. package/dist/react-resizable-panels.development.esm.js +417 -109
  19. package/dist/react-resizable-panels.development.node.cjs.js +282 -76
  20. package/dist/react-resizable-panels.development.node.cjs.mjs +2 -1
  21. package/dist/react-resizable-panels.development.node.esm.js +282 -77
  22. package/dist/react-resizable-panels.esm.js +385 -109
  23. package/dist/react-resizable-panels.esm.js.map +1 -0
  24. package/dist/react-resizable-panels.node.cjs.js +254 -76
  25. package/dist/react-resizable-panels.node.cjs.mjs +2 -1
  26. package/dist/react-resizable-panels.node.esm.js +254 -77
  27. package/package.json +1 -1
  28. package/src/Panel.ts +32 -32
  29. package/src/PanelContexts.ts +4 -2
  30. package/src/PanelGroup.ts +221 -111
  31. package/src/hooks/useWindowSplitterBehavior.ts +14 -11
  32. package/src/index.ts +11 -3
  33. package/src/types.ts +2 -1
  34. package/src/utils/group.ts +327 -28
@@ -43,8 +43,8 @@ function PanelWithForwardedRef({
43
43
  defaultSize = null,
44
44
  forwardedRef,
45
45
  id: idFromProps = null,
46
- maxSize = 100,
47
- minSize = 10,
46
+ maxSize = null,
47
+ minSize,
48
48
  onCollapse = null,
49
49
  onResize = null,
50
50
  order = null,
@@ -59,11 +59,22 @@ function PanelWithForwardedRef({
59
59
  const {
60
60
  collapsePanel,
61
61
  expandPanel,
62
+ getPanelSize,
62
63
  getPanelStyle,
63
64
  registerPanel,
64
65
  resizePanel,
66
+ units,
65
67
  unregisterPanel
66
68
  } = context;
69
+ if (minSize == null) {
70
+ if (units === "percentages") {
71
+ // Mimics legacy default value for percentage based panel groups
72
+ minSize = 10;
73
+ } else {
74
+ // There is no meaningful minimum pixel default we can provide
75
+ minSize = 0;
76
+ }
77
+ }
67
78
 
68
79
  // Use a ref to guard against users passing inline props
69
80
  const callbacksRef = useRef({
@@ -74,22 +85,6 @@ function PanelWithForwardedRef({
74
85
  callbacksRef.current.onCollapse = onCollapse;
75
86
  callbacksRef.current.onResize = onResize;
76
87
  });
77
-
78
- // Basic props validation
79
- if (minSize < 0 || minSize > 100) {
80
- throw Error(`Panel minSize must be between 0 and 100, but was ${minSize}`);
81
- } else if (maxSize < 0 || maxSize > 100) {
82
- throw Error(`Panel maxSize must be between 0 and 100, but was ${maxSize}`);
83
- } else {
84
- if (defaultSize !== null) {
85
- if (defaultSize < 0 || defaultSize > 100) {
86
- throw Error(`Panel defaultSize must be between 0 and 100, but was ${defaultSize}`);
87
- } else if (minSize > defaultSize && !collapsible) {
88
- console.error(`Panel minSize ${minSize} cannot be greater than defaultSize ${defaultSize}`);
89
- defaultSize = minSize;
90
- }
91
- }
92
- }
93
88
  const style = getPanelStyle(panelId, defaultSize);
94
89
  const committedValuesRef = useRef({
95
90
  size: parseSizeFromStyle(style)
@@ -111,11 +106,14 @@ function PanelWithForwardedRef({
111
106
  getCollapsed() {
112
107
  return committedValuesRef.current.size === 0;
113
108
  },
114
- getSize() {
115
- return committedValuesRef.current.size;
109
+ getId() {
110
+ return panelId;
111
+ },
112
+ getSize(units) {
113
+ return getPanelSize(panelId, units);
116
114
  },
117
- resize: percentage => resizePanel(panelId, percentage)
118
- }), [collapsePanel, expandPanel, panelId, resizePanel]);
115
+ resize: (percentage, units) => resizePanel(panelId, percentage, units)
116
+ }), [collapsePanel, expandPanel, getPanelSize, panelId, resizePanel]);
119
117
  return createElement(Type, {
120
118
  children,
121
119
  className: classNameFromProps,
@@ -151,7 +149,13 @@ function parseSizeFromStyle(style) {
151
149
 
152
150
  const PRECISION = 10;
153
151
 
154
- function adjustByDelta(event, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse, initialDragState) {
152
+ function adjustByDelta(event, committedValues, idBefore, idAfter, deltaPixels, prevSizes, panelSizeBeforeCollapse, initialDragState) {
153
+ const {
154
+ id: groupId,
155
+ panels,
156
+ units
157
+ } = committedValues;
158
+ const groupSizePixels = units === "pixels" ? getAvailableGroupSizePixels(groupId) : NaN;
155
159
  const {
156
160
  sizes: initialSizes
157
161
  } = initialDragState || {};
@@ -159,9 +163,6 @@ function adjustByDelta(event, panels, idBefore, idAfter, delta, prevSizes, panel
159
163
  // If we're resizing by mouse or touch, use the initial sizes as a base.
160
164
  // This has the benefit of causing force-collapsed panels to spring back open if drag is reversed.
161
165
  const baseSizes = initialSizes || prevSizes;
162
- if (delta === 0) {
163
- return baseSizes;
164
- }
165
166
  const panelsArray = panelsMapToSortedArray(panels);
166
167
  const nextSizes = baseSizes.concat();
167
168
  let deltaApplied = 0;
@@ -176,11 +177,11 @@ function adjustByDelta(event, panels, idBefore, idAfter, delta, prevSizes, panel
176
177
 
177
178
  // Max-bounds check the panel being expanded first.
178
179
  {
179
- const pivotId = delta < 0 ? idAfter : idBefore;
180
+ const pivotId = deltaPixels < 0 ? idAfter : idBefore;
180
181
  const index = panelsArray.findIndex(panel => panel.current.id === pivotId);
181
182
  const panel = panelsArray[index];
182
183
  const baseSize = baseSizes[index];
183
- const nextSize = safeResizePanel(panel, Math.abs(delta), baseSize, event);
184
+ const nextSize = safeResizePanel(units, groupSizePixels, panel, baseSize, baseSize + Math.abs(deltaPixels), event);
184
185
  if (baseSize === nextSize) {
185
186
  // If there's no room for the pivot panel to grow, we can ignore this drag update.
186
187
  return baseSizes;
@@ -188,29 +189,29 @@ function adjustByDelta(event, panels, idBefore, idAfter, delta, prevSizes, panel
188
189
  if (nextSize === 0 && baseSize > 0) {
189
190
  panelSizeBeforeCollapse.set(pivotId, baseSize);
190
191
  }
191
- delta = delta < 0 ? baseSize - nextSize : nextSize - baseSize;
192
+ deltaPixels = deltaPixels < 0 ? baseSize - nextSize : nextSize - baseSize;
192
193
  }
193
194
  }
194
- let pivotId = delta < 0 ? idBefore : idAfter;
195
+ let pivotId = deltaPixels < 0 ? idBefore : idAfter;
195
196
  let index = panelsArray.findIndex(panel => panel.current.id === pivotId);
196
197
  while (true) {
197
198
  const panel = panelsArray[index];
198
199
  const baseSize = baseSizes[index];
199
- const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
200
- const nextSize = safeResizePanel(panel, 0 - deltaRemaining, baseSize, event);
200
+ const deltaRemaining = Math.abs(deltaPixels) - Math.abs(deltaApplied);
201
+ const nextSize = safeResizePanel(units, groupSizePixels, panel, baseSize, baseSize - deltaRemaining, event);
201
202
  if (baseSize !== nextSize) {
202
203
  if (nextSize === 0 && baseSize > 0) {
203
204
  panelSizeBeforeCollapse.set(panel.current.id, baseSize);
204
205
  }
205
206
  deltaApplied += baseSize - nextSize;
206
207
  nextSizes[index] = nextSize;
207
- if (deltaApplied.toPrecision(PRECISION).localeCompare(Math.abs(delta).toPrecision(PRECISION), undefined, {
208
+ if (deltaApplied.toPrecision(PRECISION).localeCompare(Math.abs(deltaPixels).toPrecision(PRECISION), undefined, {
208
209
  numeric: true
209
210
  }) >= 0) {
210
211
  break;
211
212
  }
212
213
  }
213
- if (delta < 0) {
214
+ if (deltaPixels < 0) {
214
215
  if (--index < 0) {
215
216
  break;
216
217
  }
@@ -228,7 +229,7 @@ function adjustByDelta(event, panels, idBefore, idAfter, delta, prevSizes, panel
228
229
  }
229
230
 
230
231
  // Adjust the pivot panel before, but only by the amount that surrounding panels were able to shrink/contract.
231
- pivotId = delta < 0 ? idAfter : idBefore;
232
+ pivotId = deltaPixels < 0 ? idAfter : idBefore;
232
233
  index = panelsArray.findIndex(panel => panel.current.id === pivotId);
233
234
  nextSizes[index] = baseSizes[index] + deltaApplied;
234
235
  return nextSizes;
@@ -280,6 +281,23 @@ function getBeforeAndAfterIds(id, panelsArray) {
280
281
  const idAfter = isLastPanel ? id : panelsArray[index + 1].current.id;
281
282
  return [idBefore, idAfter];
282
283
  }
284
+ function getAvailableGroupSizePixels(groupId) {
285
+ const panelGroupElement = getPanelGroup(groupId);
286
+ if (panelGroupElement == null) {
287
+ return NaN;
288
+ }
289
+ const direction = panelGroupElement.getAttribute("data-panel-group-direction");
290
+ const resizeHandles = getResizeHandlesForGroup(groupId);
291
+ if (direction === "horizontal") {
292
+ return panelGroupElement.offsetWidth - resizeHandles.reduce((accumulated, handle) => {
293
+ return accumulated + handle.offsetWidth;
294
+ }, 0);
295
+ } else {
296
+ return panelGroupElement.offsetHeight - resizeHandles.reduce((accumulated, handle) => {
297
+ return accumulated + handle.offsetHeight;
298
+ }, 0);
299
+ }
300
+ }
283
301
 
284
302
  // This method returns a number between 1 and 100 representing
285
303
  // the % of the group's overall space this panel should occupy.
@@ -350,18 +368,24 @@ function panelsMapToSortedArray(panels) {
350
368
  }
351
369
  });
352
370
  }
353
- function safeResizePanel(panel, delta, prevSize, event) {
354
- const nextSizeUnsafe = prevSize + delta;
355
- const {
371
+ function safeResizePanel(units, groupSizePixels, panel, prevSize, nextSize, event = null) {
372
+ let {
356
373
  collapsedSize,
357
374
  collapsible,
358
375
  maxSize,
359
376
  minSize
360
377
  } = panel.current;
378
+ if (units === "pixels") {
379
+ collapsedSize = collapsedSize / groupSizePixels * 100;
380
+ if (maxSize != null) {
381
+ maxSize = maxSize / groupSizePixels * 100;
382
+ }
383
+ minSize = minSize / groupSizePixels * 100;
384
+ }
361
385
  if (collapsible) {
362
386
  if (prevSize > collapsedSize) {
363
387
  // Mimic VS COde behavior; collapse a panel if it's smaller than half of its min-size
364
- if (nextSizeUnsafe <= minSize / 2 + collapsedSize) {
388
+ if (nextSize <= minSize / 2 + collapsedSize) {
365
389
  return collapsedSize;
366
390
  }
367
391
  } else {
@@ -370,14 +394,119 @@ function safeResizePanel(panel, delta, prevSize, event) {
370
394
  // Keyboard events should expand a collapsed panel to the min size,
371
395
  // but mouse events should wait until the panel has reached its min size
372
396
  // to avoid a visual flickering when dragging between collapsed and min size.
373
- if (nextSizeUnsafe < minSize) {
397
+ if (nextSize < minSize) {
374
398
  return collapsedSize;
375
399
  }
376
400
  }
377
401
  }
378
402
  }
379
- const nextSize = Math.min(maxSize, Math.max(minSize, nextSizeUnsafe));
380
- return nextSize;
403
+ return Math.min(maxSize != null ? maxSize : 100, Math.max(minSize, nextSize));
404
+ }
405
+ function validatePanelProps(units, panelData) {
406
+ const {
407
+ collapsible,
408
+ defaultSize,
409
+ maxSize,
410
+ minSize
411
+ } = panelData.current;
412
+
413
+ // Basic props validation
414
+ if (minSize < 0 || units === "percentages" && minSize > 100) {
415
+ {
416
+ console.error(`Invalid Panel minSize provided, ${minSize}`);
417
+ }
418
+ panelData.current.minSize = 0;
419
+ }
420
+ if (maxSize != null) {
421
+ if (maxSize < 0 || units === "percentages" && maxSize > 100) {
422
+ {
423
+ console.error(`Invalid Panel maxSize provided, ${maxSize}`);
424
+ }
425
+ panelData.current.maxSize = null;
426
+ }
427
+ }
428
+ if (defaultSize !== null) {
429
+ if (defaultSize < 0 || units === "percentages" && defaultSize > 100) {
430
+ {
431
+ console.error(`Invalid Panel defaultSize provided, ${defaultSize}`);
432
+ }
433
+ panelData.current.defaultSize = null;
434
+ } else if (defaultSize < minSize && !collapsible) {
435
+ {
436
+ console.error(`Panel minSize (${minSize}) cannot be greater than defaultSize (${defaultSize})`);
437
+ }
438
+ panelData.current.defaultSize = minSize;
439
+ } else if (maxSize != null && defaultSize > maxSize) {
440
+ {
441
+ console.error(`Panel maxSize (${maxSize}) cannot be less than defaultSize (${defaultSize})`);
442
+ }
443
+ panelData.current.defaultSize = maxSize;
444
+ }
445
+ }
446
+ }
447
+ function validatePanelGroupLayout({
448
+ groupId,
449
+ panels,
450
+ nextSizes,
451
+ prevSizes,
452
+ units
453
+ }) {
454
+ // Clone because this method modifies
455
+ nextSizes = [...nextSizes];
456
+ const panelsArray = panelsMapToSortedArray(panels);
457
+ const groupSizePixels = units === "pixels" ? getAvailableGroupSizePixels(groupId) : NaN;
458
+ let remainingSize = 0;
459
+
460
+ // First, check all of the proposed sizes against the min/max constraints
461
+ for (let index = 0; index < panelsArray.length; index++) {
462
+ const panel = panelsArray[index];
463
+ const prevSize = prevSizes[index];
464
+ const nextSize = nextSizes[index];
465
+ const safeNextSize = safeResizePanel(units, groupSizePixels, panel, prevSize, nextSize);
466
+ if (nextSize != safeNextSize) {
467
+ remainingSize += nextSize - safeNextSize;
468
+ nextSizes[index] = safeNextSize;
469
+ {
470
+ console.error(`Invalid size (${nextSize}) specified for Panel "${panel.current.id}" given the panel's min/max size constraints`);
471
+ }
472
+ }
473
+ }
474
+
475
+ // If there is additional, left over space, assign it to any panel(s) that permits it
476
+ // (It's not worth taking multiple additional passes to evenly distribute)
477
+ if (remainingSize.toFixed(3) !== "0.000") {
478
+ for (let index = 0; index < panelsArray.length; index++) {
479
+ const panel = panelsArray[index];
480
+ let {
481
+ maxSize,
482
+ minSize
483
+ } = panel.current;
484
+ if (units === "pixels") {
485
+ minSize = minSize / groupSizePixels * 100;
486
+ if (maxSize != null) {
487
+ maxSize = maxSize / groupSizePixels * 100;
488
+ }
489
+ }
490
+ const size = Math.min(maxSize != null ? maxSize : 100, Math.max(minSize, nextSizes[index] + remainingSize));
491
+ if (size !== nextSizes[index]) {
492
+ remainingSize -= size - nextSizes[index];
493
+ nextSizes[index] = size;
494
+
495
+ // Fuzzy comparison to account for imprecise floating point math
496
+ if (Math.abs(remainingSize).toFixed(3) === "0.000") {
497
+ break;
498
+ }
499
+ }
500
+ }
501
+ }
502
+
503
+ // If we still have remainder, the requested layout wasn't valid and we should warn about it
504
+ if (remainingSize.toFixed(3) !== "0.000") {
505
+ {
506
+ console.error(`"Invalid panel group configuration; default panel sizes should total 100% but was ${100 - remainingSize}%`);
507
+ }
508
+ }
509
+ return nextSizes;
381
510
  }
382
511
 
383
512
  function assert(expectedCondition, message = "Assertion failed!") {
@@ -403,6 +532,7 @@ function useWindowSplitterPanelGroupBehavior({
403
532
  panels
404
533
  } = committedValuesRef.current;
405
534
  const groupElement = getPanelGroup(groupId);
535
+ assert(groupElement != null, `No group found for id "${groupId}"`);
406
536
  const {
407
537
  height,
408
538
  width
@@ -415,23 +545,28 @@ function useWindowSplitterPanelGroupBehavior({
415
545
  if (idBefore == null || idAfter == null) {
416
546
  return () => {};
417
547
  }
418
- let minSize = 0;
419
- let maxSize = 100;
548
+ let currentMinSize = 0;
549
+ let currentMaxSize = 100;
420
550
  let totalMinSize = 0;
421
551
  let totalMaxSize = 0;
422
552
 
423
553
  // A panel's effective min/max sizes also need to account for other panel's sizes.
424
554
  panelsArray.forEach(panelData => {
425
- if (panelData.current.id === idBefore) {
426
- maxSize = panelData.current.maxSize;
427
- minSize = panelData.current.minSize;
555
+ const {
556
+ id,
557
+ maxSize,
558
+ minSize
559
+ } = panelData.current;
560
+ if (id === idBefore) {
561
+ currentMinSize = minSize;
562
+ currentMaxSize = maxSize != null ? maxSize : 100;
428
563
  } else {
429
- totalMinSize += panelData.current.minSize;
430
- totalMaxSize += panelData.current.maxSize;
564
+ totalMinSize += minSize;
565
+ totalMaxSize += maxSize != null ? maxSize : 100;
431
566
  }
432
567
  });
433
- const ariaValueMax = Math.min(maxSize, 100 - totalMinSize);
434
- const ariaValueMin = Math.max(minSize, (panelsArray.length - 1) * 100 - totalMaxSize);
568
+ const ariaValueMax = Math.min(currentMaxSize, 100 - totalMinSize);
569
+ const ariaValueMin = Math.max(currentMinSize, (panelsArray.length - 1) * 100 - totalMaxSize);
435
570
  const flexGrow = getFlexGrow(panels, idBefore, sizes);
436
571
  handle.setAttribute("aria-valuemax", "" + Math.round(ariaValueMax));
437
572
  handle.setAttribute("aria-valuemin", "" + Math.round(ariaValueMin));
@@ -455,7 +590,7 @@ function useWindowSplitterPanelGroupBehavior({
455
590
  } else {
456
591
  delta = -(direction === "horizontal" ? width : height);
457
592
  }
458
- const nextSizes = adjustByDelta(event, panels, idBefore, idAfter, delta, sizes, panelSizeBeforeCollapse.current, null);
593
+ const nextSizes = adjustByDelta(event, committedValuesRef.current, idBefore, idAfter, delta, sizes, panelSizeBeforeCollapse.current, null);
459
594
  if (sizes !== nextSizes) {
460
595
  setSizes(nextSizes);
461
596
  }
@@ -762,9 +897,6 @@ const defaultStorage = {
762
897
  // * dragHandleRect, sizes:
763
898
  // When resizing is done via mouse/touch event– some initial state is stored
764
899
  // so that any panels that contract will also expand if drag direction is reversed.
765
- // TODO
766
- // Within an active drag, remember original positions to refine more easily on expand.
767
- // Look at what the Chrome devtools Sources does.
768
900
  function PanelGroupWithForwardedRef({
769
901
  autoSaveId,
770
902
  children = null,
@@ -776,7 +908,8 @@ function PanelGroupWithForwardedRef({
776
908
  onLayout,
777
909
  storage = defaultStorage,
778
910
  style: styleFromProps = {},
779
- tagName: Type = "div"
911
+ tagName: Type = "div",
912
+ units = "percentages"
780
913
  }) {
781
914
  const groupId = useUniqueId(idFromProps);
782
915
  const [activeHandleId, setActiveHandleId] = useState(null);
@@ -789,6 +922,7 @@ function PanelGroupWithForwardedRef({
789
922
  const devWarningsRef = useRef({
790
923
  didLogDefaultSizeWarning: false,
791
924
  didLogIdAndOrderWarning: false,
925
+ didLogInvalidLayoutWarning: false,
792
926
  prevPanelIds: []
793
927
  });
794
928
 
@@ -811,28 +945,52 @@ function PanelGroupWithForwardedRef({
811
945
  // Store committed values to avoid unnecessarily re-running memoization/effects functions.
812
946
  const committedValuesRef = useRef({
813
947
  direction,
948
+ id: groupId,
814
949
  panels,
815
- sizes
950
+ sizes,
951
+ units
816
952
  });
817
953
  useImperativeHandle(forwardedRef, () => ({
818
- getLayout: () => {
954
+ getId: () => groupId,
955
+ getLayout: unitsFromParams => {
819
956
  const {
820
- sizes
957
+ sizes,
958
+ units: unitsFromProps
821
959
  } = committedValuesRef.current;
822
- return sizes;
960
+ const units = unitsFromParams ?? unitsFromProps;
961
+ if (units === "pixels") {
962
+ const groupSizePixels = getAvailableGroupSizePixels(groupId);
963
+ return sizes.map(size => size / 100 * groupSizePixels);
964
+ } else {
965
+ return sizes;
966
+ }
823
967
  },
824
- setLayout: sizes => {
825
- const total = sizes.reduce((accumulated, current) => accumulated + current, 0);
826
- assert(total === 100, "Panel sizes must add up to 100%");
968
+ setLayout: (sizes, unitsFromParams) => {
827
969
  const {
828
- panels
970
+ id: groupId,
971
+ panels,
972
+ sizes: prevSizes,
973
+ units
829
974
  } = committedValuesRef.current;
975
+ if ((unitsFromParams || units) === "pixels") {
976
+ const groupSizePixels = getAvailableGroupSizePixels(groupId);
977
+ sizes = sizes.map(size => size / groupSizePixels * 100);
978
+ }
830
979
  const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
831
980
  const panelsArray = panelsMapToSortedArray(panels);
832
- setSizes(sizes);
833
- callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap);
981
+ const nextSizes = validatePanelGroupLayout({
982
+ groupId,
983
+ panels,
984
+ nextSizes: sizes,
985
+ prevSizes,
986
+ units
987
+ });
988
+ if (!areEqual(prevSizes, nextSizes)) {
989
+ setSizes(nextSizes);
990
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
991
+ }
834
992
  }
835
- }), []);
993
+ }), [groupId]);
836
994
  useWindowSplitterPanelGroupBehavior({
837
995
  committedValuesRef,
838
996
  groupId,
@@ -903,6 +1061,22 @@ function PanelGroupWithForwardedRef({
903
1061
  }
904
1062
  }
905
1063
  }, [autoSaveId, panels, sizes, storage]);
1064
+ const getPanelSize = useCallback((id, unitsFromParams) => {
1065
+ const {
1066
+ panels,
1067
+ units: unitsFromProps
1068
+ } = committedValuesRef.current;
1069
+ const panelsArray = panelsMapToSortedArray(panels);
1070
+ const index = panelsArray.findIndex(panel => panel.current.id === id);
1071
+ const size = sizes[index];
1072
+ const units = unitsFromParams ?? unitsFromProps;
1073
+ if (units === "pixels") {
1074
+ const groupSizePixels = getAvailableGroupSizePixels(groupId);
1075
+ return size / 100 * groupSizePixels;
1076
+ } else {
1077
+ return size;
1078
+ }
1079
+ }, [groupId, sizes]);
906
1080
  const getPanelStyle = useCallback((id, defaultSize) => {
907
1081
  const {
908
1082
  panels
@@ -941,6 +1115,10 @@ function PanelGroupWithForwardedRef({
941
1115
  };
942
1116
  }, [activeHandleId, disablePointerEventsDuringResize, sizes]);
943
1117
  const registerPanel = useCallback((id, panelRef) => {
1118
+ const {
1119
+ units
1120
+ } = committedValuesRef.current;
1121
+ validatePanelProps(units, panelRef);
944
1122
  setPanels(prevPanels => {
945
1123
  if (prevPanels.has(id)) {
946
1124
  return prevPanels;
@@ -977,7 +1155,10 @@ function PanelGroupWithForwardedRef({
977
1155
  }
978
1156
  const size = isHorizontal ? rect.width : rect.height;
979
1157
  const delta = movement / size * 100;
980
- const nextSizes = adjustByDelta(event, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, initialDragStateRef.current);
1158
+
1159
+ // If a validateLayout method has been provided
1160
+ // it's important to use it before updating the mouse cursor
1161
+ const nextSizes = adjustByDelta(event, committedValuesRef.current, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, initialDragStateRef.current);
981
1162
  const sizesChanged = !areEqual(prevSizes, nextSizes);
982
1163
 
983
1164
  // Don't update cursor for resizes triggered by keyboard interactions.
@@ -1004,6 +1185,8 @@ function PanelGroupWithForwardedRef({
1004
1185
  }
1005
1186
  if (sizesChanged) {
1006
1187
  const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1188
+
1189
+ // It's okay to bypass in this case because we already validated above
1007
1190
  setSizes(nextSizes);
1008
1191
 
1009
1192
  // If resize change handlers have been declared, this is the time to call them.
@@ -1057,7 +1240,7 @@ function PanelGroupWithForwardedRef({
1057
1240
  }
1058
1241
  const isLastPanel = index === panelsArray.length - 1;
1059
1242
  const delta = isLastPanel ? currentSize : collapsedSize - currentSize;
1060
- const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1243
+ const nextSizes = adjustByDelta(null, committedValuesRef.current, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1061
1244
  if (prevSizes !== nextSizes) {
1062
1245
  const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1063
1246
  setSizes(nextSizes);
@@ -1100,7 +1283,7 @@ function PanelGroupWithForwardedRef({
1100
1283
  }
1101
1284
  const isLastPanel = index === panelsArray.length - 1;
1102
1285
  const delta = isLastPanel ? collapsedSize - sizeBeforeCollapse : sizeBeforeCollapse;
1103
- const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1286
+ const nextSizes = adjustByDelta(null, committedValuesRef.current, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1104
1287
  if (prevSizes !== nextSizes) {
1105
1288
  const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1106
1289
  setSizes(nextSizes);
@@ -1110,21 +1293,34 @@ function PanelGroupWithForwardedRef({
1110
1293
  callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1111
1294
  }
1112
1295
  }, []);
1113
- const resizePanel = useCallback((id, nextSize) => {
1296
+ const resizePanel = useCallback((id, nextSize, unitsFromParams) => {
1114
1297
  const {
1298
+ id: groupId,
1115
1299
  panels,
1116
- sizes: prevSizes
1300
+ sizes: prevSizes,
1301
+ units
1117
1302
  } = committedValuesRef.current;
1303
+ if ((unitsFromParams || units) === "pixels") {
1304
+ const groupSizePixels = getAvailableGroupSizePixels(groupId);
1305
+ nextSize = nextSize / groupSizePixels * 100;
1306
+ }
1118
1307
  const panel = panels.get(id);
1119
1308
  if (panel == null) {
1120
1309
  return;
1121
1310
  }
1122
- const {
1311
+ let {
1123
1312
  collapsedSize,
1124
1313
  collapsible,
1125
1314
  maxSize,
1126
1315
  minSize
1127
1316
  } = panel.current;
1317
+ if (units === "pixels") {
1318
+ const groupSizePixels = getAvailableGroupSizePixels(groupId);
1319
+ minSize = minSize / groupSizePixels * 100;
1320
+ if (maxSize != null) {
1321
+ maxSize = maxSize / groupSizePixels * 100;
1322
+ }
1323
+ }
1128
1324
  const panelsArray = panelsMapToSortedArray(panels);
1129
1325
  const index = panelsArray.indexOf(panel);
1130
1326
  if (index < 0) {
@@ -1135,7 +1331,13 @@ function PanelGroupWithForwardedRef({
1135
1331
  return;
1136
1332
  }
1137
1333
  if (collapsible && nextSize === collapsedSize) ; else {
1138
- nextSize = Math.min(maxSize, Math.max(minSize, nextSize));
1334
+ const unsafeNextSize = nextSize;
1335
+ nextSize = Math.min(maxSize != null ? maxSize : 100, Math.max(minSize, nextSize));
1336
+ {
1337
+ if (unsafeNextSize !== nextSize) {
1338
+ console.error(`Invalid size (${unsafeNextSize}) specified for Panel "${panel.current.id}" given the panel's min/max size constraints`);
1339
+ }
1340
+ }
1139
1341
  }
1140
1342
  const [idBefore, idAfter] = getBeforeAndAfterIds(id, panelsArray);
1141
1343
  if (idBefore == null || idAfter == null) {
@@ -1143,7 +1345,7 @@ function PanelGroupWithForwardedRef({
1143
1345
  }
1144
1346
  const isLastPanel = index === panelsArray.length - 1;
1145
1347
  const delta = isLastPanel ? currentSize - nextSize : nextSize - currentSize;
1146
- const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1348
+ const nextSizes = adjustByDelta(null, committedValuesRef.current, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1147
1349
  if (prevSizes !== nextSizes) {
1148
1350
  const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1149
1351
  setSizes(nextSizes);
@@ -1158,6 +1360,7 @@ function PanelGroupWithForwardedRef({
1158
1360
  collapsePanel,
1159
1361
  direction,
1160
1362
  expandPanel,
1363
+ getPanelSize,
1161
1364
  getPanelStyle,
1162
1365
  groupId,
1163
1366
  registerPanel,
@@ -1179,8 +1382,9 @@ function PanelGroupWithForwardedRef({
1179
1382
  setActiveHandleId(null);
1180
1383
  initialDragStateRef.current = null;
1181
1384
  },
1385
+ units,
1182
1386
  unregisterPanel
1183
- }), [activeHandleId, collapsePanel, direction, expandPanel, getPanelStyle, groupId, registerPanel, registerResizeHandle, resizePanel, unregisterPanel]);
1387
+ }), [activeHandleId, collapsePanel, direction, expandPanel, getPanelSize, getPanelStyle, groupId, registerPanel, registerResizeHandle, resizePanel, units, unregisterPanel]);
1184
1388
  const style = {
1185
1389
  display: "flex",
1186
1390
  flexDirection: direction === "horizontal" ? "row" : "column",
@@ -1195,6 +1399,7 @@ function PanelGroupWithForwardedRef({
1195
1399
  "data-panel-group": "",
1196
1400
  "data-panel-group-direction": direction,
1197
1401
  "data-panel-group-id": groupId,
1402
+ "data-panel-group-units": units,
1198
1403
  style: {
1199
1404
  ...style,
1200
1405
  ...styleFromProps
@@ -1344,4 +1549,4 @@ function PanelResizeHandle({
1344
1549
  }
1345
1550
  PanelResizeHandle.displayName = "PanelResizeHandle";
1346
1551
 
1347
- export { Panel, PanelGroup, PanelResizeHandle };
1552
+ export { Panel, PanelGroup, PanelResizeHandle, getAvailableGroupSizePixels };