@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.
@@ -25,6 +25,10 @@ __export(chart_exports, {
25
25
  ChartDataGrid: () => ChartDataGrid,
26
26
  ChartEditor: () => ChartEditor,
27
27
  ChartInspector: () => ChartInspector,
28
+ ChartPresenceLayer: () => ChartPresenceLayer,
29
+ ChartSceneSvg: () => ChartSceneSvg,
30
+ ChartSparkline: () => ChartSparkline,
31
+ ChartStreamingView: () => ChartStreamingView,
28
32
  chartButtonClass: () => chartButtonClass,
29
33
  chartCellClass: () => chartCellClass,
30
34
  chartIconButtonClass: () => chartIconButtonClass,
@@ -32,10 +36,13 @@ __export(chart_exports, {
32
36
  chartLabelClass: () => chartLabelClass,
33
37
  chartSectionClass: () => chartSectionClass,
34
38
  chartSegmentClass: () => chartSegmentClass,
39
+ renderChartMark: () => renderChartMark,
35
40
  useChartCommands: () => useChartCommands,
36
41
  useChartController: () => useChartController,
42
+ useChartDirector: () => useChartDirector,
37
43
  useChartEditor: () => useChartEditor,
38
- useChartState: () => useChartState
44
+ useChartState: () => useChartState,
45
+ useChartToolProvider: () => useChartToolProvider
39
46
  });
40
47
  module.exports = __toCommonJS(chart_exports);
41
48
 
@@ -79,6 +86,26 @@ function useChartCommands(registry, controller, options = {}) {
79
86
  return registry.register((0, import_chart.createChartCommands)(controller, { prefix, group, scope }));
80
87
  }, [registry, controller, prefix, group, scope]);
81
88
  }
89
+ function useChartToolProvider(registry, controller, options = {}) {
90
+ const latest = (0, import_react.useRef)(options);
91
+ latest.current = options;
92
+ const { id, namespace } = options;
93
+ (0, import_react.useEffect)(() => {
94
+ if (!registry) return;
95
+ return registry.register((0, import_chart.createChartToolProvider)(controller, { ...latest.current, id, namespace }));
96
+ }, [registry, controller, id, namespace]);
97
+ }
98
+ function useChartDirector(controller, options = {}) {
99
+ const sceneRef = (0, import_react.useRef)(null);
100
+ const ref = (0, import_react.useRef)(null);
101
+ if (!ref.current) {
102
+ const presence = new import_chart.ChartPresence();
103
+ const size = options.size ?? { width: 640, height: 400 };
104
+ const director = new import_chart.ChartDirector(controller, presence, { scene: () => sceneRef.current ?? (0, import_chart.layoutChart)(controller.getSpec(), size), speed: options.speed });
105
+ ref.current = { presence, director };
106
+ }
107
+ return { ...ref.current, sceneRef };
108
+ }
82
109
 
83
110
  // src/chart/styles.ts
84
111
  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";
@@ -93,13 +120,13 @@ var chartCellClass = "h-7 w-full min-w-[4.5rem] bg-transparent px-2 text-right t
93
120
  var import_react2 = require("react");
94
121
  var import_chart2 = require("@pixerate/editor/chart");
95
122
  var import_jsx_runtime = require("react/jsx-runtime");
