react-resizable-panels 0.0.28 → 0.0.30
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 +22 -14
- package/dist/react-resizable-panels.d.ts +15 -5
- package/dist/react-resizable-panels.d.ts.map +1 -1
- package/dist/react-resizable-panels.js +171 -25
- package/dist/react-resizable-panels.js.map +1 -1
- package/dist/react-resizable-panels.module.js +170 -24
- package/dist/react-resizable-panels.module.js.map +1 -1
- package/package.json +1 -1
- package/src/Panel.ts +42 -7
- package/src/PanelContexts.ts +3 -0
- package/src/PanelGroup.ts +183 -22
- package/src/PanelResizeHandle.ts +1 -1
- package/src/hooks/useIsomorphicEffect.ts +9 -2
- package/src/index.ts +18 -4
- package/src/types.ts +1 -0
- package/src/utils/debounce.ts +16 -0
- package/src/utils/group.ts +46 -0
package/src/PanelGroup.ts
CHANGED
|
@@ -11,11 +11,13 @@ import {
|
|
|
11
11
|
} from "react";
|
|
12
12
|
|
|
13
13
|
import { PanelGroupContext } from "./PanelContexts";
|
|
14
|
-
import { Direction, PanelData, ResizeEvent } from "./types";
|
|
14
|
+
import { Direction, PanelData, PanelGroupOnLayout, ResizeEvent } from "./types";
|
|
15
15
|
import { loadPanelLayout, savePanelGroupLayout } from "./utils/serialization";
|
|
16
16
|
import { getDragOffset, getMovement } from "./utils/coordinates";
|
|
17
17
|
import {
|
|
18
18
|
adjustByDelta,
|
|
19
|
+
callPanelCallbacks,
|
|
20
|
+
getBeforeAndAfterIds,
|
|
19
21
|
getFlexGrow,
|
|
20
22
|
getPanelGroup,
|
|
21
23
|
getResizeHandlePanelIds,
|
|
@@ -25,6 +27,10 @@ import useIsomorphicLayoutEffect from "./hooks/useIsomorphicEffect";
|
|
|
25
27
|
import useUniqueId from "./hooks/useUniqueId";
|
|
26
28
|
import { useWindowSplitterPanelGroupBehavior } from "./hooks/useWindowSplitterBehavior";
|
|
27
29
|
import { resetGlobalCursorStyle, setGlobalCursorStyle } from "./utils/cursor";
|
|
30
|
+
import debounce from "./utils/debounce";
|
|
31
|
+
|
|
32
|
+
// Limit the frequency of localStorage updates.
|
|
33
|
+
const savePanelGroupLayoutDebounced = debounce(savePanelGroupLayout, 100);
|
|
28
34
|
|
|
29
35
|
export type CommittedValues = {
|
|
30
36
|
direction: Direction;
|
|
@@ -44,16 +50,18 @@ export type PanelGroupProps = {
|
|
|
44
50
|
className?: string;
|
|
45
51
|
direction: Direction;
|
|
46
52
|
id?: string | null;
|
|
53
|
+
onLayout?: PanelGroupOnLayout;
|
|
47
54
|
style?: CSSProperties;
|
|
48
55
|
tagName?: ElementType;
|
|
49
56
|
};
|
|
50
57
|
|
|
51
|
-
export
|
|
58
|
+
export function PanelGroup({
|
|
52
59
|
autoSaveId,
|
|
53
60
|
children = null,
|
|
54
61
|
className: classNameFromProps = "",
|
|
55
62
|
direction,
|
|
56
63
|
id: idFromProps = null,
|
|
64
|
+
onLayout = null,
|
|
57
65
|
style: styleFromProps = {},
|
|
58
66
|
tagName: Type = "div",
|
|
59
67
|
}: PanelGroupProps) {
|
|
@@ -62,11 +70,22 @@ export default function PanelGroup({
|
|
|
62
70
|
const [activeHandleId, setActiveHandleId] = useState<string | null>(null);
|
|
63
71
|
const [panels, setPanels] = useState<PanelDataMap>(new Map());
|
|
64
72
|
|
|
73
|
+
// Use a ref to guard against users passing inline props
|
|
74
|
+
const callbacksRef = useRef<{
|
|
75
|
+
onLayout: PanelGroupOnLayout | null;
|
|
76
|
+
}>({ onLayout });
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
callbacksRef.current.onLayout = onLayout;
|
|
79
|
+
});
|
|
80
|
+
|
|
65
81
|
// 0-1 values representing the relative size of each panel.
|
|
66
82
|
const [sizes, setSizes] = useState<number[]>([]);
|
|
67
83
|
|
|
68
84
|
const dragOffsetRef = useRef<number>(0);
|
|
69
85
|
|
|
86
|
+
// Used to support imperative collapse/expand API.
|
|
87
|
+
const panelSizeBeforeCollapse = useRef<Map<string, number>>(new Map());
|
|
88
|
+
|
|
70
89
|
// Store committed values to avoid unnecessarily re-running memoization/effects functions.
|
|
71
90
|
const committedValuesRef = useRef<CommittedValues>({
|
|
72
91
|
direction,
|
|
@@ -88,6 +107,15 @@ export default function PanelGroup({
|
|
|
88
107
|
sizes,
|
|
89
108
|
});
|
|
90
109
|
|
|
110
|
+
// Notify external code when sizes have changed.
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
const { onLayout } = callbacksRef.current;
|
|
113
|
+
if (onLayout) {
|
|
114
|
+
const { sizes } = committedValuesRef.current;
|
|
115
|
+
onLayout(sizes);
|
|
116
|
+
}
|
|
117
|
+
}, [sizes]);
|
|
118
|
+
|
|
91
119
|
// Once all panels have registered themselves,
|
|
92
120
|
// Compute the initial sizes based on default weights.
|
|
93
121
|
// This assumes that panels register during initial mount (no conditional rendering)!
|
|
@@ -160,7 +188,8 @@ export default function PanelGroup({
|
|
|
160
188
|
}
|
|
161
189
|
|
|
162
190
|
const panelsArray = panelsMapToSortedArray(panels);
|
|
163
|
-
|
|
191
|
+
|
|
192
|
+
savePanelGroupLayoutDebounced(autoSaveId, panelsArray, sizes);
|
|
164
193
|
}
|
|
165
194
|
}, [autoSaveId, panels, sizes]);
|
|
166
195
|
|
|
@@ -191,9 +220,13 @@ export default function PanelGroup({
|
|
|
191
220
|
|
|
192
221
|
// Without this, Panel sizes may be unintentionally overridden by their content.
|
|
193
222
|
overflow: "hidden",
|
|
223
|
+
|
|
224
|
+
// Disable pointer events inside of a panel during resize.
|
|
225
|
+
// This avoid edge cases like nested iframes.
|
|
226
|
+
pointerEvents: activeHandleId !== null ? "none" : undefined,
|
|
194
227
|
};
|
|
195
228
|
},
|
|
196
|
-
[direction, sizes]
|
|
229
|
+
[activeHandleId, direction, sizes]
|
|
197
230
|
);
|
|
198
231
|
|
|
199
232
|
const registerPanel = useCallback((id: string, panel: PanelData) => {
|
|
@@ -275,24 +308,7 @@ export default function PanelGroup({
|
|
|
275
308
|
setGlobalCursorStyle(isHorizontal ? "horizontal" : "vertical");
|
|
276
309
|
|
|
277
310
|
// If resize change handlers have been declared, this is the time to call them.
|
|
278
|
-
|
|
279
|
-
const prevSize = prevSizes[index];
|
|
280
|
-
if (prevSize !== nextSize) {
|
|
281
|
-
const { onCollapse, onResize } =
|
|
282
|
-
panelsArray[index].callbacksRef.current;
|
|
283
|
-
if (onResize) {
|
|
284
|
-
onResize(nextSize);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (onCollapse) {
|
|
288
|
-
if (prevSize === 0 && nextSize !== 0) {
|
|
289
|
-
onCollapse(false);
|
|
290
|
-
} else if (prevSize !== 0 && nextSize === 0) {
|
|
291
|
-
onCollapse(true);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
});
|
|
311
|
+
callPanelCallbacks(panelsArray, prevSizes, nextSizes);
|
|
296
312
|
|
|
297
313
|
setSizes(nextSizes);
|
|
298
314
|
}
|
|
@@ -316,14 +332,156 @@ export default function PanelGroup({
|
|
|
316
332
|
});
|
|
317
333
|
}, []);
|
|
318
334
|
|
|
335
|
+
const collapsePanel = useCallback((id: string) => {
|
|
336
|
+
const { panels, sizes: prevSizes } = committedValuesRef.current;
|
|
337
|
+
|
|
338
|
+
const panel = panels.get(id);
|
|
339
|
+
if (panel == null || !panel.collapsible) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const panelsArray = panelsMapToSortedArray(panels);
|
|
344
|
+
|
|
345
|
+
const index = panelsArray.indexOf(panel);
|
|
346
|
+
if (index < 0) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const currentSize = prevSizes[index];
|
|
351
|
+
if (currentSize === 0) {
|
|
352
|
+
// Panel is already collapsed.
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
panelSizeBeforeCollapse.current.set(id, currentSize);
|
|
357
|
+
|
|
358
|
+
const [idBefore, idAfter] = getBeforeAndAfterIds(id, panelsArray);
|
|
359
|
+
if (idBefore == null || idAfter == null) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const isLastPanel = index === panelsArray.length - 1;
|
|
364
|
+
const delta = isLastPanel ? currentSize : 0 - currentSize;
|
|
365
|
+
|
|
366
|
+
const nextSizes = adjustByDelta(
|
|
367
|
+
panels,
|
|
368
|
+
idBefore,
|
|
369
|
+
idAfter,
|
|
370
|
+
delta,
|
|
371
|
+
prevSizes
|
|
372
|
+
);
|
|
373
|
+
if (prevSizes !== nextSizes) {
|
|
374
|
+
// If resize change handlers have been declared, this is the time to call them.
|
|
375
|
+
callPanelCallbacks(panelsArray, prevSizes, nextSizes);
|
|
376
|
+
|
|
377
|
+
setSizes(nextSizes);
|
|
378
|
+
}
|
|
379
|
+
}, []);
|
|
380
|
+
|
|
381
|
+
const expandPanel = useCallback((id: string) => {
|
|
382
|
+
const { panels, sizes: prevSizes } = committedValuesRef.current;
|
|
383
|
+
|
|
384
|
+
const panel = panels.get(id);
|
|
385
|
+
if (panel == null) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const sizeBeforeCollapse =
|
|
390
|
+
panelSizeBeforeCollapse.current.get(id) || panel.minSize;
|
|
391
|
+
if (!sizeBeforeCollapse) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const panelsArray = panelsMapToSortedArray(panels);
|
|
396
|
+
|
|
397
|
+
const index = panelsArray.indexOf(panel);
|
|
398
|
+
if (index < 0) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const currentSize = prevSizes[index];
|
|
403
|
+
if (currentSize !== 0) {
|
|
404
|
+
// Panel is already expanded.
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const [idBefore, idAfter] = getBeforeAndAfterIds(id, panelsArray);
|
|
409
|
+
if (idBefore == null || idAfter == null) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const isLastPanel = index === panelsArray.length - 1;
|
|
414
|
+
const delta = isLastPanel ? 0 - sizeBeforeCollapse : sizeBeforeCollapse;
|
|
415
|
+
|
|
416
|
+
const nextSizes = adjustByDelta(
|
|
417
|
+
panels,
|
|
418
|
+
idBefore,
|
|
419
|
+
idAfter,
|
|
420
|
+
delta,
|
|
421
|
+
prevSizes
|
|
422
|
+
);
|
|
423
|
+
if (prevSizes !== nextSizes) {
|
|
424
|
+
// If resize change handlers have been declared, this is the time to call them.
|
|
425
|
+
callPanelCallbacks(panelsArray, prevSizes, nextSizes);
|
|
426
|
+
|
|
427
|
+
setSizes(nextSizes);
|
|
428
|
+
}
|
|
429
|
+
}, []);
|
|
430
|
+
|
|
431
|
+
const resizePanel = useCallback((id: string, nextSize: number) => {
|
|
432
|
+
const { panels, sizes: prevSizes } = committedValuesRef.current;
|
|
433
|
+
|
|
434
|
+
const panel = panels.get(id);
|
|
435
|
+
if (panel == null) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const panelsArray = panelsMapToSortedArray(panels);
|
|
440
|
+
|
|
441
|
+
const index = panelsArray.indexOf(panel);
|
|
442
|
+
if (index < 0) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const currentSize = prevSizes[index];
|
|
447
|
+
if (currentSize === nextSize) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const [idBefore, idAfter] = getBeforeAndAfterIds(id, panelsArray);
|
|
452
|
+
if (idBefore == null || idAfter == null) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const isLastPanel = index === panelsArray.length - 1;
|
|
457
|
+
const delta = isLastPanel ? currentSize - nextSize : nextSize - currentSize;
|
|
458
|
+
|
|
459
|
+
const nextSizes = adjustByDelta(
|
|
460
|
+
panels,
|
|
461
|
+
idBefore,
|
|
462
|
+
idAfter,
|
|
463
|
+
delta,
|
|
464
|
+
prevSizes
|
|
465
|
+
);
|
|
466
|
+
if (prevSizes !== nextSizes) {
|
|
467
|
+
// If resize change handlers have been declared, this is the time to call them.
|
|
468
|
+
callPanelCallbacks(panelsArray, prevSizes, nextSizes);
|
|
469
|
+
|
|
470
|
+
setSizes(nextSizes);
|
|
471
|
+
}
|
|
472
|
+
}, []);
|
|
473
|
+
|
|
319
474
|
const context = useMemo(
|
|
320
475
|
() => ({
|
|
321
476
|
activeHandleId,
|
|
477
|
+
collapsePanel,
|
|
322
478
|
direction,
|
|
479
|
+
expandPanel,
|
|
323
480
|
getPanelStyle,
|
|
324
481
|
groupId,
|
|
325
482
|
registerPanel,
|
|
326
483
|
registerResizeHandle,
|
|
484
|
+
resizePanel,
|
|
327
485
|
startDragging: (id: string, event: ResizeEvent) => {
|
|
328
486
|
setActiveHandleId(id);
|
|
329
487
|
|
|
@@ -337,11 +495,14 @@ export default function PanelGroup({
|
|
|
337
495
|
}),
|
|
338
496
|
[
|
|
339
497
|
activeHandleId,
|
|
498
|
+
collapsePanel,
|
|
340
499
|
direction,
|
|
500
|
+
expandPanel,
|
|
341
501
|
getPanelStyle,
|
|
342
502
|
groupId,
|
|
343
503
|
registerPanel,
|
|
344
504
|
registerResizeHandle,
|
|
505
|
+
resizePanel,
|
|
345
506
|
unregisterPanel,
|
|
346
507
|
]
|
|
347
508
|
);
|
package/src/PanelResizeHandle.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { useEffect, useLayoutEffect } from "react";
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
typeof window !== "undefined"
|
|
3
|
+
const canUseEffectHooks = !!(
|
|
4
|
+
typeof window !== "undefined" &&
|
|
5
|
+
typeof window.document !== "undefined" &&
|
|
6
|
+
typeof window.document.createElement !== "undefined"
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
const useIsomorphicLayoutEffect = canUseEffectHooks
|
|
10
|
+
? useLayoutEffect
|
|
11
|
+
: () => {};
|
|
5
12
|
|
|
6
13
|
export default useIsomorphicLayoutEffect;
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
import Panel from "./Panel";
|
|
2
|
-
import PanelGroup from "./PanelGroup";
|
|
3
|
-
import PanelResizeHandle from "./PanelResizeHandle";
|
|
1
|
+
import { Panel } from "./Panel";
|
|
2
|
+
import { PanelGroup } from "./PanelGroup";
|
|
3
|
+
import { PanelResizeHandle } from "./PanelResizeHandle";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import type { ImperativePanelHandle, PanelProps } from "./Panel";
|
|
6
|
+
import type { PanelGroupProps } from "./PanelGroup";
|
|
7
|
+
import type { PanelResizeHandleProps } from "./PanelResizeHandle";
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
Panel,
|
|
11
|
+
PanelGroup,
|
|
12
|
+
PanelResizeHandle,
|
|
13
|
+
|
|
14
|
+
// TypeScript types
|
|
15
|
+
ImperativePanelHandle,
|
|
16
|
+
PanelGroupProps,
|
|
17
|
+
PanelProps,
|
|
18
|
+
PanelResizeHandleProps,
|
|
19
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { RefObject } from "react";
|
|
|
2
2
|
|
|
3
3
|
export type Direction = "horizontal" | "vertical";
|
|
4
4
|
|
|
5
|
+
export type PanelGroupOnLayout = (sizes: number[]) => void;
|
|
5
6
|
export type PanelOnCollapse = (collapsed: boolean) => void;
|
|
6
7
|
export type PanelOnResize = (size: number) => void;
|
|
7
8
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default function debounce<T extends Function>(
|
|
2
|
+
callback: T,
|
|
3
|
+
durationMs: number = 10
|
|
4
|
+
) {
|
|
5
|
+
let timeoutId: NodeJS.Timeout | null = null;
|
|
6
|
+
|
|
7
|
+
let callable = (...args: any) => {
|
|
8
|
+
clearTimeout(timeoutId);
|
|
9
|
+
|
|
10
|
+
timeoutId = setTimeout(() => {
|
|
11
|
+
callback(...args);
|
|
12
|
+
}, durationMs);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return callable as unknown as T;
|
|
16
|
+
}
|
package/src/utils/group.ts
CHANGED
|
@@ -83,6 +83,52 @@ export function adjustByDelta(
|
|
|
83
83
|
return nextSizes;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
export function callPanelCallbacks(
|
|
87
|
+
panelsArray: PanelData[],
|
|
88
|
+
prevSizes: number[],
|
|
89
|
+
nextSizes: number[]
|
|
90
|
+
) {
|
|
91
|
+
nextSizes.forEach((nextSize, index) => {
|
|
92
|
+
const prevSize = prevSizes[index];
|
|
93
|
+
if (prevSize !== nextSize) {
|
|
94
|
+
const { callbacksRef } = panelsArray[index];
|
|
95
|
+
const { onCollapse, onResize } = callbacksRef.current;
|
|
96
|
+
|
|
97
|
+
if (onResize) {
|
|
98
|
+
onResize(nextSize);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (onCollapse) {
|
|
102
|
+
if (prevSize === 0 && nextSize !== 0) {
|
|
103
|
+
onCollapse(false);
|
|
104
|
+
} else if (prevSize !== 0 && nextSize === 0) {
|
|
105
|
+
onCollapse(true);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function getBeforeAndAfterIds(
|
|
113
|
+
id: string,
|
|
114
|
+
panelsArray: PanelData[]
|
|
115
|
+
): [idBefore: string | null, idAFter: string | null] {
|
|
116
|
+
if (panelsArray.length < 2) {
|
|
117
|
+
return [null, null];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const index = panelsArray.findIndex((panel) => panel.id === id);
|
|
121
|
+
if (index < 0) {
|
|
122
|
+
return [null, null];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const isLastPanel = index === panelsArray.length - 1;
|
|
126
|
+
const idBefore = isLastPanel ? panelsArray[index - 1].id : id;
|
|
127
|
+
const idAfter = isLastPanel ? id : panelsArray[index + 1].id;
|
|
128
|
+
|
|
129
|
+
return [idBefore, idAfter];
|
|
130
|
+
}
|
|
131
|
+
|
|
86
132
|
// This method returns a number between 1 and 100 representing
|
|
87
133
|
// the % of the group's overall space this panel should occupy.
|
|
88
134
|
export function getFlexGrow(
|