react-resizable-panels 0.0.32 → 0.0.34
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.
- package/CHANGELOG.md +10 -0
- package/README.md +8 -1
- package/dist/react-resizable-panels.d.ts +6 -1
- package/dist/react-resizable-panels.d.ts.map +1 -1
- package/dist/react-resizable-panels.js +115 -56
- package/dist/react-resizable-panels.js.map +1 -1
- package/dist/react-resizable-panels.module.js +115 -56
- package/dist/react-resizable-panels.module.js.map +1 -1
- package/package.json +1 -1
- package/src/PanelGroup.ts +116 -29
- package/src/hooks/useWindowSplitterBehavior.ts +3 -1
- package/src/index.ts +2 -0
- package/src/types.ts +5 -0
- package/src/utils/arrays.ts +13 -0
- package/src/utils/coordinates.ts +27 -8
- package/src/utils/group.ts +59 -27
- package/src/utils/serialization.ts +11 -11
package/src/PanelGroup.ts
CHANGED
|
@@ -11,15 +11,27 @@ import {
|
|
|
11
11
|
} from "react";
|
|
12
12
|
|
|
13
13
|
import { PanelGroupContext } from "./PanelContexts";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
Direction,
|
|
16
|
+
PanelData,
|
|
17
|
+
PanelGroupOnLayout,
|
|
18
|
+
ResizeEvent,
|
|
19
|
+
PanelGroupStorage,
|
|
20
|
+
} from "./types";
|
|
15
21
|
import { loadPanelLayout, savePanelGroupLayout } from "./utils/serialization";
|
|
16
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
getDragOffset,
|
|
24
|
+
getMovement,
|
|
25
|
+
isMouseEvent,
|
|
26
|
+
isTouchEvent,
|
|
27
|
+
} from "./utils/coordinates";
|
|
17
28
|
import {
|
|
18
29
|
adjustByDelta,
|
|
19
30
|
callPanelCallbacks,
|
|
20
31
|
getBeforeAndAfterIds,
|
|
21
32
|
getFlexGrow,
|
|
22
33
|
getPanelGroup,
|
|
34
|
+
getResizeHandle,
|
|
23
35
|
getResizeHandlePanelIds,
|
|
24
36
|
panelsMapToSortedArray,
|
|
25
37
|
} from "./utils/group";
|
|
@@ -28,10 +40,26 @@ import useUniqueId from "./hooks/useUniqueId";
|
|
|
28
40
|
import { useWindowSplitterPanelGroupBehavior } from "./hooks/useWindowSplitterBehavior";
|
|
29
41
|
import { resetGlobalCursorStyle, setGlobalCursorStyle } from "./utils/cursor";
|
|
30
42
|
import debounce from "./utils/debounce";
|
|
43
|
+
import { areEqual } from "./utils/arrays";
|
|
31
44
|
|
|
32
45
|
// Limit the frequency of localStorage updates.
|
|
33
46
|
const savePanelGroupLayoutDebounced = debounce(savePanelGroupLayout, 100);
|
|
34
47
|
|
|
48
|
+
function throwServerError() {
|
|
49
|
+
throw new Error('PanelGroup "storage" prop required for server rendering.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const defaultStorage: PanelGroupStorage = {
|
|
53
|
+
getItem:
|
|
54
|
+
typeof localStorage !== "undefined"
|
|
55
|
+
? (name: string) => localStorage.getItem(name)
|
|
56
|
+
: (throwServerError as any),
|
|
57
|
+
setItem:
|
|
58
|
+
typeof localStorage !== "undefined"
|
|
59
|
+
? (name: string, value: string) => localStorage.setItem(name, value)
|
|
60
|
+
: (throwServerError as any),
|
|
61
|
+
};
|
|
62
|
+
|
|
35
63
|
export type CommittedValues = {
|
|
36
64
|
direction: Direction;
|
|
37
65
|
panels: Map<string, PanelData>;
|
|
@@ -40,6 +68,19 @@ export type CommittedValues = {
|
|
|
40
68
|
|
|
41
69
|
export type PanelDataMap = Map<string, PanelData>;
|
|
42
70
|
|
|
71
|
+
// Initial drag state serves a few purposes:
|
|
72
|
+
// * dragOffset:
|
|
73
|
+
// Resize is calculated by the distance between the current pointer event and the resize handle being "dragged"
|
|
74
|
+
// This value accounts for the initial offset when the touch/click starts, so the handle doesn't appear to "jump"
|
|
75
|
+
// * dragHandleRect, sizes:
|
|
76
|
+
// When resizing is done via mouse/touch event– some initial state is stored
|
|
77
|
+
// so that any panels that contract will also expand if drag direction is reversed.
|
|
78
|
+
export type InitialDragState = {
|
|
79
|
+
dragHandleRect: DOMRect;
|
|
80
|
+
dragOffset: number;
|
|
81
|
+
sizes: number[];
|
|
82
|
+
};
|
|
83
|
+
|
|
43
84
|
// TODO
|
|
44
85
|
// Within an active drag, remember original positions to refine more easily on expand.
|
|
45
86
|
// Look at what the Chrome devtools Sources does.
|
|
@@ -51,6 +92,7 @@ export type PanelGroupProps = {
|
|
|
51
92
|
direction: Direction;
|
|
52
93
|
id?: string | null;
|
|
53
94
|
onLayout?: PanelGroupOnLayout;
|
|
95
|
+
storage?: PanelGroupStorage;
|
|
54
96
|
style?: CSSProperties;
|
|
55
97
|
tagName?: ElementType;
|
|
56
98
|
};
|
|
@@ -62,6 +104,7 @@ export function PanelGroup({
|
|
|
62
104
|
direction,
|
|
63
105
|
id: idFromProps = null,
|
|
64
106
|
onLayout = null,
|
|
107
|
+
storage = defaultStorage,
|
|
65
108
|
style: styleFromProps = {},
|
|
66
109
|
tagName: Type = "div",
|
|
67
110
|
}: PanelGroupProps) {
|
|
@@ -70,6 +113,11 @@ export function PanelGroup({
|
|
|
70
113
|
const [activeHandleId, setActiveHandleId] = useState<string | null>(null);
|
|
71
114
|
const [panels, setPanels] = useState<PanelDataMap>(new Map());
|
|
72
115
|
|
|
116
|
+
// When resizing is done via mouse/touch event–
|
|
117
|
+
// We store the initial Panel sizes in this ref, and apply move deltas to them instead of to the current sizes.
|
|
118
|
+
// This has the benefit of causing force-collapsed panels to spring back open if drag is reversed.
|
|
119
|
+
const initialDragStateRef = useRef<InitialDragState | null>(null);
|
|
120
|
+
|
|
73
121
|
// Use a ref to guard against users passing inline props
|
|
74
122
|
const callbacksRef = useRef<{
|
|
75
123
|
onLayout: PanelGroupOnLayout | null;
|
|
@@ -81,13 +129,11 @@ export function PanelGroup({
|
|
|
81
129
|
// 0-1 values representing the relative size of each panel.
|
|
82
130
|
const [sizes, setSizes] = useState<number[]>([]);
|
|
83
131
|
|
|
84
|
-
// Resize is calculated by the distance between the current pointer event and the resize handle being "dragged"
|
|
85
|
-
// This value accounts for the initial offset when the touch/click starts, so the handle doesn't appear to "jump"
|
|
86
|
-
const dragOffsetRef = useRef<number>(0);
|
|
87
|
-
|
|
88
132
|
// Used to support imperative collapse/expand API.
|
|
89
133
|
const panelSizeBeforeCollapse = useRef<Map<string, number>>(new Map());
|
|
90
134
|
|
|
135
|
+
const prevDeltaRef = useRef<number>(0);
|
|
136
|
+
|
|
91
137
|
// Store committed values to avoid unnecessarily re-running memoization/effects functions.
|
|
92
138
|
const committedValuesRef = useRef<CommittedValues>({
|
|
93
139
|
direction,
|
|
@@ -155,7 +201,7 @@ export function PanelGroup({
|
|
|
155
201
|
let defaultSizes: number[] | undefined = undefined;
|
|
156
202
|
if (autoSaveId) {
|
|
157
203
|
const panelsArray = panelsMapToSortedArray(panels);
|
|
158
|
-
defaultSizes = loadPanelLayout(autoSaveId, panelsArray);
|
|
204
|
+
defaultSizes = loadPanelLayout(autoSaveId, panelsArray, storage);
|
|
159
205
|
}
|
|
160
206
|
|
|
161
207
|
if (defaultSizes != null) {
|
|
@@ -213,7 +259,7 @@ export function PanelGroup({
|
|
|
213
259
|
|
|
214
260
|
const panelsArray = panelsMapToSortedArray(panels);
|
|
215
261
|
|
|
216
|
-
savePanelGroupLayoutDebounced(autoSaveId, panelsArray, sizes);
|
|
262
|
+
savePanelGroupLayoutDebounced(autoSaveId, panelsArray, sizes, storage);
|
|
217
263
|
}
|
|
218
264
|
}, [autoSaveId, panels, sizes]);
|
|
219
265
|
|
|
@@ -295,7 +341,7 @@ export function PanelGroup({
|
|
|
295
341
|
panelsArray,
|
|
296
342
|
direction,
|
|
297
343
|
prevSizes,
|
|
298
|
-
|
|
344
|
+
initialDragStateRef.current
|
|
299
345
|
);
|
|
300
346
|
if (movement === 0) {
|
|
301
347
|
return;
|
|
@@ -308,35 +354,53 @@ export function PanelGroup({
|
|
|
308
354
|
const delta = (movement / size) * 100;
|
|
309
355
|
|
|
310
356
|
const nextSizes = adjustByDelta(
|
|
357
|
+
event,
|
|
311
358
|
panels,
|
|
312
359
|
idBefore,
|
|
313
360
|
idAfter,
|
|
314
361
|
delta,
|
|
315
362
|
prevSizes,
|
|
316
|
-
panelSizeBeforeCollapse.current
|
|
363
|
+
panelSizeBeforeCollapse.current,
|
|
364
|
+
initialDragStateRef.current
|
|
317
365
|
);
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
366
|
+
|
|
367
|
+
const sizesChanged = !areEqual(prevSizes, nextSizes);
|
|
368
|
+
|
|
369
|
+
// Don't update cursor for resizes triggered by keyboard interactions.
|
|
370
|
+
if (isMouseEvent(event) || isTouchEvent(event)) {
|
|
371
|
+
// Watch for multiple subsequent deltas; this might occur for tiny cursor movements.
|
|
372
|
+
// In this case, Panel sizes might not change–
|
|
373
|
+
// but updating cursor in this scenario would cause a flicker.
|
|
374
|
+
if (prevDeltaRef.current != delta) {
|
|
375
|
+
if (!sizesChanged) {
|
|
376
|
+
// If the pointer has moved too far to resize the panel any further,
|
|
377
|
+
// update the cursor style for a visual clue.
|
|
378
|
+
// This mimics VS Code behavior.
|
|
379
|
+
|
|
380
|
+
if (isHorizontal) {
|
|
381
|
+
setGlobalCursorStyle(
|
|
382
|
+
movement < 0 ? "horizontal-min" : "horizontal-max"
|
|
383
|
+
);
|
|
384
|
+
} else {
|
|
385
|
+
setGlobalCursorStyle(
|
|
386
|
+
movement < 0 ? "vertical-min" : "vertical-max"
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
// Reset the cursor style to the the normal resize cursor.
|
|
391
|
+
setGlobalCursorStyle(isHorizontal ? "horizontal" : "vertical");
|
|
392
|
+
}
|
|
330
393
|
}
|
|
331
|
-
}
|
|
332
|
-
// Reset the cursor style to the the normal resize cursor.
|
|
333
|
-
setGlobalCursorStyle(isHorizontal ? "horizontal" : "vertical");
|
|
394
|
+
}
|
|
334
395
|
|
|
396
|
+
if (sizesChanged) {
|
|
335
397
|
// If resize change handlers have been declared, this is the time to call them.
|
|
336
398
|
callPanelCallbacks(panelsArray, prevSizes, nextSizes);
|
|
337
399
|
|
|
338
400
|
setSizes(nextSizes);
|
|
339
401
|
}
|
|
402
|
+
|
|
403
|
+
prevDeltaRef.current = delta;
|
|
340
404
|
};
|
|
341
405
|
|
|
342
406
|
return resizeHandler;
|
|
@@ -389,12 +453,14 @@ export function PanelGroup({
|
|
|
389
453
|
const delta = isLastPanel ? currentSize : 0 - currentSize;
|
|
390
454
|
|
|
391
455
|
const nextSizes = adjustByDelta(
|
|
456
|
+
null,
|
|
392
457
|
panels,
|
|
393
458
|
idBefore,
|
|
394
459
|
idAfter,
|
|
395
460
|
delta,
|
|
396
461
|
prevSizes,
|
|
397
|
-
panelSizeBeforeCollapse.current
|
|
462
|
+
panelSizeBeforeCollapse.current,
|
|
463
|
+
null
|
|
398
464
|
);
|
|
399
465
|
if (prevSizes !== nextSizes) {
|
|
400
466
|
// If resize change handlers have been declared, this is the time to call them.
|
|
@@ -440,12 +506,14 @@ export function PanelGroup({
|
|
|
440
506
|
const delta = isLastPanel ? 0 - sizeBeforeCollapse : sizeBeforeCollapse;
|
|
441
507
|
|
|
442
508
|
const nextSizes = adjustByDelta(
|
|
509
|
+
null,
|
|
443
510
|
panels,
|
|
444
511
|
idBefore,
|
|
445
512
|
idAfter,
|
|
446
513
|
delta,
|
|
447
514
|
prevSizes,
|
|
448
|
-
panelSizeBeforeCollapse.current
|
|
515
|
+
panelSizeBeforeCollapse.current,
|
|
516
|
+
null
|
|
449
517
|
);
|
|
450
518
|
if (prevSizes !== nextSizes) {
|
|
451
519
|
// If resize change handlers have been declared, this is the time to call them.
|
|
@@ -475,6 +543,12 @@ export function PanelGroup({
|
|
|
475
543
|
return;
|
|
476
544
|
}
|
|
477
545
|
|
|
546
|
+
if (panel.collapsible && nextSize === 0) {
|
|
547
|
+
// This is a valid resize state.
|
|
548
|
+
} else {
|
|
549
|
+
nextSize = Math.min(panel.maxSize, Math.max(panel.minSize, nextSize));
|
|
550
|
+
}
|
|
551
|
+
|
|
478
552
|
const [idBefore, idAfter] = getBeforeAndAfterIds(id, panelsArray);
|
|
479
553
|
if (idBefore == null || idAfter == null) {
|
|
480
554
|
return;
|
|
@@ -484,12 +558,14 @@ export function PanelGroup({
|
|
|
484
558
|
const delta = isLastPanel ? currentSize - nextSize : nextSize - currentSize;
|
|
485
559
|
|
|
486
560
|
const nextSizes = adjustByDelta(
|
|
561
|
+
null,
|
|
487
562
|
panels,
|
|
488
563
|
idBefore,
|
|
489
564
|
idAfter,
|
|
490
565
|
delta,
|
|
491
566
|
prevSizes,
|
|
492
|
-
panelSizeBeforeCollapse.current
|
|
567
|
+
panelSizeBeforeCollapse.current,
|
|
568
|
+
null
|
|
493
569
|
);
|
|
494
570
|
if (prevSizes !== nextSizes) {
|
|
495
571
|
// If resize change handlers have been declared, this is the time to call them.
|
|
@@ -513,11 +589,21 @@ export function PanelGroup({
|
|
|
513
589
|
startDragging: (id: string, event: ResizeEvent) => {
|
|
514
590
|
setActiveHandleId(id);
|
|
515
591
|
|
|
516
|
-
|
|
592
|
+
if (isMouseEvent(event) || isTouchEvent(event)) {
|
|
593
|
+
const handleElement = getResizeHandle(id);
|
|
594
|
+
|
|
595
|
+
initialDragStateRef.current = {
|
|
596
|
+
dragHandleRect: handleElement.getBoundingClientRect(),
|
|
597
|
+
dragOffset: getDragOffset(event, id, direction),
|
|
598
|
+
sizes: committedValuesRef.current.sizes,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
517
601
|
},
|
|
518
602
|
stopDragging: () => {
|
|
519
603
|
resetGlobalCursorStyle();
|
|
520
604
|
setActiveHandleId(null);
|
|
605
|
+
|
|
606
|
+
initialDragStateRef.current = null;
|
|
521
607
|
},
|
|
522
608
|
unregisterPanel,
|
|
523
609
|
}),
|
|
@@ -547,6 +633,7 @@ export function PanelGroup({
|
|
|
547
633
|
children: createElement(Type, {
|
|
548
634
|
children,
|
|
549
635
|
className: classNameFromProps,
|
|
636
|
+
"data-panel-group": "",
|
|
550
637
|
"data-panel-group-direction": direction,
|
|
551
638
|
"data-panel-group-id": groupId,
|
|
552
639
|
style: { ...style, ...styleFromProps },
|
|
@@ -102,12 +102,14 @@ export function useWindowSplitterPanelGroupBehavior({
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
const nextSizes = adjustByDelta(
|
|
105
|
+
event,
|
|
105
106
|
panels,
|
|
106
107
|
idBefore,
|
|
107
108
|
idAfter,
|
|
108
109
|
delta,
|
|
109
110
|
sizes,
|
|
110
|
-
panelSizeBeforeCollapse.current
|
|
111
|
+
panelSizeBeforeCollapse.current,
|
|
112
|
+
null
|
|
111
113
|
);
|
|
112
114
|
if (sizes !== nextSizes) {
|
|
113
115
|
setSizes(nextSizes);
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { PanelResizeHandle } from "./PanelResizeHandle";
|
|
|
5
5
|
import type { ImperativePanelHandle, PanelProps } from "./Panel";
|
|
6
6
|
import type { PanelGroupProps } from "./PanelGroup";
|
|
7
7
|
import type { PanelResizeHandleProps } from "./PanelResizeHandle";
|
|
8
|
+
import type { PanelGroupStorage } from "./types";
|
|
8
9
|
|
|
9
10
|
export {
|
|
10
11
|
Panel,
|
|
@@ -14,6 +15,7 @@ export {
|
|
|
14
15
|
// TypeScript types
|
|
15
16
|
ImperativePanelHandle,
|
|
16
17
|
PanelGroupProps,
|
|
18
|
+
PanelGroupStorage,
|
|
17
19
|
PanelProps,
|
|
18
20
|
PanelResizeHandleProps,
|
|
19
21
|
};
|
package/src/types.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { RefObject } from "react";
|
|
|
2
2
|
|
|
3
3
|
export type Direction = "horizontal" | "vertical";
|
|
4
4
|
|
|
5
|
+
export type PanelGroupStorage = {
|
|
6
|
+
getItem(name: string): string | null;
|
|
7
|
+
setItem(name: string, value: string): void;
|
|
8
|
+
};
|
|
9
|
+
|
|
5
10
|
export type PanelGroupOnLayout = (sizes: number[]) => void;
|
|
6
11
|
export type PanelOnCollapse = (collapsed: boolean) => void;
|
|
7
12
|
export type PanelOnResize = (size: number) => void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function areEqual(arrayA: any[], arrayB: any[]): boolean {
|
|
2
|
+
if (arrayA.length !== arrayB.length) {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
for (let index = 0; index < arrayA.length; index++) {
|
|
7
|
+
if (arrayA[index] !== arrayB[index]) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return true;
|
|
13
|
+
}
|
package/src/utils/coordinates.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PRECISION } from "../constants";
|
|
2
|
+
import { InitialDragState } from "../PanelGroup";
|
|
2
3
|
import { Direction, PanelData, ResizeEvent } from "../types";
|
|
3
4
|
import {
|
|
4
5
|
getPanelGroup,
|
|
@@ -20,7 +21,8 @@ export function getDragOffset(
|
|
|
20
21
|
event: ResizeEvent,
|
|
21
22
|
handleId: string,
|
|
22
23
|
direction: Direction,
|
|
23
|
-
initialOffset: number = 0
|
|
24
|
+
initialOffset: number = 0,
|
|
25
|
+
initialHandleElementRect: DOMRect | null = null
|
|
24
26
|
): number {
|
|
25
27
|
const isHorizontal = direction === "horizontal";
|
|
26
28
|
|
|
@@ -35,7 +37,8 @@ export function getDragOffset(
|
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
const handleElement = getResizeHandle(handleId);
|
|
38
|
-
const rect =
|
|
40
|
+
const rect =
|
|
41
|
+
initialHandleElementRect || handleElement.getBoundingClientRect();
|
|
39
42
|
const elementOffset = isHorizontal ? rect.left : rect.top;
|
|
40
43
|
|
|
41
44
|
return pointerOffset - elementOffset - initialOffset;
|
|
@@ -48,9 +51,19 @@ export function getMovement(
|
|
|
48
51
|
handleId: string,
|
|
49
52
|
panelsArray: PanelData[],
|
|
50
53
|
direction: Direction,
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
prevSizes: number[],
|
|
55
|
+
initialDragState: InitialDragState | null
|
|
53
56
|
): number {
|
|
57
|
+
const {
|
|
58
|
+
dragOffset = 0,
|
|
59
|
+
dragHandleRect,
|
|
60
|
+
sizes: initialSizes,
|
|
61
|
+
} = initialDragState || {};
|
|
62
|
+
|
|
63
|
+
// If we're resizing by mouse or touch, use the initial sizes as a base.
|
|
64
|
+
// This has the benefit of causing force-collapsed panels to spring back open if drag is reversed.
|
|
65
|
+
const baseSizes = initialSizes || prevSizes;
|
|
66
|
+
|
|
54
67
|
if (isKeyDown(event)) {
|
|
55
68
|
const isHorizontal = direction === "horizontal";
|
|
56
69
|
|
|
@@ -98,10 +111,10 @@ export function getMovement(
|
|
|
98
111
|
);
|
|
99
112
|
const targetPanel = panelsArray[targetPanelIndex];
|
|
100
113
|
if (targetPanel.collapsible) {
|
|
101
|
-
const
|
|
114
|
+
const baseSize = baseSizes[targetPanelIndex];
|
|
102
115
|
if (
|
|
103
|
-
|
|
104
|
-
|
|
116
|
+
baseSize === 0 ||
|
|
117
|
+
baseSize.toPrecision(PRECISION) ===
|
|
105
118
|
targetPanel.minSize.toPrecision(PRECISION)
|
|
106
119
|
) {
|
|
107
120
|
movement =
|
|
@@ -113,7 +126,13 @@ export function getMovement(
|
|
|
113
126
|
|
|
114
127
|
return movement;
|
|
115
128
|
} else {
|
|
116
|
-
return getDragOffset(
|
|
129
|
+
return getDragOffset(
|
|
130
|
+
event,
|
|
131
|
+
handleId,
|
|
132
|
+
direction,
|
|
133
|
+
dragOffset,
|
|
134
|
+
dragHandleRect
|
|
135
|
+
);
|
|
117
136
|
}
|
|
118
137
|
}
|
|
119
138
|
|
package/src/utils/group.ts
CHANGED
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
import { PRECISION } from "../constants";
|
|
2
|
-
import {
|
|
2
|
+
import { InitialDragState } from "../PanelGroup";
|
|
3
|
+
import { PanelData, ResizeEvent } from "../types";
|
|
3
4
|
|
|
4
5
|
export function adjustByDelta(
|
|
6
|
+
event: ResizeEvent | null,
|
|
5
7
|
panels: Map<string, PanelData>,
|
|
6
8
|
idBefore: string,
|
|
7
9
|
idAfter: string,
|
|
8
10
|
delta: number,
|
|
9
11
|
prevSizes: number[],
|
|
10
|
-
panelSizeBeforeCollapse: Map<string, number
|
|
12
|
+
panelSizeBeforeCollapse: Map<string, number>,
|
|
13
|
+
initialDragState: InitialDragState | null
|
|
11
14
|
): number[] {
|
|
15
|
+
const { sizes: initialSizes } = initialDragState || {};
|
|
16
|
+
|
|
17
|
+
// If we're resizing by mouse or touch, use the initial sizes as a base.
|
|
18
|
+
// This has the benefit of causing force-collapsed panels to spring back open if drag is reversed.
|
|
19
|
+
const baseSizes = initialSizes || prevSizes;
|
|
20
|
+
|
|
12
21
|
if (delta === 0) {
|
|
13
|
-
return
|
|
22
|
+
return baseSizes;
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
const panelsArray = panelsMapToSortedArray(panels);
|
|
17
26
|
|
|
18
|
-
const nextSizes =
|
|
27
|
+
const nextSizes = baseSizes.concat();
|
|
19
28
|
|
|
20
29
|
let deltaApplied = 0;
|
|
21
30
|
|
|
@@ -32,17 +41,18 @@ export function adjustByDelta(
|
|
|
32
41
|
const pivotId = delta < 0 ? idAfter : idBefore;
|
|
33
42
|
const index = panelsArray.findIndex((panel) => panel.id === pivotId);
|
|
34
43
|
const panel = panelsArray[index];
|
|
35
|
-
const
|
|
44
|
+
const baseSize = baseSizes[index];
|
|
36
45
|
|
|
37
|
-
const nextSize = safeResizePanel(panel, Math.abs(delta),
|
|
38
|
-
if (
|
|
39
|
-
|
|
46
|
+
const nextSize = safeResizePanel(panel, Math.abs(delta), baseSize, event);
|
|
47
|
+
if (baseSize === nextSize) {
|
|
48
|
+
// If there's no room for the pivot panel to grow, we can ignore this drag update.
|
|
49
|
+
return baseSizes;
|
|
40
50
|
} else {
|
|
41
|
-
if (nextSize === 0 &&
|
|
42
|
-
panelSizeBeforeCollapse.set(pivotId,
|
|
51
|
+
if (nextSize === 0 && baseSize > 0) {
|
|
52
|
+
panelSizeBeforeCollapse.set(pivotId, baseSize);
|
|
43
53
|
}
|
|
44
54
|
|
|
45
|
-
delta = delta < 0 ?
|
|
55
|
+
delta = delta < 0 ? baseSize - nextSize : nextSize - baseSize;
|
|
46
56
|
}
|
|
47
57
|
}
|
|
48
58
|
|
|
@@ -50,19 +60,32 @@ export function adjustByDelta(
|
|
|
50
60
|
let index = panelsArray.findIndex((panel) => panel.id === pivotId);
|
|
51
61
|
while (true) {
|
|
52
62
|
const panel = panelsArray[index];
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
const baseSize = baseSizes[index];
|
|
64
|
+
|
|
65
|
+
const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
|
|
66
|
+
|
|
67
|
+
const nextSize = safeResizePanel(
|
|
68
|
+
panel,
|
|
69
|
+
0 - deltaRemaining,
|
|
70
|
+
baseSize,
|
|
71
|
+
event
|
|
72
|
+
);
|
|
73
|
+
if (baseSize !== nextSize) {
|
|
74
|
+
if (nextSize === 0 && baseSize > 0) {
|
|
75
|
+
panelSizeBeforeCollapse.set(panel.id, baseSize);
|
|
59
76
|
}
|
|
60
77
|
|
|
61
|
-
deltaApplied +=
|
|
78
|
+
deltaApplied += baseSize - nextSize;
|
|
62
79
|
|
|
63
80
|
nextSizes[index] = nextSize;
|
|
64
81
|
|
|
65
|
-
if (
|
|
82
|
+
if (
|
|
83
|
+
deltaApplied
|
|
84
|
+
.toPrecision(PRECISION)
|
|
85
|
+
.localeCompare(Math.abs(delta).toPrecision(PRECISION), undefined, {
|
|
86
|
+
numeric: true,
|
|
87
|
+
}) >= 0
|
|
88
|
+
) {
|
|
66
89
|
break;
|
|
67
90
|
}
|
|
68
91
|
}
|
|
@@ -81,13 +104,13 @@ export function adjustByDelta(
|
|
|
81
104
|
// If we were unable to resize any of the panels panels, return the previous state.
|
|
82
105
|
// This will essentially bailout and ignore the "mousemove" event.
|
|
83
106
|
if (deltaApplied === 0) {
|
|
84
|
-
return
|
|
107
|
+
return baseSizes;
|
|
85
108
|
}
|
|
86
109
|
|
|
87
110
|
// Adjust the pivot panel before, but only by the amount that surrounding panels were able to shrink/contract.
|
|
88
111
|
pivotId = delta < 0 ? idAfter : idBefore;
|
|
89
112
|
index = panelsArray.findIndex((panel) => panel.id === pivotId);
|
|
90
|
-
nextSizes[index] =
|
|
113
|
+
nextSizes[index] = baseSizes[index] + deltaApplied;
|
|
91
114
|
|
|
92
115
|
return nextSizes;
|
|
93
116
|
}
|
|
@@ -100,15 +123,17 @@ export function callPanelCallbacks(
|
|
|
100
123
|
nextSizes.forEach((nextSize, index) => {
|
|
101
124
|
const prevSize = prevSizes[index];
|
|
102
125
|
if (prevSize !== nextSize) {
|
|
103
|
-
const { callbacksRef } = panelsArray[index];
|
|
126
|
+
const { callbacksRef, collapsible } = panelsArray[index];
|
|
104
127
|
const { onCollapse, onResize } = callbacksRef.current;
|
|
105
128
|
|
|
106
129
|
if (onResize) {
|
|
107
130
|
onResize(nextSize);
|
|
108
131
|
}
|
|
109
132
|
|
|
110
|
-
if (onCollapse) {
|
|
111
|
-
|
|
133
|
+
if (collapsible && onCollapse) {
|
|
134
|
+
// Falsy check handles both previous size of 0
|
|
135
|
+
// and initial size of undefined (when mounting)
|
|
136
|
+
if (!prevSize && nextSize !== 0) {
|
|
112
137
|
onCollapse(false);
|
|
113
138
|
} else if (prevSize !== 0 && nextSize === 0) {
|
|
114
139
|
onCollapse(true);
|
|
@@ -230,7 +255,8 @@ export function panelsMapToSortedArray(
|
|
|
230
255
|
function safeResizePanel(
|
|
231
256
|
panel: PanelData,
|
|
232
257
|
delta: number,
|
|
233
|
-
prevSize: number
|
|
258
|
+
prevSize: number,
|
|
259
|
+
event: ResizeEvent | null
|
|
234
260
|
): number {
|
|
235
261
|
const nextSizeUnsafe = prevSize + delta;
|
|
236
262
|
|
|
@@ -240,8 +266,14 @@ function safeResizePanel(
|
|
|
240
266
|
return 0;
|
|
241
267
|
}
|
|
242
268
|
} else {
|
|
243
|
-
|
|
244
|
-
|
|
269
|
+
const isKeyboardEvent = event?.type?.startsWith("key");
|
|
270
|
+
if (!isKeyboardEvent) {
|
|
271
|
+
// Keyboard events should expand a collapsed panel to the min size,
|
|
272
|
+
// but mouse events should wait until the panel has reached its min size
|
|
273
|
+
// to avoid a visual flickering when dragging between collapsed and min size.
|
|
274
|
+
if (nextSizeUnsafe < panel.minSize) {
|
|
275
|
+
return 0;
|
|
276
|
+
}
|
|
245
277
|
}
|
|
246
278
|
}
|
|
247
279
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PanelData } from "../types";
|
|
1
|
+
import { PanelData, PanelGroupStorage } from "../types";
|
|
2
2
|
|
|
3
3
|
type SerializedPanelGroupState = { [panelIds: string]: number[] };
|
|
4
4
|
|
|
@@ -17,10 +17,11 @@ function getSerializationKey(panels: PanelData[]): string {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function loadSerializedPanelGroupState(
|
|
20
|
-
autoSaveId: string
|
|
20
|
+
autoSaveId: string,
|
|
21
|
+
storage: PanelGroupStorage
|
|
21
22
|
): SerializedPanelGroupState | null {
|
|
22
23
|
try {
|
|
23
|
-
const serialized =
|
|
24
|
+
const serialized = storage.getItem(`PanelGroup:sizes:${autoSaveId}`);
|
|
24
25
|
if (serialized) {
|
|
25
26
|
const parsed = JSON.parse(serialized);
|
|
26
27
|
if (typeof parsed === "object" && parsed != null) {
|
|
@@ -34,9 +35,10 @@ function loadSerializedPanelGroupState(
|
|
|
34
35
|
|
|
35
36
|
export function loadPanelLayout(
|
|
36
37
|
autoSaveId: string,
|
|
37
|
-
panels: PanelData[]
|
|
38
|
+
panels: PanelData[],
|
|
39
|
+
storage: PanelGroupStorage
|
|
38
40
|
): number[] | null {
|
|
39
|
-
const state = loadSerializedPanelGroupState(autoSaveId);
|
|
41
|
+
const state = loadSerializedPanelGroupState(autoSaveId, storage);
|
|
40
42
|
if (state) {
|
|
41
43
|
const key = getSerializationKey(panels);
|
|
42
44
|
return state[key] || null;
|
|
@@ -48,17 +50,15 @@ export function loadPanelLayout(
|
|
|
48
50
|
export function savePanelGroupLayout(
|
|
49
51
|
autoSaveId: string,
|
|
50
52
|
panels: PanelData[],
|
|
51
|
-
sizes: number[]
|
|
53
|
+
sizes: number[],
|
|
54
|
+
storage: PanelGroupStorage
|
|
52
55
|
): void {
|
|
53
56
|
const key = getSerializationKey(panels);
|
|
54
|
-
const state = loadSerializedPanelGroupState(autoSaveId) || {};
|
|
57
|
+
const state = loadSerializedPanelGroupState(autoSaveId, storage) || {};
|
|
55
58
|
state[key] = sizes;
|
|
56
59
|
|
|
57
60
|
try {
|
|
58
|
-
|
|
59
|
-
`PanelGroup:sizes:${autoSaveId}`,
|
|
60
|
-
JSON.stringify(state)
|
|
61
|
-
);
|
|
61
|
+
storage.setItem(`PanelGroup:sizes:${autoSaveId}`, JSON.stringify(state));
|
|
62
62
|
} catch (error) {
|
|
63
63
|
console.error(error);
|
|
64
64
|
}
|