96
- function renderMark(mark, fontFamily, interactive) {
123
+ function renderChartMark(mark, fontFamily, interactive) {
97
124
  const common = {
98
125
  "data-role": mark.role,
99
126
  "data-chart-key": mark.target ? mark.key : void 0,
100
127
  "data-selected": mark.selected ? "" : void 0,
101
128
  opacity: mark.opacity,
102
- pointerEvents: mark.role === "selection" || !mark.target && mark.role !== "background" ? "none" : void 0,
129
+ pointerEvents: mark.role === "selection" || mark.role === "drag-readout" || !mark.target && mark.role !== "background" ? "none" : void 0,
103
130
  cursor: interactive && mark.target && mark.role !== "background" ? "pointer" : void 0
104
131
  };
105
132
  switch (mark.kind) {
@@ -139,12 +166,26 @@ function renderMark(mark, fontFamily, interactive) {
139
166
  );
140
167
  }
141
168
  }
142
- function ChartCanvas({ controller, width = 640, height = 400, responsive = true, theme, editable = true, measureText, emptyMessage, className }) {
169
+ function ChartPresenceLayer({ presence }) {
170
+ const { actors } = (0, import_react2.useSyncExternalStore)(presence.subscribe, presence.getState, presence.getState);
171
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("g", { "data-chart-presence": "", pointerEvents: "none", children: actors.map((a) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { transform: `translate(${a.x} ${a.y})`, "data-actor": a.id, children: [
172
+ /* @__PURE__ */ (0, import_jsx_runtime.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 }),
173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { transform: "translate(13 16)", children: [
174
+ /* @__PURE__ */ (0, import_jsx_runtime.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 } }),
175
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: 6, y: 9, dominantBaseline: "central", style: { fill: "#ffffff", fontSize: 11, fontWeight: 600, fontFamily: "inherit" }, children: a.message ?? a.name })
176
+ ] })
177
+ ] }, a.id)) });
178
+ }
179
+ function ChartSceneSvg({ scene, label, className }) {
180
+ return /* @__PURE__ */ (0, import_jsx_runtime.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)) });
181
+ }
182
+ function ChartCanvas({ controller, width = 640, height = 400, responsive = true, theme, editable = true, measureText, emptyMessage, className, presence, sceneRef }) {
143
183
  const state = useChartState(controller);
144
184
  const wrapper = (0, import_react2.useRef)(null);
145
185
  const svg = (0, import_react2.useRef)(null);
146
186
  const drag = (0, import_react2.useRef)(null);
147
187
  const [measured, setMeasured] = (0, import_react2.useState)(null);
188
+ const [frozen, setFrozen] = (0, import_react2.useState)(null);
148
189
  (0, import_react2.useEffect)(() => {
149
190
  const el = wrapper.current;
150
191
  if (!responsive || !el || typeof ResizeObserver === "undefined") return;
@@ -158,9 +199,21 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
158
199
  const sceneWidth = responsive && measured || width;
159
200
  const fullTheme = (0, import_react2.useMemo)(() => ({ ...import_chart2.CSS_VARIABLE_CHART_THEME, ...theme }), [theme]);
160
201
  const scene = (0, import_react2.useMemo)(
161
- () => (0, import_chart2.layoutChart)(state.spec, { width: sceneWidth, height, theme: fullTheme, selection: editable ? state.selection : null, measureText, emptyMessage }),
162
- [state.spec, state.selection, sceneWidth, height, fullTheme, editable, measureText, emptyMessage]
202
+ () => (0, import_chart2.layoutChart)(state.spec, {
203
+ width: sceneWidth,
204
+ height,
205
+ theme: fullTheme,
206
+ selection: editable ? state.selection : null,
207
+ measureText,
208
+ emptyMessage,
209
+ valueDomain: frozen?.valueDomain,
210
+ valueDomain2: frozen?.valueDomain2,
211
+ xDomain: frozen?.xDomain,
212
+ readout: frozen?.readout
213
+ }),
214
+ [state.spec, state.selection, sceneWidth, height, fullTheme, editable, measureText, emptyMessage, frozen]
163
215
  );
216
+ if (sceneRef) sceneRef.current = scene;
164
217
  const byKey = (0, import_react2.useMemo)(() => new Map(scene.marks.map((m) => [m.key, m])), [scene]);
165
218
  (0, import_react2.useEffect)(() => () => drag.current?.end(), []);
166
219
  const scenePoint = (e) => (0, import_chart2.chartClientToScene)(scene, svg.current.getBoundingClientRect(), e.clientX, e.clientY);
@@ -170,20 +223,32 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
170
223
  const target = key ? byKey.get(key)?.target : void 0;
171
224
  controller.select(target ?? null);
172
225
  drag.current = (0, import_chart2.startChartPointDrag)(controller, scene, target, scenePoint(e));
173
- if (drag.current) svg.current?.setPointerCapture?.(e.pointerId);
226
+ if (drag.current) {
227
+ const d = drag.current;
228
+ setFrozen({ valueDomain: d.valueDomain, valueDomain2: d.valueDomain2 ?? null, xDomain: d.xDomain ?? null, readout: d.target });
229
+ svg.current?.setPointerCapture?.(e.pointerId);
230
+ }
174
231
  };
175
232
  const onPointerMove = (e) => {
176
- if (drag.current) drag.current.move(scenePoint(e));
233
+ if (drag.current) drag.current.move(scenePoint(e), { fine: e.altKey, lockAxis: e.shiftKey, noSnap: e.ctrlKey || e.metaKey });
177
234
  };
178
235
  const endDrag = () => {
179
236
  drag.current?.end();
180
237
  drag.current = null;
238
+ setFrozen(null);
181
239
  };
182
240
  const onKeyDown = (e) => {
241
+ if (drag.current && e.key === "Escape") {
242
+ drag.current.cancel();
243
+ drag.current = null;
244
+ setFrozen(null);
245
+ e.preventDefault();
246
+ return;
247
+ }
183
248
  if (editable && (0, import_chart2.handleChartKey)(controller, scene, e)) e.preventDefault();
184
249
  };
185
250
  const label = state.spec.title || "Chart";
186
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: wrapper, className: `w-full ${className ?? ""}`, "data-chart-canvas": "", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
251
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: wrapper, className: `w-full ${className ?? ""}`, "data-chart-canvas": "", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
187
252
  "svg",
188
253
  {
189
254
  ref: svg,
@@ -199,7 +264,10 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
199
264
  onPointerUp: endDrag,
200
265
  onPointerCancel: endDrag,
201
266
  onKeyDown,
202
- children: scene.marks.map((m) => renderMark(m, scene.theme.fontFamily, editable))
267
+ children: [
268
+ scene.marks.map((m) => renderChartMark(m, scene.theme.fontFamily, editable)),
269
+ presence && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartPresenceLayer, { presence })
270
+ ]
203
271
  }
204
272
  ) });
