react-resizable-panels 0.0.14 → 0.0.15

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.
@@ -15,9 +15,9 @@ import { loadPanelLayout, savePanelGroupLayout } from "./utils/serialization";
15
15
  import { getDragOffset, getMovement } from "./utils/coordinates";
16
16
  import {
17
17
  adjustByDelta,
18
- getOffset,
18
+ getFlexGrow,
19
+ getPanelGroup,
19
20
  getResizeHandlePanelIds,
20
- getSize,
21
21
  panelsMapToSortedArray,
22
22
  } from "./utils/group";
23
23
  import { useWindowSplitterPanelGroupBehavior } from "./hooks/useWindowSplitterBehavior";
@@ -25,10 +25,8 @@ import useUniqueId from "./hooks/useUniqueId";
25
25
 
26
26
  export type CommittedValues = {
27
27
  direction: Direction;
28
- height: number;
29
28
  panels: Map<string, PanelData>;
30
29
  sizes: number[];
31
- width: number;
32
30
  };
33
31
 
34
32
  export type PanelDataMap = Map<string, PanelData>;
@@ -38,9 +36,7 @@ type Props = {
38
36
  children?: ReactNode;
39
37
  className?: string;
40
38
  direction: Direction;
41
- height: number;
42
39
  id?: string | null;
43
- width: number;
44
40
  };
45
41
 
46
42
  // TODO [panels]
