@pixerate/editor-react 0.35.1 → 0.36.0

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.
@@ -2,7 +2,11 @@
2
2
  import { useEffect, useMemo, useRef, useSyncExternalStore } from "react";
3
3
  import {
4
4
  ChartController,
5
- createChartCommands
5
+ ChartDirector,
6
+ ChartPresence,
7
+ createChartCommands,
8
+ createChartToolProvider,
9
+ layoutChart
6
10
  } from "@pixerate/editor/chart";
7
11
  function useChartController(options = {}) {
8
12
  const ref = useRef(null);
@@ -41,6 +45,26 @@ function useChartCommands(registry, controller, options = {}) {
41
45
  return registry.register(createChartCommands(controller, { prefix, group, scope }));
42
46
  }, [registry, controller, prefix, group, scope]);
43
47
  }
48
+ function useChartToolProvider(registry, controller, options = {}) {
49
+ const latest = useRef(options);
50
+ latest.current = options;
51
+ const { id, namespace } = options;
52
+ useEffect(() => {
53
+ if (!registry) return;
54
+ return registry.register(createChartToolProvider(controller, { ...latest.current, id, namespace }));
55
+ }, [registry, controller, id, namespace]);
56
+ }
57
+ function useChartDirector(controller, options = {}) {
58
+ const sceneRef = useRef(null);
59
+ const ref = useRef(null);
60
+ if (!ref.current) {
61
+ const presence = new ChartPresence();
62
+ const size = options.size ?? { width: 640, height: 400 };
63
+ const director = new ChartDirector(controller, presence, { scene: () => sceneRef.current ?? layoutChart(controller.getSpec(), size), speed: options.speed });
64
+ ref.current = { presence, director };
65
+ }
66
+ return { ...ref.current, sceneRef };
67
+ }
44
68
 
45
69
  // src/chart/styles.ts
46
70
  var chartInputClass = "h-7 w-full min-w-0 rounded-md border border-input bg-background px-2 text-xs text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-50";
@@ -52,7 +76,7 @@ var chartSectionClass = "space-y-2 border-b border-border pb-3 last:border-b-0";
52
76
  var chartCellClass = "h-7 w-full min-w-[4.5rem] bg-transparent px-2 text-right text-xs tabular-nums text-foreground focus-visible:bg-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary";
53
77
 
54
78
  // src/chart/ChartCanvas.tsx