205
273
  }
@@ -366,43 +434,60 @@ function Toggle({ label, checked, onChange, name }) {
366
434
  function Select({ id, value, options, onChange, name, labels }) {
367
435
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("select", { id, className: chartInputClass, value, "data-option": name, onChange: (e) => onChange(e.currentTarget.value), children: options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: o, children: labels?.[o] ?? cap(o) }, o)) });
368
436
  }
369
- function AxisSection({ controller, axis, value, idBase, horizontal }) {
437
+ function AxisSection({ controller, axis, value, idBase, horizontal, numeric = false }) {
370
438
  const set = (patch) => controller.setAxis(axis, patch);
371
439
  const setFormat = (patch) => set({ format: { ...value.format, ...patch } });
372
- const bound = (key) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
440
+ const bound = (key2) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
373
441
  ChartCommitInput,
374
442
  {
375
- id: `${idBase}-${axis}-${key}`,
376
- value: value[key] === null ? "" : String(value[key]),
443
+ id: `${idBase}-${axis}-${key2}`,
444
+ value: value[key2] === null ? "" : String(value[key2]),
377
445
  placeholder: "Auto",
378
446
  inputMode: "decimal",
379
- "data-option": `${axis}Axis.${key}`,
447
+ "data-option": `${axis}Axis.${key2}`,
380
448
  className: chartInputClass,
381
- onCommit: (text) => set({ [key]: text.trim() === "" ? null : Number(text) }).ok
449
+ onCommit: (text) => set({ [key2]: text.trim() === "" ? null : Number(text) }).ok
382
450
  }
383
451
  );
384
- const name = axis === "x" ? "Category axis" : "Value axis";
452
+ const name = axis === "x" ? numeric ? "X axis" : "Category axis" : axis === "y" ? "Value axis" : "Secondary axis";
453
+ const valueAxis = axis !== "x" || numeric;
454
+ const key = axis === "y2" ? "yAxis2" : `${axis}Axis`;
385
455
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: chartSectionClass, "data-section": `axis-${axis}`, children: [
386
456
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between", children: [
387
457
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("h4", { className: "text-xs font-semibold text-foreground", children: [
388
458
  name,
389
459
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "ml-1 font-normal text-muted-foreground", children: [
390
460
  "(",
391
- axis === "x" ? horizontal ? "left" : "bottom" : horizontal ? "bottom" : "left",
461
+ axis === "x" ? horizontal ? "left" : "bottom" : axis === "y2" ? horizontal ? "top" : "right" : horizontal ? "bottom" : "left",
392
462
  ")"
393
463
  ] })
394
464
  ] }),
