@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.
@@ -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,13 +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);
148
- const [valueDomain, setValueDomain] = (0, import_react2.useState)(null);
188
+ const [frozen, setFrozen] = (0, import_react2.useState)(null);
149
189
  (0, import_react2.useEffect)(() => {
150
190
  const el = wrapper.current;
151
191
  if (!responsive || !el || typeof ResizeObserver === "undefined") return;
@@ -159,9 +199,21 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
159
199
  const sceneWidth = responsive && measured || width;
160
200
  const fullTheme = (0, import_react2.useMemo)(() => ({ ...import_chart2.CSS_VARIABLE_CHART_THEME, ...theme }), [theme]);
161
201
  const scene = (0, import_react2.useMemo)(
162
- () => (0, import_chart2.layoutChart)(state.spec, { width: sceneWidth, height, theme: fullTheme, selection: editable ? state.selection : null, measureText, emptyMessage, valueDomain }),
163
- [state.spec, state.selection, sceneWidth, height, fullTheme, editable, measureText, emptyMessage, valueDomain]
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]
164
215
  );
216
+ if (sceneRef) sceneRef.current = scene;
165
217
  const byKey = (0, import_react2.useMemo)(() => new Map(scene.marks.map((m) => [m.key, m])), [scene]);
166
218
  (0, import_react2.useEffect)(() => () => drag.current?.end(), []);
167
219
  const scenePoint = (e) => (0, import_chart2.chartClientToScene)(scene, svg.current.getBoundingClientRect(), e.clientX, e.clientY);
@@ -172,23 +224,31 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
172
224
  controller.select(target ?? null);
173
225
  drag.current = (0, import_chart2.startChartPointDrag)(controller, scene, target, scenePoint(e));
174
226
  if (drag.current) {
175
- setValueDomain(drag.current.valueDomain);
227
+ const d = drag.current;
228
+ setFrozen({ valueDomain: d.valueDomain, valueDomain2: d.valueDomain2 ?? null, xDomain: d.xDomain ?? null, readout: d.target });
176
229
  svg.current?.setPointerCapture?.(e.pointerId);
177
230
  }
178
231
  };
179
232
  const onPointerMove = (e) => {
180
- 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 });
181
234
  };
182
235
  const endDrag = () => {
183
236
  drag.current?.end();
184
237
  drag.current = null;
185
- setValueDomain(null);
238
+ setFrozen(null);
186
239
  };
187
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
+ }
188
248
  if (editable && (0, import_chart2.handleChartKey)(controller, scene, e)) e.preventDefault();
189
249
  };
190
250
  const label = state.spec.title || "Chart";
191
- 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)(
192
252
  "svg",
193
253
  {
194
254
  ref: svg,
@@ -204,7 +264,10 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
204
264
  onPointerUp: endDrag,
205
265
  onPointerCancel: endDrag,
206
266
  onKeyDown,
207
- 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
+ ]
208
271
  }
209
272
  ) });
210
273
  }
@@ -371,43 +434,60 @@ function Toggle({ label, checked, onChange, name }) {
371
434
  function Select({ id, value, options, onChange, name, labels }) {
372
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)) });
373
436
  }
