@carto/ps-react-ui 4.11.1 → 4.11.3

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 (30) hide show
  1. package/dist/echart-BMPpj7n_.js +250 -0
  2. package/dist/echart-BMPpj7n_.js.map +1 -0
  3. package/dist/types/widgets-v2/echart/edge-label-clamp.d.ts +57 -0
  4. package/dist/types/widgets-v2/echart/edge-label-clamp.test.d.ts +1 -0
  5. package/dist/types/widgets-v2/wrapper/style.d.ts +5 -12
  6. package/dist/widgets-v2/bar.js +87 -99
  7. package/dist/widgets-v2/bar.js.map +1 -1
  8. package/dist/widgets-v2/echart.js +1 -1
  9. package/dist/widgets-v2/histogram.js +96 -107
  10. package/dist/widgets-v2/histogram.js.map +1 -1
  11. package/dist/widgets-v2/timeseries.js +104 -116
  12. package/dist/widgets-v2/timeseries.js.map +1 -1
  13. package/dist/widgets-v2.js +248 -244
  14. package/dist/widgets-v2.js.map +1 -1
  15. package/package.json +1 -1
  16. package/src/widgets-v2/bar/options.test.ts +31 -4
  17. package/src/widgets-v2/bar/options.ts +18 -22
  18. package/src/widgets-v2/echart/echart-ui.test.tsx +70 -0
  19. package/src/widgets-v2/echart/echart-ui.tsx +28 -0
  20. package/src/widgets-v2/echart/edge-label-clamp.test.ts +198 -0
  21. package/src/widgets-v2/echart/edge-label-clamp.ts +216 -0
  22. package/src/widgets-v2/histogram/options.test.ts +11 -4
  23. package/src/widgets-v2/histogram/options.ts +17 -21
  24. package/src/widgets-v2/timeseries/options.test.ts +9 -4
  25. package/src/widgets-v2/timeseries/options.ts +17 -22
  26. package/src/widgets-v2/wrapper/style.ts +13 -18
  27. package/src/widgets-v2/wrapper/widget-wrapper.test.tsx +66 -0
  28. package/src/widgets-v2/wrapper/widget-wrapper.tsx +7 -4
  29. package/dist/echart-CU0KmClP.js +0 -176
  30. package/dist/echart-CU0KmClP.js.map +0 -1
@@ -207,7 +207,14 @@ export function createTimeseriesOptionFactory(
207
207
  // not relativizable — so the merge branch reads the closure-time value.
208
208
  const liveFormatter = ctx?.formatter
209
209
 
210
- const { niceMinVal, niceMaxVal } = computeTimeseriesNiceBounds(seriesArr)
210
+ // Closure shared between the yAxis min/max callbacks and the label
211
+ // formatter, so only the rounded extents are labelled (matches v1 +
212
+ // bar). Delegating the extent to ECharts (rather than precomputing
213
+ // scalars from the raw data) keeps stacked lines inside the plot: when
214
+ // StackToggle marks the series, ECharts feeds the *post-stack* extent
215
+ // to these callbacks, so `niceNum` rounds the stacked total.
216
+ let niceMin = 0
217
+ let niceMax = 1
211
218
 
212
219
  // Zoom slider layout: when ZoomToggle has installed `dataZoom`, push the
213
220
  // slider above the legend (if any) and reserve room in the grid below.
@@ -278,12 +285,18 @@ export function createTimeseriesOptionFactory(
278
285
  ...(dataZoomLayout ? { dataZoom: dataZoomLayout } : {}),
279
286
  yAxis: {
280
287
  ...baseYAxis,
281
- min: niceMinVal,
282
- max: niceMaxVal,
288
+ min: (extent: { min: number }) => {
289
+ niceMin = extent.min < 0 ? niceNum(extent.min) : 0
290
+ return niceMin
291
+ },
292
+ max: (extent: { min: number; max: number }) => {
293
+ niceMax = extent.max <= 0 ? 1 : niceNum(extent.max)
294
+ return niceMax
295
+ },
283
296
  axisLabel: {
284
297
  ...((baseYAxis as { axisLabel?: object }).axisLabel ?? {}),
285
298
  formatter: (value: number) => {
286
- if (value !== niceMaxVal && value !== niceMinVal) return ''
299
+ if (value !== niceMax && value !== niceMin) return ''
287
300
  if (value === 0) return ''
288
301
  return liveFormatter ? liveFormatter(value) : String(value)
289
302
  },
@@ -328,21 +341,3 @@ function buildTimeseriesTooltipFormatter(
328
341
  return { name: String(name), seriesName, marker, value: formattedValue }
329
342
  })
330
343
  }
331
-
332
- function computeTimeseriesNiceBounds(seriesArr: TimeseriesWidgetData): {
333
- niceMinVal: number
334
- niceMaxVal: number
335
- } {
336
- let min = 0
337
- let max = -Infinity
338
- for (const series of seriesArr) {
339
- for (const d of series) {
340
- if (typeof d?.value !== 'number' || !Number.isFinite(d.value)) continue
341
- if (d.value < min) min = d.value
342
- if (d.value > max) max = d.value
343
- }
344
- }
345
- const niceMinVal = min < 0 ? niceNum(min) : 0
346
- const niceMaxVal = max <= 0 ? 1 : niceNum(max)
347
- return { niceMinVal, niceMaxVal }
348
- }
@@ -48,24 +48,6 @@ export const styles = {
48
48
  opacity: 1,
49
49
  },
50
50
  },
51
-
52
- // Disabled state — only block interaction; keep visuals unchanged.
53
- // MUI's default `.Mui-disabled` rules dim the AccordionSummary's color
54
- // and add a faded background; we override them so the wrapper looks
55
- // identical to its enabled form. The single behavioral change is
56
- // `pointer-events: none`, which propagates to every descendant.
57
- '&.Mui-disabled': {
58
- pointerEvents: 'none',
59
- bgcolor: 'background.paper',
60
- '& .MuiAccordionSummary-root.Mui-disabled': {
61
- opacity: 1,
62
- color: 'inherit',
63
- backgroundColor: 'transparent',
64
- },
65
- '& .MuiAccordionSummary-expandIconWrapper': {
66
- display: 'none',
67
- },
68
- },
69
51
  },