395
465
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Show", name: `${axis}Axis.show`, checked: value.show, onChange: (show) => set({ show }) })
396
466
  ] }),
397
467
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Title", htmlFor: `${idBase}-${axis}-title`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartCommitInput, { id: `${idBase}-${axis}-title`, value: value.title, "data-option": `${axis}Axis.title`, className: chartInputClass, onCommit: (title) => void set({ title }) }) }),
398
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Grid lines", name: `${axis}Axis.gridlines`, checked: value.gridlines, onChange: (gridlines) => set({ gridlines }) }),
399
- axis === "y" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
468
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap gap-x-4 gap-y-1", children: [
469
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Grid lines", name: `${key}.gridlines`, checked: value.gridlines, onChange: (gridlines) => set({ gridlines }) }),
470
+ axis === "x" && !numeric && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Dates", name: "xAxis.kind", checked: value.kind === "date", onChange: (dates) => set({ kind: dates ? "date" : void 0 }) })
471
+ ] }),
472
+ valueAxis && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Step", htmlFor: `${idBase}-${axis}-step`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
473
+ ChartCommitInput,
474
+ {
475
+ id: `${idBase}-${axis}-step`,
476
+ value: value.step === null ? "" : String(value.step),
477
+ placeholder: "Auto",
478
+ inputMode: "decimal",
479
+ "data-option": `${key}.step`,
480
+ className: chartInputClass,
481
+ onCommit: (text) => set({ step: text.trim() === "" ? null : Number(text) }).ok
482
+ }
483
+ ) }),
484
+ valueAxis && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
400
485
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Min / max", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex gap-1", children: [
401
486
  bound("min"),
402
487
  bound("max")
403
488
  ] }) }),
404
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Format", htmlFor: `${idBase}-y-format`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex gap-1", children: [
405
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Select, { id: `${idBase}-y-format`, name: "yAxis.format.style", value: value.format.style ?? "number", options: import_chart4.CHART_NUMBER_STYLES, onChange: (style) => setFormat({ style }) }),
489
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Format", htmlFor: `${idBase}-${axis}-format`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex gap-1", children: [
490
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Select, { id: `${idBase}-${axis}-format`, name: `${key}.format.style`, value: value.format.style ?? "number", options: import_chart4.CHART_NUMBER_STYLES, onChange: (style) => setFormat({ style }) }),
406
491
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
407
492
  ChartCommitInput,
408
493
  {
@@ -411,7 +496,7 @@ function AxisSection({ controller, axis, value, idBase, horizontal }) {
411
496
  "aria-label": "Decimals",
412
497
  title: "Decimals",
413
498
  inputMode: "numeric",
414
- "data-option": "yAxis.format.decimals",
499
+ "data-option": `${key}.format.decimals`,
415
500
  className: `${chartInputClass} w-14 shrink-0`,
416
501
  onCommit: (text) => {
417
502
  const format = { ...value.format };
@@ -422,7 +507,23 @@ function AxisSection({ controller, axis, value, idBase, horizontal }) {
422
507
  }
423
508
  )
424
509
  ] }) }),