374
- function AxisSection({ controller, axis, value, idBase, horizontal }) {
437
+ function AxisSection({ controller, axis, value, idBase, horizontal, numeric = false }) {
375
438
  const set = (patch) => controller.setAxis(axis, patch);
376
439
  const setFormat = (patch) => set({ format: { ...value.format, ...patch } });
377
- const bound = (key) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
440
+ const bound = (key2) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
378
441
  ChartCommitInput,
379
442
  {
380
- id: `${idBase}-${axis}-${key}`,
381
- value: value[key] === null ? "" : String(value[key]),
443
+ id: `${idBase}-${axis}-${key2}`,
444
+ value: value[key2] === null ? "" : String(value[key2]),
382
445
  placeholder: "Auto",
383
446
  inputMode: "decimal",
384
- "data-option": `${axis}Axis.${key}`,
447
+ "data-option": `${axis}Axis.${key2}`,
385
448
  className: chartInputClass,
386
- onCommit: (text) => set({ [key]: text.trim() === "" ? null : Number(text) }).ok
449
+ onCommit: (text) => set({ [key2]: text.trim() === "" ? null : Number(text) }).ok
387
450
  }
388
451
  );
389
- 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`;
390
455
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: chartSectionClass, "data-section": `axis-${axis}`, children: [
391
456
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between", children: [
392
457
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("h4", { className: "text-xs font-semibold text-foreground", children: [
393
458
  name,
394
459
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "ml-1 font-normal text-muted-foreground", children: [
395
460
  "(",
396
- axis === "x" ? horizontal ? "left" : "bottom" : horizontal ? "bottom" : "left",
461
+ axis === "x" ? horizontal ? "left" : "bottom" : axis === "y2" ? horizontal ? "top" : "right" : horizontal ? "bottom" : "left",
397
462
  ")"
398
463
  ] })
399
464
  ] }),
400
465
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Show", name: `${axis}Axis.show`, checked: value.show, onChange: (show) => set({ show }) })
401
466
  ] }),
402
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 }) }) }),
403
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Grid lines", name: `${axis}Axis.gridlines`, checked: value.gridlines, onChange: (gridlines) => set({ gridlines }) }),
404
- 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: [
405
485
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Min / max", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex gap-1", children: [
406
486
  bound("min"),
407
487
  bound("max")
408
488
  ] }) }),
409
- /* @__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: [
410
- /* @__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 }) }),
411
491
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
412
492
  ChartCommitInput,
413
493
  {
@@ -416,7 +496,7 @@ function AxisSection({ controller, axis, value, idBase, horizontal }) {
416
496
  "aria-label": "Decimals",
417
497
  title: "Decimals",
418
498
  inputMode: "numeric",
419
- "data-option": "yAxis.format.decimals",
499
+ "data-option": `${key}.format.decimals`,
420
500
  className: `${chartInputClass} w-14 shrink-0`,
421
501
  onCommit: (text) => {
422
502
  const format = { ...value.format };
@@ -427,7 +507,23 @@ function AxisSection({ controller, axis, value, idBase, horizontal }) {
427
507
  }
428
508
  )
429
509
  ] }) }),
430
- 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
+ ) })
431
527
  ] })
432
528
  ] });
433
529
  }
@@ -436,12 +532,28 @@ function ChartInspector({ controller, sections, className }) {
436
532
  const idBase = (0, import_react4.useId)();
437
533
  const show = (section) => !sections || sections.includes(section);
438
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";
439
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))));
440
540
  const hasBars = cartesian && types.includes("bar");
441
541
  const hasLines = cartesian && types.some((t) => t === "line" || t === "area");
442
542
  const seriesIndex = selectedSeries ? spec.series.findIndex((s) => s.id === selectedSeries.id) : -1;
443
543
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `space-y-3 text-xs ${className ?? ""}`, "data-chart-inspector": "", children: [
444
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
+ ] }),
445
557
  show("titles") && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: chartSectionClass, "data-section": "titles", children: [
446
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) }) }),
447
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) }) })
@@ -466,7 +578,26 @@ function ChartInspector({ controller, sections, className }) {
466
578
  onChange: (e) => controller.update({ innerRadius: Number(e.currentTarget.value) })
467
579
  }
468
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 }) }),
469
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 }) }),
470
601
  hasLines && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Points", name: "points", checked: spec.points, onChange: (points) => controller.update({ points }) }),
471
602
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Data labels", name: "dataLabels", checked: spec.dataLabels, onChange: (dataLabels) => controller.update({ dataLabels }) })
472
603
  ] })
@@ -478,9 +609,10 @@ function ChartInspector({ controller, sections, className }) {
478
609
  ] }),
479
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)) })
480
611
  ] }),
481
- show("axes") && cartesian && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
482
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AxisSection, { controller, axis: "x", value: spec.xAxis, idBase, horizontal: spec.orientation === "horizontal" }),
483
- /* @__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" })
484
616
  ] }),
485
617
  show("series") && selectedSeries && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: chartSectionClass, "data-section": "series", "data-series-id": selectedSeries.id, children: [
486
618
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h4", { className: "text-xs font-semibold text-foreground", children: "Series" }),
@@ -515,8 +647,44 @@ function ChartInspector({ controller, sections, className }) {
515
647
  ]
516
648
  }
517
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
+ ] }),
518
685
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
519
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 }) }),
520
688
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex-1" }),
521
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" }),
522
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" }),
@@ -545,6 +713,61 @@ function ChartEditor({ controller, showInspector = true, showDataGrid = true, sh
545
713
  showDataGrid && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChartDataGrid, { controller })
546
714
  ] });
547
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
+ }
548
771
  // Annotate the CommonJS export names for ESM import in node:
549
772
  0 && (module.exports = {
550
773
  ChartCanvas,
@@ -552,6 +775,10 @@ function ChartEditor({ controller, showInspector = true, showDataGrid = true, sh
552
775
  ChartDataGrid,
553
776
  ChartEditor,
554
777
  ChartInspector,
778
+ ChartPresenceLayer,
779
+ ChartSceneSvg,
780
+ ChartSparkline,
781
+ ChartStreamingView,
555
782
  chartButtonClass,
556
783
  chartCellClass,
557
784
  chartIconButtonClass,
@@ -559,9 +786,12 @@ function ChartEditor({ controller, showInspector = true, showDataGrid = true, sh
559
786
  chartLabelClass,
560
787
  chartSectionClass,
561
788
  chartSegmentClass,
789
+ renderChartMark,
562
790
  useChartCommands,
563
791
  useChartController,
792
+ useChartDirector,
564
793
  useChartEditor,
565
- useChartState
794
+ useChartState,
795
+ useChartToolProvider
566
796
  });
567
797
  //# sourceMappingURL=index.cjs.map