@pixerate/editor-react 0.33.0 → 0.35.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.
package/README.md CHANGED
@@ -101,6 +101,7 @@ useAgentToolProvider(registry, provider);
101
101
  | `@pixerate/editor-react/persistence` | `useAutosave` (crash-recovery snapshots bound to a history / dirty flag, flushed on `pagehide`, with a recovery check on mount) and `<RecoveryPrompt>` (also re-exported from the root entry) |
102
102
  | `@pixerate/editor-react/generation` | `GenerationSettings` (controls generated from a model's capabilities, reference slots by role, plan adjustments), `useGenerationPlan`, `JobProgress`, `useJobProgress` over `@pixerate/editor/generation` (also re-exported from the root entry) |
103
103
  | `@pixerate/editor-react/vector` | `VectorEditor` (toolbar, canvas and style panel), `VectorCanvas` (SVG canvas forwarding pointer, wheel, keyboard and clipboard events), `VectorToolbar`, `VectorStylePanel`, `VectorSceneNodes`, `VectorGradientDefs`, `useVectorEditor`, `useVectorEditorController`, `useVectorEditorState` and `useVectorCommands` over `@pixerate/editor/vector` (see [the vector editor guide](../../docs/vector-editor.md)) |
104
+ | `@pixerate/editor-react/chart` | `ChartEditor` (toolbar, chart, inspector and data grid), `ChartCanvas` (SVG chart with selection, drag-to-edit and keyboard editing), `ChartDataGrid`, `ChartInspector`, `useChartEditor`, `useChartController`, `useChartState` and `useChartCommands` over `@pixerate/editor/chart` (see [the chart editor guide](../../docs/chart-editor.md)) |
104
105
  | `@pixerate/editor-react/parameters` | `ParametersPanel` (a whole schema with groups, presets, undo / reset / randomize and keyframe diamonds), `useParameters`, `useParametersController`, `useParametersState`, `useParameterCommands`, and the field controls `ParameterFieldControl`, `ParameterSlider`, `ParameterToggle`, `ParameterSelect`, `ParameterColor`, `ParameterVector`, `ParameterImage`, `ParameterText`, `KeyframeDiamond` over `@pixerate/editor/parameters` (see [the parameters guide](../../docs/parameters.md)) |
105
106
  | `@pixerate/editor-react/canvas` | `useCanvasGraph`, `useCanvasLayout`, `useCanvasClipboard`, `useCanvasDocking`, `useCanvasShortcuts`, `useCanvasExplosion`, `useStreamingCanvas`, `AgentPresenceLayer`, plus the pure layout helpers from `@pixerate/editor/canvas` |
106
107
 
@@ -0,0 +1,562 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/chart/index.ts
21
+ var chart_exports = {};
22
+ __export(chart_exports, {
23
+ ChartCanvas: () => ChartCanvas,
24
+ ChartCommitInput: () => ChartCommitInput,
25
+ ChartDataGrid: () => ChartDataGrid,
26
+ ChartEditor: () => ChartEditor,
27
+ ChartInspector: () => ChartInspector,
28
+ chartButtonClass: () => chartButtonClass,
29
+ chartCellClass: () => chartCellClass,
30
+ chartIconButtonClass: () => chartIconButtonClass,
31
+ chartInputClass: () => chartInputClass,
32
+ chartLabelClass: () => chartLabelClass,
33
+ chartSectionClass: () => chartSectionClass,
34
+ chartSegmentClass: () => chartSegmentClass,
35
+ useChartCommands: () => useChartCommands,
36
+ useChartController: () => useChartController,
37
+ useChartEditor: () => useChartEditor,
38
+ useChartState: () => useChartState
39
+ });
40
+ module.exports = __toCommonJS(chart_exports);
41
+
42
+ // src/chart/useChartEditor.ts
43
+ var import_react = require("react");
44
+ var import_chart = require("@pixerate/editor/chart");
45
+ function useChartController(options = {}) {
46
+ const ref = (0, import_react.useRef)(null);
47
+ if (!ref.current) ref.current = new import_chart.ChartController(options);
48
+ return ref.current;
49
+ }
50
+ function useChartState(controller) {
51
+ return (0, import_react.useSyncExternalStore)(controller.subscribe, controller.getState, controller.getState);
52
+ }
53
+ function useChartEditor(controllerOrOptions = {}) {
54
+ const own = (0, import_react.useRef)(null);
55
+ let controller;
56
+ if (controllerOrOptions instanceof import_chart.ChartController) controller = controllerOrOptions;
57
+ else {
58
+ if (!own.current) own.current = new import_chart.ChartController(controllerOrOptions);
59
+ controller = own.current;
60
+ }
61
+ const state = useChartState(controller);
62
+ const actions = (0, import_react.useMemo)(
63
+ () => ({
64
+ update: (patch) => controller.update(patch),
65
+ setType: (type) => controller.setType(type),
66
+ setValue: (seriesId, index, value) => controller.setValue(seriesId, index, value),
67
+ select: (selection) => controller.select(selection),
68
+ undo: () => void controller.undo(),
69
+ redo: () => void controller.redo()
70
+ }),
71
+ [controller]
72
+ );
73
+ return { controller, state, spec: state.spec, selection: state.selection, ...actions };
74
+ }
75
+ function useChartCommands(registry, controller, options = {}) {
76
+ const { prefix, group, scope } = options;
77
+ (0, import_react.useEffect)(() => {
78
+ if (!registry) return;
79
+ return registry.register((0, import_chart.createChartCommands)(controller, { prefix, group, scope }));
80
+ }, [registry, controller, prefix, group, scope]);
81
+ }
82
+
83
+ // src/chart/styles.ts
84
+ 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";
85
+ var chartButtonClass = "inline-flex h-7 items-center justify-center gap-1 rounded-md border border-input bg-background px-2 text-xs text-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50";
86
+ var chartIconButtonClass = "inline-flex h-6 w-6 shrink-0 items-center justify-center rounded text-xs text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-40";
87
+ var chartSegmentClass = (active) => `inline-flex h-7 flex-1 items-center justify-center rounded px-2 text-xs focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary ${active ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`;
88
+ var chartLabelClass = "text-[11px] font-medium text-muted-foreground";
89
+ var chartSectionClass = "space-y-2 border-b border-border pb-3 last:border-b-0";
90
+ 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";
91
+
92
+ // src/chart/ChartCanvas.tsx
93
+ var import_react2 = require("react");
94
+ var import_chart2 = require("@pixerate/editor/chart");
95
+ var import_jsx_runtime = require("react/jsx-runtime");
96
+ function renderMark(mark, fontFamily, interactive) {
97
+ const common = {
98
+ "data-role": mark.role,
99
+ "data-chart-key": mark.target ? mark.key : void 0,
100
+ "data-selected": mark.selected ? "" : void 0,
101
+ opacity: mark.opacity,
102
+ pointerEvents: mark.role === "selection" || !mark.target && mark.role !== "background" ? "none" : void 0,
103
+ cursor: interactive && mark.target && mark.role !== "background" ? "pointer" : void 0
104
+ };
105
+ switch (mark.kind) {
106
+ case "rect":
107
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { ...common, x: mark.x, y: mark.y, width: mark.width, height: mark.height, rx: mark.radius, style: { fill: mark.fill, stroke: mark.stroke, strokeWidth: mark.strokeWidth } }, mark.key);
108
+ case "path":
109
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
110
+ "path",
111
+ {
112
+ ...common,
113
+ d: mark.d,
114
+ strokeLinejoin: "round",
115
+ strokeLinecap: "round",
116
+ strokeDasharray: mark.dash,
117
+ style: { fill: mark.fill ?? "none", stroke: mark.stroke, strokeWidth: mark.strokeWidth }
118
+ },
119
+ mark.key
120
+ );
121
+ case "circle":
122
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { ...common, cx: mark.cx, cy: mark.cy, r: mark.r, style: { fill: mark.fill, stroke: mark.stroke, strokeWidth: mark.strokeWidth } }, mark.key);
123
+ case "line":
124
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { ...common, x1: mark.x1, y1: mark.y1, x2: mark.x2, y2: mark.y2, strokeDasharray: mark.dash, style: { stroke: mark.stroke, strokeWidth: mark.strokeWidth } }, mark.key);
125
+ case "text":
126
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
127
+ "text",
128
+ {
129
+ ...common,
130
+ x: mark.x,
131
+ y: mark.y,
132
+ textAnchor: mark.anchor,
133
+ dominantBaseline: (0, import_chart2.chartTextBaseline)(mark.baseline),
134
+ transform: (0, import_chart2.chartTextTransform)(mark),
135
+ style: { fill: mark.fill, fontSize: mark.size, fontWeight: mark.weight, fontFamily, userSelect: "none" },
136
+ children: mark.text
137
+ },
138
+ mark.key
139
+ );
140
+ }
141
+ }
142
+ function ChartCanvas({ controller, width = 640, height = 400, responsive = true, theme, editable = true, measureText, emptyMessage, className }) {
143
+ const state = useChartState(controller);
144
+ const wrapper = (0, import_react2.useRef)(null);
145
+ const svg = (0, import_react2.useRef)(null);
146
+ const drag = (0, import_react2.useRef)(null);
147
+ const [measured, setMeasured] = (0, import_react2.useState)(null);
148
+ (0, import_react2.useEffect)(() => {
149
+ const el = wrapper.current;
150
+ if (!responsive || !el || typeof ResizeObserver === "undefined") return;
151
+ const observer = new ResizeObserver(([entry]) => {
152
+ const w = Math.round(entry.contentRect.width);
153
+ if (w > 0) setMeasured(w);
154
+ });
155
+ observer.observe(el);
156
+ return () => observer.disconnect();
157
+ }, [responsive]);
158
+ const sceneWidth = responsive && measured || width;
159
+ const fullTheme = (0, import_react2.useMemo)(() => ({ ...import_chart2.CSS_VARIABLE_CHART_THEME, ...theme }), [theme]);
160
+ 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]
163
+ );
164
+ const byKey = (0, import_react2.useMemo)(() => new Map(scene.marks.map((m) => [m.key, m])), [scene]);
165
+ (0, import_react2.useEffect)(() => () => drag.current?.end(), []);
166
+ const scenePoint = (e) => (0, import_chart2.chartClientToScene)(scene, svg.current.getBoundingClientRect(), e.clientX, e.clientY);
167
+ const onPointerDown = (e) => {
168
+ if (!editable || e.button !== 0) return;
169
+ const key = e.target.closest?.("[data-chart-key]")?.getAttribute("data-chart-key");
170
+ const target = key ? byKey.get(key)?.target : void 0;
171
+ controller.select(target ?? null);
172
+ drag.current = (0, import_chart2.startChartPointDrag)(controller, scene, target, scenePoint(e));
173
+ if (drag.current) svg.current?.setPointerCapture?.(e.pointerId);
174
+ };
175
+ const onPointerMove = (e) => {
176
+ if (drag.current) drag.current.move(scenePoint(e));
177
+ };
178
+ const endDrag = () => {
179
+ drag.current?.end();
180
+ drag.current = null;
181
+ };
182
+ const onKeyDown = (e) => {
183
+ if (editable && (0, import_chart2.handleChartKey)(controller, scene, e)) e.preventDefault();
184
+ };
185
+ 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)(
187
+ "svg",
188
+ {
189
+ ref: svg,
190
+ width: sceneWidth,
191
+ height,
192
+ viewBox: `0 0 ${scene.width} ${scene.height}`,
193
+ role: "img",
194
+ "aria-label": editable ? `${label}. ${(0, import_chart2.describeChartSelection)(controller, state.selection)}` : label,
195
+ tabIndex: editable ? 0 : void 0,
196
+ className: "block max-w-full touch-none select-none rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
197
+ onPointerDown,
198
+ onPointerMove,
199
+ onPointerUp: endDrag,
200
+ onPointerCancel: endDrag,
201
+ onKeyDown,
202
+ children: scene.marks.map((m) => renderMark(m, scene.theme.fontFamily, editable))
203
+ }
204
+ ) });
205
+ }
206
+
207
+ // src/chart/ChartDataGrid.tsx
208
+ var import_react3 = require("react");
209
+ var import_chart3 = require("@pixerate/editor/chart");
210
+ var import_jsx_runtime2 = require("react/jsx-runtime");
211
+ function ChartCommitInput({ value, onCommit, onKeyDown, onBlur, ...rest }) {
212
+ const [draft, setDraft] = (0, import_react3.useState)(value);
213
+ const typing = (0, import_react3.useRef)(false);
214
+ (0, import_react3.useEffect)(() => {
215
+ if (!typing.current) setDraft(value);
216
+ }, [value]);
217
+ const commit = () => {
218
+ if (!typing.current) return;
219
+ typing.current = false;
220
+ if (draft === value) return;
221
+ if (onCommit(draft) === false) setDraft(value);
222
+ };
223
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
224
+ "input",
225
+ {
226
+ ...rest,
227
+ value: draft,
228
+ onChange: (e) => {
229
+ typing.current = true;
230
+ setDraft(e.currentTarget.value);
231
+ },
232
+ onBlur: (e) => {
233
+ onBlur?.(e);
234
+ commit();
235
+ },
236
+ onKeyDown: (e) => {
237
+ onKeyDown?.(e);
238
+ if (e.defaultPrevented) return;
239
+ if (e.key === "Enter") {
240
+ e.preventDefault();
241
+ commit();
242
+ } else if (e.key === "Escape") {
243
+ typing.current = false;
244
+ setDraft(value);
245
+ }
246
+ }
247
+ }
248
+ );
249
+ }
250
+ function ChartDataGrid({ controller, categoryHeader = "Category", readOnly = false, className }) {
251
+ const { spec, selection } = useChartState(controller);
252
+ const paste = (row, column) => (e) => {
253
+ const text = e.clipboardData?.getData("text/plain") ?? "";
254
+ if (!/[\t\n]/.test(text.replace(/\n$/, ""))) return;
255
+ e.preventDefault();
256
+ controller.pasteCells(row, column, text);
257
+ };
258
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `overflow-auto rounded-md border border-border ${className ?? ""}`, "data-chart-grid": "", children: [
259
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("table", { className: "w-full border-collapse text-xs", children: [
260
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("thead", { className: "bg-muted/50", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
261
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { className: "border-b border-r border-border px-2 py-1 text-left font-medium text-muted-foreground", children: categoryHeader }),
262
+ spec.series.map((s, si) => {
263
+ const color = (0, import_chart3.chartSeriesColor)(spec, si);
264
+ const selected = selection?.kind === "series" && selection.seriesId === s.id;
265
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-1", children: [
266
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
267
+ "input",
268
+ {
269
+ type: "color",
270
+ "aria-label": `${s.name} colour`,
271
+ value: (0, import_chart3.chartColorInputValue)(color),
272
+ disabled: readOnly,
273
+ onPointerDown: () => controller.beginGesture(`color:${s.id}`),
274
+ onChange: (e) => controller.updateSeries(s.id, { color: e.currentTarget.value }),
275
+ onBlur: () => controller.endGesture(),
276
+ className: "h-5 w-5 shrink-0 cursor-pointer rounded border-0 bg-transparent p-0"
277
+ }
278
+ ),
279
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
280
+ ChartCommitInput,
281
+ {
282
+ value: s.name,
283
+ "aria-label": "Series name",
284
+ onFocus: () => controller.select({ kind: "series", seriesId: s.id }),
285
+ onCommit: (name) => void controller.updateSeries(s.id, { name }),
286
+ className: `${chartInputClass} h-6 min-w-[5rem] border-transparent bg-transparent font-medium ${s.hidden ? "text-muted-foreground line-through" : ""}`
287
+ }
288
+ ),
289
+ !readOnly && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
290
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
291
+ "button",
292
+ {
293
+ type: "button",
294
+ className: chartIconButtonClass,
295
+ title: s.hidden ? "Show series" : "Hide series",
296
+ "aria-label": s.hidden ? `Show ${s.name}` : `Hide ${s.name}`,
297
+ "aria-pressed": !s.hidden,
298
+ "data-action": "toggle-series",
299
+ onClick: () => controller.toggleSeries(s.id),
300
+ children: s.hidden ? "\u25CB" : "\u25CF"
301
+ }
302
+ ),
303
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: chartIconButtonClass, title: "Delete series", "aria-label": `Delete ${s.name}`, "data-action": "remove-series", onClick: () => controller.removeSeries(s.id), children: "\xD7" })
304
+ ] })
305
+ ] }) }, s.id);
306
+ }),
307
+ !readOnly && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { className: "w-8 border-b border-border px-1", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: chartIconButtonClass, title: "Add series", "aria-label": "Add series", "data-action": "add-series", onClick: () => controller.addSeries(), children: "+" }) })
308
+ ] }) }),
309
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: spec.categories.map((category, i) => {
310
+ const rowSelected = selection?.kind === "category" && selection.index === i;
311
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { "data-category-index": i, className: rowSelected ? "bg-primary/10" : void 0, children: [
312
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { className: "border-b border-r border-border p-0 text-left font-normal", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
313
+ ChartCommitInput,
314
+ {
315
+ value: category,
316
+ "aria-label": `Category ${i + 1}`,
317
+ onFocus: () => controller.select({ kind: "category", index: i }),
318
+ onPaste: paste(i, -1),
319
+ onCommit: (label) => void controller.renameCategory(i, label),
320
+ className: `${chartCellClass} text-left`
321
+ }
322
+ ) }),
323
+ spec.series.map((s) => {
324
+ const value = s.values[i];
325
+ const selected = selection?.kind === "point" && selection.seriesId === s.id && selection.index === i;
326
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: `border-b border-r border-border p-0 ${selected ? "bg-primary/10" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
327
+ ChartCommitInput,
328
+ {
329
+ value: value === null || value === void 0 ? "" : String(value),
330
+ inputMode: "decimal",
331
+ "aria-label": `${s.name}, ${category}`,
332
+ title: value === null ? void 0 : (0, import_chart3.formatChartNumber)(value, spec.yAxis.format),
333
+ "data-cell": `${s.id}:${i}`,
334
+ onFocus: () => controller.select({ kind: "point", seriesId: s.id, index: i }),
335
+ onPaste: paste(i, spec.series.indexOf(s)),
336
+ onCommit: (text) => controller.setValue(s.id, i, text).ok,
337
+ className: chartCellClass
338
+ }
339
+ ) }, s.id);
340
+ }),
341
+ !readOnly && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "border-b border-border px-1", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: chartIconButtonClass, title: "Delete category", "aria-label": `Delete ${category}`, "data-action": "remove-category", onClick: () => controller.removeCategory(i), children: "\xD7" }) })
342
+ ] }, i);
343
+ }) })
344
+ ] }),
345
+ !readOnly && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "p-1", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: `${chartButtonClass} h-6 border-dashed`, "data-action": "add-category", onClick: () => controller.addCategory(), children: "+ Add category" }) })
346
+ ] });
347
+ }
348
+
349
+ // src/chart/ChartInspector.tsx
350
+ var import_react4 = require("react");
351
+ var import_chart4 = require("@pixerate/editor/chart");
352
+ var import_jsx_runtime3 = require("react/jsx-runtime");
353
+ var cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
354
+ function Field({ label, htmlFor, children }) {
355
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", children: [
356
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { htmlFor, className: `${chartLabelClass} w-20 shrink-0`, children: label }),
357
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "min-w-0 flex-1", children })
358
+ ] });
359
+ }
360
+ function Toggle({ label, checked, onChange, name }) {
361
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex cursor-pointer items-center gap-2 text-xs text-foreground", children: [
362
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { type: "checkbox", className: "h-3.5 w-3.5 accent-primary", checked, "data-option": name, onChange: (e) => onChange(e.currentTarget.checked) }),
363
+ label
364
+ ] });
365
+ }
366
+ function Select({ id, value, options, onChange, name, labels }) {
367
+ 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
+ }
369
+ function AxisSection({ controller, axis, value, idBase, horizontal }) {
370
+ const set = (patch) => controller.setAxis(axis, patch);
371
+ const setFormat = (patch) => set({ format: { ...value.format, ...patch } });
372
+ const bound = (key) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
373
+ ChartCommitInput,
374
+ {
375
+ id: `${idBase}-${axis}-${key}`,
376
+ value: value[key] === null ? "" : String(value[key]),
377
+ placeholder: "Auto",
378
+ inputMode: "decimal",
379
+ "data-option": `${axis}Axis.${key}`,
380
+ className: chartInputClass,
381
+ onCommit: (text) => set({ [key]: text.trim() === "" ? null : Number(text) }).ok
382
+ }
383
+ );
384
+ const name = axis === "x" ? "Category axis" : "Value axis";
385
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: chartSectionClass, "data-section": `axis-${axis}`, children: [
386
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between", children: [
387
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("h4", { className: "text-xs font-semibold text-foreground", children: [
388
+ name,
389
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "ml-1 font-normal text-muted-foreground", children: [
390
+ "(",
391
+ axis === "x" ? horizontal ? "left" : "bottom" : horizontal ? "bottom" : "left",
392
+ ")"
393
+ ] })
394
+ ] }),
395
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Show", name: `${axis}Axis.show`, checked: value.show, onChange: (show) => set({ show }) })
396
+ ] }),
397
+ /* @__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: [
400
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Min / max", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex gap-1", children: [
401
+ bound("min"),
402
+ bound("max")
403
+ ] }) }),
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 }) }),
406
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
407
+ ChartCommitInput,
408
+ {
409
+ value: value.format.decimals === void 0 ? "" : String(value.format.decimals),
410
+ placeholder: "Auto",
411
+ "aria-label": "Decimals",
412
+ title: "Decimals",
413
+ inputMode: "numeric",
414
+ "data-option": "yAxis.format.decimals",
415
+ className: `${chartInputClass} w-14 shrink-0`,
416
+ onCommit: (text) => {
417
+ const format = { ...value.format };
418
+ if (text.trim() === "") delete format.decimals;
419
+ else format.decimals = Number(text);
420
+ return set({ format }).ok;
421
+ }
422
+ }
423
+ )
424
+ ] }) }),
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() }) }) })
426
+ ] })
427
+ ] });
428
+ }
429
+ function ChartInspector({ controller, sections, className }) {
430
+ const { spec, selectedSeries } = useChartState(controller);
431
+ const idBase = (0, import_react4.useId)();
432
+ const show = (section) => !sections || sections.includes(section);
433
+ const cartesian = (0, import_chart4.isCartesianChart)(spec.type);
434
+ const types = spec.series.map((s) => (0, import_chart4.chartSeriesType)(spec, s));
435
+ const hasBars = cartesian && types.includes("bar");
436
+ const hasLines = cartesian && types.some((t) => t === "line" || t === "area");
437
+ const seriesIndex = selectedSeries ? spec.series.findIndex((s) => s.id === selectedSeries.id) : -1;
438
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `space-y-3 text-xs ${className ?? ""}`, "data-chart-inspector": "", children: [
439
+ 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)) }) }),
440
+ show("titles") && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: chartSectionClass, "data-section": "titles", children: [
441
+ /* @__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
+ /* @__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) }) })
443
+ ] }),
444
+ show("options") && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: chartSectionClass, "data-section": "options", children: [
445
+ cartesian && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Stacking", htmlFor: `${idBase}-stacking`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Select, { id: `${idBase}-stacking`, name: "stacking", value: spec.stacking, options: import_chart4.CHART_STACKINGS, labels: { none: "None", stacked: "Stacked", percent: "100%" }, onChange: (stacking) => controller.update({ stacking }) }) }),
446
+ hasBars && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Bars", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex gap-0.5 rounded-md bg-muted p-0.5", children: ["vertical", "horizontal"].map((o) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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)) }) }),
447
+ hasLines && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Lines", htmlFor: `${idBase}-curve`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Select, { id: `${idBase}-curve`, name: "curve", value: spec.curve, options: import_chart4.CHART_CURVES, onChange: (curve) => controller.update({ curve }) }) }),
448
+ spec.type === "donut" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Hole", htmlFor: `${idBase}-hole`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
449
+ "input",
450
+ {
451
+ id: `${idBase}-hole`,
452
+ type: "range",
453
+ min: 0,
454
+ max: 0.9,
455
+ step: 0.05,
456
+ value: spec.innerRadius,
457
+ "data-option": "innerRadius",
458
+ className: "w-full accent-primary",
459
+ onPointerDown: () => controller.beginGesture("innerRadius"),
460
+ onPointerUp: () => controller.endGesture(),
461
+ onChange: (e) => controller.update({ innerRadius: Number(e.currentTarget.value) })
462
+ }
463
+ ) }),
464
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap gap-x-4 gap-y-1", children: [
465
+ hasLines && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Points", name: "points", checked: spec.points, onChange: (points) => controller.update({ points }) }),
466
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Data labels", name: "dataLabels", checked: spec.dataLabels, onChange: (dataLabels) => controller.update({ dataLabels }) })
467
+ ] })
468
+ ] }),
469
+ show("legend") && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: chartSectionClass, "data-section": "legend", children: [
470
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between", children: [
471
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h4", { className: "text-xs font-semibold text-foreground", children: "Legend" }),
472
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Show", name: "legend.show", checked: spec.legend.show, onChange: (v) => controller.setLegend({ show: v }) })
473
+ ] }),
474
+ 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
+ ] }),
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" })
479
+ ] }),
480
+ show("series") && selectedSeries && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: chartSectionClass, "data-section": "series", "data-series-id": selectedSeries.id, children: [
481
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h4", { className: "text-xs font-semibold text-foreground", children: "Series" }),
482
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Name", htmlFor: `${idBase}-series-name`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartCommitInput, { id: `${idBase}-series-name`, value: selectedSeries.name, "data-option": "series.name", className: chartInputClass, onCommit: (name) => void controller.updateSeries(selectedSeries.id, { name }) }) }),
483
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Colour", htmlFor: `${idBase}-series-color`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-1", children: [
484
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
485
+ "input",
486
+ {
487
+ id: `${idBase}-series-color`,
488
+ type: "color",
489
+ value: (0, import_chart4.chartColorInputValue)((0, import_chart4.chartSeriesColor)(spec, seriesIndex)),
490
+ "data-option": "series.color",
491
+ className: "h-7 w-9 shrink-0 cursor-pointer rounded border border-input bg-background p-0.5",
492
+ onPointerDown: () => controller.beginGesture(`color:${selectedSeries.id}`),
493
+ onBlur: () => controller.endGesture(),
494
+ onChange: (e) => controller.updateSeries(selectedSeries.id, { color: e.currentTarget.value })
495
+ }
496
+ ),
497
+ selectedSeries.color && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: chartButtonClass, "data-action": "reset-color", onClick: () => controller.updateSeries(selectedSeries.id, { color: null }), children: "Palette" })
498
+ ] }) }),
499
+ cartesian && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Draw as", htmlFor: `${idBase}-series-type`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
500
+ "select",
501
+ {
502
+ id: `${idBase}-series-type`,
503
+ className: chartInputClass,
504
+ value: selectedSeries.type ?? "",
505
+ "data-option": "series.type",
506
+ onChange: (e) => controller.updateSeries(selectedSeries.id, { type: e.currentTarget.value || null }),
507
+ children: [
508
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "Chart type" }),
509
+ import_chart4.CHART_SERIES_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: t, children: import_chart4.CHART_TYPE_LABELS[t] }, t))
510
+ ]
511
+ }
512
+ ) }),
513
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
514
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Hidden", name: "series.hidden", checked: !!selectedSeries.hidden, onChange: (hidden) => controller.updateSeries(selectedSeries.id, { hidden }) }),
515
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex-1" }),
516
+ /* @__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
+ /* @__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" }),
518
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: `${chartButtonClass} text-destructive`, "data-action": "series-delete", onClick: () => controller.removeSeries(selectedSeries.id), children: "Delete" })
519
+ ] })
520
+ ] })
521
+ ] });
522
+ }
523
+
524
+ // src/chart/ChartEditor.tsx
525
+ var import_chart5 = require("@pixerate/editor/chart");
526
+ var import_jsx_runtime4 = require("react/jsx-runtime");
527
+ function ChartEditor({ controller, showInspector = true, showDataGrid = true, showToolbar = true, height = 380, canvasProps, className }) {
528
+ const state = useChartState(controller);
529
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-chart-editor": "", children: [
530
+ showToolbar && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-1", "data-chart-toolbar": "", children: [
531
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: chartButtonClass, disabled: !state.canUndo, title: state.undoLabel ? `Undo ${state.undoLabel}` : "Undo", "data-action": "undo", onClick: () => controller.undo(), children: "Undo" }),
532
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: chartButtonClass, disabled: !state.canRedo, title: state.redoLabel ? `Redo ${state.redoLabel}` : "Redo", "data-action": "redo", onClick: () => controller.redo(), children: "Redo" }),
533
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ml-2 truncate text-xs text-muted-foreground", "data-chart-selection": "", "aria-live": "polite", children: (0, import_chart5.describeChartSelection)(controller, state.selection) })
534
+ ] }),
535
+ state.issues.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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(" ") }),
536
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
537
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "min-w-0 flex-1 rounded-md border border-border bg-background p-1", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChartCanvas, { controller, height, ...canvasProps }) }),
538
+ showInspector && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChartInspector, { controller, className: "w-full shrink-0 overflow-y-auto rounded-md border border-border p-3 md:w-64" })
539
+ ] }),
540
+ showDataGrid && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChartDataGrid, { controller })
541
+ ] });
542
+ }
543
+ // Annotate the CommonJS export names for ESM import in node:
544
+ 0 && (module.exports = {
545
+ ChartCanvas,
546
+ ChartCommitInput,
547
+ ChartDataGrid,
548
+ ChartEditor,
549
+ ChartInspector,
550
+ chartButtonClass,
551
+ chartCellClass,
552
+ chartIconButtonClass,
553
+ chartInputClass,
554
+ chartLabelClass,
555
+ chartSectionClass,
556
+ chartSegmentClass,
557
+ useChartCommands,
558
+ useChartController,
559
+ useChartEditor,
560
+ useChartState
561
+ });
562
+ //# sourceMappingURL=index.cjs.map