425
- value.format.style === "currency" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Currency", htmlFor: `${idBase}-y-currency`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartCommitInput, { id: `${idBase}-y-currency`, value: value.format.currency ?? "USD", "data-option": "yAxis.format.currency", className: chartInputClass, onCommit: (currency) => void setFormat({ currency: currency.toUpperCase() }) }) })
510
+ value.format.style === "currency" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Currency", htmlFor: `${idBase}-${axis}-currency`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartCommitInput, { id: `${idBase}-${axis}-currency`, value: value.format.currency ?? "USD", "data-option": `${key}.format.currency`, className: chartInputClass, onCommit: (currency) => void setFormat({ currency: currency.toUpperCase() }) }) }),
511
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Code", htmlFor: `${idBase}-${axis}-code`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
512
+ ChartCommitInput,
513
+ {
514
+ id: `${idBase}-${axis}-code`,
515
+ value: value.format.code ?? "",
516
+ placeholder: 'e.g. #,##0.0,,"M"',
517
+ "data-option": `${key}.format.code`,
518
+ className: chartInputClass,
519
+ onCommit: (code) => {
520
+ const format = { ...value.format };
521
+ if (code.trim()) format.code = code.trim();
522
+ else delete format.code;
523
+ return set({ format }).ok;
524
+ }
525
+ }
526
+ ) })
426
527
  ] })
427
528
  ] });
428
529
  }
@@ -431,12 +532,28 @@ function ChartInspector({ controller, sections, className }) {
431
532
  const idBase = (0, import_react4.useId)();
432
533
  const show = (section) => !sections || sections.includes(section);
433
534
  const cartesian = (0, import_chart4.isCartesianChart)(spec.type);
535
+ const family = (0, import_chart4.chartFamily)(spec);
536
+ const hasAxes = family === "cartesian" || family === "xy" || family === "waterfall" || family === "histogram" || family === "box";
434
537
  const types = spec.series.map((s) => (0, import_chart4.chartSeriesType)(spec, s));
538
+ const usesSecondary = family === "cartesian" && spec.series.some((s) => s.axis === "secondary" && !s.hidden);
539
+ const colorCheck = (0, import_chart4.checkChartColors)(spec.series.filter((s) => !s.hidden).map((s) => (0, import_chart4.chartSeriesColor)(spec, spec.series.indexOf(s))));
435
540
  const hasBars = cartesian && types.includes("bar");
436
541
  const hasLines = cartesian && types.some((t) => t === "line" || t === "area");
437
542
  const seriesIndex = selectedSeries ? spec.series.findIndex((s) => s.id === selectedSeries.id) : -1;
438
543
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `space-y-3 text-xs ${className ?? ""}`, "data-chart-inspector": "", children: [
439
544
  show("type") && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: chartSectionClass, "data-section": "type", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "grid grid-cols-3 gap-0.5 rounded-md bg-muted p-0.5", role: "radiogroup", "aria-label": "Chart type", children: import_chart4.CHART_TYPES.map((type) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", role: "radio", "aria-checked": spec.type === type, "data-chart-type": type, className: chartSegmentClass(spec.type === type), onClick: () => controller.setType(type), children: import_chart4.CHART_TYPE_LABELS[type] }, type)) }) }),
545
+ show("style") && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: chartSectionClass, "data-section": "style", children: [
546
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Layout", htmlFor: `${idBase}-layout`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("select", { id: `${idBase}-layout`, className: chartInputClass, value: "", "data-option": "preset.layout", onChange: (e) => e.currentTarget.value && controller.applyPreset({ layout: e.currentTarget.value }), children: [
547
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "Quick layout\u2026" }),
548
+ import_chart4.CHART_LAYOUT_PRESETS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: p.id, title: p.description, children: p.label }, p.id))
549
+ ] }) }),
550
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Colours", htmlFor: `${idBase}-colors`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("select", { id: `${idBase}-colors`, className: chartInputClass, value: import_chart4.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: [
551
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "Custom" }),
552
+ import_chart4.CHART_COLOR_SCHEMES.map((c) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: c.id, title: c.description, children: c.label }, c.id))
553
+ ] }) }),
554
+ !colorCheck.ok && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-[11px] leading-snug text-amber-600 dark:text-amber-400", "data-chart-color-warning": "", children: colorCheck.issues[0] }),
555
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: chartButtonClass, "data-action": "transpose", onClick: () => controller.transpose(), children: "Switch rows and columns" })
556
+ ] }),
440
557
  show("titles") && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: chartSectionClass, "data-section": "titles", children: [
441
558
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Title", htmlFor: `${idBase}-title`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartCommitInput, { id: `${idBase}-title`, value: spec.title, "data-option": "title", className: chartInputClass, onCommit: (title) => void controller.setTitle(title) }) }),
442
559
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Subtitle", htmlFor: `${idBase}-subtitle`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartCommitInput, { id: `${idBase}-subtitle`, value: spec.subtitle, "data-option": "subtitle", className: chartInputClass, onCommit: (subtitle) => void controller.setSubtitle(subtitle) }) })
@@ -461,7 +578,26 @@ function ChartInspector({ controller, sections, className }) {
461
578
  onChange: (e) => controller.update({ innerRadius: Number(e.currentTarget.value) })
462
579
  }
463
580
  ) }),
