react-resizable-panels 0.0.2 → 0.0.4

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.
@@ -10,11 +10,11 @@ import {
10
10
  } from "react";
11
11
 
12
12
  import { PanelGroupContext } from "./PanelContexts";
13
- import { Direction, Panel, PanelId } from "./types";
13
+ import { Direction, PanelData } from "./types";
14
14
 
15
15
  type Props = {
16
16
  autoSaveId?: string;
17
- children: ReactNode[];
17
+ children?: ReactNode[];
18
18
  className?: string;
19
19
  direction: Direction;
20
20
  height: number;
@@ -27,8 +27,15 @@ const PRECISION = 5;
27
27
  // Within an active drag, remember original positions to refine more easily on expand.
28
28
  // Look at what the Chrome devtools Sources does.
29
29
 
30
- export default function PanelGroup({ autoSaveId, children, className = "", direction, height, width }: Props) {
31
- const panelsRef = useRef<Panel[]>([]);
30
+ export default function PanelGroup({
31
+ autoSaveId,
32
+ children = null,
33
+ className = "",
34
+ direction,
35
+ height,
36
+ width,
37
+ }: Props) {
38
+ const [panels, setPanels] = useState<Map<string, PanelData>>(new Map());
32
39
 
33
40
  // 0-1 values representing the relative size of each panel.
34
41
  const [sizes, setSizes] = useState<number[]>([]);
@@ -37,17 +44,20 @@ export default function PanelGroup({ autoSaveId, children, className = "", direc
37
44
  const committedValuesRef = useRef<{
38
45
  direction: Direction;
39
46
  height: number;
47
+ panels: Map<string, PanelData>;
40
48
  sizes: number[];
41
49
  width: number;
42
50
  }>({
43
51
  direction,
44
52
  height,
53
+ panels,
45
54
  sizes,
46
55
  width,
47
56
  });
48
57
  useLayoutEffect(() => {
49
58
  committedValuesRef.current.direction = direction;
50
59
  committedValuesRef.current.height = height;
60
+ committedValuesRef.current.panels = panels;
51
61
  committedValuesRef.current.sizes = sizes;
52
62
  committedValuesRef.current.width = width;
53
63
  });
@@ -56,45 +66,57 @@ export default function PanelGroup({ autoSaveId, children, className = "", direc
56
66
  // Compute the initial sizes based on default weights.
57
67
  // This assumes that panels register during initial mount (no conditional rendering)!
58
68
  useLayoutEffect(() => {
59
- const panels = panelsRef.current;
60
69
  const sizes = committedValuesRef.current.sizes;
61
- if (sizes.length === panels.length) {
70
+ if (sizes.length === panels.size) {
62
71
  return;
63
72
  }
64
73
 
74
+ // TODO [panels]
75
+ // Validate that the total minSize is <= 1.
76
+
65
77
  // If this panel has been configured to persist sizing information,
66
78
  // default size should be restored from local storage if possible.
67
79
  let defaultSizes: number[] | undefined = undefined;
68
80
  if (autoSaveId) {
69
81
  try {
70
- const value = localStorage.getItem(`PanelGroup:sizes:${autoSaveId}`);
82
+ const value = localStorage.getItem(
83
+ createLocalStorageKey(autoSaveId, panels)
84
+ );
71
85
  if (value) {
72
86
  defaultSizes = JSON.parse(value);
73
87
  }
74
88
  } catch (error) {}
75
89
  }
76
90
 
77
- if (sizes.length === 0 && defaultSizes != null && defaultSizes.length === panels.length) {
91
+ if (defaultSizes != null) {
78
92
  setSizes(defaultSizes);
79
93
  } else {
80
- const totalWeight = panels.reduce((weight, panel) => {
94
+ const panelsArray = panelsMapToSortedArray(panels);
95
+ const totalWeight = panelsArray.reduce((weight, panel) => {
81
96
  return weight + panel.defaultSize;
82
97
  }, 0);
83
98
 
84
- setSizes(panels.map(panel => panel.defaultSize / totalWeight));
99
+ setSizes(panelsArray.map((panel) => panel.defaultSize / totalWeight));
85
100
  }
86
- }, [autoSaveId]);
101
+ }, [autoSaveId, panels]);
87
102
 
88
103
  useEffect(() => {
89
- if (autoSaveId && sizes.length > 0) {
104
+ if (autoSaveId) {
105
+ if (sizes.length === 0 || sizes.length !== panels.size) {
106
+ return;
107
+ }
108
+
90
109
  // If this panel has been configured to persist sizing information, save sizes to local storage.
91
- localStorage.setItem(`PanelGroup:sizes:${autoSaveId}`, JSON.stringify(sizes));
110
+ localStorage.setItem(
111
+ createLocalStorageKey(autoSaveId, panels),
112
+ JSON.stringify(sizes)
113
+ );
92
114
  }
93
- }, [autoSaveId, sizes]);
115
+ }, [autoSaveId, panels, sizes]);
94
116
 
95
117
  const getPanelStyle = useCallback(
96
- (id: PanelId): CSSProperties => {
97
- const panels = panelsRef.current;
118
+ (id: string): CSSProperties => {
119
+ const { panels } = committedValuesRef.current;
98
120
 
99
121
  const offset = getOffset(panels, id, direction, sizes, height, width);
100
122
  const size = getSize(panels, id, direction, sizes, height, width);
@@ -120,31 +142,64 @@ export default function PanelGroup({ autoSaveId, children, className = "", direc
120
142
  [direction, height, sizes, width]
121
143
  );
122
144
 
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
- }, []);
145
+ const registerPanel = useCallback((id: string, panel: PanelData) => {
146
+ setPanels((prevPanels) => {
147
+ if (prevPanels.has(id)) {
148
+ return prevPanels;
149
+ }
131
150
 
132
- const registerResizeHandle = useCallback((idBefore: PanelId, idAfter: PanelId) => {
133
- return (event: MouseEvent) => {
134
- event.preventDefault();
151
+ const nextPanels = new Map(prevPanels);
152
+ nextPanels.set(id, panel);
135
153
 
136
- const panels = panelsRef.current;
137
- const { direction, height, sizes: prevSizes, width } = committedValuesRef.current;
154
+ return nextPanels;
155
+ });
156
+ }, []);
138
157
 
139
- const isHorizontal = direction === "horizontal";
140
- const movement = isHorizontal ? event.movementX : event.movementY;
141
- const delta = isHorizontal ? movement / width : movement / height;
158
+ const registerResizeHandle = useCallback(
159
+ (idBefore: string, idAfter: string) => {
160
+ return (event: MouseEvent) => {
161
+ event.preventDefault();
162
+
163
+ const {
164
+ direction,
165
+ height,
166
+ panels,
167
+ sizes: prevSizes,
168
+ width,
169
+ } = committedValuesRef.current;
170
+
171
+ const isHorizontal = direction === "horizontal";
172
+ const movement = isHorizontal ? event.movementX : event.movementY;
173
+ const delta = isHorizontal ? movement / width : movement / height;
174
+
175
+ const nextSizes = adjustByDelta(
176
+ panels,
177
+ idBefore,
178
+ idAfter,
179
+ delta,
180
+ prevSizes
181
+ );
182
+ if (prevSizes !== nextSizes) {
183
+ setSizes(nextSizes);
184
+ }
185
+ };
142
186
 
143
- const nextSizes = adjustByDelta(panels, idBefore, idAfter, delta, prevSizes);
144
- if (prevSizes !== nextSizes) {
145
- setSizes(nextSizes);
187
+ // TODO [issues/5] Add to Map
188
+ },
189
+ []
190
+ );
191
+
192
+ const unregisterPanel = useCallback((id: string) => {
193
+ setPanels((prevPanels) => {
194
+ if (!prevPanels.has(id)) {
195
+ return prevPanels;
146
196
  }
147
- };
197
+
198
+ const nextPanels = new Map(prevPanels);
199
+ nextPanels.delete(id);
200
+
201
+ return nextPanels;
202
+ });
148
203
  }, []);
149
204
 
150
205
  const context = useMemo(
@@ -153,23 +208,28 @@ export default function PanelGroup({ autoSaveId, children, className = "", direc
153
208
  getPanelStyle,
154
209
  registerPanel,
155
210
  registerResizeHandle,
211
+ unregisterPanel,
156
212
  }),
157
- [direction, getPanelStyle, registerPanel, registerResizeHandle]
213
+ [
214
+ direction,
215
+ getPanelStyle,
216
+ registerPanel,
217
+ registerResizeHandle,
218
+ unregisterPanel,
219
+ ]
158
220
  );
159
221
 
160
222
  return (
161
223
  <PanelGroupContext.Provider value={context}>
162
- <div className={className}>
163
- {children}
164
- </div>
224
+ <div className={className}>{children}</div>
165
225
  </PanelGroupContext.Provider>
166
226
  );
167
227
  }
168
228
 
169
229
  function adjustByDelta(
170
- panels: Panel[],
171
- idBefore: PanelId,
172
- idAfter: PanelId,
230
+ panels: Map<string, PanelData>,
231
+ idBefore: string,
232
+ idAfter: string,
173
233
  delta: number,
174
234
  prevSizes: number[]
175
235
  ): number[] {
@@ -177,6 +237,8 @@ function adjustByDelta(
177
237
  return prevSizes;
178
238
  }
179
239
 
240
+ const panelsArray = panelsMapToSortedArray(panels);
241
+
180
242
  const nextSizes = prevSizes.concat();
181
243
 
182
244
  let deltaApplied = 0;
@@ -189,9 +251,9 @@ function adjustByDelta(
189
251
  // A positive delta means the panel immediately before the resizer should "expand".
190
252
  // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer.
191
253
  let pivotId = delta < 0 ? idBefore : idAfter;
192
- let index = panels.findIndex(panel => panel.id === pivotId);
254
+ let index = panelsArray.findIndex((panel) => panel.id === pivotId);
193
255
  while (true) {
194
- const panel = panels[index];
256
+ const panel = panelsArray[index];
195
257
  const prevSize = prevSizes[index];
196
258
  const nextSize = Math.max(prevSize - Math.abs(delta), panel.minSize);
197
259
  if (prevSize !== nextSize) {
@@ -209,7 +271,7 @@ function adjustByDelta(
209
271
  break;
210
272
  }
211
273
  } else {
212
- if (++index >= panels.length) {
274
+ if (++index >= panelsArray.length) {
213
275
  break;
214
276
  }
215
277
  }
@@ -223,21 +285,33 @@ function adjustByDelta(
223
285
 
224
286
  // Adjust the pivot panel before, but only by the amount that surrounding panels were able to shrink/contract.
225
287
  pivotId = delta < 0 ? idAfter : idBefore;
226
- index = panels.findIndex(panel => panel.id === pivotId);
288
+ index = panelsArray.findIndex((panel) => panel.id === pivotId);
227
289
  nextSizes[index] = prevSizes[index] + deltaApplied;
228
290
 
229
291
  return nextSizes;
230
292
  }
231
293
 
294
+ function createLocalStorageKey(
295
+ autoSaveId: string,
296
+ panels: Map<string, PanelData>
297
+ ): string {
298
+ const panelsArray = panelsMapToSortedArray(panels);
299
+ const panelIds = panelsArray.map((panel) => panel.id);
300
+
301
+ return `PanelGroup:sizes:${autoSaveId}${panelIds.join("|")}`;
302
+ }
303
+
232
304
  function getOffset(
233
- panels: Panel[],
234
- id: PanelId,
305
+ panels: Map<string, PanelData>,
306
+ id: string,
235
307
  direction: Direction,
236
308
  sizes: number[],
237
309
  height: number,
238
310
  width: number
239
311
  ): number {
240
- let index = panels.findIndex(panel => panel.id === id);
312
+ const panelsArray = panelsMapToSortedArray(panels);
313
+
314
+ let index = panelsArray.findIndex((panel) => panel.id === id);
241
315
  if (index < 0) {
242
316
  return 0;
243
317
  }
@@ -245,7 +319,7 @@ function getOffset(
245
319
  let scaledOffset = 0;
246
320
 
247
321
  for (index = index - 1; index >= 0; index--) {
248
- const panel = panels[index];
322
+ const panel = panelsArray[index];
249
323
  scaledOffset += getSize(panels, panel.id, direction, sizes, height, width);
250
324
  }
251
325
 
@@ -253,24 +327,30 @@ function getOffset(
253
327
  }
254
328
 
255
329
  function getSize(
256
- panels: Panel[],
257
- id: PanelId,
330
+ panels: Map<string, PanelData>,
331
+ id: string,
258
332
  direction: Direction,
259
333
  sizes: number[],
260
334
  height: number,
261
335
  width: number
262
336
  ): number {
263
- const index = panels.findIndex(panel => panel.id === id);
337
+ const totalSize = direction === "horizontal" ? width : height;
338
+
339
+ if (panels.size === 1) {
340
+ return totalSize;
341
+ }
342
+
343
+ const panelsArray = panelsMapToSortedArray(panels);
344
+
345
+ const index = panelsArray.findIndex((panel) => panel.id === id);
264
346
  const size = sizes[index];
265
347
  if (size == null) {
266
348
  return 0;
267
349
  }
268
350
 
269
- const totalSize = direction === "horizontal" ? width : height;
351
+ return Math.round(size * totalSize);
352
+ }
270
353
 
271
- if (panels.length === 1) {
272
- return totalSize;
273
- } else {
274
- return Math.round(size * totalSize);
275
- }
354
+ function panelsMapToSortedArray(panels: Map<string, PanelData>): PanelData[] {
355
+ return Array.from(panels.values()).sort((a, b) => a.order - b.order);
276
356
  }
@@ -1,7 +1,7 @@
1
1
  import { ReactNode, useContext, useEffect, useState } from "react";
2
2
 
3
3
  import { PanelGroupContext } from "./PanelContexts";
4
- import { PanelId, ResizeHandler } from "./types";
4
+ import { ResizeHandler } from "./types";
5
5
 
6
6
  export default function PanelResizeHandle({
7
7
  children = null,
@@ -13,17 +13,21 @@ export default function PanelResizeHandle({
13
13
  children?: ReactNode;
14
14
  className?: string;
15
15
  disabled?: boolean;
16
- panelAfter: PanelId;
17
- panelBefore: PanelId;
16
+ panelAfter: string;
17
+ panelBefore: string;
18
18
  }) {
19
19
  const context = useContext(PanelGroupContext);
20
20
  if (context === null) {
21
- throw Error(`PanelResizeHandle components must be rendered within a PanelGroup container`);
21
+ throw Error(
22
+ `PanelResizeHandle components must be rendered within a PanelGroup container`
23
+ );
22
24
  }
23
25
 
24
26
  const { direction, registerResizeHandle } = context;
25
27
 
26
- const [resizeHandler, setResizeHandler] = useState<ResizeHandler | null>(null);
28
+ const [resizeHandler, setResizeHandler] = useState<ResizeHandler | null>(
29
+ null
30
+ );
27
31
  const [isDragging, setIsDragging] = useState(false);
28
32
 
29
33
  useEffect(() => {
@@ -39,7 +43,8 @@ export default function PanelResizeHandle({
39
43
  return;
40
44
  }
41
45
 
42
- document.body.style.cursor = direction === "horizontal" ? "ew-resize" : "ns-resize";
46
+ document.body.style.cursor =
47
+ direction === "horizontal" ? "ew-resize" : "ns-resize";
43
48
 
44
49
  const onMouseLeave = (_: MouseEvent) => {
45
50
  setIsDragging(false);
@@ -72,7 +77,7 @@ export default function PanelResizeHandle({
72
77
  onMouseDown={() => setIsDragging(true)}
73
78
  onMouseUp={() => setIsDragging(false)}
74
79
  style={{
75
- cursor: direction === 'horizontal' ? 'ew-resize' : 'ns-resize'
80
+ cursor: direction === "horizontal" ? "ew-resize" : "ns-resize",
76
81
  }}
77
82
  >
78
83
  {children}
package/src/index.ts CHANGED
@@ -2,4 +2,4 @@ import Panel from "./Panel";
2
2
  import PanelGroup from "./PanelGroup";
3
3
  import PanelResizeHandle from "./PanelResizeHandle";
4
4
 
5
- export { Panel, PanelGroup, PanelResizeHandle };
5
+ export { Panel, PanelGroup, PanelResizeHandle };
package/src/types.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  export type Direction = "horizontal" | "vertical";
2
2
 
3
- export type PanelId = string;
4
-
5
- export type Panel = {
3
+ export type PanelData = {
6
4
  defaultSize: number;
7
- id: PanelId;
5
+ id: string;
8
6
  minSize: number;
7
+ order: number | null;
9
8
  };
10
9
 
11
10
  export type ResizeHandler = (event: MouseEvent) => void;
package/.proxyrc DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "/demo": {
3
- "target": "http://localhost:8100/",
4
- "pathRewrite": {
5
- "^/demo": ""
6
- }
7
- }
8
- }