@@ -52,9 +48,7 @@ export default function PanelGroup({
52
48
  children = null,
53
49
  className = "",
54
50
  direction,
55
- height,
56
51
  id: idFromProps = null,
57
- width,
58
52
  }: Props) {
59
53
  const groupId = useUniqueId(idFromProps);
60
54
 
@@ -69,18 +63,14 @@ export default function PanelGroup({
69
63
  // Store committed values to avoid unnecessarily re-running memoization/effects functions.
70
64
  const committedValuesRef = useRef<CommittedValues>({
71
65
  direction,
72
- height,
73
66
  panels,
74
67
  sizes,
75
- width,
76
68
  });
77
69
 
78
70
  useLayoutEffect(() => {
79
71
  committedValuesRef.current.direction = direction;
80
- committedValuesRef.current.height = height;
81
72
  committedValuesRef.current.panels = panels;
82
73
  committedValuesRef.current.sizes = sizes;
83
- committedValuesRef.current.width = width;
84
74
  });
85
75
 
86
76
  useWindowSplitterPanelGroupBehavior({
@@ -100,9 +90,6 @@ export default function PanelGroup({
100
90
  return;
101
91
  }
102
92
 
103
- // TODO [panels]
104
- // Validate that the total minSize is <= 1.
105
-
106
93
  // If this panel has been configured to persist sizing information,
107
94
  // default size should be restored from local storage if possible.
108
95
  let defaultSizes: number[] | undefined = undefined;
@@ -115,11 +102,40 @@ export default function PanelGroup({
115
102
  setSizes(defaultSizes);
116
103
  } else {
117
104
  const panelsArray = panelsMapToSortedArray(panels);
118
- const totalWeight = panelsArray.reduce((weight, panel) => {
119
- return weight + panel.defaultSize;
120
- }, 0);
121
105
 
122
- setSizes(panelsArray.map((panel) => panel.defaultSize / totalWeight));
106
+ let panelsWithNullDefaultSize = 0;
107
+ let totalDefaultSize = 0;
108
+ let totalMinSize = 0;
109
+
110
+ panelsArray.forEach((panel) => {
111
+ totalMinSize += panel.minSize;
112
+
113
+ if (panel.defaultSize === null) {
114
+ panelsWithNullDefaultSize++;
115
+ } else {
116
+ totalDefaultSize += panel.defaultSize;
117
+ }
118
+ });
119
+
120
+ if (totalDefaultSize > 100) {
121
+ throw new Error(
122
+ `The sum of the defaultSize of all panels in a group cannot exceed 100.`
123
+ );
124
+ } else if (totalMinSize > 100) {
125
+ throw new Error(
126
+ `The sum of the minSize of all panels in a group cannot exceed 100.`
127
+ );
128
+ }
129
+
130
+ setSizes(
131
+ panelsArray.map((panel) => {
132
+ if (panel.defaultSize === null) {
133
+ return (100 - totalDefaultSize) / panelsWithNullDefaultSize;
134
+ }
135
+
136
+ return panel.defaultSize;
137
+ })
138
+ );
123
139
  }
124
140
  }, [autoSaveId, panels]);
125
141
 
@@ -139,28 +155,11 @@ export default function PanelGroup({
139
155
  (id: string): CSSProperties => {
140
156
  const { panels } = committedValuesRef.current;
141
157
 
142
- const offset = getOffset(panels, id, direction, sizes, height, width);
143
- const size = getSize(panels, id, direction, sizes, height, width);
144
-
145
- if (direction === "horizontal") {
146
- return {
147
- height: "100%",
148
- position: "absolute",
149
- left: offset,
150
- top: 0,
151
- width: size,
152
- };
153
- } else {
154
- return {
155
- height: size,
156
- position: "absolute",
157
- left: 0,
158
- top: offset,
159
- width: "100%",
160
- };
161
- }
158
+ const size = getFlexGrow(panels, id, sizes);
159
+
160
+ return { flexGrow: size };
162
161
  },
163
- [direction, height, sizes, width]
162
+ [direction, sizes]
164
163
  );
165
164
 
166
165
  const registerPanel = useCallback((id: string, panel: PanelData) => {
@@ -183,10 +182,8 @@ export default function PanelGroup({
183
182
 
184
183
  const {
185
184
  direction,
186
- height,
187
185
  panels,
188
186
  sizes: prevSizes,
189
- width,
190
187
  } = committedValuesRef.current;
191
188
 
192
189
  const panelsArray = panelsMapToSortedArray(panels);
@@ -202,14 +199,20 @@ export default function PanelGroup({
202
199
 
203
200
  const movement = getMovement(
204
201
  event,
202
+ groupId,
205
203
  handleId,
206
- { height, width },
207
204
  direction,
208
205
  dragOffsetRef.current
209
206
  );
207
+ if (movement === 0) {
208
+ return;
209
+ }
210
210
 
211
+ const groupElement = getPanelGroup(groupId);
212
+ const rect = groupElement.getBoundingClientRect();
211
213
  const isHorizontal = direction === "horizontal";
212
- const delta = isHorizontal ? movement / width : movement / height;
214
+ const size = isHorizontal ? rect.width : rect.height;
215
+ const delta = (movement / size) * 100;
213
216
 
214
217
  const nextSizes = adjustByDelta(
215
218
  panels,
@@ -275,14 +278,17 @@ export default function PanelGroup({
275
278
  [activeHandleId]
276
279
  );
277
280
 
281
+ const style: CSSProperties = {
282
+ display: "flex",
283
+ flexDirection: direction === "horizontal" ? "row" : "column",
284
+ height: "100%",
285
+ width: "100%",
286
+ };
287
+
278
288
  return (
279
289
  <PanelContext.Provider value={panelContext}>
280
290
  <PanelGroupContext.Provider value={panelGroupContext}>
281
- <div
282
- className={className}
283
- data-panel-group-id={groupId}
284
- style={{ height, position: "relative", width }}
285
- >
291
+ <div className={className} data-panel-group-id={groupId} style={style}>
286
292
  {children}
287
293
  </div>
288
294
  </PanelGroupContext.Provider>
package/src/constants.ts CHANGED
@@ -1 +1 @@
1
- export const PRECISION = 5;
1
+ export const PRECISION = 10;
@@ -6,12 +6,13 @@ import { ResizeHandler } from "../types";
6
6
  import {
7
7
  adjustByDelta,
8
8
  getPanel,
9
+ getPanelGroup,
9
10
  getResizeHandle,
10
11
  getResizeHandleIndex,
11
12
  getResizeHandlePanelIds,
12
13
  getResizeHandles,
13
14
  getResizeHandlesForGroup,
14
- getSize,
15
+ getFlexGrow,
15
16
  panelsMapToSortedArray,
16
17
  } from "../utils/group";
17
18
 
@@ -31,7 +32,10 @@ export function useWindowSplitterPanelGroupBehavior({
31
32
  sizes: number[];
32
33
  }): void {
33
34
  useEffect(() => {
34
- const { direction, height, panels, width } = committedValuesRef.current;
35
+ const { direction, panels } = committedValuesRef.current;
36
+
37
+ const groupElement = getPanelGroup(groupId);
38
+ const { height, width } = groupElement.getBoundingClientRect();
35
39
 
36
40
  const handles = getResizeHandlesForGroup(groupId);
37
41
  const cleanupFunctions = handles.map((handle) => {
@@ -57,12 +61,11 @@ export function useWindowSplitterPanelGroupBehavior({
57
61
  const ariaValueMin =
58
62
  panelsArray.find((panel) => panel.id == idBefore)?.minSize ?? 0;
59
63
 
60
- const size = getSize(panels, idBefore, direction, sizes, height, width);
61
- const ariaValueNow = size / (direction === "horizontal" ? width : height);
64
+ const size = getFlexGrow(panels, idBefore, sizes);
62
65
 
63
66
  handle.setAttribute("aria-valuemax", "" + Math.round(100 * ariaValueMax));
64
67
  handle.setAttribute("aria-valuemin", "" + Math.round(100 * ariaValueMin));
65
- handle.setAttribute("aria-valuenow", "" + Math.round(100 * ariaValueNow));
68
+ handle.setAttribute("aria-valuenow", "" + size);
66
69
 
67
70
  const onKeyDown = (event: KeyboardEvent) => {
68
71
  switch (event.key) {
@@ -1,5 +1,5 @@
1
1
  import { Direction, ResizeEvent } from "../types";
2
- import { getResizeHandle } from "./group";
2
+ import { getPanelGroup, getResizeHandle } from "./group";
3
3
 
4
4
  export type Coordinates = {
5
5
  movement: number;
@@ -39,13 +39,16 @@ export function getDragOffset(
39
39
  // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementX
40
40
  export function getMovement(
41
41
  event: ResizeEvent,
42
+ groupId: string,
42
43
  handleId: string,
43
- { height, width }: Size,
44
44
  direction: Direction,
45
45
  initialOffset: number
46
46
  ): number {
47
47
  const isHorizontal = direction === "horizontal";
48
- const size = isHorizontal ? width : height;
48
+
49
+ const groupElement = getPanelGroup(groupId);
50
+ const rect = groupElement.getBoundingClientRect();
51
+ const size = isHorizontal ? rect.width : rect.height;
49
52
 
50
53
  if (isKeyDown(event)) {
51
54
  const denominator = event.shiftKey ? 10 : 100;
@@ -1,5 +1,5 @@
1
1
  import { PRECISION } from "../constants";
2
- import { Direction, PanelData } from "../types";
2
+ import { PanelData } from "../types";
3
3
 
4
4
  export function adjustByDelta(
5
5
  panels: Map<string, PanelData>,
@@ -66,29 +66,26 @@ export function adjustByDelta(
66
66
  return nextSizes;
67
67
  }
68
68
 
69
- export function getOffset(
69
+ // This method returns a number between 1 and 100 representing
70
+ // the % of the group's overall space this panel should occupy.
71
+ export function getFlexGrow(
70
72
  panels: Map<string, PanelData>,
71
73
  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;
74
+ sizes: number[]
75
+ ): string {
76
+ if (panels.size === 1) {
77
+ return "100";
82
78
  }
83
79
 
84
- let scaledOffset = 0;
80
+ const panelsArray = panelsMapToSortedArray(panels);
85
81
 
86
- for (index = index - 1; index >= 0; index--) {
87
- const panel = panelsArray[index];
88
- scaledOffset += getSize(panels, panel.id, direction, sizes, height, width);
82
+ const index = panelsArray.findIndex((panel) => panel.id === id);
83
+ const size = sizes[index];
84
+ if (size == null) {
85
+ return "0";
89
86
  }
90
87
 
91
- return Math.round(scaledOffset);
88
+ return size.toPrecision(PRECISION);
92
89
  }
93
90
 
94
91
  export function getPanel(id: string): HTMLDivElement | null {
@@ -99,6 +96,14 @@ export function getPanel(id: string): HTMLDivElement | null {
99
96
  return null;
100
97
  }
101
98
 
99
+ export function getPanelGroup(id: string): HTMLDivElement | null {
100
+ const element = document.querySelector(`[data-panel-group-id="${id}"]`);
101
+ if (element) {
102
+ return element as HTMLDivElement;
103
+ }
104
+ return null;
105
+ }
106
+
102
107
  export function getResizeHandle(id: string): HTMLDivElement | null {
103
108
  const element = document.querySelector(
104
109
  `[data-panel-resize-handle-id="${id}"]`
@@ -149,28 +154,3 @@ export function panelsMapToSortedArray(
149
154
  ): PanelData[] {
150
155
  return Array.from(panels.values()).sort((a, b) => a.order - b.order);
151
156
  }
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
- }
@@ -8,8 +8,11 @@ type SerializedPanelGroupState = { [panelIds: string]: number[] };
8
8
  // Pre-sorting by minSize allows remembering layouts even if panels are re-ordered/dragged.
9
9
  function getSerializationKey(panels: PanelData[]): string {
10
10
  return panels
11
- .map((panel) => panel.minSize.toPrecision(2))
12
- .sort()
11
+ .map((panel) => {
12
+ const { minSize, order } = panel;
13
+ return order ? `${order}:${minSize}` : `${minSize}`;
14
+ })
15
+ .sort((a, b) => a.localeCompare(b))
13
16
  .join(",");
14
17
  }
15
18