581
+ hasBars && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Spacing", htmlFor: `${idBase}-gap`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
582
+ "input",
583
+ {
584
+ id: `${idBase}-gap`,
585
+ type: "range",
586
+ min: 0,
587
+ max: 0.9,
588
+ step: 0.02,
589
+ value: spec.gapWidth,
590
+ "data-option": "gapWidth",
591
+ className: "w-full accent-primary",
592
+ onPointerDown: () => controller.beginGesture("gapWidth"),
593
+ onPointerUp: () => controller.endGesture(),
594
+ onChange: (e) => controller.update({ gapWidth: Number(e.currentTarget.value) })
595
+ }
596
+ ) }),
597
+ spec.dataLabels && hasBars && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Labels", htmlFor: `${idBase}-label-position`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Select, { id: `${idBase}-label-position`, name: "labelPosition", value: spec.labelPosition, options: import_chart4.CHART_LABEL_POSITIONS, onChange: (labelPosition) => controller.update({ labelPosition }) }) }),
598
+ spec.type === "histogram" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Bins", htmlFor: `${idBase}-bins`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(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 }) }),
464
599
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap gap-x-4 gap-y-1", children: [
600
+ spec.type === "waterfall" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Total bar", name: "showTotal", checked: spec.showTotal, onChange: (showTotal) => controller.update({ showTotal }) }),
465
601
  hasLines && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Points", name: "points", checked: spec.points, onChange: (points) => controller.update({ points }) }),
466
602
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Data labels", name: "dataLabels", checked: spec.dataLabels, onChange: (dataLabels) => controller.update({ dataLabels }) })
467
603
  ] })
@@ -473,9 +609,10 @@ function ChartInspector({ controller, sections, className }) {
473
609
  ] }),
474
610
  spec.legend.show && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex gap-0.5 rounded-md bg-muted p-0.5", children: import_chart4.CHART_LEGEND_POSITIONS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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)) })
475
611
  ] }),
476
- show("axes") && cartesian && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
477
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AxisSection, { controller, axis: "x", value: spec.xAxis, idBase, horizontal: spec.orientation === "horizontal" }),
478
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AxisSection, { controller, axis: "y", value: spec.yAxis, idBase, horizontal: spec.orientation === "horizontal" })
612
+ show("axes") && hasAxes && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
613
+ family !== "box" && family !== "histogram" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AxisSection, { controller, axis: "x", value: spec.xAxis, idBase, horizontal: spec.orientation === "horizontal", numeric: family === "xy" }),
614
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AxisSection, { controller, axis: "y", value: spec.yAxis, idBase, horizontal: spec.orientation === "horizontal" }),
615
+ usesSecondary && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AxisSection, { controller, axis: "y2", value: spec.yAxis2, idBase, horizontal: spec.orientation === "horizontal" })
479
616
  ] }),
