react-resizable-panels 0.0.53 → 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 (41) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/declarations/src/Panel.d.ts +6 -5
  3. package/dist/declarations/src/PanelGroup.d.ts +8 -4
  4. package/dist/declarations/src/PanelResizeHandle.d.ts +5 -2
  5. package/dist/declarations/src/index.d.ts +9 -8
  6. package/dist/declarations/src/types.d.ts +4 -2
  7. package/dist/declarations/src/utils/group.d.ts +29 -0
  8. package/dist/react-resizable-panels.browser.cjs.js +1709 -0
  9. package/dist/react-resizable-panels.browser.cjs.mjs +6 -0
  10. package/dist/react-resizable-panels.browser.development.cjs.js +1764 -0
  11. package/dist/react-resizable-panels.browser.development.cjs.mjs +6 -0
  12. package/dist/react-resizable-panels.browser.development.esm.js +1737 -0
  13. package/dist/react-resizable-panels.browser.esm.js +1682 -0
  14. package/dist/react-resizable-panels.cjs.js +395 -126
  15. package/dist/react-resizable-panels.cjs.js.map +1 -0
  16. package/dist/react-resizable-panels.cjs.mjs +2 -1
  17. package/dist/react-resizable-panels.development.cjs.js +452 -135
  18. package/dist/react-resizable-panels.development.cjs.mjs +6 -0
  19. package/dist/react-resizable-panels.development.esm.js +452 -136
  20. package/dist/react-resizable-panels.development.node.cjs.js +1579 -0
  21. package/dist/react-resizable-panels.development.node.cjs.mjs +6 -0
  22. package/dist/react-resizable-panels.development.node.esm.js +1552 -0
  23. package/dist/react-resizable-panels.esm.js +395 -127
  24. package/dist/react-resizable-panels.esm.js.map +1 -0
  25. package/dist/react-resizable-panels.node.cjs.js +1523 -0
  26. package/dist/react-resizable-panels.node.cjs.mjs +6 -0
  27. package/dist/react-resizable-panels.node.esm.js +1496 -0
  28. package/package.json +26 -1
  29. package/src/Panel.ts +37 -37
  30. package/src/PanelContexts.ts +5 -6
  31. package/src/PanelGroup.ts +269 -121
  32. package/src/PanelResizeHandle.ts +1 -4
  33. package/src/env-conditions/browser.ts +1 -0
  34. package/src/env-conditions/node.ts +1 -0
  35. package/src/env-conditions/unknown.ts +1 -0
  36. package/src/hooks/useIsomorphicEffect.ts +2 -9
  37. package/src/hooks/useWindowSplitterBehavior.ts +14 -11
  38. package/src/index.ts +11 -3
  39. package/src/types.ts +3 -1
  40. package/src/utils/group.ts +327 -28
  41. package/src/utils/ssr.ts +0 -7
