react-resizable-panels 0.0.49 → 0.0.51

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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.0.51
4
+ * [154](https://github.com/bvaughn/react-resizable-panels/issues/154): `onResize` and `onCollapse` props are called in response to `PanelGroup.setLayout`
5
+ * [123](https://github.com/bvaughn/react-resizable-panels/issues/123): `onResize` called when number of panels in a group change due to conditional rendering
6
+
7
+ ## 0.0.50
8
+ * Improved panel size validation in `PanelGroup`.
9
+
3
10
  ## 0.0.49
4
11
  * Improved development warnings and props validation checks in `PanelGroup`.
5
12
 
@@ -281,27 +281,29 @@ function adjustByDelta(event, panels, idBefore, idAfter, delta, prevSizes, panel
281
281
  nextSizes[index] = baseSizes[index] + deltaApplied;
282
282
  return nextSizes;
283
283
  }
284
- function callPanelCallbacks(panelsArray, prevSizes, nextSizes) {
285
- nextSizes.forEach((nextSize, index) => {
286
- const prevSize = prevSizes[index];
287
- if (prevSize !== nextSize) {
288
- const {
289
- callbacksRef,
290
- collapsible
291
- } = panelsArray[index].current;
284
+ function callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap) {
285
+ sizes.forEach((size, index) => {
286
+ const {
287
+ callbacksRef,
288
+ collapsible,
289
+ id
290
+ } = panelsArray[index].current;
291
+ const lastNotifiedSize = panelIdToLastNotifiedSizeMap[id];
292
+ if (lastNotifiedSize !== size) {
293
+ panelIdToLastNotifiedSizeMap[id] = size;
292
294
  const {
293
295
  onCollapse,
294
296
  onResize
295
297
  } = callbacksRef.current;
296
298
  if (onResize) {
297
- onResize(nextSize);
299
+ onResize(size);
298
300
  }
299
301
  if (collapsible && onCollapse) {
300
302
  // Falsy check handles both previous size of 0
301
303
  // and initial size of undefined (when mounting)
302
- if (!prevSize && nextSize !== 0) {
304
+ if (!lastNotifiedSize && size !== 0) {
303
305
  onCollapse(false);
304
- } else if (prevSize !== 0 && nextSize === 0) {
306
+ } else if (lastNotifiedSize !== 0 && size === 0) {
305
307
  onCollapse(true);
306
308
  }
307
309
  }
@@ -837,6 +839,7 @@ function PanelGroupWithForwardedRef({
837
839
  useEffect(() => {
838
840
  callbacksRef.current.onLayout = onLayout;
839
841
  });
842
+ const panelIdToLastNotifiedSizeMapRef = useRef({});
840
843
 
841
844
  // 0-1 values representing the relative size of each panel.
842
845
  const [sizes, setSizes] = useState([]);
@@ -861,6 +864,12 @@ function PanelGroupWithForwardedRef({
861
864
  setLayout: sizes => {
862
865
  const total = sizes.reduce((accumulated, current) => accumulated + current, 0);
863
866
  assert(total === 100, "Panel sizes must add up to 100%");
867
+ const {
868
+ panels
869
+ } = committedValuesRef.current;
870
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
871
+ const panelsArray = panelsMapToSortedArray(panels);
872
+ callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap);
864
873
  setSizes(sizes);
865
874
  }
866
875
  }), []);
@@ -885,31 +894,23 @@ function PanelGroupWithForwardedRef({
885
894
  } = callbacksRef.current;
886
895
  if (onLayout) {
887
896
  const {
897
+ panels,
888
898
  sizes
889
899
  } = committedValuesRef.current;
890
900
 
891
901
  // Don't commit layout until all panels have registered and re-rendered with their actual sizes.
892
902
  if (sizes.length > 0) {
893
903
  onLayout(sizes);
894
- }
895
- }
896
- }, [sizes]);
904
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
897
905
 
898
- // Notify Panel listeners about their initial sizes and collapsed state after mount.
899
- // Subsequent changes will be called by the resizeHandler.
900
- const didNotifyCallbacksAfterMountRef = useRef(false);
901
- useIsomorphicLayoutEffect(() => {
902
- if (didNotifyCallbacksAfterMountRef.current) {
903
- return;
904
- }
905
- const {
906
- panels,
907
- sizes
908
- } = committedValuesRef.current;
909
- if (sizes.length > 0) {
910
- didNotifyCallbacksAfterMountRef.current = true;
911
- const panelsArray = panelsMapToSortedArray(panels);
912
- callPanelCallbacks(panelsArray, [], sizes);
906
+ // When possible, we notify before the next render so that rendering work can be batched together.
907
+ // Some cases are difficult to detect though,
908
+ // for example– panels that are conditionally rendered can affect the size of neighboring panels.
909
+ // In this case, the best we can do is notify on commit.
910
+ // The callPanelCallbacks() uses its own memoization to avoid notifying panels twice in these cases.
911
+ const panelsArray = panelsMapToSortedArray(panels);
912
+ callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap);
913
+ }
913
914
  }
914
915
  }, [sizes]);
915
916
 
@@ -951,10 +952,12 @@ function PanelGroupWithForwardedRef({
951
952
  totalDefaultSize += panel.current.defaultSize;
952
953
  }
953
954
  });
954
- if (totalDefaultSize > 100 || panelsWithNullDefaultSize === 0 && totalDefaultSize !== 100) {
955
+ if (totalDefaultSize > 100) {
956
+ throw new Error(`Default panel sizes cannot exceed 100%`);
957
+ } else if (panelsArray.length > 1 && panelsWithNullDefaultSize === 0 && totalDefaultSize !== 100) {
955
958
  throw new Error(`Invalid default sizes specified for panels`);
956
959
  } else if (totalMinSize > 100) {
957
- throw new Error(`Invalid minimum sizes specified for panels`);
960
+ throw new Error(`Minimum panel sizes cannot exceed 100%`);
958
961
  }
959
962
  setSizes(panelsArray.map(panel => {
960
963
  if (panel.current.defaultSize === null) {
@@ -1071,8 +1074,10 @@ function PanelGroupWithForwardedRef({
1071
1074
  }
1072
1075
  }
1073
1076
  if (sizesChanged) {
1077
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1078
+
1074
1079
  // If resize change handlers have been declared, this is the time to call them.
1075
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
1080
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1076
1081
  setSizes(nextSizes);
1077
1082
  }
1078
1083
  prevDeltaRef.current = delta;
@@ -1117,8 +1122,10 @@ function PanelGroupWithForwardedRef({
1117
1122
  const delta = isLastPanel ? currentSize : 0 - currentSize;
1118
1123
  const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1119
1124
  if (prevSizes !== nextSizes) {
1125
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1126
+
1120
1127
  // If resize change handlers have been declared, this is the time to call them.
1121
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
1128
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1122
1129
  setSizes(nextSizes);
1123
1130
  }
1124
1131
  }, []);
@@ -1153,8 +1160,10 @@ function PanelGroupWithForwardedRef({
1153
1160
  const delta = isLastPanel ? 0 - sizeBeforeCollapse : sizeBeforeCollapse;
1154
1161
  const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1155
1162
  if (prevSizes !== nextSizes) {
1163
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1164
+
1156
1165
  // If resize change handlers have been declared, this is the time to call them.
1157
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
1166
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1158
1167
  setSizes(nextSizes);
1159
1168
  }
1160
1169
  }, []);
@@ -1187,8 +1196,10 @@ function PanelGroupWithForwardedRef({
1187
1196
  const delta = isLastPanel ? currentSize - nextSize : nextSize - currentSize;
1188
1197
  const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1189
1198
  if (prevSizes !== nextSizes) {
1199
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1200
+
1190
1201
  // If resize change handlers have been declared, this is the time to call them.
1191
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
1202
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1192
1203
  setSizes(nextSizes);
1193
1204
  }
1194
1205
  }, []);
@@ -281,27 +281,29 @@ function adjustByDelta(event, panels, idBefore, idAfter, delta, prevSizes, panel
281
281
  nextSizes[index] = baseSizes[index] + deltaApplied;
282
282
  return nextSizes;
283
283
  }
284
- function callPanelCallbacks(panelsArray, prevSizes, nextSizes) {
285
- nextSizes.forEach((nextSize, index) => {
286
- const prevSize = prevSizes[index];
287
- if (prevSize !== nextSize) {
288
- const {
289
- callbacksRef,
290
- collapsible
291
- } = panelsArray[index].current;
284
+ function callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap) {
285
+ sizes.forEach((size, index) => {
286
+ const {
287
+ callbacksRef,
288
+ collapsible,
289
+ id
290
+ } = panelsArray[index].current;
291
+ const lastNotifiedSize = panelIdToLastNotifiedSizeMap[id];
292
+ if (lastNotifiedSize !== size) {
293
+ panelIdToLastNotifiedSizeMap[id] = size;
292
294
  const {
293
295
  onCollapse,
294
296
  onResize
295
297
  } = callbacksRef.current;
296
298
  if (onResize) {
297
- onResize(nextSize);
299
+ onResize(size);
298
300
  }
299
301
  if (collapsible && onCollapse) {
300
302
  // Falsy check handles both previous size of 0
301
303
  // and initial size of undefined (when mounting)
302
- if (!prevSize && nextSize !== 0) {
304
+ if (!lastNotifiedSize && size !== 0) {
303
305
  onCollapse(false);
304
- } else if (prevSize !== 0 && nextSize === 0) {
306
+ } else if (lastNotifiedSize !== 0 && size === 0) {
305
307
  onCollapse(true);
306
308
  }
307
309
  }
@@ -844,6 +846,7 @@ function PanelGroupWithForwardedRef({
844
846
  useEffect(() => {
845
847
  callbacksRef.current.onLayout = onLayout;
846
848
  });
849
+ const panelIdToLastNotifiedSizeMapRef = useRef({});
847
850
 
848
851
  // 0-1 values representing the relative size of each panel.
849
852
  const [sizes, setSizes] = useState([]);
@@ -868,6 +871,12 @@ function PanelGroupWithForwardedRef({
868
871
  setLayout: sizes => {
869
872
  const total = sizes.reduce((accumulated, current) => accumulated + current, 0);
870
873
  assert(total === 100, "Panel sizes must add up to 100%");
874
+ const {
875
+ panels
876
+ } = committedValuesRef.current;
877
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
878
+ const panelsArray = panelsMapToSortedArray(panels);
879
+ callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap);
871
880
  setSizes(sizes);
872
881
  }
873
882
  }), []);
@@ -892,31 +901,23 @@ function PanelGroupWithForwardedRef({
892
901
  } = callbacksRef.current;
893
902
  if (onLayout) {
894
903
  const {
904
+ panels,
895
905
  sizes
896
906
  } = committedValuesRef.current;
897
907
 
898
908
  // Don't commit layout until all panels have registered and re-rendered with their actual sizes.
899
909
  if (sizes.length > 0) {
900
910
  onLayout(sizes);
901
- }
902
- }
903
- }, [sizes]);
911
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
904
912
 
905
- // Notify Panel listeners about their initial sizes and collapsed state after mount.
906
- // Subsequent changes will be called by the resizeHandler.
907
- const didNotifyCallbacksAfterMountRef = useRef(false);
908
- useIsomorphicLayoutEffect(() => {
909
- if (didNotifyCallbacksAfterMountRef.current) {
910
- return;
911
- }
912
- const {
913
- panels,
914
- sizes
915
- } = committedValuesRef.current;
916
- if (sizes.length > 0) {
917
- didNotifyCallbacksAfterMountRef.current = true;
918
- const panelsArray = panelsMapToSortedArray(panels);
919
- callPanelCallbacks(panelsArray, [], sizes);
913
+ // When possible, we notify before the next render so that rendering work can be batched together.
914
+ // Some cases are difficult to detect though,
915
+ // for example– panels that are conditionally rendered can affect the size of neighboring panels.
916
+ // In this case, the best we can do is notify on commit.
917
+ // The callPanelCallbacks() uses its own memoization to avoid notifying panels twice in these cases.
918
+ const panelsArray = panelsMapToSortedArray(panels);
919
+ callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap);
920
+ }
920
921
  }
921
922
  }, [sizes]);
922
923
 
@@ -958,10 +959,12 @@ function PanelGroupWithForwardedRef({
958
959
  totalDefaultSize += panel.current.defaultSize;
959
960
  }
960
961
  });
961
- if (totalDefaultSize > 100 || panelsWithNullDefaultSize === 0 && totalDefaultSize !== 100) {
962
+ if (totalDefaultSize > 100) {
963
+ throw new Error(`Default panel sizes cannot exceed 100%`);
964
+ } else if (panelsArray.length > 1 && panelsWithNullDefaultSize === 0 && totalDefaultSize !== 100) {
962
965
  throw new Error(`Invalid default sizes specified for panels`);
963
966
  } else if (totalMinSize > 100) {
964
- throw new Error(`Invalid minimum sizes specified for panels`);
967
+ throw new Error(`Minimum panel sizes cannot exceed 100%`);
965
968
  }
966
969
  setSizes(panelsArray.map(panel => {
967
970
  if (panel.current.defaultSize === null) {
@@ -1083,8 +1086,10 @@ function PanelGroupWithForwardedRef({
1083
1086
  }
1084
1087
  }
1085
1088
  if (sizesChanged) {
1089
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1090
+
1086
1091
  // If resize change handlers have been declared, this is the time to call them.
1087
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
1092
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1088
1093
  setSizes(nextSizes);
1089
1094
  }
1090
1095
  prevDeltaRef.current = delta;
@@ -1129,8 +1134,10 @@ function PanelGroupWithForwardedRef({
1129
1134
  const delta = isLastPanel ? currentSize : 0 - currentSize;
1130
1135
  const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1131
1136
  if (prevSizes !== nextSizes) {
1137
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1138
+
1132
1139
  // If resize change handlers have been declared, this is the time to call them.
1133
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
1140
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1134
1141
  setSizes(nextSizes);
1135
1142
  }
1136
1143
  }, []);
@@ -1165,8 +1172,10 @@ function PanelGroupWithForwardedRef({
1165
1172
  const delta = isLastPanel ? 0 - sizeBeforeCollapse : sizeBeforeCollapse;
1166
1173
  const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1167
1174
  if (prevSizes !== nextSizes) {
1175
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1176
+
1168
1177
  // If resize change handlers have been declared, this is the time to call them.
1169
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
1178
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1170
1179
  setSizes(nextSizes);
1171
1180
  }
1172
1181
  }, []);
@@ -1199,8 +1208,10 @@ function PanelGroupWithForwardedRef({
1199
1208
  const delta = isLastPanel ? currentSize - nextSize : nextSize - currentSize;
1200
1209
  const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1201
1210
  if (prevSizes !== nextSizes) {
1211
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1212
+
1202
1213
  // If resize change handlers have been declared, this is the time to call them.
1203
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
1214
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1204
1215
  setSizes(nextSizes);
1205
1216
  }
1206
1217
  }, []);
@@ -257,27 +257,29 @@ function adjustByDelta(event, panels, idBefore, idAfter, delta, prevSizes, panel
257
257
  nextSizes[index] = baseSizes[index] + deltaApplied;
258
258
  return nextSizes;
259
259
  }
260
- function callPanelCallbacks(panelsArray, prevSizes, nextSizes) {
261
- nextSizes.forEach((nextSize, index) => {
262
- const prevSize = prevSizes[index];
263
- if (prevSize !== nextSize) {
264
- const {
265
- callbacksRef,
266
- collapsible
267
- } = panelsArray[index].current;
260
+ function callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap) {
261
+ sizes.forEach((size, index) => {
262
+ const {
263
+ callbacksRef,
264
+ collapsible,
265
+ id
266
+ } = panelsArray[index].current;
267
+ const lastNotifiedSize = panelIdToLastNotifiedSizeMap[id];
268
+ if (lastNotifiedSize !== size) {
269
+ panelIdToLastNotifiedSizeMap[id] = size;
268
270
  const {
269
271
  onCollapse,
270
272
  onResize
271
273
  } = callbacksRef.current;
272
274
  if (onResize) {
273
- onResize(nextSize);
275
+ onResize(size);
274
276
  }
275
277
  if (collapsible && onCollapse) {
276
278
  // Falsy check handles both previous size of 0
277
279
  // and initial size of undefined (when mounting)
278
- if (!prevSize && nextSize !== 0) {
280
+ if (!lastNotifiedSize && size !== 0) {
279
281
  onCollapse(false);
280
- } else if (prevSize !== 0 && nextSize === 0) {
282
+ } else if (lastNotifiedSize !== 0 && size === 0) {
281
283
  onCollapse(true);
282
284
  }
283
285
  }
@@ -820,6 +822,7 @@ function PanelGroupWithForwardedRef({
820
822
  useEffect(() => {
821
823
  callbacksRef.current.onLayout = onLayout;
822
824
  });
825
+ const panelIdToLastNotifiedSizeMapRef = useRef({});
823
826
 
824
827
  // 0-1 values representing the relative size of each panel.
825
828
  const [sizes, setSizes] = useState([]);
@@ -844,6 +847,12 @@ function PanelGroupWithForwardedRef({
844
847
  setLayout: sizes => {
845
848
  const total = sizes.reduce((accumulated, current) => accumulated + current, 0);
846
849
  assert(total === 100, "Panel sizes must add up to 100%");
850
+ const {
851
+ panels
852
+ } = committedValuesRef.current;
853
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
854
+ const panelsArray = panelsMapToSortedArray(panels);
855
+ callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap);
847
856
  setSizes(sizes);
848
857
  }
849
858
  }), []);
@@ -868,31 +877,23 @@ function PanelGroupWithForwardedRef({
868
877
  } = callbacksRef.current;
869
878
  if (onLayout) {
870
879
  const {
880
+ panels,
871
881
  sizes
872
882
  } = committedValuesRef.current;
873
883
 
874
884
  // Don't commit layout until all panels have registered and re-rendered with their actual sizes.
875
885
  if (sizes.length > 0) {
876
886
  onLayout(sizes);
877
- }
878
- }
879
- }, [sizes]);
887
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
880
888
 
881
- // Notify Panel listeners about their initial sizes and collapsed state after mount.
882
- // Subsequent changes will be called by the resizeHandler.
883
- const didNotifyCallbacksAfterMountRef = useRef(false);
884
- useIsomorphicLayoutEffect(() => {
885
- if (didNotifyCallbacksAfterMountRef.current) {
886
- return;
887
- }
888
- const {
889
- panels,
890
- sizes
891
- } = committedValuesRef.current;
892
- if (sizes.length > 0) {
893
- didNotifyCallbacksAfterMountRef.current = true;
894
- const panelsArray = panelsMapToSortedArray(panels);
895
- callPanelCallbacks(panelsArray, [], sizes);
889
+ // When possible, we notify before the next render so that rendering work can be batched together.
890
+ // Some cases are difficult to detect though,
891
+ // for example– panels that are conditionally rendered can affect the size of neighboring panels.
892
+ // In this case, the best we can do is notify on commit.
893
+ // The callPanelCallbacks() uses its own memoization to avoid notifying panels twice in these cases.
894
+ const panelsArray = panelsMapToSortedArray(panels);
895
+ callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap);
896
+ }
896
897
  }
897
898
  }, [sizes]);
898
899
 
@@ -934,10 +935,12 @@ function PanelGroupWithForwardedRef({
934
935
  totalDefaultSize += panel.current.defaultSize;
935
936
  }
936
937
  });
937
- if (totalDefaultSize > 100 || panelsWithNullDefaultSize === 0 && totalDefaultSize !== 100) {
938
+ if (totalDefaultSize > 100) {
939
+ throw new Error(`Default panel sizes cannot exceed 100%`);
940
+ } else if (panelsArray.length > 1 && panelsWithNullDefaultSize === 0 && totalDefaultSize !== 100) {
938
941
  throw new Error(`Invalid default sizes specified for panels`);
939
942
  } else if (totalMinSize > 100) {
940
- throw new Error(`Invalid minimum sizes specified for panels`);
943
+ throw new Error(`Minimum panel sizes cannot exceed 100%`);
941
944
  }
942
945
  setSizes(panelsArray.map(panel => {
943
946
  if (panel.current.defaultSize === null) {
@@ -1059,8 +1062,10 @@ function PanelGroupWithForwardedRef({
1059
1062
  }
1060
1063
  }
1061
1064
  if (sizesChanged) {
1065
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1066
+
1062
1067
  // If resize change handlers have been declared, this is the time to call them.
1063
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
1068
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1064
1069
  setSizes(nextSizes);
1065
1070
  }
1066
1071
  prevDeltaRef.current = delta;
@@ -1105,8 +1110,10 @@ function PanelGroupWithForwardedRef({
1105
1110
  const delta = isLastPanel ? currentSize : 0 - currentSize;
1106
1111
  const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1107
1112
  if (prevSizes !== nextSizes) {
1113
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1114
+
1108
1115
  // If resize change handlers have been declared, this is the time to call them.
1109
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
1116
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1110
1117
  setSizes(nextSizes);
1111
1118
  }
1112
1119
  }, []);
@@ -1141,8 +1148,10 @@ function PanelGroupWithForwardedRef({
1141
1148
  const delta = isLastPanel ? 0 - sizeBeforeCollapse : sizeBeforeCollapse;
1142
1149
  const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1143
1150
  if (prevSizes !== nextSizes) {
1151
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1152
+
1144
1153
  // If resize change handlers have been declared, this is the time to call them.
1145
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
1154
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1146
1155
  setSizes(nextSizes);
1147
1156
  }
1148
1157
  }, []);
@@ -1175,8 +1184,10 @@ function PanelGroupWithForwardedRef({
1175
1184
  const delta = isLastPanel ? currentSize - nextSize : nextSize - currentSize;
1176
1185
  const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1177
1186
  if (prevSizes !== nextSizes) {
1187
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1188
+
1178
1189
  // If resize change handlers have been declared, this is the time to call them.
1179
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
1190
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1180
1191
  setSizes(nextSizes);
1181
1192
  }
1182
1193
  }, []);
@@ -257,27 +257,29 @@ function adjustByDelta(event, panels, idBefore, idAfter, delta, prevSizes, panel
257
257
  nextSizes[index] = baseSizes[index] + deltaApplied;
258
258
  return nextSizes;
259
259
  }
260
- function callPanelCallbacks(panelsArray, prevSizes, nextSizes) {
261
- nextSizes.forEach((nextSize, index) => {
262
- const prevSize = prevSizes[index];
263
- if (prevSize !== nextSize) {
264
- const {
265
- callbacksRef,
266
- collapsible
267
- } = panelsArray[index].current;
260
+ function callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap) {
261
+ sizes.forEach((size, index) => {
262
+ const {
263
+ callbacksRef,
264
+ collapsible,
265
+ id
266
+ } = panelsArray[index].current;
267
+ const lastNotifiedSize = panelIdToLastNotifiedSizeMap[id];
268
+ if (lastNotifiedSize !== size) {
269
+ panelIdToLastNotifiedSizeMap[id] = size;
268
270
  const {
269
271
  onCollapse,
270
272
  onResize
271
273
  } = callbacksRef.current;
272
274
  if (onResize) {
273
- onResize(nextSize);
275
+ onResize(size);
274
276
  }
275
277
  if (collapsible && onCollapse) {
276
278
  // Falsy check handles both previous size of 0
277
279
  // and initial size of undefined (when mounting)
278
- if (!prevSize && nextSize !== 0) {
280
+ if (!lastNotifiedSize && size !== 0) {
279
281
  onCollapse(false);
280
- } else if (prevSize !== 0 && nextSize === 0) {
282
+ } else if (lastNotifiedSize !== 0 && size === 0) {
281
283
  onCollapse(true);
282
284
  }
283
285
  }
@@ -813,6 +815,7 @@ function PanelGroupWithForwardedRef({
813
815
  useEffect(() => {
814
816
  callbacksRef.current.onLayout = onLayout;
815
817
  });
818
+ const panelIdToLastNotifiedSizeMapRef = useRef({});
816
819
 
817
820
  // 0-1 values representing the relative size of each panel.
818
821
  const [sizes, setSizes] = useState([]);
@@ -837,6 +840,12 @@ function PanelGroupWithForwardedRef({
837
840
  setLayout: sizes => {
838
841
  const total = sizes.reduce((accumulated, current) => accumulated + current, 0);
839
842
  assert(total === 100, "Panel sizes must add up to 100%");
843
+ const {
844
+ panels
845
+ } = committedValuesRef.current;
846
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
847
+ const panelsArray = panelsMapToSortedArray(panels);
848
+ callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap);
840
849
  setSizes(sizes);
841
850
  }
842
851
  }), []);
@@ -861,31 +870,23 @@ function PanelGroupWithForwardedRef({
861
870
  } = callbacksRef.current;
862
871
  if (onLayout) {
863
872
  const {
873
+ panels,
864
874
  sizes
865
875
  } = committedValuesRef.current;
866
876
 
867
877
  // Don't commit layout until all panels have registered and re-rendered with their actual sizes.
868
878
  if (sizes.length > 0) {
869
879
  onLayout(sizes);
870
- }
871
- }
872
- }, [sizes]);
880
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
873
881
 
874
- // Notify Panel listeners about their initial sizes and collapsed state after mount.
875
- // Subsequent changes will be called by the resizeHandler.
876
- const didNotifyCallbacksAfterMountRef = useRef(false);
877
- useIsomorphicLayoutEffect(() => {
878
- if (didNotifyCallbacksAfterMountRef.current) {
879
- return;
880
- }
881
- const {
882
- panels,
883
- sizes
884
- } = committedValuesRef.current;
885
- if (sizes.length > 0) {
886
- didNotifyCallbacksAfterMountRef.current = true;
887
- const panelsArray = panelsMapToSortedArray(panels);
888
- callPanelCallbacks(panelsArray, [], sizes);
882
+ // When possible, we notify before the next render so that rendering work can be batched together.
883
+ // Some cases are difficult to detect though,
884
+ // for example– panels that are conditionally rendered can affect the size of neighboring panels.
885
+ // In this case, the best we can do is notify on commit.
886
+ // The callPanelCallbacks() uses its own memoization to avoid notifying panels twice in these cases.
887
+ const panelsArray = panelsMapToSortedArray(panels);
888
+ callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap);
889
+ }
889
890
  }
890
891
  }, [sizes]);
891
892
 
@@ -927,10 +928,12 @@ function PanelGroupWithForwardedRef({
927
928
  totalDefaultSize += panel.current.defaultSize;
928
929
  }
929
930
  });
930
- if (totalDefaultSize > 100 || panelsWithNullDefaultSize === 0 && totalDefaultSize !== 100) {
931
+ if (totalDefaultSize > 100) {
932
+ throw new Error(`Default panel sizes cannot exceed 100%`);
933
+ } else if (panelsArray.length > 1 && panelsWithNullDefaultSize === 0 && totalDefaultSize !== 100) {
931
934
  throw new Error(`Invalid default sizes specified for panels`);
932
935
  } else if (totalMinSize > 100) {
933
- throw new Error(`Invalid minimum sizes specified for panels`);
936
+ throw new Error(`Minimum panel sizes cannot exceed 100%`);
934
937
  }
935
938
  setSizes(panelsArray.map(panel => {
936
939
  if (panel.current.defaultSize === null) {
@@ -1047,8 +1050,10 @@ function PanelGroupWithForwardedRef({
1047
1050
  }
1048
1051
  }
1049
1052
  if (sizesChanged) {
1053
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1054
+
1050
1055
  // If resize change handlers have been declared, this is the time to call them.
1051
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
1056
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1052
1057
  setSizes(nextSizes);
1053
1058
  }
1054
1059
  prevDeltaRef.current = delta;
@@ -1093,8 +1098,10 @@ function PanelGroupWithForwardedRef({
1093
1098
  const delta = isLastPanel ? currentSize : 0 - currentSize;
1094
1099
  const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1095
1100
  if (prevSizes !== nextSizes) {
1101
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1102
+
1096
1103
  // If resize change handlers have been declared, this is the time to call them.
1097
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
1104
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1098
1105
  setSizes(nextSizes);
1099
1106
  }
1100
1107
  }, []);
@@ -1129,8 +1136,10 @@ function PanelGroupWithForwardedRef({
1129
1136
  const delta = isLastPanel ? 0 - sizeBeforeCollapse : sizeBeforeCollapse;
1130
1137
  const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1131
1138
  if (prevSizes !== nextSizes) {
1139
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1140
+
1132
1141
  // If resize change handlers have been declared, this is the time to call them.
1133
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
1142
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1134
1143
  setSizes(nextSizes);
1135
1144
  }
1136
1145
  }, []);
@@ -1163,8 +1172,10 @@ function PanelGroupWithForwardedRef({
1163
1172
  const delta = isLastPanel ? currentSize - nextSize : nextSize - currentSize;
1164
1173
  const nextSizes = adjustByDelta(null, panels, idBefore, idAfter, delta, prevSizes, panelSizeBeforeCollapse.current, null);
1165
1174
  if (prevSizes !== nextSizes) {
1175
+ const panelIdToLastNotifiedSizeMap = panelIdToLastNotifiedSizeMapRef.current;
1176
+
1166
1177
  // If resize change handlers have been declared, this is the time to call them.
1167
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
1178
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
1168
1179
  setSizes(nextSizes);
1169
1180
  }
1170
1181
  }, []);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-resizable-panels",
3
- "version": "0.0.49",
3
+ "version": "0.0.51",
4
4
  "description": "React components for resizable panel groups/layouts",
5
5
  "author": "Brian Vaughn <brian.david.vaughn@gmail.com>",
6
6
  "license": "MIT",
package/src/PanelGroup.ts CHANGED
@@ -169,6 +169,8 @@ function PanelGroupWithForwardedRef({
169
169
  callbacksRef.current.onLayout = onLayout;
170
170
  });
171
171
 
172
+ const panelIdToLastNotifiedSizeMapRef = useRef<Record<string, number>>({});
173
+
172
174
  // 0-1 values representing the relative size of each panel.
173
175
  const [sizes, setSizes] = useState<number[]>([]);
174
176
 
@@ -199,6 +201,13 @@ function PanelGroupWithForwardedRef({
199
201
 
200
202
  assert(total === 100, "Panel sizes must add up to 100%");
201
203
 
204
+ const { panels } = committedValuesRef.current;
205
+ const panelIdToLastNotifiedSizeMap =
206
+ panelIdToLastNotifiedSizeMapRef.current;
207
+ const panelsArray = panelsMapToSortedArray(panels);
208
+
209
+ callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap);
210
+
202
211
  setSizes(sizes);
203
212
  },
204
213
  }),
@@ -224,29 +233,23 @@ function PanelGroupWithForwardedRef({
224
233
  useEffect(() => {
225
234
  const { onLayout } = callbacksRef.current!;
226
235
  if (onLayout) {
227
- const { sizes } = committedValuesRef.current;
236
+ const { panels, sizes } = committedValuesRef.current;
228
237
 
229
238
  // Don't commit layout until all panels have registered and re-rendered with their actual sizes.
230
239
  if (sizes.length > 0) {
231
240
  onLayout(sizes);
232
- }
233
- }
234
- }, [sizes]);
235
-
236
- // Notify Panel listeners about their initial sizes and collapsed state after mount.
237
- // Subsequent changes will be called by the resizeHandler.
238
- const didNotifyCallbacksAfterMountRef = useRef(false);
239
- useIsomorphicLayoutEffect(() => {
240
- if (didNotifyCallbacksAfterMountRef.current) {
241
- return;
242
- }
243
241
 
244
- const { panels, sizes } = committedValuesRef.current;
245
- if (sizes.length > 0) {
246
- didNotifyCallbacksAfterMountRef.current = true;
242
+ const panelIdToLastNotifiedSizeMap =
243
+ panelIdToLastNotifiedSizeMapRef.current;
247
244
 
248
- const panelsArray = panelsMapToSortedArray(panels);
249
- callPanelCallbacks(panelsArray, [], sizes);
245
+ // When possible, we notify before the next render so that rendering work can be batched together.
246
+ // Some cases are difficult to detect though,
247
+ // for example– panels that are conditionally rendered can affect the size of neighboring panels.
248
+ // In this case, the best we can do is notify on commit.
249
+ // The callPanelCallbacks() uses its own memoization to avoid notifying panels twice in these cases.
250
+ const panelsArray = panelsMapToSortedArray(panels);
251
+ callPanelCallbacks(panelsArray, sizes, panelIdToLastNotifiedSizeMap);
252
+ }
250
253
  }
251
254
  }, [sizes]);
252
255
 
@@ -292,13 +295,16 @@ function PanelGroupWithForwardedRef({
292
295
  }
293
296
  });
294
297
 
295
- if (
296
- totalDefaultSize > 100 ||
297
- (panelsWithNullDefaultSize === 0 && totalDefaultSize !== 100)
298
+ if (totalDefaultSize > 100) {
299
+ throw new Error(`Default panel sizes cannot exceed 100%`);
300
+ } else if (
301
+ panelsArray.length > 1 &&
302
+ panelsWithNullDefaultSize === 0 &&
303
+ totalDefaultSize !== 100
298
304
  ) {
299
305
  throw new Error(`Invalid default sizes specified for panels`);
300
306
  } else if (totalMinSize > 100) {
301
- throw new Error(`Invalid minimum sizes specified for panels`);
307
+ throw new Error(`Minimum panel sizes cannot exceed 100%`);
302
308
  }
303
309
 
304
310
  setSizes(
@@ -478,8 +484,15 @@ function PanelGroupWithForwardedRef({
478
484
  }
479
485
 
480
486
  if (sizesChanged) {
487
+ const panelIdToLastNotifiedSizeMap =
488
+ panelIdToLastNotifiedSizeMapRef.current;
489
+
481
490
  // If resize change handlers have been declared, this is the time to call them.
482
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
491
+ callPanelCallbacks(
492
+ panelsArray,
493
+ nextSizes,
494
+ panelIdToLastNotifiedSizeMap
495
+ );
483
496
 
484
497
  setSizes(nextSizes);
485
498
  }
@@ -547,8 +560,11 @@ function PanelGroupWithForwardedRef({
547
560
  null
548
561
  );
549
562
  if (prevSizes !== nextSizes) {
563
+ const panelIdToLastNotifiedSizeMap =
564
+ panelIdToLastNotifiedSizeMapRef.current;
565
+
550
566
  // If resize change handlers have been declared, this is the time to call them.
551
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
567
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
552
568
 
553
569
  setSizes(nextSizes);
554
570
  }
@@ -600,8 +616,11 @@ function PanelGroupWithForwardedRef({
600
616
  null
601
617
  );
602
618
  if (prevSizes !== nextSizes) {
619
+ const panelIdToLastNotifiedSizeMap =
620
+ panelIdToLastNotifiedSizeMapRef.current;
621
+
603
622
  // If resize change handlers have been declared, this is the time to call them.
604
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
623
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
605
624
 
606
625
  setSizes(nextSizes);
607
626
  }
@@ -655,8 +674,11 @@ function PanelGroupWithForwardedRef({
655
674
  null
656
675
  );
657
676
  if (prevSizes !== nextSizes) {
677
+ const panelIdToLastNotifiedSizeMap =
678
+ panelIdToLastNotifiedSizeMapRef.current;
679
+
658
680
  // If resize change handlers have been declared, this is the time to call them.
659
- callPanelCallbacks(panelsArray, prevSizes, nextSizes);
681
+ callPanelCallbacks(panelsArray, nextSizes, panelIdToLastNotifiedSizeMap);
660
682
 
661
683
  setSizes(nextSizes);
662
684
  }
@@ -119,25 +119,28 @@ export function adjustByDelta(
119
119
 
120
120
  export function callPanelCallbacks(
121
121
  panelsArray: PanelData[],
122
- prevSizes: number[],
123
- nextSizes: number[]
122
+ sizes: number[],
123
+ panelIdToLastNotifiedSizeMap: Record<string, number>
124
124
  ) {
125
- nextSizes.forEach((nextSize, index) => {
126
- const prevSize = prevSizes[index];
127
- if (prevSize !== nextSize) {
128
- const { callbacksRef, collapsible } = panelsArray[index].current;
125
+ sizes.forEach((size, index) => {
126
+ const { callbacksRef, collapsible, id } = panelsArray[index].current;
127
+
128
+ const lastNotifiedSize = panelIdToLastNotifiedSizeMap[id];
129
+ if (lastNotifiedSize !== size) {
130
+ panelIdToLastNotifiedSizeMap[id] = size;
131
+
129
132
  const { onCollapse, onResize } = callbacksRef.current!;
130
133
 
131
134
  if (onResize) {
132
- onResize(nextSize);
135
+ onResize(size);
133
136
  }
134
137
 
135
138
  if (collapsible && onCollapse) {
136
139
  // Falsy check handles both previous size of 0
137
140
  // and initial size of undefined (when mounting)
138
- if (!prevSize && nextSize !== 0) {
141
+ if (!lastNotifiedSize && size !== 0) {
139
142
  onCollapse(false);
140
- } else if (prevSize !== 0 && nextSize === 0) {
143
+ } else if (lastNotifiedSize !== 0 && size === 0) {
141
144
  onCollapse(true);
142
145
  }
143
146
  }