480
617
  show("series") && selectedSeries && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: chartSectionClass, "data-section": "series", "data-series-id": selectedSeries.id, children: [
481
618
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h4", { className: "text-xs font-semibold text-foreground", children: "Series" }),
@@ -510,8 +647,44 @@ function ChartInspector({ controller, sections, className }) {
510
647
  ]
511
648
  }
512
649
  ) }),
650
+ (cartesian || family === "xy") && ["line", "area", "scatter"].includes((0, import_chart4.chartSeriesType)(spec, selectedSeries)) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Marker", htmlFor: `${idBase}-series-marker`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
651
+ "select",
652
+ {
653
+ id: `${idBase}-series-marker`,
654
+ className: chartInputClass,
655
+ value: selectedSeries.marker ?? "",
656
+ "data-option": "series.marker",
657
+ onChange: (e) => controller.updateSeries(selectedSeries.id, { marker: e.currentTarget.value || null }),
658
+ children: [
659
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "Default" }),
660
+ import_chart4.CHART_MARKERS.map((m) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: m, children: cap(m) }, m))
661
+ ]
662
+ }
663
+ ) }),
664
+ (0, import_chart4.chartSupportsTrendlines)(spec.type, spec.stacking) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-1", "data-section": "trendlines", children: [
665
+ (selectedSeries.trendlines ?? []).map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-1", "data-trendline": i, children: [
666
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex-1 truncate text-muted-foreground", children: (0, import_chart4.chartTrendlineName)(t, selectedSeries.name) }),
667
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "R\xB2", name: `trendline.${i}.showR2`, checked: !!t.showR2, onChange: (showR2) => controller.updateTrendline(selectedSeries.id, i, { showR2 }) }),
668
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: chartButtonClass, "aria-label": "Delete trendline", "data-action": "trendline-delete", onClick: () => controller.removeTrendline(selectedSeries.id, i), children: "\xD7" })
669
+ ] }, i)),
670
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
671
+ "select",
672
+ {
673
+ className: chartInputClass,
674
+ value: "",
675
+ "aria-label": "Add trendline",
676
+ "data-option": "trendline.add",
677
+ onChange: (e) => e.currentTarget.value && controller.addTrendline(selectedSeries.id, { type: e.currentTarget.value }),
678
+ children: [
679
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "Add trendline\u2026" }),
680
+ import_chart4.CHART_TRENDLINE_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: t, children: cap(t.replace("-", " ")) }, t))
681
+ ]
682
+ }
683
+ )
684
+ ] }),
513
685
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
514
686
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Hidden", name: "series.hidden", checked: !!selectedSeries.hidden, onChange: (hidden) => controller.updateSeries(selectedSeries.id, { hidden }) }),
687
+ family === "cartesian" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Secondary axis", name: "series.axis", checked: selectedSeries.axis === "secondary", onChange: (secondary) => controller.updateSeries(selectedSeries.id, { axis: secondary ? "secondary" : null }) }),
515
688
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex-1" }),
516
689
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" }),
517
690
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" }),
@@ -540,6 +713,61 @@ function ChartEditor({ controller, showInspector = true, showDataGrid = true, sh
540
713
  showDataGrid && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChartDataGrid, { controller })
541
714
  ] });
542
715
  }
