@pixerate/editor-react 0.35.0 → 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,12 +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);
156
+ const [frozen, setFrozen] = useState(null);
119
157
  useEffect2(() => {
120
158
  const el = wrapper.current;
121
159
  if (!responsive || !el || typeof ResizeObserver === "undefined") return;
@@ -129,9 +167,21 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
129
167
  const sceneWidth = responsive && measured || width;
130
168
  const fullTheme = useMemo2(() => ({ ...CSS_VARIABLE_CHART_THEME, ...theme }), [theme]);
131
169
  const scene = useMemo2(
132
- () => layoutChart(state.spec, { width: sceneWidth, height, theme: fullTheme, selection: editable ? state.selection : null, measureText, emptyMessage }),
133
- [state.spec, state.selection, sceneWidth, height, fullTheme, editable, measureText, emptyMessage]
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]
134
183
  );
184
+ if (sceneRef) sceneRef.current = scene;
135
185
  const byKey = useMemo2(() => new Map(scene.marks.map((m) => [m.key, m])), [scene]);
136
186
  useEffect2(() => () => drag.current?.end(), []);
137
187
  const scenePoint = (e) => chartClientToScene(scene, svg.current.getBoundingClientRect(), e.clientX, e.clientY);
@@ -141,20 +191,32 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
141
191
  const target = key ? byKey.get(key)?.target : void 0;
142
192
  controller.select(target ?? null);
143
193
  drag.current = startChartPointDrag(controller, scene, target, scenePoint(e));
144
- if (drag.current) svg.current?.setPointerCapture?.(e.pointerId);
194
+ if (drag.current) {
195
+ const d = drag.current;
196
+ setFrozen({ valueDomain: d.valueDomain, valueDomain2: d.valueDomain2 ?? null, xDomain: d.xDomain ?? null, readout: d.target });
197
+ svg.current?.setPointerCapture?.(e.pointerId);
198
+ }
145
199
  };
146
200
  const onPointerMove = (e) => {
147
- 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 });
148
202
  };
149
203
  const endDrag = () => {
150
204
  drag.current?.end();
151
205
  drag.current = null;
206
+ setFrozen(null);
152
207
  };
153
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
+ }
154
216
  if (editable && handleChartKey(controller, scene, e)) e.preventDefault();
155
217
  };
156
218
  const label = state.spec.title || "Chart";
157
- 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(
158
220
  "svg",
159
221
  {
160
222
  ref: svg,
@@ -170,7 +232,10 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
170
232
  onPointerUp: endDrag,
171
233
  onPointerCancel: endDrag,
172
234
  onKeyDown,
173
- 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
+ ]
174
239
  }
175
240
  ) });
176
241
  }
@@ -178,7 +243,7 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
178
243
  // src/chart/ChartDataGrid.tsx