@@ -24,6 +24,8 @@ function _interopNamespace(e) {
24
24
 
25
25
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
26
26
 
27
+ const isBrowser = typeof window !== "undefined";
28
+
27
29
  // This module exists to work around Webpack issue https://github.com/webpack/webpack/issues/14814
28
30
 
29
31
  // eslint-disable-next-line no-restricted-imports
@@ -45,8 +47,7 @@ const {
45
47
  // `toString()` prevents bundlers from trying to `import { useId } from 'react'`
46
48
  const useId = React__namespace["useId".toString()];
47
49
 
48
- const canUseEffectHooks = !!(typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined");
49
- const useIsomorphicLayoutEffect = canUseEffectHooks ? useLayoutEffect : () => {};
50
+ const useIsomorphicLayoutEffect = isBrowser ? useLayoutEffect : () => {};
50
51
 
51
52
  const wrappedUseId = typeof useId === "function" ? useId : () => null;
52
53
  let counter = 0;
@@ -60,10 +61,6 @@ function useUniqueId(idFromParams = null) {
60
61
  }
61
62
 
62
63
  const PanelGroupContext = createContext(null);
63
-
64
- // Workaround for Parcel scope hoisting (which renames objects/functions).
65
- // Casting to :any is required to avoid corrupting the generated TypeScript types.
66
- // See github.com/parcel-bundler/parcel/issues/8724
67
64
  PanelGroupContext.displayName = "PanelGroupContext";
68
65
 
69
66
  function PanelWithForwardedRef({
@@ -74,8 +71,8 @@ function PanelWithForwardedRef({
74
71
  defaultSize = null,
75
72
  forwardedRef,
76
73
  id: idFromProps = null,
77
- maxSize = 100,
78
- minSize = 10,
74
+ maxSize = null,
75
+ minSize,
79
76
  onCollapse = null,
80
77
  onResize = null,
81
78
  order = null,
@@ -90,11 +87,22 @@ function PanelWithForwardedRef({
90
87
  const {
91
88
  collapsePanel,
92
89
  expandPanel,
90
+ getPanelSize,
93
91
  getPanelStyle,
94
92
  registerPanel,
95
93
  resizePanel,
94
+ units,
96
95
  unregisterPanel
97
96
  } = context;
97
+ if (minSize == null) {
98
+ if (units === "percentages") {
99
+ // Mimics legacy default value for percentage based panel groups
100
+ minSize = 10;
101
+ } else {
102
+ // There is no meaningful minimum pixel default we can provide
103
+ minSize = 0;
104
+ }
105
+ }
98
106
 
99
107
  // Use a ref to guard against users passing inline props
100
108
  const callbacksRef = useRef({
@@ -105,22 +113,6 @@ function PanelWithForwardedRef({
105
113
  callbacksRef.current.onCollapse = onCollapse;
106
114
  callbacksRef.current.onResize = onResize;
107
115
  });
108
-
109
- // Basic props validation
110
- if (minSize < 0 || minSize > 100) {
111
- throw Error(`Panel minSize must be between 0 and 100, but was ${minSize}`);
112
- } else if (maxSize < 0 || maxSize > 100) {
113
- throw Error(`Panel maxSize must be between 0 and 100, but was ${maxSize}`);
114
- } else {
115
- if (defaultSize !== null) {
116
- if (defaultSize < 0 || defaultSize > 100) {
117
- throw Error(`Panel defaultSize must be between 0 and 100, but was ${defaultSize}`);
118
- } else if (minSize > defaultSize && !collapsible) {
119
- console.error(`Panel minSize ${minSize} cannot be greater than defaultSize ${defaultSize}`);
120
- defaultSize = minSize;
121
- }
122
- }
123
- }
124
116
  const style = getPanelStyle(panelId, defaultSize);
125
117
  const committedValuesRef = useRef({
126
118
  size: parseSizeFromStyle(style)
@@ -131,6 +123,7 @@ function PanelWithForwardedRef({
131
123
  collapsible,
132
124
  defaultSize,
133
125
  id: panelId,
126
+ idWasAutoGenerated: idFromProps == null,
134
127
  maxSize,
135
128
  minSize,
136
129
  order
@@ -142,6 +135,7 @@ function PanelWithForwardedRef({
142
135
  panelDataRef.current.collapsible = collapsible;
143
136
  panelDataRef.current.defaultSize = defaultSize;
144
137
  panelDataRef.current.id = panelId;
138
+ panelDataRef.current.idWasAutoGenerated = idFromProps == null;
145
139
  panelDataRef.current.maxSize = maxSize;
146
140
  panelDataRef.current.minSize = minSize;
147
141
  panelDataRef.current.order = order;
@@ -158,11 +152,14 @@ function PanelWithForwardedRef({
158
152
  getCollapsed() {
159
153
  return committedValuesRef.current.size === 0;
160
154
  },
161
- getSize() {
162
- return committedValuesRef.current.size;
155
+ getId() {
156
+ return panelId;
163
157
  },
164
- resize: percentage => resizePanel(panelId, percentage)
165
- }), [collapsePanel, expandPanel, panelId, resizePanel]);
158
+ getSize(units) {
159
+ return getPanelSize(panelId, units);
160
+ },
161
+ resize: (percentage, units) => resizePanel(panelId, percentage, units)
162
+ }), [collapsePanel, expandPanel, getPanelSize, panelId, resizePanel]);
166
163
  return createElement(Type, {
167
164
  children,
168
165
  className: classNameFromProps,
@@ -181,10 +178,6 @@ const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
181
178
  ...props,
182
179
  forwardedRef: ref
183
180
  }));
184
-
185
- // Workaround for Parcel scope hoisting (which renames objects/functions).
186
- // Casting to :any is required to avoid corrupting the generated TypeScript types.
187
- // See github.com/parcel-bundler/parcel/issues/8724
188
181
  PanelWithForwardedRef.displayName = "Panel";
189
182
  Panel.displayName = "forwardRef(Panel)";
190
183
 
@@ -202,7 +195,13 @@ function parseSizeFromStyle(style) {
202
195
 
203
196
  const PRECISION = 10;
204
197
 
205
- function adjustByDelta(event, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse, initialDragState) {
198
+ function adjustByDelta(event, committedValues, idBefore, idAfter, deltaPixels, prevSizes, panelSizeBeforeCollapse, initialDragState) {
199
+ const {
200
+ id: groupId,
201
+ panels,
202
+ units
203
+ } = committedValues;
204
+ const groupSizePixels = units === "pixels" ? getAvailableGroupSizePixels(groupId) : NaN;
206
205
  const {
207
206
  sizes: initialSizes
208
207
  } = initialDragState || {};
@@ -210,9 +209,6 @@ function adjustByDelta(event, panels, idBefore, idAfter, delta, prevSizes, panel
210
209
  // If we're resizing by mouse or touch, use the initial sizes as a base.
211
210
  // This has the benefit of causing force-collapsed panels to spring back open if drag is reversed.
212
211
  const baseSizes = initialSizes || prevSizes;
213
- if (delta === 0) {
214
- return baseSizes;
215
- }
216
212
  const panelsArray = panelsMapToSortedArray(panels);
217
213
  const nextSizes = baseSizes.concat();
218
214
  let deltaApplied = 0;
@@ -227,11 +223,11 @@ function adjustByDelta(event, panels, idBefore, idAfter, delta, prevSizes, panel
227
223
 
228
224
  // Max-bounds check the panel being expanded first.
229
225
  {
230
- const pivotId = delta < 0 ? idAfter : idBefore;
226
+ const pivotId = deltaPixels < 0 ? idAfter : idBefore;
231
227
  const index = panelsArray.findIndex(panel => panel.current.id === pivotId);
232
228
  const panel = panelsArray[index];
233
229
  const baseSize = baseSizes[index];
234
- const nextSize = safeResizePanel(panel, Math.abs(delta), baseSize, event);
230
+ const nextSize = safeResizePanel(units, groupSizePixels, panel, baseSize, baseSize + Math.abs(deltaPixels), event);
235
231
  if (baseSize === nextSize) {
236
232
  // If there's no room for the pivot panel to grow, we can ignore this drag update.
237
233
  return baseSizes;
@@ -239,29 +235,29 @@ function adjustByDelta(event, panels, idBefore, idAfter, delta, prevSizes, panel
239
235
  if (nextSize === 0 && baseSize > 0) {
240
236
  panelSizeBeforeCollapse.set(pivotId, baseSize);
241
237
  }
242
- delta = delta < 0 ? baseSize - nextSize : nextSize - baseSize;
238
+ deltaPixels = deltaPixels < 0 ? baseSize - nextSize : nextSize - baseSize;
243
239
  }
244
240
  }
245
- let pivotId = delta < 0 ? idBefore : idAfter;
241
+ let pivotId = deltaPixels < 0 ? idBefore : idAfter;
246
242
  let index = panelsArray.findIndex(panel => panel.current.id === pivotId);
247
243
  while (true) {
248
244
  const panel = panelsArray[index];
249
245
  const baseSize = baseSizes[index];
250
- const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
251
- const nextSize = safeResizePanel(panel, 0 - deltaRemaining, baseSize, event);
246
+ const deltaRemaining = Math.abs(deltaPixels) - Math.abs(deltaApplied);
247
+ const nextSize = safeResizePanel(units, groupSizePixels, panel, baseSize, baseSize - deltaRemaining, event);
252
248
  if (baseSize !== nextSize) {
253
249
  if (nextSize === 0 && baseSize > 0) {
254
250
  panelSizeBeforeCollapse.set(panel.current.id, baseSize);
255
251
  }
256
252
  deltaApplied += baseSize - nextSize;
257
253
  nextSizes[index] = nextSize;
258
- if (deltaApplied.toPrecision(PRECISION).localeCompare(Math.abs(delta).toPrecision(PRECISION), undefined, {
254
+ if (deltaApplied.toPrecision(PRECISION).localeCompare(Math.abs(deltaPixels).toPrecision(PRECISION), undefined, {
259
255
  numeric: true
260
256
  }) >= 0) {
261
257
  break;
262
258
  }
263
259
  }
264
- if (delta < 0) {
260
+ if (deltaPixels < 0) {
265
261
  if (--index < 0) {
266
262
  break;
267
263
  }
@@ -279,7 +275,7 @@ function adjustByDelta(event, panels, idBefore, idAfter, delta, prevSizes, panel
279
275
  }
280
276
 
281
277
  // Adjust the pivot panel before, but only by the amount that surrounding panels were able to shrink/contract.
282
- pivotId = delta < 0 ? idAfter : idBefore;
278
+ pivotId = deltaPixels < 0 ? idAfter : idBefore;
283
279
  index = panelsArray.findIndex(panel => panel.current.id === pivotId);
284
280
  nextSizes[index] = baseSizes[index] + deltaApplied;
285
281
  return nextSizes;
@@ -318,6 +314,89 @@ function callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap) {
318
314
  }
319
315
  });
320
316
  }
317
+ function calculateDefaultLayout({
318
+ groupId,
319
+ panels,
320
+ units
321
+ }) {
322
+ const groupSizePixels = units === "pixels" ? getAvailableGroupSizePixels(groupId) : NaN;
323
+ const panelsArray = panelsMapToSortedArray(panels);
324
+ const sizes = Array(panelsArray.length);
325
+ let numPanelsWithSizes = 0;
326
+ let remainingSize = 100;
327
+
328
+ // Assigning default sizes requires a couple of passes:
329
+ // First, all panels with defaultSize should be set as-is
330
+ for (let index = 0; index < panelsArray.length; index++) {
331
+ const panel = panelsArray[index];
332
+ const {
333
+ defaultSize
334
+ } = panel.current;
335
+ if (defaultSize != null) {
336
+ numPanelsWithSizes++;
337
+ sizes[index] = units === "pixels" ? defaultSize / groupSizePixels * 100 : defaultSize;
338
+ remainingSize -= sizes[index];
339
+ }
340
+ }
341
+
342
+ // Remaining total size should be distributed evenly between panels
343
+ // This may require two passes, depending on min/max constraints
344
+ for (let index = 0; index < panelsArray.length; index++) {
345
+ const panel = panelsArray[index];
346
+ let {
347
+ defaultSize,
348
+ id,
349
+ maxSize,
350
+ minSize
351
+ } = panel.current;
352
+ if (defaultSize != null) {
353
+ continue;
354
+ }
355
+ if (units === "pixels") {
356
+ minSize = minSize / groupSizePixels * 100;
357
+ if (maxSize != null) {
358
+ maxSize = maxSize / groupSizePixels * 100;
359
+ }
360
+ }
361
+ const remainingPanels = panelsArray.length - numPanelsWithSizes;
362
+ const size = Math.min(maxSize != null ? maxSize : 100, Math.max(minSize, remainingSize / remainingPanels));
363
+ sizes[index] = size;
364
+ numPanelsWithSizes++;
365
+ remainingSize -= size;
366
+ }
367
+
368
+ // If there is additional, left over space, assign it to any panel(s) that permits it
369
+ // (It's not worth taking multiple additional passes to evenly distribute)
370
+ if (remainingSize !== 0) {
371
+ for (let index = 0; index < panelsArray.length; index++) {
372
+ const panel = panelsArray[index];
373
+ let {
374
+ maxSize,
375
+ minSize
376
+ } = panel.current;
377
+ if (units === "pixels") {
378
+ minSize = minSize / groupSizePixels * 100;
379
+ if (maxSize != null) {
380
+ maxSize = maxSize / groupSizePixels * 100;
381
+ }
382
+ }
383
+ const size = Math.min(maxSize != null ? maxSize : 100, Math.max(minSize, sizes[index] + remainingSize));
384
+ if (size !== sizes[index]) {
385
+ remainingSize -= size - sizes[index];
386
+ sizes[index] = size;
387
+
388
+ // Fuzzy comparison to account for imprecise floating point math
389
+ if (Math.abs(remainingSize).toFixed(3) === "0.000") {
390
+ break;
391
+ }
392
+ }
393
+ }
394
+ }
395
+
396
+ // Finally, if there is still left-over size, log an error
397
+ if (Math.abs(remainingSize).toFixed(3) !== "0.000") ;
398
+ return sizes;
399
+ }
321
400
  function getBeforeAndAfterIds(id, panelsArray) {
322
401
  if (panelsArray.length < 2) {
323
402
  return [null, null];
@@ -331,6 +410,23 @@ function getBeforeAndAfterIds(id, panelsArray) {
331
410
  const idAfter = isLastPanel ? id : panelsArray[index + 1].current.id;
332
411
  return [idBefore, idAfter];
333
412
  }
413
+ function getAvailableGroupSizePixels(groupId) {
414
+ const panelGroupElement = getPanelGroup(groupId);
415
+ if (panelGroupElement == null) {
416
+ return NaN;
417
+ }
418
+ const direction = panelGroupElement.getAttribute("data-panel-group-direction");
419
+ const resizeHandles = getResizeHandlesForGroup(groupId);
420
+ if (direction === "horizontal") {
421
+ return panelGroupElement.offsetWidth - resizeHandles.reduce((accumulated, handle) => {
422
+ return accumulated + handle.offsetWidth;
423
+ }, 0);
424
+ } else {
425
+ return panelGroupElement.offsetHeight - resizeHandles.reduce((accumulated, handle) => {
426
+ return accumulated + handle.offsetHeight;
427
+ }, 0);
428
+ }
429
+ }
334
430
 
335
431
  // This method returns a number between 1 and 100 representing
336
432
  // the % of the group's overall space this panel should occupy.
@@ -401,18 +497,24 @@ function panelsMapToSortedArray(panels) {
401
497
  }
402
498
  });
403
499
  }
404
- function safeResizePanel(panel, delta, prevSize, event) {
405
- const nextSizeUnsafe = prevSize + delta;
406
- const {
500
+ function safeResizePanel(units, groupSizePixels, panel, prevSize, nextSize, event = null) {
501
+ let {
407
502
  collapsedSize,
408
503
  collapsible,
409
504
  maxSize,
410
505
  minSize
411
506
  } = panel.current;
507
+ if (units === "pixels") {
508
+ collapsedSize = collapsedSize / groupSizePixels * 100;
509
+ if (maxSize != null) {
510
+ maxSize = maxSize / groupSizePixels * 100;
511
+ }
512
+ minSize = minSize / groupSizePixels * 100;
513
+ }
412
514
  if (collapsible) {
413
515
  if (prevSize > collapsedSize) {
414
516
  // Mimic VS COde behavior; collapse a panel if it's smaller than half of its min-size
415
- if (nextSizeUnsafe <= minSize / 2 + collapsedSize) {
517
+ if (nextSize <= minSize / 2 + collapsedSize) {
416
518
  return collapsedSize;
417
519
  }
418
520
  } else {
@@ -421,14 +523,97 @@ function safeResizePanel(panel, delta, prevSize, event) {
421
523
  // Keyboard events should expand a collapsed panel to the min size,
422
524
  // but mouse events should wait until the panel has reached its min size
423
525
  // to avoid a visual flickering when dragging between collapsed and min size.
424
- if (nextSizeUnsafe < minSize) {
526
+ if (nextSize < minSize) {
425
527
  return collapsedSize;
426
528
  }
427
529
  }
428
530
  }
429
531
  }
430
- const nextSize = Math.min(maxSize, Math.max(minSize, nextSizeUnsafe));
431
- return nextSize;
532
+ return Math.min(maxSize != null ? maxSize : 100, Math.max(minSize, nextSize));
533
+ }
534
+ function validatePanelProps(units, panelData) {
535
+ const {
536
+ collapsible,
537
+ defaultSize,
538
+ maxSize,
539
+ minSize
540
+ } = panelData.current;
541
+
542
+ // Basic props validation
543
+ if (minSize < 0 || units === "percentages" && minSize > 100) {
544
+ panelData.current.minSize = 0;
545
+ }
546
+ if (maxSize != null) {
547
+ if (maxSize < 0 || units === "percentages" && maxSize > 100) {
548
+ panelData.current.maxSize = null;
549
+ }
550
+ }
551
+ if (defaultSize !== null) {
552
+ if (defaultSize < 0 || units === "percentages" && defaultSize > 100) {
553
+ panelData.current.defaultSize = null;
554
+ } else if (defaultSize < minSize && !collapsible) {
555
+ panelData.current.defaultSize = minSize;
556
+ } else if (maxSize != null && defaultSize > maxSize) {
557
+ panelData.current.defaultSize = maxSize;
558
+ }
559
+ }
560
+ }
561
+ function validatePanelGroupLayout({
562
+ groupId,
563
+ panels,
564
+ nextSizes,
565
+ prevSizes,
566
+ units
567
+ }) {
568
+ // Clone because this method modifies
569
+ nextSizes = [...nextSizes];
570
+ const panelsArray = panelsMapToSortedArray(panels);
571
+ const groupSizePixels = units === "pixels" ? getAvailableGroupSizePixels(groupId) : NaN;
572
+ let remainingSize = 0;
573
+
574
+ // First, check all of the proposed sizes against the min/max constraints
575
+ for (let index = 0; index < panelsArray.length; index++) {
576
+ const panel = panelsArray[index];
577
+ const prevSize = prevSizes[index];
578
+ const nextSize = nextSizes[index];
579
+ const safeNextSize = safeResizePanel(units, groupSizePixels, panel, prevSize, nextSize);
580
+ if (nextSize != safeNextSize) {
581
+ remainingSize += nextSize - safeNextSize;
582
+ nextSizes[index] = safeNextSize;
583
+ }
584
+ }
585
+
586
+ // If there is additional, left over space, assign it to any panel(s) that permits it
587
+ // (It's not worth taking multiple additional passes to evenly distribute)
588
+ if (remainingSize.toFixed(3) !== "0.000") {
589
+ for (let index = 0; index < panelsArray.length; index++) {
590
+ const panel = panelsArray[index];
591
+ let {
592
+ maxSize,
593
+ minSize
594
+ } = panel.current;
595
+ if (units === "pixels") {
596
+ minSize = minSize / groupSizePixels * 100;
597
+ if (maxSize != null) {
598
+ maxSize = maxSize / groupSizePixels * 100;
599
+ }
600
+ }
601
+ const size = Math.min(maxSize != null ? maxSize : 100, Math.max(minSize, nextSizes[index] + remainingSize));
602
+ if (size !== nextSizes[index]) {
603
+ remainingSize -= size - nextSizes[index];
604
+ nextSizes[index] = size;
605
+
606
+ // Fuzzy comparison to account for imprecise floating point math
607
+ if (Math.abs(remainingSize).toFixed(3) === "0.000") {
608
+ break;
609
+ }
610
+ }
611
+ }
612
+ }
613
+
614
+ // If we still have remainder, the requested layout wasn't valid and we should warn about it
615
+ if (remainingSize.toFixed(3) !== "0.000") ;
616
+ return nextSizes;
432
617
  }
433
618
 
434
619
  function assert(expectedCondition, message = "Assertion failed!") {
@@ -454,6 +639,7 @@ function useWindowSplitterPanelGroupBehavior({
454
639
  panels
455
640
  } = committedValuesRef.current;
456
641
  const groupElement = getPanelGroup(groupId);
642
+ assert(groupElement != null, `No group found for id "${groupId}"`);
457
643
  const {
458
644
  height,
459
645
  width
@@ -466,23 +652,28 @@ function useWindowSplitterPanelGroupBehavior({
466
652
  if (idBefore == null || idAfter == null) {
467
653
  return () => {};
468
654
  }
469
- let minSize = 0;
470
- let maxSize = 100;
655
+ let currentMinSize = 0;
656
+ let currentMaxSize = 100;
471
657
  let totalMinSize = 0;
472
658
  let totalMaxSize = 0;
473
659
 
474
660
  // A panel's effective min/max sizes also need to account for other panel's sizes.
475
661
  panelsArray.forEach(panelData => {
476
- if (panelData.current.id === idBefore) {
477
- maxSize = panelData.current.maxSize;
478
- minSize = panelData.current.minSize;
662
+ const {
663
+ id,
664
+ maxSize,
665
+ minSize
666
+ } = panelData.current;
667
+ if (id === idBefore) {
668
+ currentMinSize = minSize;
669
+ currentMaxSize = maxSize != null ? maxSize : 100;
479
670
  } else {
480
- totalMinSize += panelData.current.minSize;
481
- totalMaxSize += panelData.current.maxSize;
671
+ totalMinSize += minSize;
672
+ totalMaxSize += maxSize != null ? maxSize : 100;
482
673
  }
483
674
  });
484
- const ariaValueMax = Math.min(maxSize, 100 - totalMinSize);
485
- const ariaValueMin = Math.max(minSize, (panelsArray.length - 1) * 100 - totalMaxSize);
675
+ const ariaValueMax = Math.min(currentMaxSize, 100 - totalMinSize);
676
+ const ariaValueMin = Math.max(currentMinSize, (panelsArray.length - 1) * 100 - totalMaxSize);
486
677
  const flexGrow = getFlexGrow(panels, idBefore, sizes);
487
678
  handle.setAttribute("aria-valuemax", "" + Math.round(ariaValueMax));
488
679
  handle.setAttribute("aria-valuemin", "" + Math.round(ariaValueMin));
@@ -506,7 +697,7 @@ function useWindowSplitterPanelGroupBehavior({
506
697
  } else {
507
698
  delta = -(direction === "horizontal" ? width : height);
508
699
  }
509
- const nextSizes = adjustByDelta(event, panels, idBefore, idAfter, delta, sizes, panelSizeBeforeCollapse.current, null);
700
+ const nextSizes = adjustByDelta(event, committedValuesRef.current, idBefore, idAfter, delta, sizes, panelSizeBeforeCollapse.current, null);
510
701
  if (sizes !== nextSizes) {
511
702
  setSizes(nextSizes);
512
703
  }
@@ -821,9 +1012,6 @@ const defaultStorage = {
821
1012
  // * dragHandleRect, sizes:
822
1013
  // When resizing is done via mouse/touch event– some initial state is stored
823
1014
  // so that any panels that contract will also expand if drag direction is reversed.
824
- // TODO
825
- // Within an active drag, remember original positions to refine more easily on expand.
826
- // Look at what the Chrome devtools Sources does.
827
1015
  function PanelGroupWithForwardedRef({
828
1016
  autoSaveId,
829
1017
  children = null,
@@ -835,7 +1023,8 @@ function PanelGroupWithForwardedRef({
835
1023
  onLayout,
836
1024
  storage = defaultStorage,
837
1025
  style: styleFromProps = {},
838
- tagName: Type = "div"
1026
+ tagName: Type = "div",
1027
+ units = "percentages"
839
1028
  }) {
840
1029
  const groupId = useUniqueId(idFromProps);
841
1030
  const [activeHandleId, setActiveHandleId] = useState(null);
@@ -845,6 +1034,12 @@ function PanelGroupWithForwardedRef({
845
1034
  // We store the initial Panel sizes in this ref, and apply move deltas to them instead of to the current sizes.
846
1035
  // This has the benefit of causing force-collapsed panels to spring back open if drag is reversed.
847
1036
  const initialDragStateRef = useRef(null);
1037
+ useRef({
1038
+ didLogDefaultSizeWarning: false,
1039
+ didLogIdAndOrderWarning: false,
1040
+ didLogInvalidLayoutWarning: false,
1041
+ prevPanelIds: []
1042
+ });
848
1043
 
849
1044
  // Use a ref to guard against users passing inline props
850
1045
  const callbacksRef = useRef({
@@ -865,32 +1060,58 @@ function PanelGroupWithForwardedRef({
865
1060
  // Store committed values to avoid unnecessarily re-running memoization/effects functions.
866
1061
  const committedValuesRef = useRef({
867
1062
  direction,
1063
+ id: groupId,
868
1064
  panels,
869
- sizes
1065
+ sizes,
1066
+ units
870
1067
  });
871
1068
  useImperativeHandle(forwardedRef, () => ({
872
- getLayout: () => {
1069
+ getId: () => groupId,
1070
+ getLayout: unitsFromParams => {
873
1071
  const {
874
- sizes
1072
+ sizes,
1073
+ units: unitsFromProps
875
1074
  } = committedValuesRef.current;
876
- return sizes;
1075
+ const units = unitsFromParams ?? unitsFromProps;
1076
+ if (units === "pixels") {
1077
+ const groupSizePixels = getAvailableGroupSizePixels(groupId);
1078
+ return sizes.map(size => size / 100 * groupSizePixels);
1079
+ } else {
1080
+ return sizes;
1081
+ }
877
1082
  },
878
- setLayout: sizes => {
879
- const total = sizes.reduce((accumulated, current) => accumulated + current, 0);
880
- assert(total === 100, "Panel sizes must add up to 100%");
1083
+ setLayout: (sizes, unitsFromParams) => {
881
1084
  const {
882
- panels
1085
+ id: groupId,
1086
+ panels,
1087
+ sizes: prevSizes,
1088
+ units
883
1089
  } = committedValuesRef.current;
1090
+ if ((unitsFromParams || units) === "pixels") {
1091
+ const groupSizePixels = getAvailableGroupSizePixels(groupId);
1092
+ sizes = sizes.map(size => size / groupSizePixels * 100);
1093
+ }
884
1094
  const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
885
1095
  const panelsArray = panelsMapToSortedArray(panels);
886
- setSizes(sizes);
887
- callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap);
1096
+ const nextSizes = validatePanelGroupLayout({
1097
+ groupId,
1098
+ panels,
1099
+ nextSizes: sizes,
1100
+ prevSizes,
1101
+ units
1102
+ });
1103
+ if (!areEqual(prevSizes, nextSizes)) {
1104
+ setSizes(nextSizes);
1105
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1106
+ }
888
1107
  }
889
- }), []);
1108
+ }), [groupId]);
890
1109
  useIsomorphicLayoutEffect(() => {
891
1110
  committedValuesRef.current.direction = direction;
1111
+ committedValuesRef.current.id = groupId;
892
1112
  committedValuesRef.current.panels = panels;
893
1113
  committedValuesRef.current.sizes = sizes;
1114
+ committedValuesRef.current.units = units;
894
1115
  });
895
1116
  useWindowSplitterPanelGroupBehavior({
896
1117
  committedValuesRef,
@@ -932,7 +1153,11 @@ function PanelGroupWithForwardedRef({
932
1153
  // Compute the initial sizes based on default weights.
933
1154
  // This assumes that panels register during initial mount (no conditional rendering)!
934
1155
  useIsomorphicLayoutEffect(() => {
935
- const sizes = committedValuesRef.current.sizes;
1156
+ const {
1157
+ id: groupId,
1158
+ sizes,
1159
+ units
1160
+ } = committedValuesRef.current;
936
1161
  if (sizes.length === panels.size) {
937
1162
  // Only compute (or restore) default sizes once per panel configuration.
938
1163
  return;
@@ -946,39 +1171,23 @@ function PanelGroupWithForwardedRef({
946
1171
  defaultSizes = loadPanelLayout(autoSaveId, panelsArray, storage);
947
1172
  }
948
1173
  if (defaultSizes != null) {
949
- setSizes(defaultSizes);
1174
+ // Validate saved sizes in case something has changed since last render
1175
+ // e.g. for pixel groups, this could be the size of the window
1176
+ const validatedSizes = validatePanelGroupLayout({
1177
+ groupId,
1178
+ panels,
1179
+ nextSizes: defaultSizes,
1180
+ prevSizes: defaultSizes,
1181
+ units
1182
+ });
1183
+ setSizes(validatedSizes);
950
1184
  } else {
951
- const panelsArray = panelsMapToSortedArray(panels);
952
- let panelsWithNullDefaultSize = 0;
953
- let totalDefaultSize = 0;
954
- let totalMinSize = 0;
955
-
956
- // TODO
957
- // Implicit default size calculations below do not account for inferred min/max size values.
958
- // e.g. if Panel A has a maxSize of 40 then Panels A and B can't both have an implicit default size of 50.
959
- // For now, these logic edge cases are left to the user to handle via props.
960
-
961
- panelsArray.forEach(panel => {
962
- totalMinSize += panel.current.minSize;
963
- if (panel.current.defaultSize === null) {
964
- panelsWithNullDefaultSize++;
965
- } else {
966
- totalDefaultSize += panel.current.defaultSize;
967
- }
1185
+ const sizes = calculateDefaultLayout({
1186
+ groupId,
1187
+ panels,
1188
+ units
968
1189
  });
969
- if (totalDefaultSize > 100) {
970
- throw new Error(`Default panel sizes cannot exceed 100%`);
971
- } else if (panelsArray.length > 1 && panelsWithNullDefaultSize === 0 && totalDefaultSize !== 100) {
972
- throw new Error(`Invalid default sizes specified for panels`);
973
- } else if (totalMinSize > 100) {
974
- throw new Error(`Minimum panel sizes cannot exceed 100%`);
975
- }
976
- setSizes(panelsArray.map(panel => {
977
- if (panel.current.defaultSize === null) {
978
- return (100 - totalDefaultSize) / panelsWithNullDefaultSize;
979
- }
980
- return panel.current.defaultSize;
981
- }));
1190
+ setSizes(sizes);
982
1191
  }
983
1192
  }, [autoSaveId, panels, storage]);
984
1193
  useEffect(() => {
@@ -996,6 +1205,48 @@ function PanelGroupWithForwardedRef({
996
1205
  debounceMap[autoSaveId](autoSaveId, panelsArray, sizes, storage);
997
1206
  }
998
1207
  }, [autoSaveId, panels, sizes, storage]);
1208
+ useIsomorphicLayoutEffect(() => {
1209
+ // Pixel panel constraints need to be reassessed after a group resize
1210
+ // We can avoid the ResizeObserver overhead for relative layouts
1211
+ if (units === "pixels") {
1212
+ const resizeObserver = new ResizeObserver(() => {
1213
+ const {
1214
+ panels,
1215
+ sizes: prevSizes
1216
+ } = committedValuesRef.current;
1217
+ const nextSizes = validatePanelGroupLayout({
1218
+ groupId,
1219
+ panels,
1220
+ nextSizes: prevSizes,
1221
+ prevSizes,
1222
+ units
1223
+ });
1224
+ if (!areEqual(prevSizes, nextSizes)) {
1225
+ setSizes(nextSizes);
1226
+ }
1227
+ });
1228
+ resizeObserver.observe(getPanelGroup(groupId));
1229
+ return () => {
1230
+ resizeObserver.disconnect();
1231
+ };
1232
+ }
1233
+ }, [groupId, units]);
1234
+ const getPanelSize = useCallback((id, unitsFromParams) => {
1235
+ const {
1236
+ panels,
1237
+ units: unitsFromProps
1238
+ } = committedValuesRef.current;
1239
+ const panelsArray = panelsMapToSortedArray(panels);
1240
+ const index = panelsArray.findIndex(panel => panel.current.id === id);
1241
+ const size = sizes[index];
1242
+ const units = unitsFromParams ?? unitsFromProps;
1243
+ if (units === "pixels") {
1244
+ const groupSizePixels = getAvailableGroupSizePixels(groupId);
1245
+ return size / 100 * groupSizePixels;
1246
+ } else {
1247
+ return size;
1248
+ }
1249
+ }, [groupId, sizes]);
999
1250
  const getPanelStyle = useCallback((id, defaultSize) => {
1000
1251
  const {
1001
1252
  panels
@@ -1026,6 +1277,10 @@ function PanelGroupWithForwardedRef({
1026
1277
  };
1027
1278
  }, [activeHandleId, disablePointerEventsDuringResize, sizes]);
1028
1279
  const registerPanel = useCallback((id, panelRef) => {
1280
+ const {
1281
+ units
1282
+ } = committedValuesRef.current;
1283
+ validatePanelProps(units, panelRef);
1029
1284
  setPanels(prevPanels => {
1030
1285
  if (prevPanels.has(id)) {
1031
1286
  return prevPanels;
@@ -1062,7 +1317,10 @@ function PanelGroupWithForwardedRef({
1062
1317
  }
1063
1318
  const size = isHorizontal ? rect.width : rect.height;
1064
1319
  const delta = movement / size * 100;
1065
- const nextSizes = adjustByDelta(event, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, initialDragStateRef.current);
1320
+
1321
+ // If a validateLayout method has been provided
1322
+ // it's important to use it before updating the mouse cursor
1323
+ const nextSizes = adjustByDelta(event, committedValuesRef.current, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, initialDragStateRef.current);
1066
1324
  const sizesChanged = !areEqual(prevSizes, nextSizes);
1067
1325
 
1068
1326
  // Don't update cursor for resizes triggered by keyboard interactions.
@@ -1089,6 +1347,8 @@ function PanelGroupWithForwardedRef({
1089
1347
  }
1090
1348
  if (sizesChanged) {
1091
1349
  const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1350
+
1351
+ // It's okay to bypass in this case because we already validated above
1092
1352
  setSizes(nextSizes);
1093
1353
 
1094
1354
  // If resize change handlers have been declared, this is the time to call them.
@@ -1142,7 +1402,7 @@ function PanelGroupWithForwardedRef({
1142
1402
  }
1143
1403
  const isLastPanel = index === panelsArray.length - 1;
1144
1404
  const delta = isLastPanel ? currentSize : collapsedSize - currentSize;
1145
- const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1405
+ const nextSizes = adjustByDelta(null, committedValuesRef.current, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1146
1406
  if (prevSizes !== nextSizes) {
1147
1407
  const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1148
1408
  setSizes(nextSizes);
@@ -1185,7 +1445,7 @@ function PanelGroupWithForwardedRef({
1185
1445
  }
1186
1446
  const isLastPanel = index === panelsArray.length - 1;
1187
1447
  const delta = isLastPanel ? collapsedSize - sizeBeforeCollapse : sizeBeforeCollapse;
1188
- const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1448
+ const nextSizes = adjustByDelta(null, committedValuesRef.current, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1189
1449
  if (prevSizes !== nextSizes) {
1190
1450
  const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1191
1451
  setSizes(nextSizes);
@@ -1195,21 +1455,34 @@ function PanelGroupWithForwardedRef({
1195
1455
  callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1196
1456
  }
1197
1457
  }, []);
1198
- const resizePanel = useCallback((id, nextSize) => {
1458
+ const resizePanel = useCallback((id, nextSize, unitsFromParams) => {
1199
1459
  const {
1460
+ id: groupId,
1200
1461
  panels,
1201
- sizes: prevSizes
1462
+ sizes: prevSizes,
1463
+ units
1202
1464
  } = committedValuesRef.current;
1465
+ if ((unitsFromParams || units) === "pixels") {
1466
+ const groupSizePixels = getAvailableGroupSizePixels(groupId);
1467
+ nextSize = nextSize / groupSizePixels * 100;
1468
+ }
1203
1469
  const panel = panels.get(id);
1204
1470
  if (panel == null) {
1205
1471
  return;
1206
1472
  }
1207
- const {
1473
+ let {
1208
1474
  collapsedSize,
1209
1475
  collapsible,
1210
1476
  maxSize,
1211
1477
  minSize
1212
1478
  } = panel.current;
1479
+ if (units === "pixels") {
1480
+ const groupSizePixels = getAvailableGroupSizePixels(groupId);
1481
+ minSize = minSize / groupSizePixels * 100;
1482
+ if (maxSize != null) {
1483
+ maxSize = maxSize / groupSizePixels * 100;
1484
+ }
1485
+ }
1213
1486
  const panelsArray = panelsMapToSortedArray(panels);
1214
1487
  const index = panelsArray.indexOf(panel);
1215
1488
  if (index < 0) {
@@ -1220,7 +1493,7 @@ function PanelGroupWithForwardedRef({
1220
1493
  return;
1221
1494
  }
1222
1495
  if (collapsible && nextSize === collapsedSize) ; else {
1223
- nextSize = Math.min(maxSize, Math.max(minSize, nextSize));
1496
+ nextSize = Math.min(maxSize != null ? maxSize : 100, Math.max(minSize, nextSize));
1224
1497
  }
1225
1498
  const [idBefore, idAfter] = getBeforeAndAfterIds(id, panelsArray);
1226
1499
  if (idBefore == null || idAfter == null) {
@@ -1228,7 +1501,7 @@ function PanelGroupWithForwardedRef({
1228
1501
  }
1229
1502
  const isLastPanel = index === panelsArray.length - 1;
1230
1503
  const delta = isLastPanel ? currentSize - nextSize : nextSize - currentSize;
1231
- const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1504
+ const nextSizes = adjustByDelta(null, committedValuesRef.current, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1232
1505
  if (prevSizes !== nextSizes) {
1233
1506
  const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1234
1507
  setSizes(nextSizes);
@@ -1243,6 +1516,7 @@ function PanelGroupWithForwardedRef({
1243
1516
  collapsePanel,
1244
1517
  direction,
1245
1518
  expandPanel,
1519
+ getPanelSize,
1246
1520
  getPanelStyle,
1247
1521
  groupId,
1248
1522
  registerPanel,
@@ -1264,8 +1538,9 @@ function PanelGroupWithForwardedRef({
1264
1538
  setActiveHandleId(null);
1265
1539
  initialDragStateRef.current = null;
1266
1540
  },
1541
+ units,
1267
1542
  unregisterPanel
1268
- }), [activeHandleId, collapsePanel, direction, expandPanel, getPanelStyle, groupId, registerPanel, registerResizeHandle, resizePanel, unregisterPanel]);
1543
+ }), [activeHandleId, collapsePanel, direction, expandPanel, getPanelSize, getPanelStyle, groupId, registerPanel, registerResizeHandle, resizePanel, units, unregisterPanel]);
1269
1544
  const style = {
1270
1545
  display: "flex",
1271
1546
  flexDirection: direction === "horizontal" ? "row" : "column",
@@ -1280,6 +1555,7 @@ function PanelGroupWithForwardedRef({
1280
1555
  "data-panel-group": "",
1281
1556
  "data-panel-group-direction": direction,
1282
1557
  "data-panel-group-id": groupId,
1558
+ "data-panel-group-units": units,
1283
1559
  style: {
1284
1560
  ...style,
1285
1561
  ...styleFromProps
@@ -1292,10 +1568,6 @@ const PanelGroup = forwardRef((props, ref) => createElement(PanelGroupWithForwar
1292
1568
  ...props,
1293
1569
  forwardedRef: ref
1294
1570
  }));
1295
-
1296
- // Workaround for Parcel scope hoisting (which renames objects/functions).
1297
- // Casting to :any is required to avoid corrupting the generated TypeScript types.
1298
- // See github.com/parcel-bundler/parcel/issues/8724
1299
1571
  PanelGroupWithForwardedRef.displayName = "PanelGroup";
1300
1572
  PanelGroup.displayName = "forwardRef(PanelGroup)";
1301
1573
 
@@ -1431,12 +1703,9 @@ function PanelResizeHandle({
1431
1703
  tabIndex: 0
1432
1704
  });
1433
1705
  }
1434
-
1435
- // Workaround for Parcel scope hoisting (which renames objects/functions).
1436
- // Casting to :any is required to avoid corrupting the generated TypeScript types.
1437
- // See github.com/parcel-bundler/parcel/issues/8724
1438
1706
  PanelResizeHandle.displayName = "PanelResizeHandle";
1439
1707
 
1440
1708
  exports.Panel = Panel;
1441
1709
  exports.PanelGroup = PanelGroup;
1442
1710
  exports.PanelResizeHandle = PanelResizeHandle;
1711
+ exports.getAvailableGroupSizePixels = getAvailableGroupSizePixels;