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