179
244
  import { useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
180
245
  import { chartColorInputValue, chartSeriesColor, formatChartNumber } from "@pixerate/editor/chart";
181
- import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
246
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
182
247
  function ChartCommitInput({ value, onCommit, onKeyDown, onBlur, ...rest }) {
183
248
  const [draft, setDraft] = useState2(value);
184
249
  const typing = useRef3(false);
@@ -226,14 +291,14 @@ function ChartDataGrid({ controller, categoryHeader = "Category", readOnly = fal
226
291
  e.preventDefault();
227
292
  controller.pasteCells(row, column, text);
228
293
  };
229
- return /* @__PURE__ */ jsxs("div", { className: `overflow-auto rounded-md border border-border ${className ?? ""}`, "data-chart-grid": "", children: [
230
- /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-xs", children: [
231
- /* @__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: [
232
297
  /* @__PURE__ */ jsx2("th", { className: "border-b border-r border-border px-2 py-1 text-left font-medium text-muted-foreground", children: categoryHeader }),
233
298
  spec.series.map((s, si) => {
234
299
  const color = chartSeriesColor(spec, si);
235
300
  const selected = selection?.kind === "series" && selection.seriesId === s.id;
236
- 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: [
237
302
  /* @__PURE__ */ jsx2(
238
303
  "input",
239
304
  {
@@ -257,7 +322,7 @@ function ChartDataGrid({ controller, categoryHeader = "Category", readOnly = fal
257
322
  className: `${chartInputClass} h-6 min-w-[5rem] border-transparent bg-transparent font-medium ${s.hidden ? "text-muted-foreground line-through" : ""}`
258
323
  }
259
324
  ),
260
- !readOnly && /* @__PURE__ */ jsxs(Fragment, { children: [
325
+ !readOnly && /* @__PURE__ */ jsxs2(Fragment, { children: [
261
326
  /* @__PURE__ */ jsx2(
262
327
  "button",
263
328
  {
@@ -279,7 +344,7 @@ function ChartDataGrid({ controller, categoryHeader = "Category", readOnly = fal
279
344
  ] }) }),
280
345
  /* @__PURE__ */ jsx2("tbody", { children: spec.categories.map((category, i) => {
281
346
  const rowSelected = selection?.kind === "category" && selection.index === i;
282
- 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: [
283
348
  /* @__PURE__ */ jsx2("th", { className: "border-b border-r border-border p-0 text-left font-normal", children: /* @__PURE__ */ jsx2(
284
349
  ChartCommitInput,
285
350
  {
@@ -320,6 +385,15 @@ function ChartDataGrid({ controller, categoryHeader = "Category", readOnly = fal
320
385
  // src/chart/ChartInspector.tsx
321
386
  import { useId } from "react";
322
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,
323
397
  CHART_CURVES,
324
398
  CHART_LEGEND_POSITIONS,
325
399
  CHART_NUMBER_STYLES,
@@ -332,16 +406,16 @@ import {
332
406
  chartSeriesType,
333
407
  isCartesianChart
334
408
  } from "@pixerate/editor/chart";
335
- 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";
336
410
  var cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
337
411
  function Field({ label, htmlFor, children }) {
338
- return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
412
+ return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
339
413
  /* @__PURE__ */ jsx3("label", { htmlFor, className: `${chartLabelClass} w-20 shrink-0`, children: label }),
340
414
  /* @__PURE__ */ jsx3("div", { className: "min-w-0 flex-1", children })
341
415
  ] });
342
416
  }
343
417
  function Toggle({ label, checked, onChange, name }) {
344
- 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: [
345
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) }),
346
420
  label
347
421
  ] });
@@ -349,43 +423,60 @@ function Toggle({ label, checked, onChange, name }) {
349
423
  function Select({ id, value, options, onChange, name, labels }) {
350
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)) });
351
425
  }
352
- function AxisSection({ controller, axis, value, idBase, horizontal }) {
426
+ function AxisSection({ controller, axis, value, idBase, horizontal, numeric = false }) {
353
427
  const set = (patch) => controller.setAxis(axis, patch);
354
428
  const setFormat = (patch) => set({ format: { ...value.format, ...patch } });
355
- const bound = (key) => /* @__PURE__ */ jsx3(
429
+ const bound = (key2) => /* @__PURE__ */ jsx3(
356
430
  ChartCommitInput,
357
431
  {
358
- id: `${idBase}-${axis}-${key}`,
359
- value: value[key] === null ? "" : String(value[key]),
432
+ id: `${idBase}-${axis}-${key2}`,
433
+ value: value[key2] === null ? "" : String(value[key2]),
360
434
  placeholder: "Auto",
361
435
  inputMode: "decimal",
362
- "data-option": `${axis}Axis.${key}`,
436
+ "data-option": `${axis}Axis.${key2}`,
363
437
  className: chartInputClass,
364
- onCommit: (text) => set({ [key]: text.trim() === "" ? null : Number(text) }).ok
438
+ onCommit: (text) => set({ [key2]: text.trim() === "" ? null : Number(text) }).ok
365
439
  }
366
440
  );
367
- const name = axis === "x" ? "Category axis" : "Value axis";
368
- return /* @__PURE__ */ jsxs2("div", { className: chartSectionClass, "data-section": `axis-${axis}`, children: [
369
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [
370
- /* @__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: [
371
447
  name,
372
- /* @__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: [
373
449
  "(",
374
- axis === "x" ? horizontal ? "left" : "bottom" : horizontal ? "bottom" : "left",
450
+ axis === "x" ? horizontal ? "left" : "bottom" : axis === "y2" ? horizontal ? "top" : "right" : horizontal ? "bottom" : "left",
375
451
  ")"
376
452
  ] })
377
453
  ] }),
378
454
  /* @__PURE__ */ jsx3(Toggle, { label: "Show", name: `${axis}Axis.show`, checked: value.show, onChange: (show) => set({ show }) })
379
455
  ] }),
380
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 }) }) }),
381
- /* @__PURE__ */ jsx3(Toggle, { label: "Grid lines", name: `${axis}Axis.gridlines`, checked: value.gridlines, onChange: (gridlines) => set({ gridlines }) }),
382
- axis === "y" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
383
- /* @__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: [
384
475
  bound("min"),
385
476
  bound("max")
386
477
  ] }) }),
387
- /* @__PURE__ */ jsx3(Field, { label: "Format", htmlFor: `${idBase}-y-format`, children: /* @__PURE__ */ jsxs2("div", { className: "flex gap-1", children: [
388
- /* @__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 }) }),
389
480
  /* @__PURE__ */ jsx3(
390
481
  ChartCommitInput,
391
482
  {
@@ -394,7 +485,7 @@ function AxisSection({ controller, axis, value, idBase, horizontal }) {
394
485
  "aria-label": "Decimals",
395
486
  title: "Decimals",
396
487
  inputMode: "numeric",
397
- "data-option": "yAxis.format.decimals",
488
+ "data-option": `${key}.format.decimals`,
398
489
  className: `${chartInputClass} w-14 shrink-0`,
399
490
  onCommit: (text) => {
400
491
  const format = { ...value.format };
@@ -405,7 +496,23 @@ function AxisSection({ controller, axis, value, idBase, horizontal }) {
405
496
  }
406
497
  )
407
498
  ] }) }),
408
- 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
+ ) })
409
516
  ] })
410
517
  ] });
411
518
  }
@@ -414,17 +521,33 @@ function ChartInspector({ controller, sections, className }) {
414
521
  const idBase = useId();
415
522
  const show = (section) => !sections || sections.includes(section);
416
523
  const cartesian = isCartesianChart(spec.type);
524
+ const family = chartFamily(spec);
525
+ const hasAxes = family === "cartesian" || family === "xy" || family === "waterfall" || family === "histogram" || family === "box";
417
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))));
418
529
  const hasBars = cartesian && types.includes("bar");
419
530
  const hasLines = cartesian && types.some((t) => t === "line" || t === "area");
420
531
  const seriesIndex = selectedSeries ? spec.series.findIndex((s) => s.id === selectedSeries.id) : -1;
421
- 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: [
422
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)) }) }),
423
- 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: [
424
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) }) }),
425
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) }) })
426
549
  ] }),
