react-resizable-panels 1.0.2 → 1.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.
@@ -800,11 +800,15 @@ function initializeDefaultStorage(storageObject) {
800
800
  }
801
801
  }
802
802
 
803
+ function getPanelGroupKey(autoSaveId) {
804
+ return `react-resizable-panels:${autoSaveId}`;
805
+ }
806
+
803
807
  // Note that Panel ids might be user-provided (stable) or useId generated (non-deterministic)
804
808
  // so they should not be used as part of the serialization key.
805
809
  // Using the min/max size attributes should work well enough as a backup.
806
810
  // Pre-sorting by minSize allows remembering layouts even if panels are re-ordered/dragged.
807
- function getSerializationKey(panels) {
811
+ function getPanelKey(panels) {
808
812
  return panels.map(panel => {
809
813
  const {
810
814
  constraints,
@@ -821,7 +825,8 @@ function getSerializationKey(panels) {
821
825
  }
822
826
  function loadSerializedPanelGroupState(autoSaveId, storage) {
823
827
  try {
824
- const serialized = storage.getItem(`PanelGroup:layout:${autoSaveId}`);
828
+ const panelGroupKey = getPanelGroupKey(autoSaveId);
829
+ const serialized = storage.getItem(panelGroupKey);
825
830
  if (serialized) {
826
831
  const parsed = JSON.parse(serialized);
827
832
  if (typeof parsed === "object" && parsed != null) {
@@ -831,12 +836,17 @@ function loadSerializedPanelGroupState(autoSaveId, storage) {
831
836
  } catch (error) {}
832
837
  return null;
833
838
  }
834
- function savePanelGroupLayout(autoSaveId, panels, sizes, storage) {
835
- const key = getSerializationKey(panels);
836
- const state = loadSerializedPanelGroupState(autoSaveId, storage) || {};
837
- state[key] = sizes;
839
+ function savePanelGroupState(autoSaveId, panels, panelSizesBeforeCollapse, sizes, storage) {
840
+ var _loadSerializedPanelG2;
841
+ const panelGroupKey = getPanelGroupKey(autoSaveId);
842
+ const panelKey = getPanelKey(panels);
843
+ const state = (_loadSerializedPanelG2 = loadSerializedPanelGroupState(autoSaveId, storage)) !== null && _loadSerializedPanelG2 !== void 0 ? _loadSerializedPanelG2 : {};
844
+ state[panelKey] = {
845
+ expandToSizes: Object.fromEntries(panelSizesBeforeCollapse.entries()),
846
+ layout: sizes
847
+ };
838
848
  try {
839
- storage.setItem(`PanelGroup:layout:${autoSaveId}`, JSON.stringify(state));
849
+ storage.setItem(panelGroupKey, JSON.stringify(state));
840
850
  } catch (error) {
841
851
  console.error(error);
842
852
  }
@@ -933,7 +943,6 @@ function PanelGroupWithForwardedRef({
933
943
  const groupId = useUniqueId(idFromProps);
934
944
  const [dragState, setDragState] = useState(null);
935
945
  const [layout, setLayout] = useState([]);
936
- useState([]);
937
946
  const panelIdToLastNotifiedSizeMapRef = useRef({});
938
947
  const panelSizeBeforeCollapseRef = useRef(new Map());
939
948
  const prevDeltaRef = useRef(0);
@@ -1008,13 +1017,15 @@ function PanelGroupWithForwardedRef({
1008
1017
 
1009
1018
  // Limit the frequency of localStorage updates.
1010
1019
  if (debouncedSave == null) {
1011
- debouncedSave = debounce(savePanelGroupLayout, LOCAL_STORAGE_DEBOUNCE_INTERVAL);
1020
+ debouncedSave = debounce(savePanelGroupState, LOCAL_STORAGE_DEBOUNCE_INTERVAL);
1012
1021
  debounceMap[autoSaveId] = debouncedSave;
1013
1022
  }
1014
1023
 
1015
- // Clone panel data array before saving since this array is mutated.
1016
- // If we don't clone, we run the risk of saving the wrong panel and layout pair.
1017
- debouncedSave(autoSaveId, [...panelDataArray], layout, storage);
1024
+ // Clone mutable data before passing to the debounced function,
1025
+ // else we run the risk of saving an incorrect combination of mutable and immutable values to state.
1026
+ const clonedPanelDataArray = [...panelDataArray];
1027
+ const clonedPanelSizesBeforeCollapse = new Map(panelSizeBeforeCollapseRef.current);
1028
+ debouncedSave(autoSaveId, clonedPanelDataArray, clonedPanelSizesBeforeCollapse, layout, storage);
1018
1029
  }
1019
1030
  }, [autoSaveId, layout, storage]);
1020
1031
 
@@ -1140,7 +1151,7 @@ function PanelGroupWithForwardedRef({
1140
1151
  panelDataArray
1141
1152
  } = eagerValuesRef.current;
1142
1153
  const {
1143
- collapsedSize,
1154
+ collapsedSize = 0,
1144
1155
  collapsible,
1145
1156
  panelSize
1146
1157
  } = panelDataHelper(panelDataArray, panelData, layout);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-resizable-panels",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "React components for resizable panel groups/layouts",
5
5
  "author": "Brian Vaughn <brian.david.vaughn@gmail.com>",
6
6
  "license": "MIT",
@@ -89,13 +89,19 @@ describe("PanelGroup", () => {
89
89
  assert(mostRecentLayout);
90
90
 
91
91
  verifyExpandedPanelGroupLayout(mostRecentLayout, [50, 50]);
92
+ expect(leftPanelRef.current?.isCollapsed()).toBe(false);
93
+ expect(rightPanelRef.current?.isCollapsed()).toBe(false);
92
94
  act(() => {
93
95
  leftPanelRef.current?.collapse();
94
96
  });
97
+ expect(leftPanelRef.current?.isCollapsed()).toBe(true);
98
+ expect(rightPanelRef.current?.isCollapsed()).toBe(false);
95
99
  verifyExpandedPanelGroupLayout(mostRecentLayout, [0, 100]);
96
100
  act(() => {
97
101
  leftPanelRef.current?.expand();
98
102
  });
103
+ expect(leftPanelRef.current?.isCollapsed()).toBe(false);
104
+ expect(rightPanelRef.current?.isCollapsed()).toBe(false);
99
105
  verifyExpandedPanelGroupLayout(mostRecentLayout, [50, 50]);
100
106
  });
101
107
 
@@ -103,14 +109,20 @@ describe("PanelGroup", () => {
103
109
  assert(mostRecentLayout);
104
110
 
105
111
  verifyExpandedPanelGroupLayout(mostRecentLayout, [50, 50]);
112
+ expect(leftPanelRef.current?.isCollapsed()).toBe(false);
113
+ expect(rightPanelRef.current?.isCollapsed()).toBe(false);
106
114
  act(() => {
107
115
  rightPanelRef.current?.collapse();
108
116
  });
109
117
  verifyExpandedPanelGroupLayout(mostRecentLayout, [100, 0]);
118
+ expect(leftPanelRef.current?.isCollapsed()).toBe(false);
119
+ expect(rightPanelRef.current?.isCollapsed()).toBe(true);
110
120
  act(() => {
111
121
  rightPanelRef.current?.expand();
112
122
  });
113
123
  verifyExpandedPanelGroupLayout(mostRecentLayout, [50, 50]);
124
+ expect(leftPanelRef.current?.isCollapsed()).toBe(false);
125
+ expect(rightPanelRef.current?.isCollapsed()).toBe(false);
114
126
  });
115
127
 
116
128
  it("should re-expand to the most recent size before collapsing", () => {
package/src/PanelGroup.ts CHANGED
@@ -20,7 +20,10 @@ import { getResizeHandleElement } from "./utils/dom/getResizeHandleElement";
20
20
  import { isKeyDown, isMouseEvent, isTouchEvent } from "./utils/events";
21
21
  import { getResizeEventCursorPosition } from "./utils/getResizeEventCursorPosition";
22
22
  import { initializeDefaultStorage } from "./utils/initializeDefaultStorage";
23
- import { loadPanelLayout, savePanelGroupLayout } from "./utils/serialization";
23
+ import {
24
+ loadPanelGroupState,
25
+ savePanelGroupState,
26
+ } from "./utils/serialization";
24
27
  import { validatePanelConstraints } from "./utils/validatePanelConstraints";
25
28
  import { validatePanelGroupLayout } from "./utils/validatePanelGroupLayout";
26
29
  import {
@@ -79,7 +82,7 @@ export type PanelGroupProps = Omit<HTMLAttributes<ElementType>, "id"> &
79
82
  }>;
80
83
 
81
84
  const debounceMap: {
82
- [key: string]: typeof savePanelGroupLayout;
85
+ [key: string]: typeof savePanelGroupState;
83
86
  } = {};
84
87
 
85
88
  function PanelGroupWithForwardedRef({
@@ -102,7 +105,6 @@ function PanelGroupWithForwardedRef({
102
105
 
103
106
  const [dragState, setDragState] = useState<DragState | null>(null);
104
107
  const [layout, setLayout] = useState<number[]>([]);
105
- const [panelDataArray, setPanelDataArray] = useState<PanelData[]>([]);
106
108
 
107
109
  const panelIdToLastNotifiedSizeMapRef = useRef<Record<string, number>>({});
108
110
  const panelSizeBeforeCollapseRef = useRef<Map<string, number>>(new Map());
@@ -218,16 +220,26 @@ function PanelGroupWithForwardedRef({
218
220
  // Limit the frequency of localStorage updates.
219
221
  if (debouncedSave == null) {
220
222
  debouncedSave = debounce(
221
- savePanelGroupLayout,
223
+ savePanelGroupState,
222
224
  LOCAL_STORAGE_DEBOUNCE_INTERVAL
223
225
  );
224
226
 
225
227
  debounceMap[autoSaveId] = debouncedSave;
226
228
  }
227
229
 
228
- // Clone panel data array before saving since this array is mutated.
229
- // If we don't clone, we run the risk of saving the wrong panel and layout pair.
230
- debouncedSave(autoSaveId, [...panelDataArray], layout, storage);
230
+ // Clone mutable data before passing to the debounced function,
231
+ // else we run the risk of saving an incorrect combination of mutable and immutable values to state.
232
+ const clonedPanelDataArray = [...panelDataArray];
233
+ const clonedPanelSizesBeforeCollapse = new Map(
234
+ panelSizeBeforeCollapseRef.current
235
+ );
236
+ debouncedSave(
237
+ autoSaveId,
238
+ clonedPanelDataArray,
239
+ clonedPanelSizesBeforeCollapse,
240
+ layout,
241
+ storage
242
+ );
231
243
  }
232
244
  }, [autoSaveId, layout, storage]);
233
245
 
@@ -442,11 +454,11 @@ function PanelGroupWithForwardedRef({
442
454
  const isPanelCollapsed = useCallback((panelData: PanelData) => {
443
455
  const { layout, panelDataArray } = eagerValuesRef.current;
444
456
 
445
- const { collapsedSize, collapsible, panelSize } = panelDataHelper(
446
- panelDataArray,
447
- panelData,
448
- layout
449
- );
457
+ const {
458
+ collapsedSize = 0,
459
+ collapsible,
460
+ panelSize,
461
+ } = panelDataHelper(panelDataArray, panelData, layout);
450
462
 
451
463
  return collapsible === true && panelSize === collapsedSize;
452
464
  }, []);
@@ -500,7 +512,13 @@ function PanelGroupWithForwardedRef({
500
512
  // default size should be restored from local storage if possible.
501
513
  let unsafeLayout: number[] | null = null;
502
514
  if (autoSaveId) {
503
- unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage);
515
+ const state = loadPanelGroupState(autoSaveId, panelDataArray, storage);
516
+ if (state) {
517
+ panelSizeBeforeCollapseRef.current = new Map(
518
+ Object.entries(state.expandToSizes)
519
+ );
520
+ unsafeLayout = state.layout;
521
+ }
504
522
  }
505
523
 
506
524
  if (unsafeLayout == null) {
@@ -1,13 +1,26 @@
1
1
  import { PanelData } from "../Panel";
2
2
  import { PanelGroupStorage } from "../PanelGroup";
3
3
 
4
- type SerializedPanelGroupState = { [panelIds: string]: number[] };
4
+ export type PanelConfigurationState = {
5
+ expandToSizes: {
6
+ [panelId: string]: number;
7
+ };
8
+ layout: number[];
9
+ };
10
+
11
+ export type SerializedPanelGroupState = {
12
+ [panelIds: string]: PanelConfigurationState;
13
+ };
14
+
15
+ function getPanelGroupKey(autoSaveId: string): string {
16
+ return `react-resizable-panels:${autoSaveId}`;
17
+ }
5
18
 
6
19
  // Note that Panel ids might be user-provided (stable) or useId generated (non-deterministic)
7
20
  // so they should not be used as part of the serialization key.
8
21
  // Using the min/max size attributes should work well enough as a backup.
9
22
  // Pre-sorting by minSize allows remembering layouts even if panels are re-ordered/dragged.
10
- function getSerializationKey(panels: PanelData[]): string {
23
+ function getPanelKey(panels: PanelData[]): string {
11
24
  return panels
12
25
  .map((panel) => {
13
26
  const { constraints, id, idIsFromProps, order } = panel;
@@ -28,11 +41,12 @@ function loadSerializedPanelGroupState(
28
41
  storage: PanelGroupStorage
29
42
  ): SerializedPanelGroupState | null {
30
43
  try {
31
- const serialized = storage.getItem(`PanelGroup:layout:${autoSaveId}`);
44
+ const panelGroupKey = getPanelGroupKey(autoSaveId);
45
+ const serialized = storage.getItem(panelGroupKey);
32
46
  if (serialized) {
33
47
  const parsed = JSON.parse(serialized);
34
48
  if (typeof parsed === "object" && parsed != null) {
35
- return parsed;
49
+ return parsed as SerializedPanelGroupState;
36
50
  }
37
51
  }
38
52
  } catch (error) {}
@@ -40,32 +54,33 @@ function loadSerializedPanelGroupState(
40
54
  return null;
41
55
  }
42
56
 
43
- export function loadPanelLayout(
57
+ export function loadPanelGroupState(
44
58
  autoSaveId: string,
45
59
  panels: PanelData[],
46
60
  storage: PanelGroupStorage
47
- ): number[] | null {
48
- const state = loadSerializedPanelGroupState(autoSaveId, storage);
49
- if (state) {
50
- const key = getSerializationKey(panels);
51
- return state[key] ?? null;
52
- }
53
-
54
- return null;
61
+ ): PanelConfigurationState | null {
62
+ const state = loadSerializedPanelGroupState(autoSaveId, storage) ?? {};
63
+ const panelKey = getPanelKey(panels);
64
+ return state[panelKey] ?? null;
55
65
  }
56
66
 
57
- export function savePanelGroupLayout(
67
+ export function savePanelGroupState(
58
68
  autoSaveId: string,
59
69
  panels: PanelData[],
70
+ panelSizesBeforeCollapse: Map<string, number>,
60
71
  sizes: number[],
61
72
  storage: PanelGroupStorage
62
73
  ): void {
63
- const key = getSerializationKey(panels);
64
- const state = loadSerializedPanelGroupState(autoSaveId, storage) || {};
65
- state[key] = sizes;
74
+ const panelGroupKey = getPanelGroupKey(autoSaveId);
75
+ const panelKey = getPanelKey(panels);
76
+ const state = loadSerializedPanelGroupState(autoSaveId, storage) ?? {};
77
+ state[panelKey] = {
78
+ expandToSizes: Object.fromEntries(panelSizesBeforeCollapse.entries()),
79
+ layout: sizes,
80
+ };
66
81
 
67
82
  try {
68
- storage.setItem(`PanelGroup:layout:${autoSaveId}`, JSON.stringify(state));
83
+ storage.setItem(panelGroupKey, JSON.stringify(state));
69
84
  } catch (error) {
70
85
  console.error(error);
71
86
  }