solid-recharts 0.2.0 → 0.2.1

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.
Files changed (82) hide show
  1. package/package.json +5 -3
  2. package/src/cartesian/Area.tsx +40 -55
  3. package/src/cartesian/Bar.tsx +66 -58
  4. package/src/cartesian/BarStack.tsx +5 -2
  5. package/src/cartesian/Brush.tsx +1 -1
  6. package/src/cartesian/Funnel.tsx +15 -19
  7. package/src/cartesian/Line.tsx +34 -51
  8. package/src/cartesian/LineDrawShape.tsx +8 -6
  9. package/src/cartesian/ReferenceArea.tsx +25 -35
  10. package/src/cartesian/ReferenceDot.tsx +24 -34
  11. package/src/cartesian/ReferenceLine.tsx +25 -34
  12. package/src/cartesian/Scatter.tsx +25 -37
  13. package/src/cartesian/XAxis.tsx +54 -67
  14. package/src/cartesian/YAxis.tsx +53 -66
  15. package/src/cartesian/ZAxis.tsx +27 -40
  16. package/src/chart/SunburstChart.tsx +1 -1
  17. package/src/component/ActivePoints.tsx +0 -9
  18. package/src/component/Dots.tsx +7 -3
  19. package/src/context/ErrorBarContext.tsx +15 -24
  20. package/src/polar/Pie.tsx +36 -48
  21. package/src/polar/PolarAngleAxis.tsx +41 -50
  22. package/src/polar/PolarRadiusAxis.tsx +40 -48
  23. package/src/polar/Radar.tsx +22 -34
  24. package/src/polar/RadialBar.tsx +21 -39
  25. package/src/shape/Curve.tsx +1 -1
  26. package/src/shape/Symbols.tsx +1 -1
  27. package/src/state/SetLegendPayload.ts +19 -27
  28. package/src/state/SetTooltipEntrySettings.tsx +9 -26
  29. package/src/state/cartesianAxisSlice.ts +40 -60
  30. package/src/state/createCollectionActions.ts +51 -0
  31. package/src/state/createSelector.ts +97 -0
  32. package/src/state/externalEventsMiddleware.ts +2 -11
  33. package/src/state/graphicalItemsSlice.ts +27 -40
  34. package/src/state/isEventThrottled.ts +12 -0
  35. package/src/state/keyboardEventsMiddleware.ts +2 -8
  36. package/src/state/layoutSlice.ts +8 -4
  37. package/src/state/legendSlice.ts +24 -26
  38. package/src/state/mouseEventsMiddleware.ts +2 -13
  39. package/src/state/patched.ts +19 -0
  40. package/src/state/referenceElementsSlice.ts +40 -31
  41. package/src/state/registerInStore.ts +68 -0
  42. package/src/state/selectors/areaSelectors.ts +13 -36
  43. package/src/state/selectors/arrayEqualityCheck.ts +32 -7
  44. package/src/state/selectors/axisSelectors.ts +215 -169
  45. package/src/state/selectors/barSelectors.ts +8 -36
  46. package/src/state/selectors/combiners/combineConfiguredScale.ts +1 -1
  47. package/src/state/selectors/combiners/combineRealScaleType.ts +1 -1
  48. package/src/state/selectors/dataSelectors.ts +35 -16
  49. package/src/state/selectors/numberDomainEqualityCheck.ts +16 -8
  50. package/src/state/selectors/selectAxisForGraphicalItem.ts +52 -0
  51. package/src/state/tooltipSlice.ts +93 -64
  52. package/src/util/ActiveShapeUtils.tsx +0 -7
  53. package/src/util/BarUtils.tsx +27 -22
  54. package/src/util/ChartUtils.ts +69 -25
  55. package/src/util/propsAreEqual.ts +3 -33
  56. package/src/util/stacks/stackTypes.ts +1 -1
  57. package/src/util/types.ts +1 -1
  58. package/test/cartesian/CartesianAxis.spec.tsx +1 -1
  59. package/test/cartesian/GraphicalItemClipPath.scale-behavior.spec.tsx +1 -1
  60. package/test/cartesian/ReferenceLine/getEndPoints.spec.ts +1 -1
  61. package/test/cartesian/XAxis/XAxis.timescale.spec.tsx +1 -1
  62. package/test/helper/mockAxes.ts +1 -1
  63. package/test/shape/Curve.spec.tsx +1 -1
  64. package/test/state/createCollectionActions.spec.ts +87 -0
  65. package/test/state/createSelector.spec.ts +158 -0
  66. package/test/state/registerInStore.spec.tsx +101 -0
  67. package/test/state/selectors/combiners/combineConfiguredScale.spec.ts +1 -1
  68. package/test/state/selectors/combiners/combineRealScaleType.spec.ts +1 -1
  69. package/test/state/selectors/selectorMemoization.spec.tsx +136 -0
  70. package/test/util/CartesianUtils/CartesianUtils.spec.ts +1 -1
  71. package/test/util/ChartUtils/ChartUtils.spec.ts +1 -1
  72. package/test/util/ChartUtils/getCateCoordinateOfLine.spec.ts +14 -0
  73. package/test/util/ChartUtils/getStackedData.spec.ts +1 -1
  74. package/test/util/ChartUtils/truncateByDomain.spec.ts +1 -1
  75. package/test/util/scale/createCategoricalInverse.spec.ts +1 -1
  76. package/test-results/.last-run.json +4 -0
  77. package/src/state/reduxDevtoolsJsonStringifyReplacer.ts +0 -15
  78. package/src/util/axisPropsAreEqual.ts +0 -42
  79. package/src/util/resolveDefaultProps.tsx +0 -8
  80. package/src/util/typedDataKey.ts +0 -8
  81. package/test/state/reduxDevtoolsJsonStringifyReplacer.spec.ts +0 -25
  82. package/test/util/axisPropsAreEqual.spec.ts +0 -93