427
- show("options") && /* @__PURE__ */ jsxs2("div", { className: chartSectionClass, "data-section": "options", children: [
550
+ show("options") && /* @__PURE__ */ jsxs3("div", { className: chartSectionClass, "data-section": "options", children: [
428
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 }) }) }),
429
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)) }) }),
430
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 }) }) }),
@@ -444,26 +567,46 @@ function ChartInspector({ controller, sections, className }) {
444
567
  onChange: (e) => controller.update({ innerRadius: Number(e.currentTarget.value) })
445
568
  }
446
569
  ) }),
447
- /* @__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 }) }),
448
590
  hasLines && /* @__PURE__ */ jsx3(Toggle, { label: "Points", name: "points", checked: spec.points, onChange: (points) => controller.update({ points }) }),
449
591
  /* @__PURE__ */ jsx3(Toggle, { label: "Data labels", name: "dataLabels", checked: spec.dataLabels, onChange: (dataLabels) => controller.update({ dataLabels }) })
450
592
  ] })
451
593
  ] }),
452
- show("legend") && /* @__PURE__ */ jsxs2("div", { className: chartSectionClass, "data-section": "legend", children: [
453
- /* @__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: [
454
596
  /* @__PURE__ */ jsx3("h4", { className: "text-xs font-semibold text-foreground", children: "Legend" }),
455
597
  /* @__PURE__ */ jsx3(Toggle, { label: "Show", name: "legend.show", checked: spec.legend.show, onChange: (v) => controller.setLegend({ show: v }) })
456
598
  ] }),
