react-resizable-panels 0.0.1
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/.proxyrc +8 -0
- package/README.md +50 -0
- package/dist/index.8be10522.js +28334 -0
- package/dist/index.8be10522.js.map +1 -0
- package/dist/index.d85b50e4.css +95 -0
- package/dist/index.d85b50e4.css.map +1 -0
- package/dist/index.html +9 -0
- package/dist/react-resizable-panels.d.ts +28 -0
- package/dist/react-resizable-panels.d.ts.map +1 -0
- package/dist/react-resizable-panels.js +272 -0
- package/dist/react-resizable-panels.js.map +1 -0
- package/dist/react-resizable-panels.module.js +266 -0
- package/dist/react-resizable-panels.module.js.map +1 -0
- package/package.json +35 -0
- package/src/Panel.tsx +46 -0
- package/src/PanelContexts.ts +10 -0
- package/src/PanelGroup.tsx +276 -0
- package/src/PanelResizeHandle.tsx +81 -0
- package/src/index.ts +5 -0
- package/src/types.ts +11 -0
- package/tsconfig.json +6 -0
- package/website/demo.tsx +140 -0
- package/website/index.html +9 -0
- package/website/index.tsx +12 -0
- package/website/root.css +15 -0
- package/website/styles.module.css +77 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import {jsx as $hgUW1$jsx} from "react/jsx-runtime";
|
|
2
|
+
import {useContext as $hgUW1$useContext, useLayoutEffect as $hgUW1$useLayoutEffect, createContext as $hgUW1$createContext, useRef as $hgUW1$useRef, useState as $hgUW1$useState, useEffect as $hgUW1$useEffect, useCallback as $hgUW1$useCallback, useMemo as $hgUW1$useMemo} from "react";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const $3a3142702e7d9c06$export$7d8c6d083caec74a = (0, $hgUW1$createContext)(null);
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
function $be6cfb11a13b6f3f$export$2e2bcd8739ae039({ children: children , className: className = "" , defaultSize: defaultSize = 0.1 , id: id , minSize: minSize = 0.1 }) {
|
|
11
|
+
const context = (0, $hgUW1$useContext)((0, $3a3142702e7d9c06$export$7d8c6d083caec74a));
|
|
12
|
+
if (context === null) throw Error(`Panel components must be rendered within a PanelGroup container`);
|
|
13
|
+
const { getPanelStyle: getPanelStyle , registerPanel: registerPanel } = context;
|
|
14
|
+
(0, $hgUW1$useLayoutEffect)(()=>{
|
|
15
|
+
const panel = {
|
|
16
|
+
defaultSize: defaultSize,
|
|
17
|
+
id: id,
|
|
18
|
+
minSize: minSize
|
|
19
|
+
};
|
|
20
|
+
registerPanel(id, panel);
|
|
21
|
+
}, [
|
|
22
|
+
defaultSize,
|
|
23
|
+
minSize,
|
|
24
|
+
registerPanel,
|
|
25
|
+
id
|
|
26
|
+
]);
|
|
27
|
+
const style = getPanelStyle(id);
|
|
28
|
+
return /*#__PURE__*/ (0, $hgUW1$jsx)("div", {
|
|
29
|
+
className: className,
|
|
30
|
+
style: style,
|
|
31
|
+
children: children
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
const $87c6267876d46c1c$var$PRECISION = 5;
|
|
40
|
+
function $87c6267876d46c1c$export$2e2bcd8739ae039({ autoSaveId: autoSaveId , children: children , className: className = "" , direction: direction , height: height , width: width }) {
|
|
41
|
+
const panelsRef = (0, $hgUW1$useRef)([]);
|
|
42
|
+
// 0-1 values representing the relative size of each panel.
|
|
43
|
+
const [sizes, setSizes] = (0, $hgUW1$useState)([]);
|
|
44
|
+
// Store committed values to avoid unnecessarily re-running memoization/effects functions.
|
|
45
|
+
const committedValuesRef = (0, $hgUW1$useRef)({
|
|
46
|
+
direction: direction,
|
|
47
|
+
height: height,
|
|
48
|
+
sizes: sizes,
|
|
49
|
+
width: width
|
|
50
|
+
});
|
|
51
|
+
(0, $hgUW1$useLayoutEffect)(()=>{
|
|
52
|
+
committedValuesRef.current.direction = direction;
|
|
53
|
+
committedValuesRef.current.height = height;
|
|
54
|
+
committedValuesRef.current.sizes = sizes;
|
|
55
|
+
committedValuesRef.current.width = width;
|
|
56
|
+
});
|
|
57
|
+
// Once all panels have registered themselves,
|
|
58
|
+
// Compute the initial sizes based on default weights.
|
|
59
|
+
// This assumes that panels register during initial mount (no conditional rendering)!
|
|
60
|
+
(0, $hgUW1$useLayoutEffect)(()=>{
|
|
61
|
+
const panels = panelsRef.current;
|
|
62
|
+
const sizes = committedValuesRef.current.sizes;
|
|
63
|
+
if (sizes.length === panels.length) return;
|
|
64
|
+
// If this panel has been configured to persist sizing information,
|
|
65
|
+
// default size should be restored from local storage if possible.
|
|
66
|
+
let defaultSizes = undefined;
|
|
67
|
+
if (autoSaveId) try {
|
|
68
|
+
const value = localStorage.getItem(`PanelGroup:sizes:${autoSaveId}`);
|
|
69
|
+
if (value) defaultSizes = JSON.parse(value);
|
|
70
|
+
} catch (error) {}
|
|
71
|
+
if (sizes.length === 0 && defaultSizes != null && defaultSizes.length === panels.length) setSizes(defaultSizes);
|
|
72
|
+
else {
|
|
73
|
+
const totalWeight = panels.reduce((weight, panel)=>{
|
|
74
|
+
return weight + panel.defaultSize;
|
|
75
|
+
}, 0);
|
|
76
|
+
setSizes(panels.map((panel)=>panel.defaultSize / totalWeight));
|
|
77
|
+
}
|
|
78
|
+
}, [
|
|
79
|
+
autoSaveId
|
|
80
|
+
]);
|
|
81
|
+
(0, $hgUW1$useEffect)(()=>{
|
|
82
|
+
if (autoSaveId && sizes.length > 0) // If this panel has been configured to persist sizing information, save sizes to local storage.
|
|
83
|
+
localStorage.setItem(`PanelGroup:sizes:${autoSaveId}`, JSON.stringify(sizes));
|
|
84
|
+
}, [
|
|
85
|
+
autoSaveId,
|
|
86
|
+
sizes
|
|
87
|
+
]);
|
|
88
|
+
const getPanelStyle = (0, $hgUW1$useCallback)((id)=>{
|
|
89
|
+
const panels = panelsRef.current;
|
|
90
|
+
const offset = $87c6267876d46c1c$var$getOffset(panels, id, direction, sizes, height, width);
|
|
91
|
+
const size = $87c6267876d46c1c$var$getSize(panels, id, direction, sizes, height, width);
|
|
92
|
+
if (direction === "horizontal") return {
|
|
93
|
+
height: "100%",
|
|
94
|
+
position: "absolute",
|
|
95
|
+
left: offset,
|
|
96
|
+
top: 0,
|
|
97
|
+
width: size
|
|
98
|
+
};
|
|
99
|
+
else return {
|
|
100
|
+
height: size,
|
|
101
|
+
position: "absolute",
|
|
102
|
+
left: 0,
|
|
103
|
+
top: offset,
|
|
104
|
+
width: "100%"
|
|
105
|
+
};
|
|
106
|
+
}, [
|
|
107
|
+
direction,
|
|
108
|
+
height,
|
|
109
|
+
sizes,
|
|
110
|
+
width
|
|
111
|
+
]);
|
|
112
|
+
const registerPanel = (0, $hgUW1$useCallback)((id, panel)=>{
|
|
113
|
+
const panels = panelsRef.current;
|
|
114
|
+
const index = panels.findIndex((panel)=>panel.id === id);
|
|
115
|
+
if (index >= 0) panels.splice(index, 1);
|
|
116
|
+
panels.push(panel);
|
|
117
|
+
}, []);
|
|
118
|
+
const registerResizeHandle = (0, $hgUW1$useCallback)((idBefore, idAfter)=>{
|
|
119
|
+
return (event)=>{
|
|
120
|
+
event.preventDefault();
|
|
121
|
+
const panels = panelsRef.current;
|
|
122
|
+
const { direction: direction , height: height , sizes: prevSizes , width: width } = committedValuesRef.current;
|
|
123
|
+
const isHorizontal = direction === "horizontal";
|
|
124
|
+
const movement = isHorizontal ? event.movementX : event.movementY;
|
|
125
|
+
const delta = isHorizontal ? movement / width : movement / height;
|
|
126
|
+
const nextSizes = $87c6267876d46c1c$var$adjustByDelta(panels, idBefore, idAfter, delta, prevSizes);
|
|
127
|
+
if (prevSizes !== nextSizes) setSizes(nextSizes);
|
|
128
|
+
};
|
|
129
|
+
}, []);
|
|
130
|
+
const context = (0, $hgUW1$useMemo)(()=>({
|
|
131
|
+
direction: direction,
|
|
132
|
+
getPanelStyle: getPanelStyle,
|
|
133
|
+
registerPanel: registerPanel,
|
|
134
|
+
registerResizeHandle: registerResizeHandle
|
|
135
|
+
}), [
|
|
136
|
+
direction,
|
|
137
|
+
getPanelStyle,
|
|
138
|
+
registerPanel,
|
|
139
|
+
registerResizeHandle
|
|
140
|
+
]);
|
|
141
|
+
return /*#__PURE__*/ (0, $hgUW1$jsx)((0, $3a3142702e7d9c06$export$7d8c6d083caec74a).Provider, {
|
|
142
|
+
value: context,
|
|
143
|
+
children: /*#__PURE__*/ (0, $hgUW1$jsx)("div", {
|
|
144
|
+
className: className,
|
|
145
|
+
children: children
|
|
146
|
+
})
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
function $87c6267876d46c1c$var$adjustByDelta(panels, idBefore, idAfter, delta, prevSizes) {
|
|
150
|
+
if (delta === 0) return prevSizes;
|
|
151
|
+
const nextSizes = prevSizes.concat();
|
|
152
|
+
let deltaApplied = 0;
|
|
153
|
+
// A resizing panel affects the panels before or after it.
|
|
154
|
+
//
|
|
155
|
+
// A negative delta means the panel immediately after the resizer should grow/expand by decreasing its offset.
|
|
156
|
+
// Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
|
|
157
|
+
//
|
|
158
|
+
// A positive delta means the panel immediately before the resizer should "expand".
|
|
159
|
+
// This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.
|
|
160
|
+
let pivotId = delta < 0 ? idBefore : idAfter;
|
|
161
|
+
let index = panels.findIndex((panel)=>panel.id === pivotId);
|
|
162
|
+
while(true){
|
|
163
|
+
const panel = panels[index];
|
|
164
|
+
const prevSize = prevSizes[index];
|
|
165
|
+
const nextSize = Math.max(prevSize - Math.abs(delta), panel.minSize);
|
|
166
|
+
if (prevSize !== nextSize) {
|
|
167
|
+
deltaApplied += prevSize - nextSize;
|
|
168
|
+
nextSizes[index] = nextSize;
|
|
169
|
+
if (deltaApplied.toPrecision($87c6267876d46c1c$var$PRECISION) >= delta.toPrecision($87c6267876d46c1c$var$PRECISION)) break;
|
|
170
|
+
}
|
|
171
|
+
if (delta < 0) {
|
|
172
|
+
if (--index < 0) break;
|
|
173
|
+
} else {
|
|
174
|
+
if (++index >= panels.length) break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// If we were unable to resize any of the panels panels, return the previous state.
|
|
178
|
+
// This will essentially bailout and ignore the "mousemove" event.
|
|
179
|
+
if (deltaApplied === 0) return prevSizes;
|
|
180
|
+
// Adjust the pivot panel before, but only by the amount that surrounding panels were able to shrink/contract.
|
|
181
|
+
pivotId = delta < 0 ? idAfter : idBefore;
|
|
182
|
+
index = panels.findIndex((panel)=>panel.id === pivotId);
|
|
183
|
+
nextSizes[index] = prevSizes[index] + deltaApplied;
|
|
184
|
+
return nextSizes;
|
|
185
|
+
}
|
|
186
|
+
function $87c6267876d46c1c$var$getOffset(panels, id, direction, sizes, height, width) {
|
|
187
|
+
let index = panels.findIndex((panel)=>panel.id === id);
|
|
188
|
+
if (index < 0) return 0;
|
|
189
|
+
let scaledOffset = 0;
|
|
190
|
+
for(index = index - 1; index >= 0; index--){
|
|
191
|
+
const panel = panels[index];
|
|
192
|
+
scaledOffset += $87c6267876d46c1c$var$getSize(panels, panel.id, direction, sizes, height, width);
|
|
193
|
+
}
|
|
194
|
+
return Math.round(scaledOffset);
|
|
195
|
+
}
|
|
196
|
+
function $87c6267876d46c1c$var$getSize(panels, id, direction, sizes, height, width) {
|
|
197
|
+
const index = panels.findIndex((panel)=>panel.id === id);
|
|
198
|
+
const size = sizes[index];
|
|
199
|
+
if (size == null) return 0;
|
|
200
|
+
const totalSize = direction === "horizontal" ? width : height;
|
|
201
|
+
if (panels.length === 1) return totalSize;
|
|
202
|
+
else return Math.round(size * totalSize);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
function $74f1c1b08e3b0df5$export$2e2bcd8739ae039({ children: children = null , className: className = "" , disabled: disabled = false , panelAfter: panelAfter , panelBefore: panelBefore }) {
|
|
210
|
+
const context = (0, $hgUW1$useContext)((0, $3a3142702e7d9c06$export$7d8c6d083caec74a));
|
|
211
|
+
if (context === null) throw Error(`PanelResizeHandle components must be rendered within a PanelGroup container`);
|
|
212
|
+
const { direction: direction , registerResizeHandle: registerResizeHandle } = context;
|
|
213
|
+
const [resizeHandler, setResizeHandler] = (0, $hgUW1$useState)(null);
|
|
214
|
+
const [isDragging, setIsDragging] = (0, $hgUW1$useState)(false);
|
|
215
|
+
(0, $hgUW1$useEffect)(()=>{
|
|
216
|
+
if (disabled) setResizeHandler(null);
|
|
217
|
+
else setResizeHandler(()=>registerResizeHandle(panelBefore, panelAfter));
|
|
218
|
+
}, [
|
|
219
|
+
disabled,
|
|
220
|
+
panelAfter,
|
|
221
|
+
panelBefore,
|
|
222
|
+
registerResizeHandle
|
|
223
|
+
]);
|
|
224
|
+
(0, $hgUW1$useEffect)(()=>{
|
|
225
|
+
if (disabled || resizeHandler == null || !isDragging) return;
|
|
226
|
+
document.body.style.cursor = direction === "horizontal" ? "ew-resize" : "ns-resize";
|
|
227
|
+
const onMouseLeave = (_)=>{
|
|
228
|
+
setIsDragging(false);
|
|
229
|
+
};
|
|
230
|
+
const onMouseMove = (event)=>{
|
|
231
|
+
resizeHandler(event);
|
|
232
|
+
};
|
|
233
|
+
const onMouseUp = (_)=>{
|
|
234
|
+
setIsDragging(false);
|
|
235
|
+
};
|
|
236
|
+
document.body.addEventListener("mouseleave", onMouseLeave);
|
|
237
|
+
document.body.addEventListener("mousemove", onMouseMove);
|
|
238
|
+
document.body.addEventListener("mouseup", onMouseUp);
|
|
239
|
+
return ()=>{
|
|
240
|
+
document.body.style.cursor = "";
|
|
241
|
+
document.body.removeEventListener("mouseleave", onMouseLeave);
|
|
242
|
+
document.body.removeEventListener("mousemove", onMouseMove);
|
|
243
|
+
document.body.removeEventListener("mouseup", onMouseUp);
|
|
244
|
+
};
|
|
245
|
+
}, [
|
|
246
|
+
direction,
|
|
247
|
+
disabled,
|
|
248
|
+
isDragging,
|
|
249
|
+
resizeHandler
|
|
250
|
+
]);
|
|
251
|
+
return /*#__PURE__*/ (0, $hgUW1$jsx)("div", {
|
|
252
|
+
className: className,
|
|
253
|
+
onMouseDown: ()=>setIsDragging(true),
|
|
254
|
+
onMouseUp: ()=>setIsDragging(false),
|
|
255
|
+
style: {
|
|
256
|
+
cursor: direction === "horizontal" ? "ew-resize" : "ns-resize"
|
|
257
|
+
},
|
|
258
|
+
children: children
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
export {$be6cfb11a13b6f3f$export$2e2bcd8739ae039 as Panel, $87c6267876d46c1c$export$2e2bcd8739ae039 as PanelGroup, $74f1c1b08e3b0df5$export$2e2bcd8739ae039 as PanelResizeHandle};
|
|
266
|
+
//# sourceMappingURL=react-resizable-panels.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mappings":"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","sources":["src/index.ts","src/Panel.tsx","src/PanelContexts.ts","src/PanelGroup.tsx","src/PanelResizeHandle.tsx"],"sourcesContent":["import Panel from \"./Panel\";\nimport PanelGroup from \"./PanelGroup\";\nimport PanelResizeHandle from \"./PanelResizeHandle\";\n\nexport { Panel, PanelGroup, PanelResizeHandle };","import { ReactNode, useContext, useLayoutEffect } from \"react\";\n\nimport { PanelGroupContext } from \"./PanelContexts\";\nimport { PanelId } from \"./types\";\n\n// TODO [panels]\n// Support min pixel size too.\n// PanelGroup should warn if total width is less min pixel widths.\nexport default function Panel({\n children,\n className = \"\",\n defaultSize = 0.1,\n id,\n minSize = 0.1,\n}: {\n children: ReactNode;\n className?: string;\n defaultSize?: number;\n id: PanelId;\n minSize?: number;\n}) {\n const context = useContext(PanelGroupContext);\n if (context === null) {\n throw Error(`Panel components must be rendered within a PanelGroup container`);\n }\n\n const { getPanelStyle, registerPanel } = context;\n\n useLayoutEffect(() => {\n const panel = {\n defaultSize,\n id,\n minSize,\n };\n\n registerPanel(id, panel);\n }, [defaultSize, minSize, registerPanel, id]);\n\n const style = getPanelStyle(id);\n\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n","import { CSSProperties, createContext } from \"react\";\n\nimport { Panel, PanelId, ResizeHandler } from \"./types\";\n\nexport const PanelGroupContext = createContext<{\n direction: \"horizontal\" | \"vertical\";\n getPanelStyle: (id: PanelId) => CSSProperties;\n registerResizeHandle: (idBefore: PanelId, idAfter: PanelId) => ResizeHandler;\n registerPanel: (id: PanelId, panel: Panel) => void;\n} | null>(null);\n","import {\n CSSProperties,\n ReactNode,\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport { PanelGroupContext } from \"./PanelContexts\";\nimport { Direction, Panel, PanelId } from \"./types\";\n\ntype Props = {\n autoSaveId?: string;\n children: ReactNode[];\n className?: string;\n direction: Direction;\n height: number;\n width: number;\n};\n\nconst PRECISION = 5;\n\n// TODO [panels]\n// Within an active drag, remember original positions to refine more easily on expand.\n// Look at what the Chrome devtools Sources does.\n\nexport default function PanelGroup({ autoSaveId, children, className = \"\", direction, height, width }: Props) {\n const panelsRef = useRef<Panel[]>([]);\n\n // 0-1 values representing the relative size of each panel.\n const [sizes, setSizes] = useState<number[]>([]);\n\n // Store committed values to avoid unnecessarily re-running memoization/effects functions.\n const committedValuesRef = useRef<{\n direction: Direction;\n height: number;\n sizes: number[];\n width: number;\n }>({\n direction,\n height,\n sizes,\n width,\n });\n useLayoutEffect(() => {\n committedValuesRef.current.direction = direction;\n committedValuesRef.current.height = height;\n committedValuesRef.current.sizes = sizes;\n committedValuesRef.current.width = width;\n });\n\n // Once all panels have registered themselves,\n // Compute the initial sizes based on default weights.\n // This assumes that panels register during initial mount (no conditional rendering)!\n useLayoutEffect(() => {\n const panels = panelsRef.current;\n const sizes = committedValuesRef.current.sizes;\n if (sizes.length === panels.length) {\n return;\n }\n\n // If this panel has been configured to persist sizing information,\n // default size should be restored from local storage if possible.\n let defaultSizes: number[] | undefined = undefined;\n if (autoSaveId) {\n try {\n const value = localStorage.getItem(`PanelGroup:sizes:${autoSaveId}`);\n if (value) {\n defaultSizes = JSON.parse(value);\n }\n } catch (error) {}\n }\n\n if (sizes.length === 0 && defaultSizes != null && defaultSizes.length === panels.length) {\n setSizes(defaultSizes);\n } else {\n const totalWeight = panels.reduce((weight, panel) => {\n return weight + panel.defaultSize;\n }, 0);\n\n setSizes(panels.map(panel => panel.defaultSize / totalWeight));\n }\n }, [autoSaveId]);\n\n useEffect(() => {\n if (autoSaveId && sizes.length > 0) {\n // If this panel has been configured to persist sizing information, save sizes to local storage.\n localStorage.setItem(`PanelGroup:sizes:${autoSaveId}`, JSON.stringify(sizes));\n }\n }, [autoSaveId, sizes]);\n\n const getPanelStyle = useCallback(\n (id: PanelId): CSSProperties => {\n const panels = panelsRef.current;\n\n const offset = getOffset(panels, id, direction, sizes, height, width);\n const size = getSize(panels, id, direction, sizes, height, width);\n\n if (direction === \"horizontal\") {\n return {\n height: \"100%\",\n position: \"absolute\",\n left: offset,\n top: 0,\n width: size,\n };\n } else {\n return {\n height: size,\n position: \"absolute\",\n left: 0,\n top: offset,\n width: \"100%\",\n };\n }\n },\n [direction, height, sizes, width]\n );\n\n const registerPanel = useCallback((id: PanelId, panel: Panel) => {\n const panels = panelsRef.current;\n const index = panels.findIndex(panel => panel.id === id);\n if (index >= 0) {\n panels.splice(index, 1);\n }\n panels.push(panel);\n }, []);\n\n const registerResizeHandle = useCallback((idBefore: PanelId, idAfter: PanelId) => {\n return (event: MouseEvent) => {\n event.preventDefault();\n\n const panels = panelsRef.current;\n const { direction, height, sizes: prevSizes, width } = committedValuesRef.current;\n\n const isHorizontal = direction === \"horizontal\";\n const movement = isHorizontal ? event.movementX : event.movementY;\n const delta = isHorizontal ? movement / width : movement / height;\n\n const nextSizes = adjustByDelta(panels, idBefore, idAfter, delta, prevSizes);\n if (prevSizes !== nextSizes) {\n setSizes(nextSizes);\n }\n };\n }, []);\n\n const context = useMemo(\n () => ({\n direction,\n getPanelStyle,\n registerPanel,\n registerResizeHandle,\n }),\n [direction, getPanelStyle, registerPanel, registerResizeHandle]\n );\n\n return (\n <PanelGroupContext.Provider value={context}>\n <div className={className}>\n {children}\n </div>\n </PanelGroupContext.Provider>\n );\n}\n\nfunction adjustByDelta(\n panels: Panel[],\n idBefore: PanelId,\n idAfter: PanelId,\n delta: number,\n prevSizes: number[]\n): number[] {\n if (delta === 0) {\n return prevSizes;\n }\n\n const nextSizes = prevSizes.concat();\n\n let deltaApplied = 0;\n\n // A resizing panel affects the panels before or after it.\n //\n // A negative delta means the panel immediately after the resizer should grow/expand by decreasing its offset.\n // Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.\n //\n // A positive delta means the panel immediately before the resizer should \"expand\".\n // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.\n let pivotId = delta < 0 ? idBefore : idAfter;\n let index = panels.findIndex(panel => panel.id === pivotId);\n while (true) {\n const panel = panels[index];\n const prevSize = prevSizes[index];\n const nextSize = Math.max(prevSize - Math.abs(delta), panel.minSize);\n if (prevSize !== nextSize) {\n deltaApplied += prevSize - nextSize;\n\n nextSizes[index] = nextSize;\n\n if (deltaApplied.toPrecision(PRECISION) >= delta.toPrecision(PRECISION)) {\n break;\n }\n }\n\n if (delta < 0) {\n if (--index < 0) {\n break;\n }\n } else {\n if (++index >= panels.length) {\n break;\n }\n }\n }\n\n // If we were unable to resize any of the panels panels, return the previous state.\n // This will essentially bailout and ignore the \"mousemove\" event.\n if (deltaApplied === 0) {\n return prevSizes;\n }\n\n // Adjust the pivot panel before, but only by the amount that surrounding panels were able to shrink/contract.\n pivotId = delta < 0 ? idAfter : idBefore;\n index = panels.findIndex(panel => panel.id === pivotId);\n nextSizes[index] = prevSizes[index] + deltaApplied;\n\n return nextSizes;\n}\n\nfunction getOffset(\n panels: Panel[],\n id: PanelId,\n direction: Direction,\n sizes: number[],\n height: number,\n width: number\n): number {\n let index = panels.findIndex(panel => panel.id === id);\n if (index < 0) {\n return 0;\n }\n\n let scaledOffset = 0;\n\n for (index = index - 1; index >= 0; index--) {\n const panel = panels[index];\n scaledOffset += getSize(panels, panel.id, direction, sizes, height, width);\n }\n\n return Math.round(scaledOffset);\n}\n\nfunction getSize(\n panels: Panel[],\n id: PanelId,\n direction: Direction,\n sizes: number[],\n height: number,\n width: number\n): number {\n const index = panels.findIndex(panel => panel.id === id);\n const size = sizes[index];\n if (size == null) {\n return 0;\n }\n\n const totalSize = direction === \"horizontal\" ? width : height;\n\n if (panels.length === 1) {\n return totalSize;\n } else {\n return Math.round(size * totalSize);\n }\n}\n","import { ReactNode, useContext, useEffect, useState } from \"react\";\n\nimport { PanelGroupContext } from \"./PanelContexts\";\nimport { PanelId, ResizeHandler } from \"./types\";\n\nexport default function PanelResizeHandle({\n children = null,\n className = \"\",\n disabled = false,\n panelAfter,\n panelBefore,\n}: {\n children?: ReactNode;\n className?: string;\n disabled?: boolean;\n panelAfter: PanelId;\n panelBefore: PanelId;\n}) {\n const context = useContext(PanelGroupContext);\n if (context === null) {\n throw Error(`PanelResizeHandle components must be rendered within a PanelGroup container`);\n }\n\n const { direction, registerResizeHandle } = context;\n\n const [resizeHandler, setResizeHandler] = useState<ResizeHandler | null>(null);\n const [isDragging, setIsDragging] = useState(false);\n\n useEffect(() => {\n if (disabled) {\n setResizeHandler(null);\n } else {\n setResizeHandler(() => registerResizeHandle(panelBefore, panelAfter));\n }\n }, [disabled, panelAfter, panelBefore, registerResizeHandle]);\n\n useEffect(() => {\n if (disabled || resizeHandler == null || !isDragging) {\n return;\n }\n\n document.body.style.cursor = direction === \"horizontal\" ? \"ew-resize\" : \"ns-resize\";\n\n const onMouseLeave = (_: MouseEvent) => {\n setIsDragging(false);\n };\n\n const onMouseMove = (event: MouseEvent) => {\n resizeHandler(event);\n };\n\n const onMouseUp = (_: MouseEvent) => {\n setIsDragging(false);\n };\n\n document.body.addEventListener(\"mouseleave\", onMouseLeave);\n document.body.addEventListener(\"mousemove\", onMouseMove);\n document.body.addEventListener(\"mouseup\", onMouseUp);\n\n return () => {\n document.body.style.cursor = \"\";\n\n document.body.removeEventListener(\"mouseleave\", onMouseLeave);\n document.body.removeEventListener(\"mousemove\", onMouseMove);\n document.body.removeEventListener(\"mouseup\", onMouseUp);\n };\n }, [direction, disabled, isDragging, resizeHandler]);\n\n return (\n <div\n className={className}\n onMouseDown={() => setIsDragging(true)}\n onMouseUp={() => setIsDragging(false)}\n style={{\n cursor: direction === 'horizontal' ? 'ew-resize' : 'ns-resize'\n }}\n >\n {children}\n </div>\n );\n}\n"],"names":[],"version":3,"file":"react-resizable-panels.module.js.map"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-resizable-panels",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "React components for resizable panel groups/layouts",
|
|
5
|
+
"author": "Brian Vaughn <brian.david.vaughn@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/bvaughn/react-resizable-panels.git"
|
|
10
|
+
},
|
|
11
|
+
"source": "src/index.ts",
|
|
12
|
+
"main": "dist/react-resizable-panels.js",
|
|
13
|
+
"module": "dist/react-resizable-panels.module.js",
|
|
14
|
+
"types": "dist/react-resizable-panels.d.ts",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"dev": "parcel 'website/index.html'",
|
|
17
|
+
"watch": "parcel watch",
|
|
18
|
+
"build": "parcel build"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@parcel/packager-ts": "2.8.2",
|
|
22
|
+
"@parcel/transformer-typescript-types": "2.8.2",
|
|
23
|
+
"parcel": "latest",
|
|
24
|
+
"process": "^0.11.10",
|
|
25
|
+
"react": "latest",
|
|
26
|
+
"react-dom": "latest",
|
|
27
|
+
"react-virtualized-auto-sizer": "latest",
|
|
28
|
+
"typescript": ">=3.0.0"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"react": "^16.14.0 || ^17.0.0 || ^18.0.0",
|
|
33
|
+
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/Panel.tsx
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ReactNode, useContext, useLayoutEffect } from "react";
|
|
2
|
+
|
|
3
|
+
import { PanelGroupContext } from "./PanelContexts";
|
|
4
|
+
import { PanelId } from "./types";
|
|
5
|
+
|
|
6
|
+
// TODO [panels]
|
|
7
|
+
// Support min pixel size too.
|
|
8
|
+
// PanelGroup should warn if total width is less min pixel widths.
|
|
9
|
+
export default function Panel({
|
|
10
|
+
children,
|
|
11
|
+
className = "",
|
|
12
|
+
defaultSize = 0.1,
|
|
13
|
+
id,
|
|
14
|
+
minSize = 0.1,
|
|
15
|
+
}: {
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
className?: string;
|
|
18
|
+
defaultSize?: number;
|
|
19
|
+
id: PanelId;
|
|
20
|
+
minSize?: number;
|
|
21
|
+
}) {
|
|
22
|
+
const context = useContext(PanelGroupContext);
|
|
23
|
+
if (context === null) {
|
|
24
|
+
throw Error(`Panel components must be rendered within a PanelGroup container`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { getPanelStyle, registerPanel } = context;
|
|
28
|
+
|
|
29
|
+
useLayoutEffect(() => {
|
|
30
|
+
const panel = {
|
|
31
|
+
defaultSize,
|
|
32
|
+
id,
|
|
33
|
+
minSize,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
registerPanel(id, panel);
|
|
37
|
+
}, [defaultSize, minSize, registerPanel, id]);
|
|
38
|
+
|
|
39
|
+
const style = getPanelStyle(id);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className={className} style={style}>
|
|
43
|
+
{children}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CSSProperties, createContext } from "react";
|
|
2
|
+
|
|
3
|
+
import { Panel, PanelId, ResizeHandler } from "./types";
|
|
4
|
+
|
|
5
|
+
export const PanelGroupContext = createContext<{
|
|
6
|
+
direction: "horizontal" | "vertical";
|
|
7
|
+
getPanelStyle: (id: PanelId) => CSSProperties;
|
|
8
|
+
registerResizeHandle: (idBefore: PanelId, idAfter: PanelId) => ResizeHandler;
|
|
9
|
+
registerPanel: (id: PanelId, panel: Panel) => void;
|
|
10
|
+
} | null>(null);
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CSSProperties,
|
|
3
|
+
ReactNode,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useLayoutEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
} from "react";
|
|
11
|
+
|
|
12
|
+
import { PanelGroupContext } from "./PanelContexts";
|
|
13
|
+
import { Direction, Panel, PanelId } from "./types";
|
|
14
|
+
|
|
15
|
+
type Props = {
|
|
16
|
+
autoSaveId?: string;
|
|
17
|
+
children: ReactNode[];
|
|
18
|
+
className?: string;
|
|
19
|
+
direction: Direction;
|
|
20
|
+
height: number;
|
|
21
|
+
width: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const PRECISION = 5;
|
|
25
|
+
|
|
26
|
+
// TODO [panels]
|
|
27
|
+
// Within an active drag, remember original positions to refine more easily on expand.
|
|
28
|
+
// Look at what the Chrome devtools Sources does.
|
|
29
|
+
|
|
30
|
+
export default function PanelGroup({ autoSaveId, children, className = "", direction, height, width }: Props) {
|
|
31
|
+
const panelsRef = useRef<Panel[]>([]);
|
|
32
|
+
|
|
33
|
+
// 0-1 values representing the relative size of each panel.
|
|
34
|
+
const [sizes, setSizes] = useState<number[]>([]);
|
|
35
|
+
|
|
36
|
+
// Store committed values to avoid unnecessarily re-running memoization/effects functions.
|
|
37
|
+
const committedValuesRef = useRef<{
|
|
38
|
+
direction: Direction;
|
|
39
|
+
height: number;
|
|
40
|
+
sizes: number[];
|
|
41
|
+
width: number;
|
|
42
|
+
}>({
|
|
43
|
+
direction,
|
|
44
|
+
height,
|
|
45
|
+
sizes,
|
|
46
|
+
width,
|
|
47
|
+
});
|
|
48
|
+
useLayoutEffect(() => {
|
|
49
|
+
committedValuesRef.current.direction = direction;
|
|
50
|
+
committedValuesRef.current.height = height;
|
|
51
|
+
committedValuesRef.current.sizes = sizes;
|
|
52
|
+
committedValuesRef.current.width = width;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Once all panels have registered themselves,
|
|
56
|
+
// Compute the initial sizes based on default weights.
|
|
57
|
+
// This assumes that panels register during initial mount (no conditional rendering)!
|
|
58
|
+
useLayoutEffect(() => {
|
|
59
|
+
const panels = panelsRef.current;
|
|
60
|
+
const sizes = committedValuesRef.current.sizes;
|
|
61
|
+
if (sizes.length === panels.length) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// If this panel has been configured to persist sizing information,
|
|
66
|
+
// default size should be restored from local storage if possible.
|
|
67
|
+
let defaultSizes: number[] | undefined = undefined;
|
|
68
|
+
if (autoSaveId) {
|
|
69
|
+
try {
|
|
70
|
+
const value = localStorage.getItem(`PanelGroup:sizes:${autoSaveId}`);
|
|
71
|
+
if (value) {
|
|
72
|
+
defaultSizes = JSON.parse(value);
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (sizes.length === 0 && defaultSizes != null && defaultSizes.length === panels.length) {
|
|
78
|
+
setSizes(defaultSizes);
|
|
79
|
+
} else {
|
|
80
|
+
const totalWeight = panels.reduce((weight, panel) => {
|
|
81
|
+
return weight + panel.defaultSize;
|
|
82
|
+
}, 0);
|
|
83
|
+
|
|
84
|
+
setSizes(panels.map(panel => panel.defaultSize / totalWeight));
|
|
85
|
+
}
|
|
86
|
+
}, [autoSaveId]);
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (autoSaveId && sizes.length > 0) {
|
|
90
|
+
// If this panel has been configured to persist sizing information, save sizes to local storage.
|
|
91
|
+
localStorage.setItem(`PanelGroup:sizes:${autoSaveId}`, JSON.stringify(sizes));
|
|
92
|
+
}
|
|
93
|
+
}, [autoSaveId, sizes]);
|
|
94
|
+
|
|
95
|
+
const getPanelStyle = useCallback(
|
|
96
|
+
(id: PanelId): CSSProperties => {
|
|
97
|
+
const panels = panelsRef.current;
|
|
98
|
+
|
|
99
|
+
const offset = getOffset(panels, id, direction, sizes, height, width);
|
|
100
|
+
const size = getSize(panels, id, direction, sizes, height, width);
|
|
101
|
+
|
|
102
|
+
if (direction === "horizontal") {
|
|
103
|
+
return {
|
|
104
|
+
height: "100%",
|
|
105
|
+
position: "absolute",
|
|
106
|
+
left: offset,
|
|
107
|
+
top: 0,
|
|
108
|
+
width: size,
|
|
109
|
+
};
|
|
110
|
+
} else {
|
|
111
|
+
return {
|
|
112
|
+
height: size,
|
|
113
|
+
position: "absolute",
|
|
114
|
+
left: 0,
|
|
115
|
+
top: offset,
|
|
116
|
+
width: "100%",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
[direction, height, sizes, width]
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const registerPanel = useCallback((id: PanelId, panel: Panel) => {
|
|
124
|
+
const panels = panelsRef.current;
|
|
125
|
+
const index = panels.findIndex(panel => panel.id === id);
|
|
126
|
+
if (index >= 0) {
|
|
127
|
+
panels.splice(index, 1);
|
|
128
|
+
}
|
|
129
|
+
panels.push(panel);
|
|
130
|
+
}, []);
|
|
131
|
+
|
|
132
|
+
const registerResizeHandle = useCallback((idBefore: PanelId, idAfter: PanelId) => {
|
|
133
|
+
return (event: MouseEvent) => {
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
|
|
136
|
+
const panels = panelsRef.current;
|
|
137
|
+
const { direction, height, sizes: prevSizes, width } = committedValuesRef.current;
|
|
138
|
+
|
|
139
|
+
const isHorizontal = direction === "horizontal";
|
|
140
|
+
const movement = isHorizontal ? event.movementX : event.movementY;
|
|
141
|
+
const delta = isHorizontal ? movement / width : movement / height;
|
|
142
|
+
|
|
143
|
+
const nextSizes = adjustByDelta(panels, idBefore, idAfter, delta, prevSizes);
|
|
144
|
+
if (prevSizes !== nextSizes) {
|
|
145
|
+
setSizes(nextSizes);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}, []);
|
|
149
|
+
|
|
150
|
+
const context = useMemo(
|
|
151
|
+
() => ({
|
|
152
|
+
direction,
|
|
153
|
+
getPanelStyle,
|
|
154
|
+
registerPanel,
|
|
155
|
+
registerResizeHandle,
|
|
156
|
+
}),
|
|
157
|
+
[direction, getPanelStyle, registerPanel, registerResizeHandle]
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<PanelGroupContext.Provider value={context}>
|
|
162
|
+
<div className={className}>
|
|
163
|
+
{children}
|
|
164
|
+
</div>
|
|
165
|
+
</PanelGroupContext.Provider>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function adjustByDelta(
|
|
170
|
+
panels: Panel[],
|
|
171
|
+
idBefore: PanelId,
|
|
172
|
+
idAfter: PanelId,
|
|
173
|
+
delta: number,
|
|
174
|
+
prevSizes: number[]
|
|
175
|
+
): number[] {
|
|
176
|
+
if (delta === 0) {
|
|
177
|
+
return prevSizes;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const nextSizes = prevSizes.concat();
|
|
181
|
+
|
|
182
|
+
let deltaApplied = 0;
|
|
183
|
+
|
|
184
|
+
// A resizing panel affects the panels before or after it.
|
|
185
|
+
//
|
|
186
|
+
// A negative delta means the panel immediately after the resizer should grow/expand by decreasing its offset.
|
|
187
|
+
// Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
|
|
188
|
+
//
|
|
189
|
+
// A positive delta means the panel immediately before the resizer should "expand".
|
|
190
|
+
// This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.
|
|
191
|
+
let pivotId = delta < 0 ? idBefore : idAfter;
|
|
192
|
+
let index = panels.findIndex(panel => panel.id === pivotId);
|
|
193
|
+
while (true) {
|
|
194
|
+
const panel = panels[index];
|
|
195
|
+
const prevSize = prevSizes[index];
|
|
196
|
+
const nextSize = Math.max(prevSize - Math.abs(delta), panel.minSize);
|
|
197
|
+
if (prevSize !== nextSize) {
|
|
198
|
+
deltaApplied += prevSize - nextSize;
|
|
199
|
+
|
|
200
|
+
nextSizes[index] = nextSize;
|
|
201
|
+
|
|
202
|
+
if (deltaApplied.toPrecision(PRECISION) >= delta.toPrecision(PRECISION)) {
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (delta < 0) {
|
|
208
|
+
if (--index < 0) {
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
if (++index >= panels.length) {
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// If we were unable to resize any of the panels panels, return the previous state.
|
|
219
|
+
// This will essentially bailout and ignore the "mousemove" event.
|
|
220
|
+
if (deltaApplied === 0) {
|
|
221
|
+
return prevSizes;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Adjust the pivot panel before, but only by the amount that surrounding panels were able to shrink/contract.
|
|
225
|
+
pivotId = delta < 0 ? idAfter : idBefore;
|
|
226
|
+
index = panels.findIndex(panel => panel.id === pivotId);
|
|
227
|
+
nextSizes[index] = prevSizes[index] + deltaApplied;
|
|
228
|
+
|
|
229
|
+
return nextSizes;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function getOffset(
|
|
233
|
+
panels: Panel[],
|
|
234
|
+
id: PanelId,
|
|
235
|
+
direction: Direction,
|
|
236
|
+
sizes: number[],
|
|
237
|
+
height: number,
|
|
238
|
+
width: number
|
|
239
|
+
): number {
|
|
240
|
+
let index = panels.findIndex(panel => panel.id === id);
|
|
241
|
+
if (index < 0) {
|
|
242
|
+
return 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let scaledOffset = 0;
|
|
246
|
+
|
|
247
|
+
for (index = index - 1; index >= 0; index--) {
|
|
248
|
+
const panel = panels[index];
|
|
249
|
+
scaledOffset += getSize(panels, panel.id, direction, sizes, height, width);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return Math.round(scaledOffset);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function getSize(
|
|
256
|
+
panels: Panel[],
|
|
257
|
+
id: PanelId,
|
|
258
|
+
direction: Direction,
|
|
259
|
+
sizes: number[],
|
|
260
|
+
height: number,
|
|
261
|
+
width: number
|
|
262
|
+
): number {
|
|
263
|
+
const index = panels.findIndex(panel => panel.id === id);
|
|
264
|
+
const size = sizes[index];
|
|
265
|
+
if (size == null) {
|
|
266
|
+
return 0;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const totalSize = direction === "horizontal" ? width : height;
|
|
270
|
+
|
|
271
|
+
if (panels.length === 1) {
|
|
272
|
+
return totalSize;
|
|
273
|
+
} else {
|
|
274
|
+
return Math.round(size * totalSize);
|
|
275
|
+
}
|
|
276
|
+
}
|