55
- import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState } from "react";
79
+ import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState, useSyncExternalStore as useSyncExternalStore2 } from "react";
56
80
  import {
57
81
  CSS_VARIABLE_CHART_THEME,
58
82
  chartClientToScene,
@@ -60,17 +84,17 @@ import {
60
84
  chartTextTransform,
61
85
  describeChartSelection,
62
86
  handleChartKey,
63
- layoutChart,
87
+ layoutChart as layoutChart2,
64
88
  startChartPointDrag
65
89
  } from "@pixerate/editor/chart";
66
- import { jsx } from "react/jsx-runtime";
67
- function renderMark(mark, fontFamily, interactive) {
90
+ import { jsx, jsxs } from "react/jsx-runtime";
91
+ function renderChartMark(mark, fontFamily, interactive) {
68
92
  const common = {
69
93
  "data-role": mark.role,
70
94
  "data-chart-key": mark.target ? mark.key : void 0,
71
95
  "data-selected": mark.selected ? "" : void 0,
72
96
  opacity: mark.opacity,
73
- pointerEvents: mark.role === "selection" || !mark.target && mark.role !== "background" ? "none" : void 0,
97
+ pointerEvents: mark.role === "selection" || mark.role === "drag-readout" || !mark.target && mark.role !== "background" ? "none" : void 0,
74
98
  cursor: interactive && mark.target && mark.role !== "background" ? "pointer" : void 0
75
99
  };
76
100
  switch (mark.kind) {
@@ -110,13 +134,26 @@ function renderMark(mark, fontFamily, interactive) {
110
134
  );
111
135
  }
112
136
  }
113
- function ChartCanvas({ controller, width = 640, height = 400, responsive = true, theme, editable = true, measureText, emptyMessage, className }) {
137
+ function ChartPresenceLayer({ presence }) {
138
+ const { actors } = useSyncExternalStore2(presence.subscribe, presence.getState, presence.getState);
139
+ return /* @__PURE__ */ jsx("g", { "data-chart-presence": "", pointerEvents: "none", children: actors.map((a) => /* @__PURE__ */ jsxs("g", { transform: `translate(${a.x} ${a.y})`, "data-actor": a.id, children: [
140
+ /* @__PURE__ */ jsx("path", { d: "M0,0 L0,14 L4,10.5 L7,17 L9.5,16 L6.5,9.5 L11.5,9.5 Z", style: { fill: a.color, stroke: "#ffffff", strokeWidth: 1 }, transform: a.pressed ? "scale(0.9)" : void 0 }),
141
+ /* @__PURE__ */ jsxs("g", { transform: "translate(13 16)", children: [
142
+ /* @__PURE__ */ jsx("rect", { x: 0, y: 0, rx: 4, height: 18, width: Math.max(28, (a.message ?? a.name).length * 6.4 + 12), style: { fill: a.color } }),
143
+ /* @__PURE__ */ jsx("text", { x: 6, y: 9, dominantBaseline: "central", style: { fill: "#ffffff", fontSize: 11, fontWeight: 600, fontFamily: "inherit" }, children: a.message ?? a.name })
144
+ ] })
145
+ ] }, a.id)) });
146
+ }
147
+ function ChartSceneSvg({ scene, label, className }) {
148
+ return /* @__PURE__ */ jsx("svg", { width: scene.width, height: scene.height, viewBox: `0 0 ${scene.width} ${scene.height}`, role: "img", "aria-label": label, className: `block max-w-full ${className ?? ""}`, children: scene.marks.map((m) => renderChartMark(m, scene.theme.fontFamily, false)) });
149
+ }
150
+ function ChartCanvas({ controller, width = 640, height = 400, responsive = true, theme, editable = true, measureText, emptyMessage, className, presence, sceneRef }) {
114
151
  const state = useChartState(controller);
115
152
  const wrapper = useRef2(null);
116
153
  const svg = useRef2(null);
117
154
  const drag = useRef2(null);
118
155
  const [measured, setMeasured] = useState(null);
119
- const [valueDomain, setValueDomain] = useState(null);
156
+ const [frozen, setFrozen] = useState(null);
120
157
  useEffect2(() => {
121
158
  const el = wrapper.current;
122
159
  if (!responsive || !el || typeof ResizeObserver === "undefined") return;
@@ -130,9 +167,21 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
130
167
  const sceneWidth = responsive && measured || width;
131
168
  const fullTheme = useMemo2(() => ({ ...CSS_VARIABLE_CHART_THEME, ...theme }), [theme]);
132
169
  const scene = useMemo2(
133
- () => layoutChart(state.spec, { width: sceneWidth, height, theme: fullTheme, selection: editable ? state.selection : null, measureText, emptyMessage, valueDomain }),
134
- [state.spec, state.selection, sceneWidth, height, fullTheme, editable, measureText, emptyMessage, valueDomain]
170
+ () => layoutChart2(state.spec, {
171
+ width: sceneWidth,
172
+ height,
173
+ theme: fullTheme,
174
+ selection: editable ? state.selection : null,
175
+ measureText,
176
+ emptyMessage,
177
+ valueDomain: frozen?.valueDomain,
178
+ valueDomain2: frozen?.valueDomain2,
179
+ xDomain: frozen?.xDomain,
180
+ readout: frozen?.readout
181
+ }),
182
+ [state.spec, state.selection, sceneWidth, height, fullTheme, editable, measureText, emptyMessage, frozen]
135
183
  );
184
+ if (sceneRef) sceneRef.current = scene;
136
185
  const byKey = useMemo2(() => new Map(scene.marks.map((m) => [m.key, m])), [scene]);
137
186
  useEffect2(() => () => drag.current?.end(), []);
138
187
  const scenePoint = (e) => chartClientToScene(scene, svg.current.getBoundingClientRect(), e.clientX, e.clientY);
@@ -143,23 +192,31 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
143
192
  controller.select(target ?? null);
144
193
  drag.current = startChartPointDrag(controller, scene, target, scenePoint(e));
145
194
  if (drag.current) {
146
- setValueDomain(drag.current.valueDomain);
195
+ const d = drag.current;
196
+ setFrozen({ valueDomain: d.valueDomain, valueDomain2: d.valueDomain2 ?? null, xDomain: d.xDomain ?? null, readout: d.target });
147
197
  svg.current?.setPointerCapture?.(e.pointerId);
148
198
  }
149
199
  };
150
200
  const onPointerMove = (e) => {
151
- if (drag.current) drag.current.move(scenePoint(e));
201
+ if (drag.current) drag.current.move(scenePoint(e), { fine: e.altKey, lockAxis: e.shiftKey, noSnap: e.ctrlKey || e.metaKey });
152
202
  };
153
203
  const endDrag = () => {
154
204
  drag.current?.end();
155
205
  drag.current = null;
156
- setValueDomain(null);
206
+ setFrozen(null);
157
207
  };
158
208
  const onKeyDown = (e) => {
209
+ if (drag.current && e.key === "Escape") {
210
+ drag.current.cancel();
211
+ drag.current = null;
212
+ setFrozen(null);
213
+ e.preventDefault();
214
+ return;
215
+ }
159
216
  if (editable && handleChartKey(controller, scene, e)) e.preventDefault();
160
217
  };
161
218
  const label = state.spec.title || "Chart";
162
- return /* @__PURE__ */ jsx("div", { ref: wrapper, className: `w-full ${className ?? ""}`, "data-chart-canvas": "", children: /* @__PURE__ */ jsx(
219
+ return /* @__PURE__ */ jsx("div", { ref: wrapper, className: `w-full ${className ?? ""}`, "data-chart-canvas": "", children: /* @__PURE__ */ jsxs(
163
220
  "svg",
164
221
  {
165
222
  ref: svg,
@@ -175,7 +232,10 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
175
232
  onPointerUp: endDrag,
176
233
  onPointerCancel: endDrag,
177
234
  onKeyDown,
178
- children: scene.marks.map((m) => renderMark(m, scene.theme.fontFamily, editable))
235
+ children: [
236
+ scene.marks.map((m) => renderChartMark(m, scene.theme.fontFamily, editable)),
237
+ presence && /* @__PURE__ */ jsx(ChartPresenceLayer, { presence })
238
+ ]
179
239
  }
180
240
  ) });
181
241
  }
@@ -183,7 +243,7 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
183
243
  // src/chart/ChartDataGrid.tsx
184
244
  import { useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
185
245
  import { chartColorInputValue, chartSeriesColor, formatChartNumber } from "@pixerate/editor/chart";
186
- import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
246
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
187
247
  function ChartCommitInput({ value, onCommit, onKeyDown, onBlur, ...rest }) {
188
248
  const [draft, setDraft] = useState2(value);
189
249
  const typing = useRef3(false);
@@ -231,14 +291,14 @@ function ChartDataGrid({ controller, categoryHeader = "Category", readOnly = fal
231
291
  e.preventDefault();
232
292
  controller.pasteCells(row, column, text);
233
293
  };
234
- return /* @__PURE__ */ jsxs("div", { className: `overflow-auto rounded-md border border-border ${className ?? ""}`, "data-chart-grid": "", children: [
235
- /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-xs", children: [
236
- /* @__PURE__ */ jsx2("thead", { className: "bg-muted/50", children: /* @__PURE__ */ jsxs("tr", { children: [
294
+ return /* @__PURE__ */ jsxs2("div", { className: `overflow-auto rounded-md border border-border ${className ?? ""}`, "data-chart-grid": "", children: [
295
+ /* @__PURE__ */ jsxs2("table", { className: "w-full border-collapse text-xs", children: [
296
+ /* @__PURE__ */ jsx2("thead", { className: "bg-muted/50", children: /* @__PURE__ */ jsxs2("tr", { children: [
237
297
  /* @__PURE__ */ jsx2("th", { className: "border-b border-r border-border px-2 py-1 text-left font-medium text-muted-foreground", children: categoryHeader }),
238
298
  spec.series.map((s, si) => {
239
299
  const color = chartSeriesColor(spec, si);
240
300
  const selected = selection?.kind === "series" && selection.seriesId === s.id;
241
- return /* @__PURE__ */ jsx2("th", { "data-series-id": s.id, className: `border-b border-r border-border px-1 py-1 font-medium ${selected ? "bg-primary/10" : ""}`, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
301
+ return /* @__PURE__ */ jsx2("th", { "data-series-id": s.id, className: `border-b border-r border-border px-1 py-1 font-medium ${selected ? "bg-primary/10" : ""}`, children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1", children: [
242
302
  /* @__PURE__ */ jsx2(
243
303
  "input",
244
304
  {
@@ -262,7 +322,7 @@ function ChartDataGrid({ controller, categoryHeader = "Category", readOnly = fal
262
322
  className: `${chartInputClass} h-6 min-w-[5rem] border-transparent bg-transparent font-medium ${s.hidden ? "text-muted-foreground line-through" : ""}`
263
323
  }
264
324
  ),
265
- !readOnly && /* @__PURE__ */ jsxs(Fragment, { children: [
325
+ !readOnly && /* @__PURE__ */ jsxs2(Fragment, { children: [
266
326
  /* @__PURE__ */ jsx2(
267
327
  "button",
268
328
  {
@@ -284,7 +344,7 @@ function ChartDataGrid({ controller, categoryHeader = "Category", readOnly = fal
284
344
  ] }) }),
285
345
  /* @__PURE__ */ jsx2("tbody", { children: spec.categories.map((category, i) => {
286
346
  const rowSelected = selection?.kind === "category" && selection.index === i;
287
- return /* @__PURE__ */ jsxs("tr", { "data-category-index": i, className: rowSelected ? "bg-primary/10" : void 0, children: [
347
+ return /* @__PURE__ */ jsxs2("tr", { "data-category-index": i, className: rowSelected ? "bg-primary/10" : void 0, children: [
288
348
  /* @__PURE__ */ jsx2("th", { className: "border-b border-r border-border p-0 text-left font-normal", children: /* @__PURE__ */ jsx2(
289
349
  ChartCommitInput,
290
350
  {
@@ -325,6 +385,15 @@ function ChartDataGrid({ controller, categoryHeader = "Category", readOnly = fal
325
385
  // src/chart/ChartInspector.tsx
326
386
  import { useId } from "react";
327
387
  import {
388
+ CHART_COLOR_SCHEMES,
389
+ CHART_LABEL_POSITIONS,
390
+ CHART_LAYOUT_PRESETS,
391
+ CHART_MARKERS,
392
+ CHART_TRENDLINE_TYPES,
393
+ checkChartColors,
394
+ chartFamily,
395
+ chartSupportsTrendlines,
396
+ chartTrendlineName,
328
397
  CHART_CURVES,
329
398
  CHART_LEGEND_POSITIONS,
330
399
  CHART_NUMBER_STYLES,
@@ -337,16 +406,16 @@ import {
337
406
  chartSeriesType,
338
407
  isCartesianChart
339
408
  } from "@pixerate/editor/chart";
340
- import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
409
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
341
410
  var cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
342
411
  function Field({ label, htmlFor, children }) {
343
- return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
412
+ return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
344
413
  /* @__PURE__ */ jsx3("label", { htmlFor, className: `${chartLabelClass} w-20 shrink-0`, children: label }),
345
414
  /* @__PURE__ */ jsx3("div", { className: "min-w-0 flex-1", children })
346
415
  ] });
347
416
  }
348
417
  function Toggle({ label, checked, onChange, name }) {
349
- return /* @__PURE__ */ jsxs2("label", { className: "flex cursor-pointer items-center gap-2 text-xs text-foreground", children: [
418
+ return /* @__PURE__ */ jsxs3("label", { className: "flex cursor-pointer items-center gap-2 text-xs text-foreground", children: [
350
419
  /* @__PURE__ */ jsx3("input", { type: "checkbox", className: "h-3.5 w-3.5 accent-primary", checked, "data-option": name, onChange: (e) => onChange(e.currentTarget.checked) }),
351
420
  label
352
421
  ] });
@@ -354,43 +423,60 @@ function Toggle({ label, checked, onChange, name }) {
354
423
  function Select({ id, value, options, onChange, name, labels }) {
355
424
  return /* @__PURE__ */ jsx3("select", { id, className: chartInputClass, value, "data-option": name, onChange: (e) => onChange(e.currentTarget.value), children: options.map((o) => /* @__PURE__ */ jsx3("option", { value: o, children: labels?.[o] ?? cap(o) }, o)) });
356
425
  }
357
- function AxisSection({ controller, axis, value, idBase, horizontal }) {
426
+ function AxisSection({ controller, axis, value, idBase, horizontal, numeric = false }) {
358
427
  const set = (patch) => controller.setAxis(axis, patch);
359
428
  const setFormat = (patch) => set({ format: { ...value.format, ...patch } });
360
- const bound = (key) => /* @__PURE__ */ jsx3(
429
+ const bound = (key2) => /* @__PURE__ */ jsx3(
361
430
  ChartCommitInput,
362
431
  {
363
- id: `${idBase}-${axis}-${key}`,
364
- value: value[key] === null ? "" : String(value[key]),
432
+ id: `${idBase}-${axis}-${key2}`,
433
+ value: value[key2] === null ? "" : String(value[key2]),
365
434
  placeholder: "Auto",
366
435
  inputMode: "decimal",
367
- "data-option": `${axis}Axis.${key}`,
436
+ "data-option": `${axis}Axis.${key2}`,
368
437
  className: chartInputClass,
369
- onCommit: (text) => set({ [key]: text.trim() === "" ? null : Number(text) }).ok
438
+ onCommit: (text) => set({ [key2]: text.trim() === "" ? null : Number(text) }).ok
370
439
  }
371
440
  );
372
- const name = axis === "x" ? "Category axis" : "Value axis";
373
- return /* @__PURE__ */ jsxs2("div", { className: chartSectionClass, "data-section": `axis-${axis}`, children: [
374
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [
375
- /* @__PURE__ */ jsxs2("h4", { className: "text-xs font-semibold text-foreground", children: [
441
+ const name = axis === "x" ? numeric ? "X axis" : "Category axis" : axis === "y" ? "Value axis" : "Secondary axis";
442
+ const valueAxis = axis !== "x" || numeric;
443
+ const key = axis === "y2" ? "yAxis2" : `${axis}Axis`;
444
+ return /* @__PURE__ */ jsxs3("div", { className: chartSectionClass, "data-section": `axis-${axis}`, children: [
445
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
446
+ /* @__PURE__ */ jsxs3("h4", { className: "text-xs font-semibold text-foreground", children: [
376
447
  name,
377
- /* @__PURE__ */ jsxs2("span", { className: "ml-1 font-normal text-muted-foreground", children: [
448
+ /* @__PURE__ */ jsxs3("span", { className: "ml-1 font-normal text-muted-foreground", children: [
378
449
  "(",
379
- axis === "x" ? horizontal ? "left" : "bottom" : horizontal ? "bottom" : "left",
450
+ axis === "x" ? horizontal ? "left" : "bottom" : axis === "y2" ? horizontal ? "top" : "right" : horizontal ? "bottom" : "left",
380
451
  ")"
381
452
  ] })
382
453
  ] }),
383
454
  /* @__PURE__ */ jsx3(Toggle, { label: "Show", name: `${axis}Axis.show`, checked: value.show, onChange: (show) => set({ show }) })
384
455
  ] }),
385
456
  /* @__PURE__ */ jsx3(Field, { label: "Title", htmlFor: `${idBase}-${axis}-title`, children: /* @__PURE__ */ jsx3(ChartCommitInput, { id: `${idBase}-${axis}-title`, value: value.title, "data-option": `${axis}Axis.title`, className: chartInputClass, onCommit: (title) => void set({ title }) }) }),
386
- /* @__PURE__ */ jsx3(Toggle, { label: "Grid lines", name: `${axis}Axis.gridlines`, checked: value.gridlines, onChange: (gridlines) => set({ gridlines }) }),
387
- axis === "y" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
388
- /* @__PURE__ */ jsx3(Field, { label: "Min / max", children: /* @__PURE__ */ jsxs2("div", { className: "flex gap-1", children: [
457
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap gap-x-4 gap-y-1", children: [
458
+ /* @__PURE__ */ jsx3(Toggle, { label: "Grid lines", name: `${key}.gridlines`, checked: value.gridlines, onChange: (gridlines) => set({ gridlines }) }),
459
+ axis === "x" && !numeric && /* @__PURE__ */ jsx3(Toggle, { label: "Dates", name: "xAxis.kind", checked: value.kind === "date", onChange: (dates) => set({ kind: dates ? "date" : void 0 }) })
460
+ ] }),
461
+ valueAxis && /* @__PURE__ */ jsx3(Field, { label: "Step", htmlFor: `${idBase}-${axis}-step`, children: /* @__PURE__ */ jsx3(
462
+ ChartCommitInput,
463
+ {
464
+ id: `${idBase}-${axis}-step`,
465
+ value: value.step === null ? "" : String(value.step),
466
+ placeholder: "Auto",
467
+ inputMode: "decimal",
468
+ "data-option": `${key}.step`,
469
+ className: chartInputClass,
470
+ onCommit: (text) => set({ step: text.trim() === "" ? null : Number(text) }).ok
471
+ }
472
+ ) }),
473
+ valueAxis && /* @__PURE__ */ jsxs3(Fragment2, { children: [
474
+ /* @__PURE__ */ jsx3(Field, { label: "Min / max", children: /* @__PURE__ */ jsxs3("div", { className: "flex gap-1", children: [
389
475
  bound("min"),
390
476
  bound("max")
391
477
  ] }) }),
392
- /* @__PURE__ */ jsx3(Field, { label: "Format", htmlFor: `${idBase}-y-format`, children: /* @__PURE__ */ jsxs2("div", { className: "flex gap-1", children: [
393
- /* @__PURE__ */ jsx3(Select, { id: `${idBase}-y-format`, name: "yAxis.format.style", value: value.format.style ?? "number", options: CHART_NUMBER_STYLES, onChange: (style) => setFormat({ style }) }),
478
+ /* @__PURE__ */ jsx3(Field, { label: "Format", htmlFor: `${idBase}-${axis}-format`, children: /* @__PURE__ */ jsxs3("div", { className: "flex gap-1", children: [
479
+ /* @__PURE__ */ jsx3(Select, { id: `${idBase}-${axis}-format`, name: `${key}.format.style`, value: value.format.style ?? "number", options: CHART_NUMBER_STYLES, onChange: (style) => setFormat({ style }) }),
394
480
  /* @__PURE__ */ jsx3(
395
481
  ChartCommitInput,
396
482
  {
@@ -399,7 +485,7 @@ function AxisSection({ controller, axis, value, idBase, horizontal }) {
399
485
  "aria-label": "Decimals",
400
486
  title: "Decimals",
401
487
  inputMode: "numeric",
402
- "data-option": "yAxis.format.decimals",
488
+ "data-option": `${key}.format.decimals`,
403
489
  className: `${chartInputClass} w-14 shrink-0`,
404
490
  onCommit: (text) => {
405
491
  const format = { ...value.format };
@@ -410,7 +496,23 @@ function AxisSection({ controller, axis, value, idBase, horizontal }) {
410
496
  }
411
497
  )
412
498
  ] }) }),
413
- value.format.style === "currency" && /* @__PURE__ */ jsx3(Field, { label: "Currency", htmlFor: `${idBase}-y-currency`, children: /* @__PURE__ */ jsx3(ChartCommitInput, { id: `${idBase}-y-currency`, value: value.format.currency ?? "USD", "data-option": "yAxis.format.currency", className: chartInputClass, onCommit: (currency) => void setFormat({ currency: currency.toUpperCase() }) }) })
499
+ value.format.style === "currency" && /* @__PURE__ */ jsx3(Field, { label: "Currency", htmlFor: `${idBase}-${axis}-currency`, children: /* @__PURE__ */ jsx3(ChartCommitInput, { id: `${idBase}-${axis}-currency`, value: value.format.currency ?? "USD", "data-option": `${key}.format.currency`, className: chartInputClass, onCommit: (currency) => void setFormat({ currency: currency.toUpperCase() }) }) }),
500
+ /* @__PURE__ */ jsx3(Field, { label: "Code", htmlFor: `${idBase}-${axis}-code`, children: /* @__PURE__ */ jsx3(
501
+ ChartCommitInput,
502
+ {
503
+ id: `${idBase}-${axis}-code`,
504
+ value: value.format.code ?? "",
505
+ placeholder: 'e.g. #,##0.0,,"M"',
506
+ "data-option": `${key}.format.code`,
507
+ className: chartInputClass,
508
+ onCommit: (code) => {
509
+ const format = { ...value.format };
510
+ if (code.trim()) format.code = code.trim();
511
+ else delete format.code;
512
+ return set({ format }).ok;
513
+ }
514
+ }
515
+ ) })
414
516
  ] })
415
517
  ] });
416
518
  }
@@ -419,17 +521,33 @@ function ChartInspector({ controller, sections, className }) {
419
521
  const idBase = useId();
420
522
  const show = (section) => !sections || sections.includes(section);
421
523
  const cartesian = isCartesianChart(spec.type);
524
+ const family = chartFamily(spec);
525
+ const hasAxes = family === "cartesian" || family === "xy" || family === "waterfall" || family === "histogram" || family === "box";
422
526
  const types = spec.series.map((s) => chartSeriesType(spec, s));
527
+ const usesSecondary = family === "cartesian" && spec.series.some((s) => s.axis === "secondary" && !s.hidden);
528
+ const colorCheck = checkChartColors(spec.series.filter((s) => !s.hidden).map((s) => chartSeriesColor2(spec, spec.series.indexOf(s))));
423
529
  const hasBars = cartesian && types.includes("bar");
424
530
  const hasLines = cartesian && types.some((t) => t === "line" || t === "area");
425
531
  const seriesIndex = selectedSeries ? spec.series.findIndex((s) => s.id === selectedSeries.id) : -1;
426
- return /* @__PURE__ */ jsxs2("div", { className: `space-y-3 text-xs ${className ?? ""}`, "data-chart-inspector": "", children: [
532
+ return /* @__PURE__ */ jsxs3("div", { className: `space-y-3 text-xs ${className ?? ""}`, "data-chart-inspector": "", children: [
427
533
  show("type") && /* @__PURE__ */ jsx3("div", { className: chartSectionClass, "data-section": "type", children: /* @__PURE__ */ jsx3("div", { className: "grid grid-cols-3 gap-0.5 rounded-md bg-muted p-0.5", role: "radiogroup", "aria-label": "Chart type", children: CHART_TYPES.map((type) => /* @__PURE__ */ jsx3("button", { type: "button", role: "radio", "aria-checked": spec.type === type, "data-chart-type": type, className: chartSegmentClass(spec.type === type), onClick: () => controller.setType(type), children: CHART_TYPE_LABELS[type] }, type)) }) }),
428
- show("titles") && /* @__PURE__ */ jsxs2("div", { className: chartSectionClass, "data-section": "titles", children: [
534
+ show("style") && /* @__PURE__ */ jsxs3("div", { className: chartSectionClass, "data-section": "style", children: [
535
+ /* @__PURE__ */ jsx3(Field, { label: "Layout", htmlFor: `${idBase}-layout`, children: /* @__PURE__ */ jsxs3("select", { id: `${idBase}-layout`, className: chartInputClass, value: "", "data-option": "preset.layout", onChange: (e) => e.currentTarget.value && controller.applyPreset({ layout: e.currentTarget.value }), children: [
536
+ /* @__PURE__ */ jsx3("option", { value: "", children: "Quick layout\u2026" }),
537
+ CHART_LAYOUT_PRESETS.map((p) => /* @__PURE__ */ jsx3("option", { value: p.id, title: p.description, children: p.label }, p.id))
538
+ ] }) }),
539
+ /* @__PURE__ */ jsx3(Field, { label: "Colours", htmlFor: `${idBase}-colors`, children: /* @__PURE__ */ jsxs3("select", { id: `${idBase}-colors`, className: chartInputClass, value: CHART_COLOR_SCHEMES.find((c) => c.palette.join() === spec.palette.join())?.id ?? "", "data-option": "preset.colors", onChange: (e) => e.currentTarget.value && controller.applyPreset({ colors: e.currentTarget.value }), children: [
540
+ /* @__PURE__ */ jsx3("option", { value: "", children: "Custom" }),
541
+ CHART_COLOR_SCHEMES.map((c) => /* @__PURE__ */ jsx3("option", { value: c.id, title: c.description, children: c.label }, c.id))
542
+ ] }) }),
543
+ !colorCheck.ok && /* @__PURE__ */ jsx3("p", { className: "text-[11px] leading-snug text-amber-600 dark:text-amber-400", "data-chart-color-warning": "", children: colorCheck.issues[0] }),
544
+ /* @__PURE__ */ jsx3("button", { type: "button", className: chartButtonClass, "data-action": "transpose", onClick: () => controller.transpose(), children: "Switch rows and columns" })
545
+ ] }),
546
+ show("titles") && /* @__PURE__ */ jsxs3("div", { className: chartSectionClass, "data-section": "titles", children: [
429
547
  /* @__PURE__ */ jsx3(Field, { label: "Title", htmlFor: `${idBase}-title`, children: /* @__PURE__ */ jsx3(ChartCommitInput, { id: `${idBase}-title`, value: spec.title, "data-option": "title", className: chartInputClass, onCommit: (title) => void controller.setTitle(title) }) }),
430
548
  /* @__PURE__ */ jsx3(Field, { label: "Subtitle", htmlFor: `${idBase}-subtitle`, children: /* @__PURE__ */ jsx3(ChartCommitInput, { id: `${idBase}-subtitle`, value: spec.subtitle, "data-option": "subtitle", className: chartInputClass, onCommit: (subtitle) => void controller.setSubtitle(subtitle) }) })
431
549
  ] }),
432
- show("options") && /* @__PURE__ */ jsxs2("div", { className: chartSectionClass, "data-section": "options", children: [
550
+ show("options") && /* @__PURE__ */ jsxs3("div", { className: chartSectionClass, "data-section": "options", children: [
433
551
  cartesian && /* @__PURE__ */ jsx3(Field, { label: "Stacking", htmlFor: `${idBase}-stacking`, children: /* @__PURE__ */ jsx3(Select, { id: `${idBase}-stacking`, name: "stacking", value: spec.stacking, options: CHART_STACKINGS, labels: { none: "None", stacked: "Stacked", percent: "100%" }, onChange: (stacking) => controller.update({ stacking }) }) }),
434
552
  hasBars && /* @__PURE__ */ jsx3(Field, { label: "Bars", children: /* @__PURE__ */ jsx3("div", { className: "flex gap-0.5 rounded-md bg-muted p-0.5", children: ["vertical", "horizontal"].map((o) => /* @__PURE__ */ jsx3("button", { type: "button", "aria-pressed": spec.orientation === o, "data-option": `orientation.${o}`, className: chartSegmentClass(spec.orientation === o), onClick: () => controller.update({ orientation: o }), children: cap(o) }, o)) }) }),
435
553
  hasLines && /* @__PURE__ */ jsx3(Field, { label: "Lines", htmlFor: `${idBase}-curve`, children: /* @__PURE__ */ jsx3(Select, { id: `${idBase}-curve`, name: "curve", value: spec.curve, options: CHART_CURVES, onChange: (curve) => controller.update({ curve }) }) }),
@@ -449,26 +567,46 @@ function ChartInspector({ controller, sections, className }) {
449
567
  onChange: (e) => controller.update({ innerRadius: Number(e.currentTarget.value) })
450
568
  }
451
569
  ) }),
452
- /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap gap-x-4 gap-y-1", children: [
570
+ hasBars && /* @__PURE__ */ jsx3(Field, { label: "Spacing", htmlFor: `${idBase}-gap`, children: /* @__PURE__ */ jsx3(
571
+ "input",
572
+ {
573
+ id: `${idBase}-gap`,
574
+ type: "range",
575
+ min: 0,
576
+ max: 0.9,
577
+ step: 0.02,
578
+ value: spec.gapWidth,
579
+ "data-option": "gapWidth",
580
+ className: "w-full accent-primary",
581
+ onPointerDown: () => controller.beginGesture("gapWidth"),
582
+ onPointerUp: () => controller.endGesture(),
583
+ onChange: (e) => controller.update({ gapWidth: Number(e.currentTarget.value) })
584
+ }
585
+ ) }),
586
+ spec.dataLabels && hasBars && /* @__PURE__ */ jsx3(Field, { label: "Labels", htmlFor: `${idBase}-label-position`, children: /* @__PURE__ */ jsx3(Select, { id: `${idBase}-label-position`, name: "labelPosition", value: spec.labelPosition, options: CHART_LABEL_POSITIONS, onChange: (labelPosition) => controller.update({ labelPosition }) }) }),
587
+ spec.type === "histogram" && /* @__PURE__ */ jsx3(Field, { label: "Bins", htmlFor: `${idBase}-bins`, children: /* @__PURE__ */ jsx3(ChartCommitInput, { id: `${idBase}-bins`, value: spec.bins === null ? "" : String(spec.bins), placeholder: "Auto", inputMode: "numeric", "data-option": "bins", className: chartInputClass, onCommit: (text) => controller.update({ bins: text.trim() === "" ? null : Number(text) }).ok }) }),
588
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap gap-x-4 gap-y-1", children: [
589
+ spec.type === "waterfall" && /* @__PURE__ */ jsx3(Toggle, { label: "Total bar", name: "showTotal", checked: spec.showTotal, onChange: (showTotal) => controller.update({ showTotal }) }),
453
590
  hasLines && /* @__PURE__ */ jsx3(Toggle, { label: "Points", name: "points", checked: spec.points, onChange: (points) => controller.update({ points }) }),
454
591
  /* @__PURE__ */ jsx3(Toggle, { label: "Data labels", name: "dataLabels", checked: spec.dataLabels, onChange: (dataLabels) => controller.update({ dataLabels }) })
455
592
  ] })
456
593
  ] }),
457
- show("legend") && /* @__PURE__ */ jsxs2("div", { className: chartSectionClass, "data-section": "legend", children: [
458
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [
594
+ show("legend") && /* @__PURE__ */ jsxs3("div", { className: chartSectionClass, "data-section": "legend", children: [
595
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
459
596
  /* @__PURE__ */ jsx3("h4", { className: "text-xs font-semibold text-foreground", children: "Legend" }),
460
597
  /* @__PURE__ */ jsx3(Toggle, { label: "Show", name: "legend.show", checked: spec.legend.show, onChange: (v) => controller.setLegend({ show: v }) })
461
598
  ] }),
462
599
  spec.legend.show && /* @__PURE__ */ jsx3("div", { className: "flex gap-0.5 rounded-md bg-muted p-0.5", children: CHART_LEGEND_POSITIONS.map((p) => /* @__PURE__ */ jsx3("button", { type: "button", "aria-pressed": spec.legend.position === p, "data-option": `legend.${p}`, className: chartSegmentClass(spec.legend.position === p), onClick: () => controller.setLegend({ position: p }), children: cap(p) }, p)) })
463
600
  ] }),
464
- show("axes") && cartesian && /* @__PURE__ */ jsxs2(Fragment2, { children: [
465
- /* @__PURE__ */ jsx3(AxisSection, { controller, axis: "x", value: spec.xAxis, idBase, horizontal: spec.orientation === "horizontal" }),
466
- /* @__PURE__ */ jsx3(AxisSection, { controller, axis: "y", value: spec.yAxis, idBase, horizontal: spec.orientation === "horizontal" })
601
+ show("axes") && hasAxes && /* @__PURE__ */ jsxs3(Fragment2, { children: [
602
+ family !== "box" && family !== "histogram" && /* @__PURE__ */ jsx3(AxisSection, { controller, axis: "x", value: spec.xAxis, idBase, horizontal: spec.orientation === "horizontal", numeric: family === "xy" }),
603
+ /* @__PURE__ */ jsx3(AxisSection, { controller, axis: "y", value: spec.yAxis, idBase, horizontal: spec.orientation === "horizontal" }),
604
+ usesSecondary && /* @__PURE__ */ jsx3(AxisSection, { controller, axis: "y2", value: spec.yAxis2, idBase, horizontal: spec.orientation === "horizontal" })
467
605
  ] }),
468
- show("series") && selectedSeries && /* @__PURE__ */ jsxs2("div", { className: chartSectionClass, "data-section": "series", "data-series-id": selectedSeries.id, children: [
606
+ show("series") && selectedSeries && /* @__PURE__ */ jsxs3("div", { className: chartSectionClass, "data-section": "series", "data-series-id": selectedSeries.id, children: [
469
607
  /* @__PURE__ */ jsx3("h4", { className: "text-xs font-semibold text-foreground", children: "Series" }),
470
608
  /* @__PURE__ */ jsx3(Field, { label: "Name", htmlFor: `${idBase}-series-name`, children: /* @__PURE__ */ jsx3(ChartCommitInput, { id: `${idBase}-series-name`, value: selectedSeries.name, "data-option": "series.name", className: chartInputClass, onCommit: (name) => void controller.updateSeries(selectedSeries.id, { name }) }) }),
471
- /* @__PURE__ */ jsx3(Field, { label: "Colour", htmlFor: `${idBase}-series-color`, children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1", children: [
609
+ /* @__PURE__ */ jsx3(Field, { label: "Colour", htmlFor: `${idBase}-series-color`, children: /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1", children: [
472
610
  /* @__PURE__ */ jsx3(
473
611
  "input",
474
612
  {
@@ -484,7 +622,7 @@ function ChartInspector({ controller, sections, className }) {
484
622
  ),
485
623
  selectedSeries.color && /* @__PURE__ */ jsx3("button", { type: "button", className: chartButtonClass, "data-action": "reset-color", onClick: () => controller.updateSeries(selectedSeries.id, { color: null }), children: "Palette" })
486
624
  ] }) }),
487
- cartesian && /* @__PURE__ */ jsx3(Field, { label: "Draw as", htmlFor: `${idBase}-series-type`, children: /* @__PURE__ */ jsxs2(
625
+ cartesian && /* @__PURE__ */ jsx3(Field, { label: "Draw as", htmlFor: `${idBase}-series-type`, children: /* @__PURE__ */ jsxs3(
488
626
  "select",
489
627
  {
490
628
  id: `${idBase}-series-type`,
@@ -498,8 +636,44 @@ function ChartInspector({ controller, sections, className }) {
498
636
  ]
499
637
  }
500
638
  ) }),
501
- /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-center gap-1", children: [
639
+ (cartesian || family === "xy") && ["line", "area", "scatter"].includes(chartSeriesType(spec, selectedSeries)) && /* @__PURE__ */ jsx3(Field, { label: "Marker", htmlFor: `${idBase}-series-marker`, children: /* @__PURE__ */ jsxs3(
640
+ "select",
641
+ {
642
+ id: `${idBase}-series-marker`,
643
+ className: chartInputClass,
644
+ value: selectedSeries.marker ?? "",
645
+ "data-option": "series.marker",
646
+ onChange: (e) => controller.updateSeries(selectedSeries.id, { marker: e.currentTarget.value || null }),
647
+ children: [
648
+ /* @__PURE__ */ jsx3("option", { value: "", children: "Default" }),
649
+ CHART_MARKERS.map((m) => /* @__PURE__ */ jsx3("option", { value: m, children: cap(m) }, m))
650
+ ]
651
+ }
652
+ ) }),
653
+ chartSupportsTrendlines(spec.type, spec.stacking) && /* @__PURE__ */ jsxs3("div", { className: "space-y-1", "data-section": "trendlines", children: [
654
+ (selectedSeries.trendlines ?? []).map((t, i) => /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1", "data-trendline": i, children: [
655
+ /* @__PURE__ */ jsx3("span", { className: "flex-1 truncate text-muted-foreground", children: chartTrendlineName(t, selectedSeries.name) }),
656
+ /* @__PURE__ */ jsx3(Toggle, { label: "R\xB2", name: `trendline.${i}.showR2`, checked: !!t.showR2, onChange: (showR2) => controller.updateTrendline(selectedSeries.id, i, { showR2 }) }),
657
+ /* @__PURE__ */ jsx3("button", { type: "button", className: chartButtonClass, "aria-label": "Delete trendline", "data-action": "trendline-delete", onClick: () => controller.removeTrendline(selectedSeries.id, i), children: "\xD7" })
658
+ ] }, i)),
659
+ /* @__PURE__ */ jsxs3(
660
+ "select",
661
+ {
662
+ className: chartInputClass,
663
+ value: "",
664
+ "aria-label": "Add trendline",
665
+ "data-option": "trendline.add",
666
+ onChange: (e) => e.currentTarget.value && controller.addTrendline(selectedSeries.id, { type: e.currentTarget.value }),
667
+ children: [
668
+ /* @__PURE__ */ jsx3("option", { value: "", children: "Add trendline\u2026" }),
669
+ CHART_TRENDLINE_TYPES.map((t) => /* @__PURE__ */ jsx3("option", { value: t, children: cap(t.replace("-", " ")) }, t))
670
+ ]
671
+ }
672
+ )
673
+ ] }),
674
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-1", children: [
502
675
  /* @__PURE__ */ jsx3(Toggle, { label: "Hidden", name: "series.hidden", checked: !!selectedSeries.hidden, onChange: (hidden) => controller.updateSeries(selectedSeries.id, { hidden }) }),
676
+ family === "cartesian" && /* @__PURE__ */ jsx3(Toggle, { label: "Secondary axis", name: "series.axis", checked: selectedSeries.axis === "secondary", onChange: (secondary) => controller.updateSeries(selectedSeries.id, { axis: secondary ? "secondary" : null }) }),
503
677
  /* @__PURE__ */ jsx3("span", { className: "flex-1" }),
504
678
  /* @__PURE__ */ jsx3("button", { type: "button", className: chartButtonClass, disabled: seriesIndex <= 0, "aria-label": "Move series earlier", "data-action": "series-up", onClick: () => controller.moveSeries(selectedSeries.id, seriesIndex - 1), children: "\u2191" }),
505
679
  /* @__PURE__ */ jsx3("button", { type: "button", className: chartButtonClass, disabled: seriesIndex >= spec.series.length - 1, "aria-label": "Move series later", "data-action": "series-down", onClick: () => controller.moveSeries(selectedSeries.id, seriesIndex + 1), children: "\u2193" }),
@@ -511,29 +685,95 @@ function ChartInspector({ controller, sections, className }) {
511
685
 
512
686
  // src/chart/ChartEditor.tsx
513
687
  import { describeChartSelection as describeChartSelection2 } from "@pixerate/editor/chart";
514
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
688
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
515
689
  function ChartEditor({ controller, showInspector = true, showDataGrid = true, showToolbar = true, height = 380, canvasProps, className }) {
516
690
  const state = useChartState(controller);
517
- return /* @__PURE__ */ jsxs3("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-chart-editor": "", children: [
518
- showToolbar && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1", "data-chart-toolbar": "", children: [
691
+ return /* @__PURE__ */ jsxs4("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-chart-editor": "", children: [
692
+ showToolbar && /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1", "data-chart-toolbar": "", children: [
519
693
  /* @__PURE__ */ jsx4("button", { type: "button", className: chartButtonClass, disabled: !state.canUndo, title: state.undoLabel ? `Undo ${state.undoLabel}` : "Undo", "data-action": "undo", onClick: () => controller.undo(), children: "Undo" }),
520
694
  /* @__PURE__ */ jsx4("button", { type: "button", className: chartButtonClass, disabled: !state.canRedo, title: state.redoLabel ? `Redo ${state.redoLabel}` : "Redo", "data-action": "redo", onClick: () => controller.redo(), children: "Redo" }),
521
695
  /* @__PURE__ */ jsx4("span", { className: "ml-2 truncate text-xs text-muted-foreground", "data-chart-selection": "", "aria-live": "polite", children: describeChartSelection2(controller, state.selection) })
522
696
  ] }),
523
697
  state.issues.length > 0 && /* @__PURE__ */ jsx4("p", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 px-2 py-1 text-xs text-destructive", "data-chart-issues": "", children: state.issues.map((i) => i.message).join(" ") }),
524
- /* @__PURE__ */ jsxs3("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
698
+ /* @__PURE__ */ jsxs4("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
525
699
  /* @__PURE__ */ jsx4("div", { className: "min-w-0 flex-1 rounded-md border border-border bg-background p-1", children: /* @__PURE__ */ jsx4(ChartCanvas, { controller, height, ...canvasProps }) }),
526
700
  showInspector && /* @__PURE__ */ jsx4(ChartInspector, { controller, className: "w-full shrink-0 overflow-y-auto rounded-md border border-border p-3 md:w-64" })
527
701
  ] }),
528
702
  showDataGrid && /* @__PURE__ */ jsx4(ChartDataGrid, { controller })
529
703
  ] });
530
704
  }
705
+
706
+ // src/chart/ChartStreamingView.tsx
707
+ import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState3 } from "react";
708
+ import {
709
+ CSS_VARIABLE_CHART_THEME as CSS_VARIABLE_CHART_THEME2,
710
+ ChartStream,
711
+ blendChartScenes,
712
+ easeChartTween,
713
+ layoutChart as layoutChart3,
714
+ layoutSparkline
715
+ } from "@pixerate/editor/chart";
716
+ import { jsx as jsx5 } from "react/jsx-runtime";
717
+ function ChartStreamingView({ source, complete = false, format = "auto", width = 640, height = 360, theme, adoptInterval = 300, tween = 450, placeholder, onIssues, className }) {
718
+ const stream = useMemo3(() => new ChartStream(format), [format]);
719
+ const fullTheme = useMemo3(() => ({ ...CSS_VARIABLE_CHART_THEME2, ...theme }), [theme]);
720
+ const [scene, setScene] = useState3(null);
721
+ const shown = useRef4(null);
722
+ const lastAdopt = useRef4(0);
723
+ const frame = useRef4(null);
724
+ const pending = useRef4(null);
725
+ useEffect4(() => {
726
+ const adopt = () => {
727
+ const read = stream.update(source, complete);
728
+ if (complete && onIssues) onIssues(read.issues);
729
+ if (!read.spec) return;
730
+ const next = layoutChart3(read.spec, { width, height, theme: fullTheme, valueDomain: read.valueDomain });
731
+ const from = shown.current;
732
+ lastAdopt.current = Date.now();
733
+ if (frame.current !== null) cancelAnimationFrame(frame.current);
734
+ if (!from || tween <= 0 || typeof requestAnimationFrame === "undefined") {
735
+ shown.current = next;
736
+ setScene(next);
737
+ return;
738
+ }
739
+ const start = performance.now();
740
+ const step = () => {
741
+ const t = easeChartTween((performance.now() - start) / tween);
742
+ const s = blendChartScenes(from, next, t);
743
+ shown.current = s;
744
+ setScene(s);
745
+ frame.current = t < 1 ? requestAnimationFrame(step) : null;
746
+ };
747
+ frame.current = requestAnimationFrame(step);
748
+ };
749
+ if (pending.current) clearTimeout(pending.current);
750
+ const wait = complete ? 0 : Math.max(0, adoptInterval - (Date.now() - lastAdopt.current));
751
+ if (wait === 0) adopt();
752
+ else pending.current = setTimeout(adopt, wait);
753
+ return () => {
754
+ if (pending.current) clearTimeout(pending.current);
755
+ };
756
+ }, [source, complete, stream, width, height, fullTheme, adoptInterval, tween, onIssues]);
757
+ useEffect4(() => () => {
758
+ if (frame.current !== null) cancelAnimationFrame(frame.current);
759
+ }, []);
760
+ return /* @__PURE__ */ jsx5("div", { className, "data-chart-streaming": complete ? "complete" : "streaming", "aria-busy": !complete, children: scene ? /* @__PURE__ */ jsx5(ChartSceneSvg, { scene, label: complete ? void 0 : "Chart loading" }) : placeholder ?? /* @__PURE__ */ jsx5("div", { style: { height }, className: "flex items-center justify-center text-xs text-muted-foreground", children: "Drawing chart\u2026" }) });
761
+ }
762
+ function ChartSparkline({ values, width = 80, height = 20, label, className, ...options }) {
763
+ const scene = useMemo3(() => layoutSparkline(values, { ...options, width, height }), [values, width, height, options.kind, options.color, options.negativeColor, options.markers, options.highLow, options.min, options.max]);
764
+ const present = values.filter((v) => v !== null);
765
+ return /* @__PURE__ */ jsx5(ChartSceneSvg, { scene, label: label ?? (present.length ? `Trend from ${present[0]} to ${present[present.length - 1]}` : "No data"), className: `inline-block align-middle ${className ?? ""}` });
766
+ }
531
767
  export {
532
768
  ChartCanvas,
533
769
  ChartCommitInput,
534
770
  ChartDataGrid,
535
771
  ChartEditor,
536
772
  ChartInspector,
773
+ ChartPresenceLayer,
774
+ ChartSceneSvg,
775
+ ChartSparkline,
776
+ ChartStreamingView,
537
777
  chartButtonClass,
538
778
  chartCellClass,
539
779
  chartIconButtonClass,
@@ -541,9 +781,12 @@ export {
541
781
  chartLabelClass,
542
782
  chartSectionClass,
543
783
  chartSegmentClass,
784
+ renderChartMark,
544
785
  useChartCommands,
545
786
  useChartController,
787
+ useChartDirector,
546
788
  useChartEditor,
547
- useChartState
789
+ useChartState,
790
+ useChartToolProvider
548
791
  };
549
792
  //# sourceMappingURL=index.js.map