70
52
  loading: {
71
53
  position: 'absolute',
@@ -83,6 +65,19 @@ export const styles = {
83
65
  alignItems: 'center',
84
66
  },
85
67
  },
68
+ // Disabled state — only the collapse toggle is inert (enforced by the
69
+ // guarded `onChange` handler, not `pointer-events`). Actions and Options in
70
+ // the summary row, and the details panel, all keep their events. We just
71
+ // drop the toggle affordance: hide the chevron (handled in the component)
72
+ // and clear the pointer cursor. MUI's AccordionSummary sets
73
+ // `cursor: pointer` via `&:hover:not(.Mui-disabled)` — a two-class selector
74
+ // that outranks a plain `cursor` rule — so we override at matching
75
+ // specificity. Action/Option buttons restore their own pointer cursor.
76
+ summaryDisabled: {
77
+ '&, &:hover:not(.Mui-disabled)': {
78
+ cursor: 'default',
79
+ },
80
+ },
86
81
  titleCell: {
87
82
  flexGrow: 1,
88
83
  flexShrink: 1,
@@ -186,4 +186,70 @@ describe('<Wrapper>', () => {
186
186
  )
187
187
  expect(screen.getByTestId('body')).toBeTruthy()
188
188
  })
189
+
190
+ it('disables only the summary, leaving the details panel interactive', () => {
191
+ let clicks = 0
192
+ render(
193
+ withProvider(
194
+ 'wrap-10',
195
+ <Wrapper title='t' disabled>
196
+ <Content>
197
+ <button data-testid='body-btn' onClick={() => (clicks += 1)}>
198
+ click
199
+ </button>
200
+ </Content>
201
+ </Wrapper>,
202
+ ),
203
+ )
204
+ // The details panel keeps its events even while the wrapper is disabled.
205
+ fireEvent.click(screen.getByTestId('body-btn'))
206
+ expect(clicks).toBe(1)
207
+ })
208
+
209
+ it('does not toggle when the disabled summary is clicked', () => {
210
+ let lastCollapsed: boolean | undefined
211
+ render(
212
+ withProvider(
213
+ 'wrap-11',
214
+ <Wrapper
215
+ title='t'
216
+ disabled
217
+ onCollapseChange={(c) => (lastCollapsed = c)}
218
+ >
219
+ <Content>body</Content>
220
+ </Wrapper>,
221
+ ),
222
+ )
223
+ fireEvent.click(screen.getByText('t'))
224
+ expect(lastCollapsed).toBeUndefined()
225
+ })
226
+
227
+ it('keeps summary actions clickable while the wrapper is disabled', () => {
228
+ let actionClicks = 0
229
+ let lastCollapsed: boolean | undefined
230
+ render(
231
+ withProvider(
232
+ 'wrap-12',
233
+ <Wrapper
234
+ title='t'
235
+ disabled
236
+ onCollapseChange={(c) => (lastCollapsed = c)}
237
+ >
238
+ <Actions>
239
+ <button
240
+ data-testid='action-btn'
241
+ onClick={() => (actionClicks += 1)}
242
+ >
243
+ A
244
+ </button>
245
+ </Actions>
246
+ <Content>body</Content>
247
+ </Wrapper>,
248
+ ),
249
+ )
250
+ fireEvent.click(screen.getByTestId('action-btn'))
251
+ // The action fires, and clicking it must not toggle the disabled wrapper.
252
+ expect(actionClicks).toBe(1)
253
+ expect(lastCollapsed).toBeUndefined()
254
+ })
189
255
  })
