react-resizable-panels 0.0.8 → 0.0.9
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 +4 -1
- package/dist/react-resizable-panels.d.ts.map +1 -1
- package/dist/react-resizable-panels.js +290 -86
- package/dist/react-resizable-panels.js.map +1 -1
- package/dist/react-resizable-panels.module.js +290 -86
- package/dist/react-resizable-panels.module.js.map +1 -1
- package/package.json +1 -1
- package/src/Panel.tsx +6 -1
- package/src/PanelGroup.tsx +39 -141
- package/src/PanelResizeHandle.tsx +11 -1
- package/src/constants.ts +1 -0
- package/src/hooks/useWindowSplitterBehavior.ts +185 -0
- package/src/types.ts +1 -1
- package/src/utils/coordinates.ts +118 -0
- package/src/utils/group.ts +176 -0
- package/src/utils/touch.ts +0 -44
package/src/types.ts
CHANGED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Direction, ResizeEvent } from "../types";
|
|
2
|
+
|
|
3
|
+
export type Coordinates = {
|
|
4
|
+
screenX: number;
|
|
5
|
+
screenY: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type Dimensions = {
|
|
9
|
+
height: number;
|
|
10
|
+
width: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type Movement = {
|
|
14
|
+
movementX: number;
|
|
15
|
+
movementY: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const element = document.createElement("div");
|
|
19
|
+
element.getBoundingClientRect();
|
|
20
|
+
|
|
21
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementX
|
|
22
|
+
export function getUpdatedCoordinates(
|
|
23
|
+
event: ResizeEvent,
|
|
24
|
+
prevCoordinates: Coordinates,
|
|
25
|
+
dimensions: Dimensions,
|
|
26
|
+
direction: Direction
|
|
27
|
+
): Coordinates & Movement {
|
|
28
|
+
const { screenX: prevScreenX, screenY: prevScreenY } = prevCoordinates;
|
|
29
|
+
const { height, width } = dimensions;
|
|
30
|
+
|
|
31
|
+
const getMovementBetween = (current: number, prev: number) =>
|
|
32
|
+
prev === 0 ? 0 : current - prev;
|
|
33
|
+
|
|
34
|
+
if (isKeyDown(event)) {
|
|
35
|
+
let movementX = 0;
|
|
36
|
+
let movementY = 0;
|
|
37
|
+
|
|
38
|
+
const size = direction === "horizontal" ? width : height;
|
|
39
|
+
const denominator = event.shiftKey ? 10 : 100;
|
|
40
|
+
const delta = size / denominator;
|
|
41
|
+
|
|
42
|
+
switch (event.key) {
|
|
43
|
+
case "ArrowDown":
|
|
44
|
+
movementY = delta;
|
|
45
|
+
break;
|
|
46
|
+
case "ArrowLeft":
|
|
47
|
+
movementX = -delta;
|
|
48
|
+
break;
|
|
49
|
+
case "ArrowRight":
|
|
50
|
+
movementX = delta;
|
|
51
|
+
break;
|
|
52
|
+
case "ArrowUp":
|
|
53
|
+
movementY = -delta;
|
|
54
|
+
break;
|
|
55
|
+
case "End":
|
|
56
|
+
if (direction === "horizontal") {
|
|
57
|
+
movementX = size;
|
|
58
|
+
} else {
|
|
59
|
+
movementY = size;
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
case "Home":
|
|
63
|
+
if (direction === "horizontal") {
|
|
64
|
+
movementX = -size;
|
|
65
|
+
} else {
|
|
66
|
+
movementY = -size;
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Estimate screen X/Y to be the center of the resize handle.
|
|
72
|
+
// Otherwise the first mouse/touch event after a keyboard event will appear to "jump"
|
|
73
|
+
let screenX = 0;
|
|
74
|
+
let screenY = 0;
|
|
75
|
+
if (document.activeElement) {
|
|
76
|
+
const rect = document.activeElement.getBoundingClientRect();
|
|
77
|
+
screenX = rect.left + rect.width / 2;
|
|
78
|
+
screenY = rect.top + rect.height / 2;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
movementX,
|
|
83
|
+
movementY,
|
|
84
|
+
screenX,
|
|
85
|
+
screenY,
|
|
86
|
+
};
|
|
87
|
+
} else if (isTouchMoveEvent(event)) {
|
|
88
|
+
const firstTouch = event.touches[0];
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
movementX: getMovementBetween(firstTouch.screenX, prevScreenX),
|
|
92
|
+
movementY: getMovementBetween(firstTouch.screenY, prevScreenY),
|
|
93
|
+
screenX: firstTouch.screenX,
|
|
94
|
+
screenY: firstTouch.screenY,
|
|
95
|
+
};
|
|
96
|
+
} else if (isMouseMoveEvent(event)) {
|
|
97
|
+
return {
|
|
98
|
+
movementX: getMovementBetween(event.screenX, prevScreenX),
|
|
99
|
+
movementY: getMovementBetween(event.screenY, prevScreenY),
|
|
100
|
+
screenX: event.screenX,
|
|
101
|
+
screenY: event.screenY,
|
|
102
|
+
};
|
|
103
|
+
} else {
|
|
104
|
+
throw Error(`Unsupported event type: "${(event as any).type}"`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function isKeyDown(event: ResizeEvent): event is KeyboardEvent {
|
|
109
|
+
return event.type === "keydown";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function isMouseMoveEvent(event: ResizeEvent): event is MouseEvent {
|
|
113
|
+
return event.type === "mousemove";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function isTouchMoveEvent(event: ResizeEvent): event is TouchEvent {
|
|
117
|
+
return event.type === "touchmove";
|
|
118
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { PRECISION } from "../constants";
|
|
2
|
+
import { Direction, PanelData } from "../types";
|
|
3
|
+
|
|
4
|
+
export function adjustByDelta(
|
|
5
|
+
panels: Map<string, PanelData>,
|
|
6
|
+
idBefore: string,
|
|
7
|
+
idAfter: string,
|
|
8
|
+
delta: number,
|
|
9
|
+
prevSizes: number[]
|
|
10
|
+
): number[] {
|
|
11
|
+
if (delta === 0) {
|
|
12
|
+
return prevSizes;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const panelsArray = panelsMapToSortedArray(panels);
|
|
16
|
+
|
|
17
|
+
const nextSizes = prevSizes.concat();
|
|
18
|
+
|
|
19
|
+
let deltaApplied = 0;
|
|
20
|
+
|
|
21
|
+
// A resizing panel affects the panels before or after it.
|
|
22
|
+
//
|
|
23
|
+
// A negative delta means the panel immediately after the resizer should grow/expand by decreasing its offset.
|
|
24
|
+
// Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
|
|
25
|
+
//
|
|
26
|
+
// A positive delta means the panel immediately before the resizer should "expand".
|
|
27
|
+
// This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.
|
|
28
|
+
let pivotId = delta < 0 ? idBefore : idAfter;
|
|
29
|
+
let index = panelsArray.findIndex((panel) => panel.id === pivotId);
|
|
30
|
+
while (true) {
|
|
31
|
+
const panel = panelsArray[index];
|
|
32
|
+
const prevSize = prevSizes[index];
|
|
33
|
+
const nextSize = Math.max(prevSize - Math.abs(delta), panel.minSize);
|
|
34
|
+
if (prevSize !== nextSize) {
|
|
35
|
+
deltaApplied += prevSize - nextSize;
|
|
36
|
+
|
|
37
|
+
nextSizes[index] = nextSize;
|
|
38
|
+
|
|
39
|
+
if (deltaApplied.toPrecision(PRECISION) >= delta.toPrecision(PRECISION)) {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (delta < 0) {
|
|
45
|
+
if (--index < 0) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
if (++index >= panelsArray.length) {
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// If we were unable to resize any of the panels panels, return the previous state.
|
|
56
|
+
// This will essentially bailout and ignore the "mousemove" event.
|
|
57
|
+
if (deltaApplied === 0) {
|
|
58
|
+
return prevSizes;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Adjust the pivot panel before, but only by the amount that surrounding panels were able to shrink/contract.
|
|
62
|
+
pivotId = delta < 0 ? idAfter : idBefore;
|
|
63
|
+
index = panelsArray.findIndex((panel) => panel.id === pivotId);
|
|
64
|
+
nextSizes[index] = prevSizes[index] + deltaApplied;
|
|
65
|
+
|
|
66
|
+
return nextSizes;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function getOffset(
|
|
70
|
+
panels: Map<string, PanelData>,
|
|
71
|
+
id: string,
|
|
72
|
+
direction: Direction,
|
|
73
|
+
sizes: number[],
|
|
74
|
+
height: number,
|
|
75
|
+
width: number
|
|
76
|
+
): number {
|
|
77
|
+
const panelsArray = panelsMapToSortedArray(panels);
|
|
78
|
+
|
|
79
|
+
let index = panelsArray.findIndex((panel) => panel.id === id);
|
|
80
|
+
if (index < 0) {
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let scaledOffset = 0;
|
|
85
|
+
|
|
86
|
+
for (index = index - 1; index >= 0; index--) {
|
|
87
|
+
const panel = panelsArray[index];
|
|
88
|
+
scaledOffset += getSize(panels, panel.id, direction, sizes, height, width);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return Math.round(scaledOffset);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function getPanel(id: string): HTMLDivElement | null {
|
|
95
|
+
const element = document.querySelector(`[data-panel-id="${id}"]`);
|
|
96
|
+
if (element) {
|
|
97
|
+
return element as HTMLDivElement;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getResizeHandle(id: string): HTMLDivElement | null {
|
|
103
|
+
const element = document.querySelector(
|
|
104
|
+
`[data-panel-resize-handle-id="${id}"]`
|
|
105
|
+
);
|
|
106
|
+
if (element) {
|
|
107
|
+
return element as HTMLDivElement;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function getResizeHandleIndex(id: string): number | null {
|
|
113
|
+
const handles = getResizeHandles();
|
|
114
|
+
const index = handles.findIndex(
|
|
115
|
+
(handle) => handle.getAttribute("data-panel-resize-handle-id") === id
|
|
116
|
+
);
|
|
117
|
+
return index ?? null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function getResizeHandles(): HTMLDivElement[] {
|
|
121
|
+
return Array.from(document.querySelectorAll(`[data-panel-resize-handle-id]`));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function getResizeHandlesForGroup(groupId: string): HTMLDivElement[] {
|
|
125
|
+
return Array.from(
|
|
126
|
+
document.querySelectorAll(
|
|
127
|
+
`[data-panel-resize-handle-id][data-panel-group-id="${groupId}"]`
|
|
128
|
+
)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function getResizeHandlePanelIds(
|
|
133
|
+
groupId: string,
|
|
134
|
+
handleId: string,
|
|
135
|
+
panelsArray: PanelData[]
|
|
136
|
+
): [idBefore: string | null, idAfter: string | null] {
|
|
137
|
+
const handle = getResizeHandle(handleId);
|
|
138
|
+
const handles = getResizeHandlesForGroup(groupId);
|
|
139
|
+
const index = handles.indexOf(handle);
|
|
140
|
+
|
|
141
|
+
const idBefore: string | null = panelsArray[index]?.id ?? null;
|
|
142
|
+
const idAfter: string | null = panelsArray[index + 1]?.id ?? null;
|
|
143
|
+
|
|
144
|
+
return [idBefore, idAfter];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function panelsMapToSortedArray(
|
|
148
|
+
panels: Map<string, PanelData>
|
|
149
|
+
): PanelData[] {
|
|
150
|
+
return Array.from(panels.values()).sort((a, b) => a.order - b.order);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function getSize(
|
|
154
|
+
panels: Map<string, PanelData>,
|
|
155
|
+
id: string,
|
|
156
|
+
direction: Direction,
|
|
157
|
+
sizes: number[],
|
|
158
|
+
height: number,
|
|
159
|
+
width: number
|
|
160
|
+
): number {
|
|
161
|
+
const totalSize = direction === "horizontal" ? width : height;
|
|
162
|
+
|
|
163
|
+
if (panels.size === 1) {
|
|
164
|
+
return totalSize;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const panelsArray = panelsMapToSortedArray(panels);
|
|
168
|
+
|
|
169
|
+
const index = panelsArray.findIndex((panel) => panel.id === id);
|
|
170
|
+
const size = sizes[index];
|
|
171
|
+
if (size == null) {
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return Math.round(size * totalSize);
|
|
176
|
+
}
|
package/src/utils/touch.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { ResizeEvent } from "../types";
|
|
2
|
-
|
|
3
|
-
export type Coordinates = {
|
|
4
|
-
screenX: number;
|
|
5
|
-
screenY: number;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export type Movement = {
|
|
9
|
-
movementX: number;
|
|
10
|
-
movementY: number;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementX
|
|
14
|
-
export function getUpdatedCoordinates(
|
|
15
|
-
event: ResizeEvent,
|
|
16
|
-
prevCoordinates: Coordinates
|
|
17
|
-
): Coordinates & Movement {
|
|
18
|
-
const { screenX: prevScreenX, screenY: prevScreenY } = prevCoordinates;
|
|
19
|
-
|
|
20
|
-
const getMovementBetween = (current: number, prev: number) =>
|
|
21
|
-
prev === 0 ? 0 : current - prev;
|
|
22
|
-
|
|
23
|
-
if (isTouchMoveEvent(event)) {
|
|
24
|
-
const firstTouch = event.touches[0];
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
movementX: getMovementBetween(firstTouch.screenX, prevScreenX),
|
|
28
|
-
movementY: getMovementBetween(firstTouch.screenY, prevScreenY),
|
|
29
|
-
screenX: firstTouch.screenX,
|
|
30
|
-
screenY: firstTouch.screenY,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
movementX: getMovementBetween(event.screenX, prevScreenX),
|
|
36
|
-
movementY: getMovementBetween(event.screenY, prevScreenY),
|
|
37
|
-
screenX: event.screenX,
|
|
38
|
-
screenY: event.screenY,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function isTouchMoveEvent(event: ResizeEvent): event is TouchEvent {
|
|
43
|
-
return event.type === "touchmove";
|
|
44
|
-
}
|