716
+
717
+ // src/chart/ChartStreamingView.tsx
718
+ var import_react5 = require("react");
719
+ var import_chart6 = require("@pixerate/editor/chart");
720
+ var import_jsx_runtime5 = require("react/jsx-runtime");
721
+ function ChartStreamingView({ source, complete = false, format = "auto", width = 640, height = 360, theme, adoptInterval = 300, tween = 450, placeholder, onIssues, className }) {
722
+ const stream = (0, import_react5.useMemo)(() => new import_chart6.ChartStream(format), [format]);
723
+ const fullTheme = (0, import_react5.useMemo)(() => ({ ...import_chart6.CSS_VARIABLE_CHART_THEME, ...theme }), [theme]);
724
+ const [scene, setScene] = (0, import_react5.useState)(null);
725
+ const shown = (0, import_react5.useRef)(null);
726
+ const lastAdopt = (0, import_react5.useRef)(0);
727
+ const frame = (0, import_react5.useRef)(null);
728
+ const pending = (0, import_react5.useRef)(null);
729
+ (0, import_react5.useEffect)(() => {
730
+ const adopt = () => {
731
+ const read = stream.update(source, complete);
732
+ if (complete && onIssues) onIssues(read.issues);
733
+ if (!read.spec) return;
734
+ const next = (0, import_chart6.layoutChart)(read.spec, { width, height, theme: fullTheme, valueDomain: read.valueDomain });
735
+ const from = shown.current;
736
+ lastAdopt.current = Date.now();
737
+ if (frame.current !== null) cancelAnimationFrame(frame.current);
738
+ if (!from || tween <= 0 || typeof requestAnimationFrame === "undefined") {
739
+ shown.current = next;
740
+ setScene(next);
741
+ return;
742
+ }
743
+ const start = performance.now();
744
+ const step = () => {
745
+ const t = (0, import_chart6.easeChartTween)((performance.now() - start) / tween);
746
+ const s = (0, import_chart6.blendChartScenes)(from, next, t);
747
+ shown.current = s;
748
+ setScene(s);
749
+ frame.current = t < 1 ? requestAnimationFrame(step) : null;
750
+ };
751
+ frame.current = requestAnimationFrame(step);
752
+ };
753
+ if (pending.current) clearTimeout(pending.current);
754
+ const wait = complete ? 0 : Math.max(0, adoptInterval - (Date.now() - lastAdopt.current));
755
+ if (wait === 0) adopt();
756
+ else pending.current = setTimeout(adopt, wait);
757
+ return () => {
758
+ if (pending.current) clearTimeout(pending.current);
759
+ };
760
+ }, [source, complete, stream, width, height, fullTheme, adoptInterval, tween, onIssues]);
761
+ (0, import_react5.useEffect)(() => () => {
762
+ if (frame.current !== null) cancelAnimationFrame(frame.current);
763
+ }, []);
764
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, "data-chart-streaming": complete ? "complete" : "streaming", "aria-busy": !complete, children: scene ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChartSceneSvg, { scene, label: complete ? void 0 : "Chart loading" }) : placeholder ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { height }, className: "flex items-center justify-center text-xs text-muted-foreground", children: "Drawing chart\u2026" }) });
765
+ }
766
+ function ChartSparkline({ values, width = 80, height = 20, label, className, ...options }) {
767
+ const scene = (0, import_react5.useMemo)(() => (0, import_chart6.layoutSparkline)(values, { ...options, width, height }), [values, width, height, options.kind, options.color, options.negativeColor, options.markers, options.highLow, options.min, options.max]);
768
+ const present = values.filter((v) => v !== null);
769
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChartSceneSvg, { scene, label: label ?? (present.length ? `Trend from ${present[0]} to ${present[present.length - 1]}` : "No data"), className: `inline-block align-middle ${className ?? ""}` });
770
+ }
543
771
  // Annotate the CommonJS export names for ESM import in node:
544
772
  0 && (module.exports = {
545
773
  ChartCanvas,
@@ -547,6 +775,10 @@ function ChartEditor({ controller, showInspector = true, showDataGrid = true, sh
547
775
  ChartDataGrid,
548
776
  ChartEditor,
549
777
  ChartInspector,
778
+ ChartPresenceLayer,
779
+ ChartSceneSvg,
780
+ ChartSparkline,
781
+ ChartStreamingView,
550
782
  chartButtonClass,
551
783
  chartCellClass,
552
784
  chartIconButtonClass,
@@ -554,9 +786,12 @@ function ChartEditor({ controller, showInspector = true, showDataGrid = true, sh
554
786
  chartLabelClass,
555
787
  chartSectionClass,
556
788
  chartSegmentClass,
789
+ renderChartMark,
557
790
  useChartCommands,
558
791
  useChartController,
792
+ useChartDirector,
559
793
  useChartEditor,
560
- useChartState
794
+ useChartState,
795
+ useChartToolProvider
561
796
  });
562
797
  //# sourceMappingURL=index.cjs.map