@@ -103,11 +103,12 @@ export function Wrapper({
103
103
  const effectiveCollapsed = isControlled ? collapsed : internalCollapsed
104
104
  const handleAccordionToggle = useCallback(
105
105
  (_: unknown, expanded: boolean) => {
106
+ if (disabled) return
106
107
  const next = !expanded
107
108
  onCollapseChange?.(next)
108
109
  setInternalCollapsed(next)
109
110
  },
110
- [onCollapseChange],
111
+ [disabled, onCollapseChange],
111
112
  )
112
113
  const _labels = { ...DEFAULT_WRAPPER_LABELS, ...labels }
113
114
 
@@ -121,7 +122,6 @@ export function Wrapper({
121
122
  data-collapsed={effectiveCollapsed ? 'true' : undefined}
122
123
  expanded={!effectiveCollapsed}
123
124
  onChange={handleAccordionToggle}
124
- disabled={disabled}
125
125
  disableGutters
126
126
  elevation={0}
127
127
  variant={variant}
@@ -136,9 +136,12 @@ export function Wrapper({
136
136
  ) : null}
137
137
 
138
138
  <AccordionSummary
139
- expandIcon={<ExpandIcon fontSize='small' {...iconProps} />}
139
+ expandIcon={
140
+ disabled ? null : <ExpandIcon fontSize='small' {...iconProps} />
141
+ }
140
142
  aria-label={ariaLabel}
141
- sx={styles.summary}
143
+ aria-disabled={disabled || undefined}
144
+ sx={{ ...styles.summary, ...(disabled ? styles.summaryDisabled : null) }}
142
145
  >
143
146
  <SmartTooltip title={title}>
144
147
  {({ ref }) => (
@@ -1,176 +0,0 @@
1
- import { jsx as j } from "react/jsx-runtime";
2
- import { c as k } from "react/compiler-runtime";
3
- import { useRef as M, useEffectEvent as w, useEffect as T } from "react";
4
- import { u as N, a as D, m as L } from "./widget-store-registry-_W4Z4xp-.js";
5
- import { u as U } from "./widget-context-DTGO0Yta.js";
6
- import "zustand";
7
- import { Box as P } from "@mui/material";
8
- import * as C from "echarts";
9
- const z = /* @__PURE__ */ new Map();
10
- let g = null;
11
- function K() {
12
- return typeof ResizeObserver > "u" ? null : (g ??= new ResizeObserver((r) => {
13
- for (const e of r)
14
- z.get(e.target)?.();
15
- }), g);
16
- }
17
- const W = () => {
18
- };
19
- function $(r, e) {
20
- const t = K();
21
- return t ? (z.set(r, e), t.observe(r), () => {
22
- z.delete(r), g && (g.unobserve(r), z.size === 0 && (g.disconnect(), g = null));
23
- }) : W;
24
- }
25
- function se() {
26
- g && g.disconnect(), z.clear(), g = null;
27
- }
28
- const B = {
29
- root: {
30
- width: "100%",
31
- minHeight: 0
32
- }
33
- }, G = {
34
- renderer: "svg",
35
- height: 304
36
- };
37
- function H(r) {
38
- const e = k(21), {
39
- option: t,
40
- replaceMerge: s,
41
- onEvents: c,
42
- init: a,
43
- onInstance: b,
44
- className: x,
45
- sx: y
46
- } = r, h = M(null), i = M(null), l = M(null), A = M(null);
47
- let v;
48
- e[0] !== b ? (v = (n) => {
49
- b?.(n);
50
- }, e[0] = b, e[1] = v) : v = e[1];
51
- const f = w(v);
52
- let E;
53
- e[2] !== a || e[3] !== f ? (E = () => {
54
- if (!h.current)
55
- return;
56
- const n = C.init(h.current, null, {
57
- ...G,
58
- ...a
59
- });
60
- return i.current = n, f(n), () => {
61
- f(null), n.dispose(), i.current = null;
62
- };
63
- }, e[2] = a, e[3] = f, e[4] = E) : E = e[4];
64
- let u;
65
- e[5] !== a ? (u = [a], e[5] = a, e[6] = u) : u = e[6], T(E, u);
66
- let F, d;
67
- e[7] !== t || e[8] !== s ? (F = () => {
68
- const n = J(t), I = q(t), S = new Set(s ?? []);
69
- n !== l.current && S.add("series"), I !== A.current && S.add("dataset"), l.current = n, A.current = I, i.current?.setOption(t, {
70
- notMerge: !1,
71
- lazyUpdate: !0,
72
- replaceMerge: S.size > 0 ? Array.from(S) : void 0
73
- });
74
- }, d = [t, s], e[7] = t, e[8] = s, e[9] = F, e[10] = d) : (F = e[9], d = e[10]), T(F, d);
75
- let _, m;
76
- e[11] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (_ = () => {
77
- const n = h.current;
78
- if (n)
79
- return $(n, () => {
80
- i.current?.resize();
81
- });
82
- }, m = [], e[11] = _, e[12] = m) : (_ = e[11], m = e[12]), T(_, m);
83
- let O, p;
84
- e[13] !== c ? (O = () => {
85
- const n = i.current;
86
- if (!(!n || !c)) {
87
- for (const [I, S] of Object.entries(c))
88
- n.on(I, S);
89
- return () => {
90
- for (const [I, S] of Object.entries(c))
91
- n.off(I, S);
92
- };
93
- }
94
- }, p = [c], e[13] = c, e[14] = O, e[15] = p) : (O = e[14], p = e[15]), T(O, p);
95
- let o;
96
- e[16] !== y ? (o = {
97
- ...B.root,
98
- ...y
99
- }, e[16] = y, e[17] = o) : o = e[17];
100
- let R;
101
- return e[18] !== x || e[19] !== o ? (R = /* @__PURE__ */ j(P, { ref: h, className: x, sx: o }), e[18] = x, e[19] = o, e[20] = R) : R = e[20], R;
102
- }
103
- function J(r) {
104
- const e = r.series;
105
- return Array.isArray(e) ? e.map((t) => {
106
- if (t == null || typeof t != "object") return String(t);
107
- const s = t;
108
- return [
109
- s.type,
110
- s.datasetIndex,
111
- s.name,
112
- s.stack,
113
- // `encode` is small; stringify is cheap and stable for plain objects.
114
- JSON.stringify(s.encode ?? null)
115
- ].join("|");
116
- }).join("||") : e ? "1" : "";
117
- }
118
- function q(r) {
119
- const e = r.dataset;
120
- return Array.isArray(e) ? String(e.length) : e ? "1" : "";
121
- }
122
- const Q = ["toolbox"], V = (r) => ({
123
- data: r.data,
124
- configTransforms: r.configTransforms,
125
- formatter: r.formatter,
126
- labelFormatter: r.labelFormatter
127
- });
128
- function oe(r) {
129
- const e = k(22), {
130
- optionFactory: t,
131
- onEvents: s,
132
- init: c,
133
- className: a
134
- } = r, b = U(), x = N(b, V);
135
- let y;
136
- e[0] !== b ? (y = (o) => L(b, o), e[0] = b, e[1] = y) : y = e[1];
137
- const h = y, {
138
- data: i,
139
- configTransforms: l,
140
- formatter: A,
141
- labelFormatter: v
142
- } = x;
143
- let f;
144
- e[2] !== t ? (f = t(void 0, void 0), e[2] = t, e[3] = f) : f = e[3];
145
- const E = f;
146
- let u;
147
- e[4] !== l || e[5] !== E ? (u = D(E, l), e[4] = l, e[5] = E, e[6] = u) : u = e[6];
148
- const F = u;
149
- let d;
150
- if (e[7] !== l) {
151
- const o = new Set(Q);
152
- for (const R of l)
153
- if (R.enabled && R.replaceMergeKeys?.length)
154
- for (const n of R.replaceMergeKeys)
155
- o.add(n);
156
- d = Array.from(o).sort(), e[7] = l, e[8] = d;
157
- } else
158
- d = e[8];
159
- const _ = d;
160
- let m;
161
- e[9] !== t || e[10] !== i || e[11] !== A || e[12] !== v || e[13] !== F ? (m = t(F, i, {
162
- formatter: A,
163
- labelFormatter: v
164
- }), e[9] = t, e[10] = i, e[11] = A, e[12] = v, e[13] = F, e[14] = m) : m = e[14];
165
- const O = m;
166
- let p;
167
- return e[15] !== a || e[16] !== c || e[17] !== s || e[18] !== h || e[19] !== O || e[20] !== _ ? (p = /* @__PURE__ */ j(H, { option: O, replaceMerge: _, onEvents: s, init: c, onInstance: h, className: a }), e[15] = a, e[16] = c, e[17] = s, e[18] = h, e[19] = O, e[20] = _, e[21] = p) : p = e[21], p;
168
- }
169
- export {
170
- G as D,
171
- oe as E,
172
- se as _,
173
- H as a,
174
- $ as o
175
- };
176
- //# sourceMappingURL=echart-CU0KmClP.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"echart-CU0KmClP.js","sources":["../src/widgets-v2/echart/shared-resize-observer.ts","../src/widgets-v2/echart/style.ts","../src/widgets-v2/echart/echart-ui.tsx","../src/widgets-v2/echart/echart.tsx"],"sourcesContent":["type ResizeCallback = () => void\n\nconst callbacks = new Map<Element, ResizeCallback>()\nlet observer: ResizeObserver | null = null\n\n/**\n * Returns the singleton ResizeObserver, lazily constructing it on first use.\n * Returns `null` when `ResizeObserver` is not available in the global scope\n * (SSR, very old browsers, or tests that intentionally remove it).\n */\nfunction getObserver(): ResizeObserver | null {\n if (typeof ResizeObserver === 'undefined') return null\n observer ??= new ResizeObserver((entries) => {\n for (const entry of entries) {\n const callback = callbacks.get(entry.target)\n callback?.()\n }\n })\n return observer\n}\n\nconst NOOP_CLEANUP = (): void => undefined\n\nexport function observeResize(\n element: Element,\n callback: ResizeCallback,\n): () => void {\n const ro = getObserver()\n // Gracefully degrade when no ResizeObserver is available — consumers\n // still see one initial measure() pass via their own effect; we just\n // skip the subsequent resize-driven re-measures.\n if (!ro) return NOOP_CLEANUP\n callbacks.set(element, callback)\n ro.observe(element)\n return () => {\n callbacks.delete(element)\n if (observer) {\n observer.unobserve(element)\n // Disconnect the singleton once the last subscriber leaves so the\n // observer doesn't outlive its consumers in SSR teardown / micro-\n // frontend unmount scenarios. A subsequent observeResize() will\n // lazily re-create it via getObserver().\n if (callbacks.size === 0) {\n observer.disconnect()\n observer = null\n }\n }\n }\n}\n\n/** @internal — for tests only. */\nexport function __resetSharedResizeObserver(): void {\n if (observer) observer.disconnect()\n callbacks.clear()\n observer = null\n}\n","import type { SxProps, Theme } from '@mui/material'\n\nexport const styles = {\n root: {\n width: '100%',\n minHeight: 0,\n },\n} satisfies Record<string, SxProps<Theme>>\n","import { useEffect, useEffectEvent, useRef } from 'react'\nimport { Box, type SxProps, type Theme } from '@mui/material'\nimport * as echarts from 'echarts'\nimport { observeResize } from './shared-resize-observer'\nimport { styles } from './style'\n\nexport const DEFAULT_INIT_OPTS = {\n renderer: 'svg',\n height: 304,\n} satisfies echarts.EChartsInitOpts\n\nexport type EchartsEventHandler = (event: unknown) => void\n\nexport interface EchartUIProps {\n option: echarts.EChartsOption\n /**\n * Keys to merge as arrays (replace by index) instead of by id. The middleware\n * memoizes this content-stably, so identical sets don't re-trigger setOption.\n */\n replaceMerge?: readonly string[]\n /**\n * Opaque ECharts event passthrough — handlers fire untransformed.\n *\n * **Must be referentially stable** (memoize it with `useMemo` /\n * `useCallback`, or hoist to module scope). The binding effect depends on\n * the `onEvents` object identity; an inline `{ click: handler }` literal\n * recreates the object on every render and causes every listener to be\n * detached and re-attached each commit.\n */\n onEvents?: Record<string, EchartsEventHandler>\n /**\n * Init options forwarded to echarts.init. Captured at mount only — to change\n * `renderer` or `height` after mount, unmount and remount the chart.\n * Defaults: `{ renderer: 'svg', height: 304 }`.\n */\n init?: echarts.EChartsInitOpts\n /**\n * Optional callback fired once after the ECharts instance is created\n * (`onInstance(chart)`) and once when it's about to be disposed\n * (`onInstance(null)`). Used by `Widget.Echart` to publish the live\n * instance to the per-widget registry so other actions (e.g.\n * `ZoomToggle`'s disable handler) can reach it without DOM lookups.\n * No need to memoize — the bridge wraps it in `useEffectEvent` so an\n * unstable reference does not re-init the chart.\n */\n onInstance?: (chart: echarts.ECharts | null) => void\n className?: string\n sx?: SxProps<Theme>\n}\n\nexport function EchartUI({\n option,\n replaceMerge,\n onEvents,\n init,\n onInstance,\n className,\n sx,\n}: EchartUIProps) {\n const containerRef = useRef<HTMLDivElement>(null)\n const chartRef = useRef<echarts.ECharts | null>(null)\n\n // Structural fingerprint of the last applied series / dataset arrays.\n // Used to decide whether the next `setOption` should *replace* those\n // components (when their shape changed) or *merge* them (when only\n // styling-level fields differ — e.g. a new `itemStyle.color` callback\n // from a selection update). Replacing wipes ECharts' per-series runtime\n // state (hover, animation cache, brush areas), so we want to do it only\n // when the structure actually moved.\n const seriesFingerprintRef = useRef<string | null>(null)\n const datasetFingerprintRef = useRef<string | null>(null)\n\n // Stable notify wrapper — always reads the latest `onInstance` prop\n // without forcing the init effect below to re-run when the parent\n // passes a fresh function reference. The init effect now only fires\n // when `init` actually changes (documented as mount-only anyway).\n const notifyInstance = useEffectEvent(\n (chart: echarts.ECharts | null): void => {\n onInstance?.(chart)\n },\n )\n\n // Init / dispose. `init` is captured once at mount.\n useEffect(() => {\n if (!containerRef.current) return undefined\n const chart = echarts.init(containerRef.current, null, {\n ...DEFAULT_INIT_OPTS,\n ...init,\n })\n chartRef.current = chart\n notifyInstance(chart)\n return () => {\n // Notify observers *before* disposal so any queued imperative\n // dispatches (e.g. ZoomToggle's `setOption` cleanup) see the\n // instance disappear before its DOM is torn down.\n notifyInstance(null)\n chart.dispose()\n chartRef.current = null\n }\n }, [init])\n\n // Apply option / replaceMerge changes via merge mode (no remount required).\n useEffect(() => {\n const seriesFp = computeSeriesFingerprint(option)\n const datasetFp = computeDatasetFingerprint(option)\n const augmented = new Set<string>(replaceMerge ?? [])\n if (seriesFp !== seriesFingerprintRef.current) augmented.add('series')\n if (datasetFp !== datasetFingerprintRef.current) augmented.add('dataset')\n seriesFingerprintRef.current = seriesFp\n datasetFingerprintRef.current = datasetFp\n chartRef.current?.setOption(option, {\n notMerge: false,\n lazyUpdate: true,\n replaceMerge: augmented.size > 0 ? Array.from(augmented) : undefined,\n })\n }, [option, replaceMerge])\n\n // Resize via shared singleton observer.\n useEffect(() => {\n const node = containerRef.current\n if (!node) return undefined\n return observeResize(node, () => {\n chartRef.current?.resize()\n })\n }, [])\n\n // Bind / unbind opaque event handlers.\n useEffect(() => {\n const chart = chartRef.current\n if (!chart || !onEvents) return undefined\n for (const [event, handler] of Object.entries(onEvents)) {\n chart.on(event, handler)\n }\n return () => {\n for (const [event, handler] of Object.entries(onEvents)) {\n chart.off(event, handler)\n }\n }\n }, [onEvents])\n\n return (\n <Box\n ref={containerRef}\n className={className}\n sx={{ ...styles.root, ...sx }}\n />\n )\n}\n\n/**\n * Cheap structural digest of the option's `series` array — covers the fields\n * that actually demand a `replaceMerge` (length, type, datasetIndex, name,\n * stack, encode). Excludes runtime-style fields like `itemStyle` (callbacks\n * have unstable identity but don't change structure).\n */\nfunction computeSeriesFingerprint(option: echarts.EChartsOption): string {\n const series = option.series\n if (!Array.isArray(series)) return series ? '1' : ''\n return series\n .map((s) => {\n if (s == null || typeof s !== 'object') return String(s)\n const o = s as Record<string, unknown>\n return [\n o.type,\n o.datasetIndex,\n o.name,\n o.stack,\n // `encode` is small; stringify is cheap and stable for plain objects.\n JSON.stringify(o.encode ?? null),\n ].join('|')\n })\n .join('||')\n}\n\n/**\n * Structural digest of the option's `dataset` array — count is the only\n * thing that needs `replaceMerge` here. Row-level changes are picked up by\n * ECharts' default merge.\n */\nfunction computeDatasetFingerprint(option: echarts.EChartsOption): string {\n const dataset = option.dataset\n if (!Array.isArray(dataset)) return dataset ? '1' : ''\n return String(dataset.length)\n}\n","import { useCallback, useMemo } from 'react'\nimport type * as echarts from 'echarts'\nimport {\n applyTransforms,\n setEchartInstance,\n useWidgetId,\n useWidgetShallow,\n type Transform,\n} from '../stores'\nimport { EchartUI, type EchartsEventHandler } from './echart-ui'\n\n/**\n * Default ECharts `replaceMerge` keys for every widget. `dataZoom` and\n * `brush` are omitted so ZoomToggle / BrushToggle's user-driven runtime\n * state (slider range, brushed areas in `multiple` mode) survives unrelated\n * re-renders. `series` and `dataset` are also omitted: EchartUI fingerprints\n * them per-render and adds them to `replaceMerge` only when their shape\n * actually changes, keeping ECharts' per-series runtime state alive when\n * only callback-style fields differ.\n *\n * Lives in this module (not the generic store) because it's an ECharts\n * concept. Module-private — callers should not need it.\n */\nconst DEFAULT_REPLACE_MERGE: readonly string[] = ['toolbox']\n\n/**\n * Reactive context passed to every {@link OptionFactory} call so the\n * factory can rebuild render-time pieces (axis label / tooltip formatters)\n * from the live store. Drives RelativeData / consumer-formatter changes\n * through to the chart without rebuilding the structural option.\n */\nexport interface OptionFactoryContext {\n formatter?: (value: number) => string\n labelFormatter?: (value: string | number) => string | number\n}\n\n/**\n * The per-widget option factory — a single callable that owns BOTH phases\n * of option construction:\n *\n * - **Structural phase** — when `option == null`, return the theme-aware\n * structural option (tooltip / legend / color palette / series\n * template, optionally merged with a consumer-supplied `optionsOverride`).\n * No data is read. `<Widget.Echart>` calls the factory with\n * `(undefined, undefined)` synchronously during render to derive the\n * structural base; `configTransforms` (Stack/Zoom/Brush) then mutate it\n * in the same render pass.\n * - **Merge phase** — when `option` is defined, fuse `data` into the\n * post-configTransforms option at fusion time. `<Widget.Echart>` calls\n * the factory with `(transformed, data, ctx)` on every render.\n *\n * The two phases share a closure (the factory creator captures `theme`,\n * `formatter`, `labelFormatter`, `seriesNames`, `selection`, `optionsOverride`,\n * …), so structural and merge agree on the same widget configuration.\n *\n * The third arg `ctx` carries the **live** store-side formatters at the\n * call site — distinct from the closure-time formatters because actions\n * like RelativeData can install a percent formatter on the store after\n * the factory was constructed. The merge phase reads from `ctx`; the\n * structural phase typically uses the closure-time values.\n */\nexport type OptionFactory = (\n option: echarts.EChartsOption | undefined,\n data: unknown,\n ctx?: OptionFactoryContext,\n) => echarts.EChartsOption\n\nexport interface EchartProps {\n /**\n * The per-widget {@link OptionFactory}. Required — `<Widget.Echart>`\n * derives the structural option from it (so configTransforms have a base\n * to mutate) and fuses `state.data` into the post-pipeline option at\n * render time. Wrap the factory creator in `useMemo` so its identity is\n * stable across renders.\n */\n optionFactory: OptionFactory\n onEvents?: Record<string, EchartsEventHandler>\n init?: echarts.EChartsInitOpts\n className?: string\n}\n\ninterface EchartSlice {\n data: unknown\n configTransforms: readonly Transform[]\n formatter?: (value: number) => string\n labelFormatter?: (value: string | number) => string | number\n}\n\nconst echartSelector = (s: {\n data: unknown\n configTransforms: readonly Transform[]\n formatter?: (value: number) => string\n labelFormatter?: (value: string | number) => string | number\n}): EchartSlice => ({\n data: s.data,\n configTransforms: s.configTransforms,\n formatter: s.formatter,\n labelFormatter: s.labelFormatter,\n})\n\n/**\n * Stateful Echart bridge — owns the entire ECharts coupling. The whole\n * option pipeline lives here, not in the store:\n *\n * 1. **Structural** — `optionFactory(undefined, undefined)` produces the\n * theme-aware base. Memoized on the factory identity, so the consumer's\n * `useMemo` ID gates the rebuild.\n * 2. **Transformed** — `applyTransforms(structural, configTransforms)`\n * applies any registered configTransforms (Stack/Zoom/Brush) over the\n * structural base. Memoized on `[structural, configTransforms]`.\n * 3. **`replaceMerge`** — derived from the enabled configTransforms'\n * `replaceMergeKeys`, deduped and sorted, seeded with\n * {@link DEFAULT_REPLACE_MERGE}. Memoized on `[configTransforms]` so\n * ECharts sees a stable array reference across non-transform changes.\n * 4. **Merge** — `optionFactory(transformed, data, ctx)` fuses post-\n * pipeline data into the option. Reactive `ctx` carries the live store\n * formatters so RelativeData's percent formatter flows through without\n * a structural rebuild.\n *\n * The `<Widget.Provider>` doesn't know about the factory at all: it stays\n * a renderer-agnostic shell. The `ProviderProps` surface has no ECharts\n * coupling, so non-Echart widgets don't transitively import the type.\n */\nexport function Echart({\n optionFactory,\n onEvents,\n init,\n className,\n}: EchartProps) {\n const id = useWidgetId()\n const slice = useWidgetShallow(id, echartSelector)\n\n // Publish the live ECharts instance to the per-id registry so actions\n // (e.g. `ZoomToggle`'s disable handler) can reach it imperatively. The\n // callback identity is stable across renders (only `id` is in deps), so\n // EchartUI's init effect doesn't see a fresh `onInstance` and re-init.\n const onInstance = useCallback(\n (chart: echarts.ECharts | null) => setEchartInstance(id, chart),\n [id],\n )\n\n // Destructure so React Compiler sees specific deps instead of inferring\n // the whole `slice` object. `useWidgetShallow` already shallow-compares\n // the slice, so per-field deps drive each memo exactly when its inputs\n // change.\n const {\n data: sliceData,\n configTransforms,\n formatter: sliceFormatter,\n labelFormatter: sliceLabelFormatter,\n } = slice\n\n const structural = useMemo(\n () => optionFactory(undefined, undefined),\n [optionFactory],\n )\n\n const transformed = useMemo(\n () =>\n applyTransforms(structural, configTransforms) as echarts.EChartsOption,\n [structural, configTransforms],\n )\n\n const replaceMerge = useMemo(() => {\n const keys = new Set<string>(DEFAULT_REPLACE_MERGE)\n for (const xf of configTransforms) {\n if (xf.enabled && xf.replaceMergeKeys?.length) {\n for (const k of xf.replaceMergeKeys) keys.add(k)\n }\n }\n return Array.from(keys).sort()\n }, [configTransforms])\n\n const option = useMemo(\n () =>\n optionFactory(transformed, sliceData, {\n formatter: sliceFormatter,\n labelFormatter: sliceLabelFormatter,\n }),\n [\n optionFactory,\n transformed,\n sliceData,\n sliceFormatter,\n sliceLabelFormatter,\n ],\n )\n\n return (\n <EchartUI\n option={option}\n replaceMerge={replaceMerge}\n onEvents={onEvents}\n init={init}\n onInstance={onInstance}\n className={className}\n />\n )\n}\n"],"names":["callbacks","Map","observer","getObserver","ResizeObserver","entries","entry","callback","get","target","NOOP_CLEANUP","observeResize","element","ro","set","observe","delete","unobserve","size","disconnect","__resetSharedResizeObserver","clear","styles","root","width","minHeight","DEFAULT_INIT_OPTS","renderer","height","EchartUI","t0","$","_c","option","replaceMerge","onEvents","init","onInstance","className","sx","containerRef","useRef","chartRef","seriesFingerprintRef","datasetFingerprintRef","t1","chart","notifyInstance","useEffectEvent","t2","current","chart_0","echarts","dispose","t3","useEffect","t4","t5","seriesFp","computeSeriesFingerprint","datasetFp","computeDatasetFingerprint","augmented","Set","add","setOption","notMerge","lazyUpdate","Array","from","undefined","t6","t7","Symbol","for","node","resize","t8","t9","chart_1","event","handler","Object","on","event_0","handler_0","off","t10","t11","Box","series","isArray","map","s","String","o","type","datasetIndex","name","stack","JSON","stringify","encode","join","dataset","length","DEFAULT_REPLACE_MERGE","echartSelector","data","configTransforms","formatter","labelFormatter","Echart","optionFactory","id","useWidgetId","slice","useWidgetShallow","setEchartInstance","sliceData","sliceFormatter","sliceLabelFormatter","structural","applyTransforms","transformed","keys","xf","enabled","replaceMergeKeys","k","sort"],"mappings":"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"}