react-resizable-panels 0.0.27 → 0.0.29

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/src/PanelGroup.ts CHANGED
@@ -5,7 +5,6 @@ import {
5
5
  ReactNode,
6
6
  useCallback,
7
7
  useEffect,
8
- useLayoutEffect,
9
8
  useMemo,
10
9
  useRef,
11
10
  useState,
@@ -17,13 +16,16 @@ import { loadPanelLayout, savePanelGroupLayout } from "./utils/serialization";
17
16
  import { getDragOffset, getMovement } from "./utils/coordinates";
18
17
  import {
19
18
  adjustByDelta,
19
+ callPanelCallbacks,
20
+ getBeforeAndAfterIds,
20
21
  getFlexGrow,
21
22
  getPanelGroup,
22
23
  getResizeHandlePanelIds,
23
24
  panelsMapToSortedArray,
24
25
  } from "./utils/group";
25
- import { useWindowSplitterPanelGroupBehavior } from "./hooks/useWindowSplitterBehavior";
26
+ import useIsomorphicLayoutEffect from "./hooks/useIsomorphicEffect";
26
27
  import useUniqueId from "./hooks/useUniqueId";
28
+ import { useWindowSplitterPanelGroupBehavior } from "./hooks/useWindowSplitterBehavior";
27
29
  import { resetGlobalCursorStyle, setGlobalCursorStyle } from "./utils/cursor";
28
30
 
29
31
  export type CommittedValues = {
@@ -48,7 +50,7 @@ export type PanelGroupProps = {
48
50
  tagName?: ElementType;
49
51
  };
50
52
 
51
- export default function PanelGroup({
53
+ export function PanelGroup({
52
54
  autoSaveId,
53
55
  children = null,
54
56
  className: classNameFromProps = "",
@@ -67,6 +69,9 @@ export default function PanelGroup({
67
69
 
68
70
  const dragOffsetRef = useRef<number>(0);
69
71
 
72
+ // Used to support imperative collapse/expand API.
73
+ const panelSizeBeforeCollapse = useRef<Map<string, number>>(new Map());
74
+
70
75
  // Store committed values to avoid unnecessarily re-running memoization/effects functions.
71
76
  const committedValuesRef = useRef<CommittedValues>({
72
77
  direction,
@@ -74,7 +79,7 @@ export default function PanelGroup({
74
79
  sizes,
75
80
  });
76
81
 
77
- useLayoutEffect(() => {
82
+ useIsomorphicLayoutEffect(() => {
78
83
  committedValuesRef.current.direction = direction;
79
84
  committedValuesRef.current.panels = panels;
80
85
  committedValuesRef.current.sizes = sizes;
@@ -91,7 +96,7 @@ export default function PanelGroup({
91
96
  // Once all panels have registered themselves,
92
97
  // Compute the initial sizes based on default weights.
93
98
  // This assumes that panels register during initial mount (no conditional rendering)!
94
- useLayoutEffect(() => {
99
+ useIsomorphicLayoutEffect(() => {
95
100
  const sizes = committedValuesRef.current.sizes;
96
101
  if (sizes.length === panels.size) {
97
102
  // Only compute (or restore) default sizes once per panel configuration.
@@ -168,6 +173,20 @@ export default function PanelGroup({
168
173
  (id: string): CSSProperties => {
169
174
  const { panels } = committedValuesRef.current;
170
175
 
176
+ // Before mounting, Panels will not yet have registered themselves.
177
+ // This includes server rendering.
178
+ // At this point the best we can do is render everything with the same size.
179
+ if (panels.size === 0) {
180
+ return {
181
+ flexBasis: "auto",
182
+ flexGrow: 1,
183
+ flexShrink: 1,
184
+
185
+ // Without this, Panel sizes may be unintentionally overridden by their content.
186
+ overflow: "hidden",
187
+ };
188
+ }
189
+
171
190
  const flexGrow = getFlexGrow(panels, id, sizes);
172
191
 
173
192
  return {
@@ -177,9 +196,13 @@ export default function PanelGroup({
177
196
 
178
197
  // Without this, Panel sizes may be unintentionally overridden by their content.
179
198
  overflow: "hidden",
199
+
200
+ // Disable pointer events inside of a panel during resize.
201
+ // This avoid edge cases like nested iframes.
202
+ pointerEvents: activeHandleId !== null ? "none" : undefined,
180
203
  };
181
204
  },
182
- [direction, sizes]
205
+ [activeHandleId, direction, sizes]
183
206
  );
184
207
 
185
208
  const registerPanel = useCallback((id: string, panel: PanelData) => {
@@ -261,24 +284,7 @@ export default function PanelGroup({
261
284
  setGlobalCursorStyle(isHorizontal ? "horizontal" : "vertical");
262
285
 
263
286
  // If resize change handlers have been declared, this is the time to call them.
264
- nextSizes.forEach((nextSize, index) => {
265
- const prevSize = prevSizes[index];
266
- if (prevSize !== nextSize) {
267
- const { onCollapse, onResize } =
268
- panelsArray[index].callbacksRef.current;
269
- if (onResize) {
270
- onResize(nextSize);
271
- }
272
-
273
- if (onCollapse) {
274
- if (prevSize === 0 && nextSize !== 0) {
275
- onCollapse(false);
276
- } else if (prevSize !== 0 && nextSize === 0) {
277
- onCollapse(true);
278
- }
279
- }
280
- }
281
- });
287
+ callPanelCallbacks(panelsArray, prevSizes, nextSizes);
282
288
 
283
289
  setSizes(nextSizes);
284
290
  }
@@ -302,14 +308,156 @@ export default function PanelGroup({
302
308
  });
303
309
  }, []);
304
310
 
311
+ const collapsePanel = useCallback((id: string) => {
312
+ const { panels, sizes: prevSizes } = committedValuesRef.current;
313
+
314
+ const panel = panels.get(id);
315
+ if (panel == null || !panel.collapsible) {
316
+ return;
317
+ }
318
+
319
+ const panelsArray = panelsMapToSortedArray(panels);
320
+
321
+ const index = panelsArray.indexOf(panel);
322
+ if (index < 0) {
323
+ return;
324
+ }
325
+
326
+ const currentSize = prevSizes[index];
327
+ if (currentSize === 0) {
328
+ // Panel is already collapsed.
329
+ return;
330
+ }
331
+
332
+ panelSizeBeforeCollapse.current.set(id, currentSize);
333
+
334
+ const [idBefore, idAfter] = getBeforeAndAfterIds(id, panelsArray);
335
+ if (idBefore == null || idAfter == null) {
336
+ return;
337
+ }
338
+
339
+ const isLastPanel = index === panelsArray.length - 1;
340
+ const delta = isLastPanel ? currentSize : 0 - currentSize;
341
+
342
+ const nextSizes = adjustByDelta(
343
+ panels,
344
+ idBefore,
345
+ idAfter,
346
+ delta,
347
+ prevSizes
348
+ );
349
+ if (prevSizes !== nextSizes) {
350
+ // If resize change handlers have been declared, this is the time to call them.
351
+ callPanelCallbacks(panelsArray, prevSizes, nextSizes);
352
+
353
+ setSizes(nextSizes);
354
+ }
355
+ }, []);
356
+
357
+ const expandPanel = useCallback((id: string) => {
358
+ const { panels, sizes: prevSizes } = committedValuesRef.current;
359
+
360
+ const panel = panels.get(id);
361
+ if (panel == null) {
362
+ return;
363
+ }
364
+
365
+ const sizeBeforeCollapse =
366
+ panelSizeBeforeCollapse.current.get(id) || panel.minSize;
367
+ if (!sizeBeforeCollapse) {
368
+ return;
369
+ }
370
+
371
+ const panelsArray = panelsMapToSortedArray(panels);
372
+
373
+ const index = panelsArray.indexOf(panel);
374
+ if (index < 0) {
375
+ return;
376
+ }
377
+
378
+ const currentSize = prevSizes[index];
379
+ if (currentSize !== 0) {
380
+ // Panel is already expanded.
381
+ return;
382
+ }
383
+
384
+ const [idBefore, idAfter] = getBeforeAndAfterIds(id, panelsArray);
385
+ if (idBefore == null || idAfter == null) {
386
+ return;
387
+ }
388
+
389
+ const isLastPanel = index === panelsArray.length - 1;
390
+ const delta = isLastPanel ? 0 - sizeBeforeCollapse : sizeBeforeCollapse;
391
+
392
+ const nextSizes = adjustByDelta(
393
+ panels,
394
+ idBefore,
395
+ idAfter,
396
+ delta,
397
+ prevSizes
398
+ );
399
+ if (prevSizes !== nextSizes) {
400
+ // If resize change handlers have been declared, this is the time to call them.
401
+ callPanelCallbacks(panelsArray, prevSizes, nextSizes);
402
+
403
+ setSizes(nextSizes);
404
+ }
405
+ }, []);
406
+
407
+ const resizePanel = useCallback((id: string, nextSize: number) => {
408
+ const { panels, sizes: prevSizes } = committedValuesRef.current;
409
+
410
+ const panel = panels.get(id);
411
+ if (panel == null) {
412
+ return;
413
+ }
414
+
415
+ const panelsArray = panelsMapToSortedArray(panels);
416
+
417
+ const index = panelsArray.indexOf(panel);
418
+ if (index < 0) {
419
+ return;
420
+ }
421
+
422
+ const currentSize = prevSizes[index];
423
+ if (currentSize === nextSize) {
424
+ return;
425
+ }
426
+
427
+ const [idBefore, idAfter] = getBeforeAndAfterIds(id, panelsArray);
428
+ if (idBefore == null || idAfter == null) {
429
+ return;
430
+ }
431
+
432
+ const isLastPanel = index === panelsArray.length - 1;
433
+ const delta = isLastPanel ? currentSize - nextSize : nextSize - currentSize;
434
+
435
+ const nextSizes = adjustByDelta(
436
+ panels,
437
+ idBefore,
438
+ idAfter,
439
+ delta,
440
+ prevSizes
441
+ );
442
+ if (prevSizes !== nextSizes) {
443
+ // If resize change handlers have been declared, this is the time to call them.
444
+ callPanelCallbacks(panelsArray, prevSizes, nextSizes);
445
+
446
+ setSizes(nextSizes);
447
+ }
448
+ }, []);
449
+
305
450
  const context = useMemo(
306
451
  () => ({
307
452
  activeHandleId,
453
+ collapsePanel,
308
454
  direction,
455
+ expandPanel,
309
456
  getPanelStyle,
310
457
  groupId,
311
458
  registerPanel,
312
459
  registerResizeHandle,
460
+ resizePanel,
313
461
  startDragging: (id: string, event: ResizeEvent) => {
314
462
  setActiveHandleId(id);
315
463
 
@@ -323,11 +471,14 @@ export default function PanelGroup({
323
471
  }),
324
472
  [
325
473
  activeHandleId,
474
+ collapsePanel,
326
475
  direction,
476
+ expandPanel,
327
477
  getPanelStyle,
328
478
  groupId,
329
479
  registerPanel,
330
480
  registerResizeHandle,
481
+ resizePanel,
331
482
  unregisterPanel,
332
483
  ]
333
484
  );
@@ -27,7 +27,7 @@ export type PanelResizeHandleProps = {
27
27
  tagName?: ElementType;
28
28
  };
29
29
 
30
- export default function PanelResizeHandle({
30
+ export function PanelResizeHandle({
31
31
  children = null,
32
32
  className: classNameFromProps = "",
33
33
  disabled = false,
@@ -0,0 +1,13 @@
1
+ import { useEffect, useLayoutEffect } from "react";
2
+
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
+ : () => {};
12
+
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
- export { Panel, PanelGroup, PanelResizeHandle };
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
+ };
@@ -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(