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
@@ -1,27 +1,29 @@
1
- import { InitialDragState } from "../PanelGroup";
1
+ import { isDevelopment } from "#is-development";
2
+ import { CommittedValues, InitialDragState } from "../PanelGroup";
2
3
  import { PRECISION } from "../constants";
3
- import { PanelData, ResizeEvent } from "../types";
4
+ import { PanelData, ResizeEvent, Units } from "../types";
4
5
 
5
6
  export function adjustByDelta(
6
7
  event: ResizeEvent | null,
7
- panels: Map<string, PanelData>,
8
+ committedValues: CommittedValues,
8
9
  idBefore: string,
9
10
  idAfter: string,
10
- delta: number,
11
+ deltaPixels: number,
11
12
  prevSizes: number[],
12
13
  panelSizeBeforeCollapse: Map<string, number>,
13
14
  initialDragState: InitialDragState | null
14
15
  ): number[] {
16
+ const { id: groupId, panels, units } = committedValues;
17
+
18
+ const groupSizePixels =
19
+ units === "pixels" ? getAvailableGroupSizePixels(groupId) : NaN;
20
+
15
21
  const { sizes: initialSizes } = initialDragState || {};
16
22
 
17
23
  // If we're resizing by mouse or touch, use the initial sizes as a base.
18
24
  // This has the benefit of causing force-collapsed panels to spring back open if drag is reversed.
19
25
  const baseSizes = initialSizes || prevSizes;
20
26
 
21
- if (delta === 0) {
22
- return baseSizes;
23
- }
24
-
25
27
  const panelsArray = panelsMapToSortedArray(panels);
26
28
 
27
29
  const nextSizes = baseSizes.concat();
@@ -38,14 +40,21 @@ export function adjustByDelta(
38
40
 
39
41
  // Max-bounds check the panel being expanded first.
40
42
  {
41
- const pivotId = delta < 0 ? idAfter : idBefore;
43
+ const pivotId = deltaPixels < 0 ? idAfter : idBefore;
42
44
  const index = panelsArray.findIndex(
43
45
  (panel) => panel.current.id === pivotId
44
46
  );
45
47
  const panel = panelsArray[index];
46
48
  const baseSize = baseSizes[index];
47
49
 
48
- const nextSize = safeResizePanel(panel, Math.abs(delta), baseSize, event);
50
+ const nextSize = safeResizePanel(
51
+ units,
52
+ groupSizePixels,
53
+ panel,
54
+ baseSize,
55
+ baseSize + Math.abs(deltaPixels),
56
+ event
57
+ );
49
58
  if (baseSize === nextSize) {
50
59
  // If there's no room for the pivot panel to grow, we can ignore this drag update.
51
60
  return baseSizes;
@@ -54,22 +63,24 @@ export function adjustByDelta(
54
63
  panelSizeBeforeCollapse.set(pivotId, baseSize);
55
64
  }
56
65
 
57
- delta = delta < 0 ? baseSize - nextSize : nextSize - baseSize;
66
+ deltaPixels = deltaPixels < 0 ? baseSize - nextSize : nextSize - baseSize;
58
67
  }
59
68
  }
60
69
 
61
- let pivotId = delta < 0 ? idBefore : idAfter;
70
+ let pivotId = deltaPixels < 0 ? idBefore : idAfter;
62
71
  let index = panelsArray.findIndex((panel) => panel.current.id === pivotId);
63
72
  while (true) {
64
73
  const panel = panelsArray[index];
65
74
  const baseSize = baseSizes[index];
66
75
 
67
- const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
76
+ const deltaRemaining = Math.abs(deltaPixels) - Math.abs(deltaApplied);
68
77
 
69
78
  const nextSize = safeResizePanel(
79
+ units,
80
+ groupSizePixels,
70
81
  panel,
71
- 0 - deltaRemaining,
72
82
  baseSize,
83
+ baseSize - deltaRemaining,
73
84
  event
74
85
  );
75
86
  if (baseSize !== nextSize) {
@@ -84,15 +95,19 @@ export function adjustByDelta(
84
95
  if (
85
96
  deltaApplied
86
97
  .toPrecision(PRECISION)
87
- .localeCompare(Math.abs(delta).toPrecision(PRECISION), undefined, {
88
- numeric: true,
89
- }) >= 0
98
+ .localeCompare(
99
+ Math.abs(deltaPixels).toPrecision(PRECISION),
100
+ undefined,
101
+ {
102
+ numeric: true,
103
+ }
104
+ ) >= 0
90
105
  ) {
91
106
  break;
92
107
  }
93
108
  }
94
109
 
95
- if (delta < 0) {
110
+ if (deltaPixels < 0) {
96
111
  if (--index < 0) {
97
112
  break;
98
113
  }
@@ -110,7 +125,7 @@ export function adjustByDelta(
110
125
  }
111
126
 
112
127
  // Adjust the pivot panel before, but only by the amount that surrounding panels were able to shrink/contract.
113
- pivotId = delta < 0 ? idAfter : idBefore;
128
+ pivotId = deltaPixels < 0 ? idAfter : idBefore;
114
129
  index = panelsArray.findIndex((panel) => panel.current.id === pivotId);
115
130
  nextSizes[index] = baseSizes[index] + deltaApplied;
116
131
 
@@ -159,6 +174,115 @@ export function callPanelCallbacks(
159
174
  });
160
175
  }
161
176
 
177
+ export function calculateDefaultLayout({
178
+ groupId,
179
+ panels,
180
+ units,
181
+ }: {
182
+ groupId: string;
183
+ panels: Map<string, PanelData>;
184
+ units: Units;
185
+ }): number[] {
186
+ const groupSizePixels =
187
+ units === "pixels" ? getAvailableGroupSizePixels(groupId) : NaN;
188
+ const panelsArray = panelsMapToSortedArray(panels);
189
+ const sizes = Array<number>(panelsArray.length);
190
+
191
+ let numPanelsWithSizes = 0;
192
+ let remainingSize = 100;
193
+
194
+ // Assigning default sizes requires a couple of passes:
195
+ // First, all panels with defaultSize should be set as-is
196
+ for (let index = 0; index < panelsArray.length; index++) {
197
+ const panel = panelsArray[index];
198
+ const { defaultSize } = panel.current;
199
+
200
+ if (defaultSize != null) {
201
+ numPanelsWithSizes++;
202
+
203
+ sizes[index] =
204
+ units === "pixels"
205
+ ? (defaultSize / groupSizePixels) * 100
206
+ : defaultSize;
207
+
208
+ remainingSize -= sizes[index];
209
+ }
210
+ }
211
+
212
+ // Remaining total size should be distributed evenly between panels
213
+ // This may require two passes, depending on min/max constraints
214
+ for (let index = 0; index < panelsArray.length; index++) {
215
+ const panel = panelsArray[index];
216
+ let { defaultSize, id, maxSize, minSize } = panel.current;
217
+ if (defaultSize != null) {
218
+ continue;
219
+ }
220
+
221
+ if (units === "pixels") {
222
+ minSize = (minSize / groupSizePixels) * 100;
223
+ if (maxSize != null) {
224
+ maxSize = (maxSize / groupSizePixels) * 100;
225
+ }
226
+ }
227
+
228
+ const remainingPanels = panelsArray.length - numPanelsWithSizes;
229
+ const size = Math.min(
230
+ maxSize != null ? maxSize : 100,
231
+ Math.max(minSize, remainingSize / remainingPanels)
232
+ );
233
+
234
+ sizes[index] = size;
235
+ numPanelsWithSizes++;
236
+ remainingSize -= size;
237
+ }
238
+
239
+ // If there is additional, left over space, assign it to any panel(s) that permits it
240
+ // (It's not worth taking multiple additional passes to evenly distribute)
241
+ if (remainingSize !== 0) {
242
+ for (let index = 0; index < panelsArray.length; index++) {
243
+ const panel = panelsArray[index];
244
+ let { maxSize, minSize } = panel.current;
245
+
246
+ if (units === "pixels") {
247
+ minSize = (minSize / groupSizePixels) * 100;
248
+ if (maxSize != null) {
249
+ maxSize = (maxSize / groupSizePixels) * 100;
250
+ }
251
+ }
252
+
253
+ const size = Math.min(
254
+ maxSize != null ? maxSize : 100,
255
+ Math.max(minSize, sizes[index] + remainingSize)
256
+ );
257
+
258
+ if (size !== sizes[index]) {
259
+ remainingSize -= size - sizes[index];
260
+ sizes[index] = size;
261
+
262
+ // Fuzzy comparison to account for imprecise floating point math
263
+ if (Math.abs(remainingSize).toFixed(3) === "0.000") {
264
+ break;
265
+ }
266
+ }
267
+ }
268
+ }
269
+
270
+ // Finally, if there is still left-over size, log an error
271
+ if (Math.abs(remainingSize).toFixed(3) !== "0.000") {
272
+ if (isDevelopment) {
273
+ console.error(
274
+ `Invalid panel group configuration; default panel sizes should total 100% but was ${(
275
+ 100 - remainingSize
276
+ ).toFixed(
277
+ 1
278
+ )}%. This can cause the cursor to become misaligned while dragging.`
279
+ );
280
+ }
281
+ }
282
+
283
+ return sizes;
284
+ }
285
+
162
286
  export function getBeforeAndAfterIds(
163
287
  id: string,
164
288
  panelsArray: PanelData[]
@@ -179,6 +303,33 @@ export function getBeforeAndAfterIds(
179
303
  return [idBefore, idAfter];
180
304
  }
181
305
 
306
+ export function getAvailableGroupSizePixels(groupId: string): number {
307
+ const panelGroupElement = getPanelGroup(groupId);
308
+ if (panelGroupElement == null) {
309
+ return NaN;
310
+ }
311
+
312
+ const direction = panelGroupElement.getAttribute(
313
+ "data-panel-group-direction"
314
+ );
315
+ const resizeHandles = getResizeHandlesForGroup(groupId);
316
+ if (direction === "horizontal") {
317
+ return (
318
+ panelGroupElement.offsetWidth -
319
+ resizeHandles.reduce((accumulated, handle) => {
320
+ return accumulated + handle.offsetWidth;
321
+ }, 0)
322
+ );
323
+ } else {
324
+ return (
325
+ panelGroupElement.offsetHeight -
326
+ resizeHandles.reduce((accumulated, handle) => {
327
+ return accumulated + handle.offsetHeight;
328
+ }, 0)
329
+ );
330
+ }
331
+ }
332
+
182
333
  // This method returns a number between 1 and 100 representing
183
334
  // the % of the group's overall space this panel should occupy.
184
335
  export function getFlexGrow(
@@ -280,20 +431,28 @@ export function panelsMapToSortedArray(
280
431
  });
281
432
  }
282
433
 
283
- function safeResizePanel(
434
+ export function safeResizePanel(
435
+ units: Units,
436
+ groupSizePixels: number,
284
437
  panel: PanelData,
285
- delta: number,
286
438
  prevSize: number,
287
- event: ResizeEvent | null
439
+ nextSize: number,
440
+ event: ResizeEvent | null = null
288
441
  ): number {
289
- const nextSizeUnsafe = prevSize + delta;
442
+ let { collapsedSize, collapsible, maxSize, minSize } = panel.current;
290
443
 
291
- const { collapsedSize, collapsible, maxSize, minSize } = panel.current;
444
+ if (units === "pixels") {
445
+ collapsedSize = (collapsedSize / groupSizePixels) * 100;
446
+ if (maxSize != null) {
447
+ maxSize = (maxSize / groupSizePixels) * 100;
448
+ }
449
+ minSize = (minSize / groupSizePixels) * 100;
450
+ }
292
451
 
293
452
  if (collapsible) {
294
453
  if (prevSize > collapsedSize) {
295
454
  // Mimic VS COde behavior; collapse a panel if it's smaller than half of its min-size
296
- if (nextSizeUnsafe <= minSize / 2 + collapsedSize) {
455
+ if (nextSize <= minSize / 2 + collapsedSize) {
297
456
  return collapsedSize;
298
457
  }
299
458
  } else {
@@ -302,14 +461,154 @@ function safeResizePanel(
302
461
  // Keyboard events should expand a collapsed panel to the min size,
303
462
  // but mouse events should wait until the panel has reached its min size
304
463
  // to avoid a visual flickering when dragging between collapsed and min size.
305
- if (nextSizeUnsafe < minSize) {
464
+ if (nextSize < minSize) {
306
465
  return collapsedSize;
307
466
  }
308
467
  }
309
468
  }
310
469
  }
311
470
 
312
- const nextSize = Math.min(maxSize, Math.max(minSize, nextSizeUnsafe));
471
+ return Math.min(maxSize != null ? maxSize : 100, Math.max(minSize, nextSize));
472
+ }
473
+
474
+ export function validatePanelProps(units: Units, panelData: PanelData) {
475
+ const { collapsible, defaultSize, maxSize, minSize } = panelData.current;
476
+
477
+ // Basic props validation
478
+ if (minSize < 0 || (units === "percentages" && minSize > 100)) {
479
+ if (isDevelopment) {
480
+ console.error(`Invalid Panel minSize provided, ${minSize}`);
481
+ }
482
+
483
+ panelData.current.minSize = 0;
484
+ }
485
+
486
+ if (maxSize != null) {
487
+ if (maxSize < 0 || (units === "percentages" && maxSize > 100)) {
488
+ if (isDevelopment) {
489
+ console.error(`Invalid Panel maxSize provided, ${maxSize}`);
490
+ }
491
+
492
+ panelData.current.maxSize = null;
493
+ }
494
+ }
495
+
496
+ if (defaultSize !== null) {
497
+ if (defaultSize < 0 || (units === "percentages" && defaultSize > 100)) {
498
+ if (isDevelopment) {
499
+ console.error(`Invalid Panel defaultSize provided, ${defaultSize}`);
500
+ }
501
+
502
+ panelData.current.defaultSize = null;
503
+ } else if (defaultSize < minSize && !collapsible) {
504
+ if (isDevelopment) {
505
+ console.error(
506
+ `Panel minSize (${minSize}) cannot be greater than defaultSize (${defaultSize})`
507
+ );
508
+ }
509
+
510
+ panelData.current.defaultSize = minSize;
511
+ } else if (maxSize != null && defaultSize > maxSize) {
512
+ if (isDevelopment) {
513
+ console.error(
514
+ `Panel maxSize (${maxSize}) cannot be less than defaultSize (${defaultSize})`
515
+ );
516
+ }
517
+
518
+ panelData.current.defaultSize = maxSize;
519
+ }
520
+ }
521
+ }
522
+
523
+ export function validatePanelGroupLayout({
524
+ groupId,
525
+ panels,
526
+ nextSizes,
527
+ prevSizes,
528
+ units,
529
+ }: {
530
+ groupId: string;
531
+ panels: Map<string, PanelData>;
532
+ nextSizes: number[];
533
+ prevSizes: number[];
534
+ units: Units;
535
+ }): number[] {
536
+ // Clone because this method modifies
537
+ nextSizes = [...nextSizes];
538
+
539
+ const panelsArray = panelsMapToSortedArray(panels);
540
+
541
+ const groupSizePixels =
542
+ units === "pixels" ? getAvailableGroupSizePixels(groupId) : NaN;
543
+
544
+ let remainingSize = 0;
545
+
546
+ // First, check all of the proposed sizes against the min/max constraints
547
+ for (let index = 0; index < panelsArray.length; index++) {
548
+ const panel = panelsArray[index];
549
+ const prevSize = prevSizes[index];
550
+ const nextSize = nextSizes[index];
551
+ const safeNextSize = safeResizePanel(
552
+ units,
553
+ groupSizePixels,
554
+ panel,
555
+ prevSize,
556
+ nextSize
557
+ );
558
+ if (nextSize != safeNextSize) {
559
+ remainingSize += nextSize - safeNextSize;
560
+ nextSizes[index] = safeNextSize;
561
+
562
+ if (isDevelopment) {
563
+ console.error(
564
+ `Invalid size (${nextSize}) specified for Panel "${panel.current.id}" given the panel's min/max size constraints`
565
+ );
566
+ }
567
+ }
568
+ }
569
+
570
+ // If there is additional, left over space, assign it to any panel(s) that permits it
571
+ // (It's not worth taking multiple additional passes to evenly distribute)
572
+ if (remainingSize.toFixed(3) !== "0.000") {
573
+ for (let index = 0; index < panelsArray.length; index++) {
574
+ const panel = panelsArray[index];
575
+
576
+ let { maxSize, minSize } = panel.current;
577
+
578
+ if (units === "pixels") {
579
+ minSize = (minSize / groupSizePixels) * 100;
580
+ if (maxSize != null) {
581
+ maxSize = (maxSize / groupSizePixels) * 100;
582
+ }
583
+ }
584
+
585
+ const size = Math.min(
586
+ maxSize != null ? maxSize : 100,
587
+ Math.max(minSize, nextSizes[index] + remainingSize)
588
+ );
589
+
590
+ if (size !== nextSizes[index]) {
591
+ remainingSize -= size - nextSizes[index];
592
+ nextSizes[index] = size;
593
+
594
+ // Fuzzy comparison to account for imprecise floating point math
595
+ if (Math.abs(remainingSize).toFixed(3) === "0.000") {
596
+ break;
597
+ }
598
+ }
599
+ }
600
+ }
601
+
602
+ // If we still have remainder, the requested layout wasn't valid and we should warn about it
603
+ if (remainingSize.toFixed(3) !== "0.000") {
604
+ if (isDevelopment) {
605
+ console.error(
606
+ `"Invalid panel group configuration; default panel sizes should total 100% but was ${
607
+ 100 - remainingSize
608
+ }%`
609
+ );
610
+ }
611
+ }
313
612
 
314
- return nextSize;
613
+ return nextSizes;
315
614
  }