457
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)) })
458
600
  ] }),
459
- show("axes") && cartesian && /* @__PURE__ */ jsxs2(Fragment2, { children: [
460
- /* @__PURE__ */ jsx3(AxisSection, { controller, axis: "x", value: spec.xAxis, idBase, horizontal: spec.orientation === "horizontal" }),
461
- /* @__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" })
462
605
  ] }),
463
- 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: [
464
607
  /* @__PURE__ */ jsx3("h4", { className: "text-xs font-semibold text-foreground", children: "Series" }),
465
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 }) }) }),
466
- /* @__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: [
467
610
  /* @__PURE__ */ jsx3(
468
611
  "input",
469
612
  {
@@ -479,7 +622,7 @@ function ChartInspector({ controller, sections, className }) {
479
622
  ),
480
623
  selectedSeries.color && /* @__PURE__ */ jsx3("button", { type: "button", className: chartButtonClass, "data-action": "reset-color", onClick: () => controller.updateSeries(selectedSeries.id, { color: null }), children: "Palette" })
481
624
  ] }) }),
482
- 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(
483
626
  "select",
484
627
  {
485
628
  id: `${idBase}-series-type`,
@@ -493,8 +636,44 @@ function ChartInspector({ controller, sections, className }) {
493
636
  ]
494
637
  }
495
638
  ) }),
496
- /* @__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: [
497
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 }) }),
498
677
  /* @__PURE__ */ jsx3("span", { className: "flex-1" }),
499
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" }),
500
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" }),
@@ -506,29 +685,95 @@ function ChartInspector({ controller, sections, className }) {
506
685
 
507
686
  // src/chart/ChartEditor.tsx
508
687
  import { describeChartSelection as describeChartSelection2 } from "@pixerate/editor/chart";
509
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
688
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
510
689
  function ChartEditor({ controller, showInspector = true, showDataGrid = true, showToolbar = true, height = 380, canvasProps, className }) {
511
690
  const state = useChartState(controller);
512
- return /* @__PURE__ */ jsxs3("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-chart-editor": "", children: [
513
- 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: [
514
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" }),
515
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" }),
516
695
  /* @__PURE__ */ jsx4("span", { className: "ml-2 truncate text-xs text-muted-foreground", "data-chart-selection": "", "aria-live": "polite", children: describeChartSelection2(controller, state.selection) })
517
696
  ] }),
518
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(" ") }),
519
- /* @__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: [
520
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 }) }),
521
700
  showInspector && /* @__PURE__ */ jsx4(ChartInspector, { controller, className: "w-full shrink-0 overflow-y-auto rounded-md border border-border p-3 md:w-64" })
522
701
  ] }),
523
702
  showDataGrid && /* @__PURE__ */ jsx4(ChartDataGrid, { controller })
524
703
  ] });
525
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
+ }
526
767
  export {
527
768
  ChartCanvas,
528
769
  ChartCommitInput,
529
770
  ChartDataGrid,
530
771
  ChartEditor,
531
772
  ChartInspector,
773
+ ChartPresenceLayer,
774
+ ChartSceneSvg,
775
+ ChartSparkline,
776
+ ChartStreamingView,
532
777
  chartButtonClass,
533
778
  chartCellClass,
534
779
  chartIconButtonClass,
@@ -536,9 +781,12 @@ export {
536
781
  chartLabelClass,
537
782
  chartSectionClass,
538
783
  chartSegmentClass,
784
+ renderChartMark,
539
785
  useChartCommands,
540
786
  useChartController,
787
+ useChartDirector,
541
788
  useChartEditor,
542
- useChartState
789
+ useChartState,
790
+ useChartToolProvider
543
791
  };
544
792
  //# sourceMappingURL=index.js.map