@@ -37,6 +37,10 @@ import {
37
37
  selectYAxisIdFromGraphicalItemId,
38
38
  } from "./graphicalItemSelectors";
39
39
  import { combineBarPosition } from "./combiners/combineBarPosition";
40
+ import {
41
+ selectAxisWithScaleForGraphicalItem,
42
+ selectTicksForGraphicalItem,
43
+ } from "./selectAxisForGraphicalItem";
40
44
 
41
45
  const selectSynchronisedBarSettings = (
42
46
  state: RechartsRootState,
@@ -187,38 +191,6 @@ export const selectBarPosition = (
187
191
  selectSynchronisedBarSettings(state, id),
188
192
  );
189
193
 
190
- const selectXAxisWithScale = (
191
- state: RechartsRootState,
192
- id: GraphicalItemId,
193
- isPanorama: boolean,
194
- ) => {
195
- const xAxisId = selectXAxisIdFromGraphicalItemId(state, id);
196
- return xAxisId == null ? undefined : selectAxisWithScale(state, "xAxis", xAxisId, isPanorama);
197
- };
198
-
199
- const selectYAxisWithScale = (
200
- state: RechartsRootState,
201
- id: GraphicalItemId,
202
- isPanorama: boolean,
203
- ) => {
204
- const yAxisId = selectYAxisIdFromGraphicalItemId(state, id);
205
- return yAxisId == null ? undefined : selectAxisWithScale(state, "yAxis", yAxisId, isPanorama);
206
- };
207
-
208
- const selectXAxisTicks = (state: RechartsRootState, id: GraphicalItemId, isPanorama: boolean) => {
209
- const xAxisId = selectXAxisIdFromGraphicalItemId(state, id);
210
- return xAxisId == null
211
- ? undefined
212
- : selectTicksOfGraphicalItem(state, "xAxis", xAxisId, isPanorama);
213
- };
214
-
215
- const selectYAxisTicks = (state: RechartsRootState, id: GraphicalItemId, isPanorama: boolean) => {
216
- const yAxisId = selectYAxisIdFromGraphicalItemId(state, id);
217
- return yAxisId == null
218
- ? undefined
219
- : selectTicksOfGraphicalItem(state, "yAxis", yAxisId, isPanorama);
220
- };
221
-
222
194
  const selectBarStackGroups = (
223
195
  state: RechartsRootState,
224
196
  id: GraphicalItemId,
@@ -253,10 +225,10 @@ export const selectBarRectangles = (
253
225
  ): ReadonlyArray<BarRectangleItem> | undefined => {
254
226
  const axisViewBox = selectAxisViewBox(state);
255
227
  const offset = selectChartOffsetInternal(state);
256
- const xAxis = selectXAxisWithScale(state, id, isPanorama);
257
- const yAxis = selectYAxisWithScale(state, id, isPanorama);
258
- const xAxisTicks = selectXAxisTicks(state, id, isPanorama);
259
- const yAxisTicks = selectYAxisTicks(state, id, isPanorama);
228
+ const xAxis = selectAxisWithScaleForGraphicalItem(state, "xAxis", id, isPanorama);
229
+ const yAxis = selectAxisWithScaleForGraphicalItem(state, "yAxis", id, isPanorama);
230
+ const xAxisTicks = selectTicksForGraphicalItem(state, "xAxis", id, isPanorama);
231
+ const yAxisTicks = selectTicksForGraphicalItem(state, "yAxis", id, isPanorama);
260
232
  const pos = selectBarPosition(state, id, isPanorama);
261
233
  const layout: LayoutType = selectChartLayout(state);
262
234
  const { chartData, dataStartIndex, dataEndIndex } =
@@ -7,7 +7,7 @@
7
7
  // we spread the namespace into a plain record and narrow each candidate to a
8
8
  // scale factory through a user-defined type guard before calling it. Behavior is
9
9
  // identical (unknown names / non-function exports → `undefined`).
10
- import * as d3Scales from "victory-vendor/d3-scale";
10
+ import * as d3Scales from "d3-scale";
11
11
  import {
12
12
  CategoricalDomain,
13
13
  CategoricalDomainItem,
@@ -1,6 +1,6 @@
1
1
  // Ported from recharts 3.8.1 src/state/selectors/combiners/combineRealScaleType.ts
2
2
  // (commit 7a23854).
3
- import * as d3Scales from "victory-vendor/d3-scale";
3
+ import * as d3Scales from "d3-scale";
4
4
  import { RechartsScaleType } from "../../../util/types";
5
5
  import { BaseCartesianAxis } from "../../cartesianAxisSlice";
6
6
  import { upperFirst } from "../../../util/DataUtils";
@@ -1,12 +1,26 @@
1
1
  // Ported from recharts 3.8.1 src/state/selectors/dataSelectors.ts (commit 7a23854).
2
2
  //
3
- // Solid-native: plain selector functions, no reselect. recharts wraps several of
4
- // these in `createSelector` purely for memoization (to swallow spurious Immer
5
- // reference churn); in the Solid store, memoization comes from `useAppSelector`'s
6
- // `createMemo` at the consumer, so these stay plain functions. Behavior is
7
- // identical.
3
+ // The chartData-projecting selectors below are wrapped in the Solid-native
4
+ // `createSelector` (ADR-0002) so they present a stable reference when the data
5
+ // is unchanged the leaf-input fix the memoized selector graph depends on. The
6
+ // remaining `slice`-returning helpers stay plain (they intentionally produce a
7
+ // fresh window each call and their wrapped consumers absorb the churn).
8
8
  import { RechartsRootState } from "../store";
9
9
  import { ChartData, ChartDataState } from "../chartDataSlice";
10
+ import { createSelector } from "../createSelector";
11
+
12
+ // LEAF-INPUT rule (ADR-0002 "Known constraint"): these read the individual
13
+ // leaves of the chartData slice — never the slice root `state.chartData`, whose
14
+ // proxy reference is permanently stable in Solid and so can never invalidate a
15
+ // reference-keyed cache. Reading the leaves (the `chartData` array is replaced
16
+ // wholesale by `setChartData`; the indexes are primitives) lets `createSelector`
17
+ // return a stable projected object when nothing changed and a fresh one when it
18
+ // did — the reselect semantics the downstream selector graph relies on.
19
+ const pickChartData = (state: RechartsRootState): ChartData | undefined =>
20
+ state.chartData.chartData;
21
+ const pickComputedData = (state: RechartsRootState): unknown => state.chartData.computedData;
22
+ const pickDataStartIndex = (state: RechartsRootState): number => state.chartData.dataStartIndex;
23
+ const pickDataEndIndex = (state: RechartsRootState): number => state.chartData.dataEndIndex;
10
24
 
11
25
  /**
12
26
  * Always returns the data with the indexes set by a Brush. That might or might
@@ -15,23 +29,28 @@ import { ChartData, ChartDataState } from "../chartDataSlice";
15
29
  * {@link selectChartDataAndAlwaysIgnoreIndexes} or
16
30
  * {@link selectChartDataWithIndexesIfNotInPanoramaPosition4} when that matters.
17
31
  */
18
- export const selectChartDataWithIndexes = (state: RechartsRootState): ChartDataState =>
19
- state.chartData;
32
+ export const selectChartDataWithIndexes: (state: RechartsRootState) => ChartDataState =
33
+ createSelector(
34
+ [pickChartData, pickComputedData, pickDataStartIndex, pickDataEndIndex],
35
+ (chartData, computedData, dataStartIndex, dataEndIndex) => ({
36
+ chartData,
37
+ computedData,
38
+ dataStartIndex,
39
+ dataEndIndex,
40
+ }),
41
+ );
20
42
 
21
43
  /**
22
44
  * Always returns the full range of data, ignoring Brush indexes. Useful for the
23
45
  * Brush panorama, Legend, and Tooltip.
24
46
  */
25
- export const selectChartDataAndAlwaysIgnoreIndexes = (state: RechartsRootState): ChartDataState => {
26
- const dataState = selectChartDataWithIndexes(state);
27
- const dataEndIndex = dataState.chartData != null ? dataState.chartData.length - 1 : 0;
28
- return {
29
- chartData: dataState.chartData,
30
- computedData: dataState.computedData,
31
- dataEndIndex,
47
+ export const selectChartDataAndAlwaysIgnoreIndexes: (state: RechartsRootState) => ChartDataState =
48
+ createSelector([pickChartData, pickComputedData], (chartData, computedData) => ({
49
+ chartData,
50
+ computedData,
51
+ dataEndIndex: chartData != null ? chartData.length - 1 : 0,
32
52
  dataStartIndex: 0,
33
- };
34
- };
53
+ }));
35
54
 
36
55
  export const selectChartDataWithIndexesIfNotInPanoramaPosition4 = (
37
56
  state: RechartsRootState,
@@ -1,8 +1,16 @@
1
- // SKIPPED intentionally NOT ported to the SolidJS port.
2
- //
3
- // Reason: reselect resultEqualityCheck Solid memoizes via createMemo, not needed
4
- //
5
- // TODO: do not port this file as-is. The Solid-native port replaces it (see the
6
- // reason above). If a consumer needs this path, wire it to the Solid equivalent
7
- // instead.
8
- export {};
1
+ // Ported from recharts 3.9.2 src/state/selectors/numberDomainEqualityCheck.ts.
2
+ // See arrayEqualityCheck.ts for why this is now ported (ADR-0002).
3
+ import type { NumberDomain } from "../../util/types";
4
+
5
+ export const numberDomainEqualityCheck = (
6
+ a: NumberDomain | undefined,
7
+ b: NumberDomain | undefined,
8
+ ): boolean => {
9
+ if (a === b) {
10
+ return true;
11
+ }
12
+ if (a == null || b == null) {
13
+ return false;
14
+ }
15
+ return a[0] === b[0] && a[1] === b[1];
16
+ };
@@ -0,0 +1,52 @@
1
+ // Resolves the scaled axis / ticks a cartesian graphical item is bound to, from
2
+ // the item's id. Area and Bar each declared these four wrappers privately (and
3
+ // Bar carried a dead `axisId == null` guard — selectX/YAxisIdFromGraphicalItemId
4
+ // never returns null, it falls back to defaultAxisId). Concentrated here so the
5
+ // "which axis does this item read" resolution lives once.
6
+ //
7
+ // A leaf module (like areaSelectors/barSelectors): it imports from both
8
+ // axisSelectors and graphicalItemSelectors, so it introduces no import cycle.
9
+ import { RechartsRootState } from "../store";
10
+ import { GraphicalItemId } from "../graphicalItemsSlice";
11
+ import {
12
+ selectAxisWithScale,
13
+ selectTicksOfGraphicalItem,
14
+ type BaseAxisWithScale,
15
+ } from "./axisSelectors";
16
+ import {
17
+ selectXAxisIdFromGraphicalItemId,
18
+ selectYAxisIdFromGraphicalItemId,
19
+ } from "./graphicalItemSelectors";
20
+ import type { TickItem } from "../../util/types";
21
+
22
+ type CartesianAxisType = "xAxis" | "yAxis";
23
+
24
+ const axisIdForGraphicalItem = (
25
+ state: RechartsRootState,
26
+ axisType: CartesianAxisType,
27
+ id: GraphicalItemId,
28
+ ) =>
29
+ axisType === "xAxis"
30
+ ? selectXAxisIdFromGraphicalItemId(state, id)
31
+ : selectYAxisIdFromGraphicalItemId(state, id);
32
+
33
+ export const selectAxisWithScaleForGraphicalItem = (
34
+ state: RechartsRootState,
35
+ axisType: CartesianAxisType,
36
+ id: GraphicalItemId,
37
+ isPanorama: boolean,
38
+ ): BaseAxisWithScale | undefined =>
39
+ selectAxisWithScale(state, axisType, axisIdForGraphicalItem(state, axisType, id), isPanorama);
40
+
41
+ export const selectTicksForGraphicalItem = (
42
+ state: RechartsRootState,
43
+ axisType: CartesianAxisType,
44
+ id: GraphicalItemId,
45
+ isPanorama: boolean,
46
+ ): TickItem[] | undefined =>
47
+ selectTicksOfGraphicalItem(
48
+ state,
49
+ axisType,
50
+ axisIdForGraphicalItem(state, axisType, id),
51
+ isPanorama,
52
+ );
@@ -7,6 +7,8 @@
7
7
  // graphicalItemsSlice (SetTooltipEntrySettings is the sole caller and each
8
8
  // graphical item registers exactly one entry, so the id is unique).
9
9
  import { createStore } from "solid-js";
10
+ import { createCollectionActions } from "./createCollectionActions";
11
+ import { patched } from "./patched";
10
12
  import { TooltipTrigger } from "../chart/types";
11
13
  import type { NameType, Payload, ValueType } from "../component/DefaultTooltipContent";
12
14
  import { CartesianViewBoxRequired, Coordinate, DataKey, PolarCoordinate } from "../util/types";
@@ -179,96 +181,121 @@ export function createTooltipSlice(initialState: TooltipState = initialTooltipSt
179
181
  settings: { ...initialState.settings },
180
182
  });
181
183
 
182
- const actions: TooltipActions = {
183
- addTooltipEntrySettings(payload) {
184
- setState((s) => {
185
- s.tooltipItemPayloads = [...s.tooltipItemPayloads, payload];
186
- });
187
- },
188
- replaceTooltipEntrySettings({ prev, next }) {
184
+ // Deviation: recharts matches `prev` by reference (`indexOf`), but a Solid store
185
+ // proxies stored objects so the raw `prev` never `===` the proxied element. The
186
+ // collection matches by `settings.graphicalItemId` instead — the same proxy-safe
187
+ // identity strategy as graphicalItemsSlice. Each graphical item registers exactly
188
+ // one entry, so the id is unique.
189
+ const entries = createCollectionActions<TooltipPayloadConfiguration>(
190
+ (updater) =>
189
191
  setState((s) => {
190
- // Deviation: recharts matches `prev` by reference (`indexOf`), but a
191
- // Solid store proxies stored objects so the raw `prev` never `===` the
192
- // proxied element. Match by `settings.graphicalItemId` instead — the
193
- // same proxy-safe identity strategy as graphicalItemsSlice. Each
194
- // graphical item registers exactly one entry, so the id is unique.
195
- const index = s.tooltipItemPayloads.findIndex(
196
- (p) => p.settings.graphicalItemId === prev.settings.graphicalItemId,
197
- );
198
- if (index > -1) {
199
- const nextItems = s.tooltipItemPayloads.slice();
200
- nextItems[index] = next;
201
- s.tooltipItemPayloads = nextItems;
192
+ const next = updater(s.tooltipItemPayloads);
193
+ if (next !== s.tooltipItemPayloads) {
194
+ s.tooltipItemPayloads = next;
202
195
  }
203
- });
204
- },
205
- removeTooltipEntrySettings(payload) {
206
- setState((s) => {
207
- s.tooltipItemPayloads = s.tooltipItemPayloads.filter(
208
- (p) => p.settings.graphicalItemId !== payload.settings.graphicalItemId,
209
- );
210
- });
211
- },
196
+ }),
197
+ (p) => p.settings.graphicalItemId,
198
+ );
199
+
200
+ const actions: TooltipActions = {
201
+ addTooltipEntrySettings: entries.add,
202
+ replaceTooltipEntrySettings: entries.replace,
203
+ removeTooltipEntrySettings: entries.remove,
212
204
  setTooltipSettingsState(payload) {
213
205
  setState((s) => {
214
206
  s.settings = payload;
215
207
  });
216
208
  },
217
209
  setActiveMouseOverItemIndex(payload) {
210
+ // Slice immutability contract (ADR-0002): replace each written interaction
211
+ // node so `state.tooltip.<node>` yields a new reference and the selector
212
+ // graph invalidates by input-reference comparison. In-place leaf writes
213
+ // keep the node reference stable, so reference-keyed selectors would never
214
+ // recompute — see the store-proxy-reference-identity memory.
218
215
  setState((s) => {
219
- s.syncInteraction.active = false;
220
- s.syncInteraction.sourceViewBox = undefined;
221
- s.keyboardInteraction.active = false;
222
- s.itemInteraction.hover.active = true;
223
- s.itemInteraction.hover.index = payload.activeIndex;
224
- s.itemInteraction.hover.dataKey = payload.activeDataKey;
225
- s.itemInteraction.hover.graphicalItemId = payload.activeGraphicalItemId;
226
- s.itemInteraction.hover.coordinate = payload.activeCoordinate;
216
+ s.syncInteraction = patched(s.syncInteraction, {
217
+ active: false,
218
+ sourceViewBox: undefined,
219
+ });
220
+ s.keyboardInteraction = patched(s.keyboardInteraction, { active: false });
221
+ s.itemInteraction = patched(s.itemInteraction, {
222
+ hover: {
223
+ active: true,
224
+ index: payload.activeIndex,
225
+ dataKey: payload.activeDataKey,
226
+ graphicalItemId: payload.activeGraphicalItemId,
227
+ coordinate: payload.activeCoordinate,
228
+ },
229
+ });
227
230
  });
228
231
  },
229
232
  mouseLeaveChart() {
230
233
  setState((s) => {
231
- s.itemInteraction.hover.active = false;
232
- s.axisInteraction.hover.active = false;
234
+ s.itemInteraction = patched(s.itemInteraction, {
235
+ hover: patched(s.itemInteraction.hover, { active: false }),
236
+ });
237
+ s.axisInteraction = patched(s.axisInteraction, {
238
+ hover: patched(s.axisInteraction.hover, { active: false }),
239
+ });
233
240
  });
234
241
  },
235
242
  mouseLeaveItem() {
236
243
  setState((s) => {
237
- s.itemInteraction.hover.active = false;
244
+ s.itemInteraction = patched(s.itemInteraction, {
245
+ hover: patched(s.itemInteraction.hover, { active: false }),
246
+ });
238
247
  });
239
248
  },
240
249
  setActiveClickItemIndex(payload) {
241
250
  setState((s) => {
242
- s.syncInteraction.active = false;
243
- s.syncInteraction.sourceViewBox = undefined;
244
- s.itemInteraction.click.active = true;
245
- s.keyboardInteraction.active = false;
246
- s.itemInteraction.click.index = payload.activeIndex;
247
- s.itemInteraction.click.dataKey = payload.activeDataKey;
248
- s.itemInteraction.click.graphicalItemId = payload.activeGraphicalItemId;
249
- s.itemInteraction.click.coordinate = payload.activeCoordinate;
251
+ s.syncInteraction = patched(s.syncInteraction, {
252
+ active: false,
253
+ sourceViewBox: undefined,
254
+ });
255
+ s.keyboardInteraction = patched(s.keyboardInteraction, { active: false });
256
+ s.itemInteraction = patched(s.itemInteraction, {
257
+ click: {
258
+ active: true,
259
+ index: payload.activeIndex,
260
+ dataKey: payload.activeDataKey,
261
+ graphicalItemId: payload.activeGraphicalItemId,
262
+ coordinate: payload.activeCoordinate,
263
+ },
264
+ });
250
265
  });
251
266
  },
252
267
  setMouseOverAxisIndex(payload) {
253
268
  setState((s) => {
254
- s.syncInteraction.active = false;
255
- s.syncInteraction.sourceViewBox = undefined;
256
- s.axisInteraction.hover.active = true;
257
- s.keyboardInteraction.active = false;
258
- s.axisInteraction.hover.index = payload.activeIndex;
259
- s.axisInteraction.hover.dataKey = payload.activeDataKey;
260
- s.axisInteraction.hover.coordinate = payload.activeCoordinate;
269
+ s.syncInteraction = patched(s.syncInteraction, {
270
+ active: false,
271
+ sourceViewBox: undefined,
272
+ });
273
+ s.keyboardInteraction = patched(s.keyboardInteraction, { active: false });
274
+ s.axisInteraction = patched(s.axisInteraction, {
275
+ hover: patched(s.axisInteraction.hover, {
276
+ active: true,
277
+ index: payload.activeIndex,
278
+ dataKey: payload.activeDataKey,
279
+ coordinate: payload.activeCoordinate,
280
+ }),
281
+ });
261
282
  });
262
283
  },
263
284
  setMouseClickAxisIndex(payload) {
264
285
  setState((s) => {
265
- s.syncInteraction.active = false;
266
- s.syncInteraction.sourceViewBox = undefined;
267
- s.keyboardInteraction.active = false;
268
- s.axisInteraction.click.active = true;
269
- s.axisInteraction.click.index = payload.activeIndex;
270
- s.axisInteraction.click.dataKey = payload.activeDataKey;
271
- s.axisInteraction.click.coordinate = payload.activeCoordinate;
286
+ s.syncInteraction = patched(s.syncInteraction, {
287
+ active: false,
288
+ sourceViewBox: undefined,
289
+ });
290
+ s.keyboardInteraction = patched(s.keyboardInteraction, { active: false });
291
+ s.axisInteraction = patched(s.axisInteraction, {
292
+ click: patched(s.axisInteraction.click, {
293
+ active: true,
294
+ index: payload.activeIndex,
295
+ dataKey: payload.activeDataKey,
296
+ coordinate: payload.activeCoordinate,
297
+ }),
298
+ });
272
299
  });
273
300
  },
274
301
  setSyncInteraction(payload) {
@@ -278,9 +305,11 @@ export function createTooltipSlice(initialState: TooltipState = initialTooltipSt
278
305
  },
279
306
  setKeyboardInteraction(payload) {
280
307
  setState((s) => {
281
- s.keyboardInteraction.active = payload.active;
282
- s.keyboardInteraction.index = payload.activeIndex;
283
- s.keyboardInteraction.coordinate = payload.activeCoordinate;
308
+ s.keyboardInteraction = patched(s.keyboardInteraction, {
309
+ active: payload.active,
310
+ index: payload.activeIndex,
311
+ coordinate: payload.activeCoordinate,
312
+ });
284
313
  });
285
314
  },
286
315
  };
@@ -28,13 +28,6 @@ function mergeShapeProps<PropsType extends object>(
28
28
  return { ...props, ...option };
29
29
  }
30
30
 
31
- /** Extracts a shape option's props (object form); element form is N/A (no cloneElement). */
32
- export function getPropsFromShapeOption<PropsType extends object>(
33
- option: Partial<PropsType>,
34
- ): Partial<PropsType> {
35
- return option;
36
- }
37
-
38
31
  function getShapeIndex(shapeProps: object): string | number | undefined {
39
32
  if (!("index" in shapeProps)) {
40
33
  return undefined;
@@ -2,8 +2,7 @@
2
2
  //
3
3
  // Exports `BarRectangle`/`defaultBarShape` for background bars and custom shapes;
4
4
  // `MinPointSize` + `minPointSizeCallback` for computeBarRectangles.
5
- import invariant from "tiny-invariant";
6
- import { createMemo } from "solid-js";
5
+ import { createMemo, omit } from "solid-js";
7
6
  import type { JSX } from "@solidjs/web";
8
7
  import { Rectangle, type RectangleProps } from "../shape/Rectangle";
9
8
  import type { BarShapeProps } from "../cartesian/Bar";
@@ -27,11 +26,13 @@ export type BarRectangleProps = Omit<BarShapeProps, "value" | "tooltipPosition"
27
26
  type BarRectangleShapeProps = Omit<BarRectangleProps, "option">;
28
27
 
29
28
  export function BarRectangle(props: BarRectangleProps): JSX.Element {
30
- const shapeProps = createMemo((): BarRectangleShapeProps => {
31
- const { option: _option, ...rest } = props;
32
- void _option;
33
- return rest;
34
- });
29
+ // Snapshot of every prop except `option`. `omit` (not native
30
+ // `{ option, ...rest }`) is required because callers may pass a merged props
31
+ // proxy (e.g. `<BarRectangle {...rect} isActive={…} />`), whose rest-destructure
32
+ // fails to exclude `option` and leaks it to custom shape/background functions.
33
+ // Spreading into a plain object keeps the downstream <Shape> reads a static
34
+ // snapshot (no STRICT_READ), while the memo re-runs when any prop changes.
35
+ const shapeProps = createMemo((): BarRectangleShapeProps => ({ ...omit(props, "option") }));
35
36
  return (
36
37
  <Shape<BarRectangleShapeProps, SVGPathElement>
37
38
  option={props.option}
@@ -56,19 +57,23 @@ export type MinPointSize = number | ((value: number | undefined | null, index: n
56
57
  * @returns minPointSize
57
58
  */
58
59
  export const minPointSizeCallback =
59
- (minPointSize: MinPointSize, defaultValue = 0) =>
60
- (value: unknown, index: number): number => {
61
- if (isNumber(minPointSize)) {
62
- return minPointSize;
63
- }
64
- const isValueNumberOrNil = isNumber(value) || isNullish(value);
65
- if (isValueNumberOrNil) {
66
- return minPointSize(value, index);
67
- }
60
+ // `_defaultValue` is part of the callback's signature but unreachable at runtime:
61
+ // the only branch that returned it now throws (see below), as upstream's
62
+ // tiny-invariant did. Kept positionally for API parity.
63
+ (minPointSize: MinPointSize, _defaultValue = 0) =>
64
+ (value: unknown, index: number): number => {
65
+ if (isNumber(minPointSize)) {
66
+ return minPointSize;
67
+ }
68
+ const isValueNumberOrNil = isNumber(value) || isNullish(value);
69
+ if (isValueNumberOrNil) {
70
+ return minPointSize(value, index);
71
+ }
68
72
 
69
- invariant(
70
- isValueNumberOrNil,
71
- `minPointSize callback function received a value with type of ${typeof value}. Currently only numbers or null/undefined are supported.`,
72
- );
73
- return defaultValue;
74
- };
73
+ // Reached only when the value is neither a number nor nil; upstream asserts
74
+ // this via tiny-invariant, which always throws here — a plain throw is
75
+ // behavior-equivalent and drops the dependency.
76
+ throw new Error(
77
+ `minPointSize callback function received a value with type of ${typeof value}. Currently only numbers or null/undefined are supported.`,
78
+ );
79
+ };