@thomasfosterau/charts 0.0.0 → 0.1.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.
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Public, dependency-free TypeScript types for the portable chart specification.
3
+ *
4
+ * These are hand-authored mirrors of the Effect Schema definitions in
5
+ * `internal/`. They are the only types consumers see — no Effect types leak
6
+ * through the package boundary. A type-parity test asserts these stay
7
+ * structurally identical to the schema-derived types.
8
+ *
9
+ * Design invariants (see the README § Capability matrix):
10
+ * - Grammar-level only: marks and encodings, never geometry, pixels, or
11
+ * renderer names.
12
+ * - Every encoding channel carries an explicit `type`; interpreters
13
+ * reconstruct typed values from declared types, never by sniffing JSON.
14
+ * - The portable subset targets the intersection of native charting
15
+ * vocabularies (Swift Charts, Compose). Web-only richness goes in
16
+ * `extensions` and degrades gracefully.
17
+ * - Optional members are bare `prop?: T`, the one exception to the
18
+ * workspace's `T | undefined` rule: these types describe a JSON document,
19
+ * where a key is present with a value or absent and `undefined` does not
20
+ * exist. The schemas say the same (`Schema.optionalKey`), so `parseSpec`
21
+ * rejects a present-but-undefined key, and widening either side would make
22
+ * the emitted JSON Schema admit `null`.
23
+ */
24
+ /** The current portable chart spec version; what a new spec should declare. */
25
+ declare const SPEC_VERSION = "1.1";
26
+ /**
27
+ * The spec versions this interpreter accepts. `'1.1'` adds the `x2` / `y2`
28
+ * range channels and the `tree` mark; every `'1'` spec is a valid `'1.1'`
29
+ * spec. See the README § Spec versioning for the minor-version rule.
30
+ */
31
+ type SpecVersion = '1' | '1.1';
32
+ /**
33
+ * The mark (geometric primitive) used to render the chart.
34
+ *
35
+ * The intersection of the native charting vocabularies (Swift Charts
36
+ * BarMark/LineMark/PointMark/AreaMark and their Compose equivalents), plus
37
+ * `tree` (spec 1.1): a hierarchy of labelled nodes joined by links, laid
38
+ * out by the core. It is the first mark outside the intersection, allowed
39
+ * because the core lays it out with no renderer dependency and every
40
+ * interpreter column has a normative degradation (README § Capability matrix).
41
+ */
42
+ type Mark = 'bar' | 'line' | 'point' | 'area' | 'tree';
43
+ /** Layout options of the `tree` mark (spec 1.1). Grammar-level only: no pixels. */
44
+ interface TreeOptions {
45
+ /** `vertical`: roots at the top (default). `horizontal`: roots at the start edge. */
46
+ readonly orientation?: 'vertical' | 'horizontal';
47
+ /** How a parent joins its children (default `orthogonal`). */
48
+ readonly linkStyle?: 'orthogonal' | 'curved';
49
+ }
50
+ /**
51
+ * The declared type of an encoded field.
52
+ *
53
+ * - `quantitative` — continuous numeric values
54
+ * - `temporal` — points in time, encoded as ISO 8601 strings or epoch milliseconds
55
+ * - `ordinal` — discrete, ordered categories
56
+ * - `nominal` — discrete, unordered categories
57
+ */
58
+ type FieldType = 'quantitative' | 'ordinal' | 'nominal' | 'temporal';
59
+ /** A mapping from a data field to a visual channel, with an explicit type. */
60
+ interface Channel {
61
+ /** The name of the field in each datum that this channel reads. */
62
+ readonly field: string;
63
+ /** The declared type of the field. Interpreters must respect this, never sniff values. */
64
+ readonly type: FieldType;
65
+ }
66
+ /** Field-to-channel mappings. At minimum, `x` and `y` are expected for all v1 marks. */
67
+ interface Encoding {
68
+ readonly x?: Channel;
69
+ readonly y?: Channel;
70
+ /**
71
+ * The end of a range on the x axis (spec 1.1): with `mark: 'bar'`, each
72
+ * bar spans `x` to `x2`, a range bar (a Gantt row). Same `type` as `x`,
73
+ * `quantitative` or `temporal`.
74
+ */
75
+ readonly x2?: Channel;
76
+ /** The end of a range on the y axis (spec 1.1), as `x2` is for x. */
77
+ readonly y2?: Channel;
78
+ /** Spec 1.1, mark `tree`: the field naming each row. Ids are unique. */
79
+ readonly id?: Channel;
80
+ /** Spec 1.1, mark `tree`: the field holding the parent row's id; `null` or absent for a root. */
81
+ readonly parent?: Channel;
82
+ /** Spec 1.1, mark `tree`: the text of each node; defaults to its id. */
83
+ readonly label?: Channel;
84
+ readonly color?: Channel;
85
+ readonly size?: Channel;
86
+ readonly opacity?: Channel;
87
+ }
88
+ /** A single value inside a datum. Inline data rows are flat records of JSON primitives. */
89
+ type DatumValue = string | number | boolean | null;
90
+ /** A single data row. */
91
+ type Datum = {
92
+ readonly [field: string]: DatumValue;
93
+ };
94
+ /** Inline data: small datasets embedded directly in the spec. */
95
+ interface InlineData {
96
+ readonly values: ReadonlyArray<Datum>;
97
+ }
98
+ /**
99
+ * The kind of an external data reference.
100
+ *
101
+ * - `query` — a live query (e.g. a Durable Object or database query) known to the host
102
+ * - `node` — another content node in the host document store
103
+ * - `url` — an addressable resource returning JSON rows
104
+ */
105
+ type DataRefKind = 'query' | 'node' | 'url';
106
+ /** A reference to an externally stored dataset, resolved by the host/interpreter. */
107
+ interface DataRef {
108
+ readonly ref: {
109
+ readonly kind: DataRefKind;
110
+ readonly id: string;
111
+ };
112
+ }
113
+ /** The chart's data: exactly one of inline values or an external reference. */
114
+ type Data = InlineData | DataRef;
115
+ /**
116
+ * Optional per-scale hints. Interpreters may infer or override these;
117
+ * they are hints, not geometry.
118
+ */
119
+ interface ScaleHint {
120
+ /**
121
+ * Explicit domain. Numbers for quantitative, ISO strings for temporal,
122
+ * category values for ordinal/nominal.
123
+ */
124
+ readonly domain?: ReadonlyArray<number | string>;
125
+ /** Round the domain to human-friendly values. */
126
+ readonly nice?: boolean;
127
+ /** Force the domain to include zero. */
128
+ readonly zero?: boolean;
129
+ }
130
+ /** Per-channel scale hints. */
131
+ interface Scales {
132
+ readonly x?: ScaleHint;
133
+ readonly y?: ScaleHint;
134
+ }
135
+ /** Portable presentation hints honoured (or gracefully ignored) by every interpreter. */
136
+ interface Config {
137
+ readonly title?: string;
138
+ /** Whether tooltips are enabled. A trivial declarative hint — real interaction is per-platform. */
139
+ readonly tooltip?: boolean;
140
+ }
141
+ /**
142
+ * Web-only, non-portable hints (e.g. preferred renderer, interaction
143
+ * descriptors). Native interpreters ignore this entirely.
144
+ */
145
+ type Extensions = {
146
+ readonly [key: string]: unknown;
147
+ };
148
+ /**
149
+ * The portable chart specification (Layer 1).
150
+ *
151
+ * This is the durable, cross-platform contract. It is pure JSON,
152
+ * grammar-level, and versioned. Everything else — geometry cores, framework
153
+ * components, renderers — is a per-platform interpreter of this spec.
154
+ */
155
+ interface ChartSpec {
156
+ readonly specVersion: SpecVersion;
157
+ readonly mark: Mark;
158
+ readonly encoding: Encoding;
159
+ readonly data: Data;
160
+ readonly scales?: Scales;
161
+ readonly config?: Config;
162
+ readonly extensions?: Extensions;
163
+ /** Spec 1.1: layout options of the `tree` mark; only valid with it. */
164
+ readonly tree?: TreeOptions;
165
+ }
166
+ /** Returns true if the spec's data is inline values. */
167
+ declare function isInlineData(data: Data): data is InlineData;
168
+ /** Returns true if the spec's data is an external reference. */
169
+ declare function isDataRef(data: Data): data is DataRef;
170
+ /** Thrown when a value is not a valid portable chart spec. */
171
+ declare class SpecValidationError extends Error {
172
+ readonly name = "SpecValidationError";
173
+ constructor(message: string);
174
+ }
175
+ /** The result of {@link validateSpec}. */
176
+ type ValidationResult = {
177
+ readonly ok: true;
178
+ readonly value: ChartSpec;
179
+ } | {
180
+ readonly ok: false;
181
+ readonly errors: ReadonlyArray<string>;
182
+ };
183
+ /**
184
+ * Parses an unknown JSON value into a {@link ChartSpec}.
185
+ *
186
+ * @throws {SpecValidationError} when the value is not a valid spec.
187
+ */
188
+ declare function parseSpec(input: unknown): ChartSpec;
189
+ /**
190
+ * Validates an unknown JSON value against the portable chart spec,
191
+ * returning a result instead of throwing.
192
+ */
193
+ declare function validateSpec(input: unknown): ValidationResult;
194
+ /**
195
+ * Encodes a {@link ChartSpec} back to a plain JSON-safe value, suitable for
196
+ * persisting (e.g. into ProseMirror node attrs).
197
+ */
198
+ declare function encodeSpec(spec: ChartSpec): unknown;
199
+ export { Scales as C, ScaleHint as S, TreeOptions as T, Extensions as _, validateSpec as a, Mark as b, Channel as c, Data as d, DataRef as f, Encoding as g, DatumValue as h, parseSpec as i, ChartSpec as l, Datum as m, ValidationResult as n, isDataRef as o, DataRefKind as p, encodeSpec as r, isInlineData as s, SpecValidationError as t, Config as u, FieldType as v, SpecVersion as w, SPEC_VERSION as x, InlineData as y };
200
+ //# sourceMappingURL=index-M0KOYMo4.d.mts.map
@@ -0,0 +1,2 @@
1
+ import { C as Scales, S as ScaleHint, T as TreeOptions, _ as Extensions, a as validateSpec, b as Mark, c as Channel, d as Data, f as DataRef, g as Encoding, h as DatumValue, i as parseSpec, l as ChartSpec, m as Datum, n as ValidationResult, o as isDataRef, p as DataRefKind, r as encodeSpec, s as isInlineData, t as SpecValidationError, u as Config, v as FieldType, w as SpecVersion, x as SPEC_VERSION, y as InlineData } from "./index-M0KOYMo4.mjs";
2
+ export { type Channel, type ChartSpec, type Config, type Data, type DataRef, type DataRefKind, type Datum, type DatumValue, type Encoding, type Extensions, type FieldType, type InlineData, type Mark, SPEC_VERSION, type ScaleHint, type Scales, SpecValidationError, type SpecVersion, type TreeOptions, type ValidationResult, encodeSpec, isDataRef, isInlineData, parseSpec, validateSpec };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ export { s as SPEC_VERSION, t as SpecValidationError, n as encodeSpec, a as isDataRef, o as isInlineData, r as parseSpec, i as validateSpec } from "./spec-FF2Tds2a.mjs";
@@ -0,0 +1,106 @@
1
+ import { l as ChartSpec } from "./index-M0KOYMo4.mjs";
2
+ import { T as ChartContext, k as DataRows, w as Box } from "./index-B32E5tbG.mjs";
3
+ import { Node, NodeSpec } from "prosemirror-model";
4
+ import { EditorView, NodeView } from "prosemirror-view";
5
+ /** The canonical name for the chart node in a ProseMirror schema. */
6
+ export declare const CHART_NODE_NAME = "chart";
7
+ /** The DOM attribute carrying the serialised spec. */
8
+ export declare const CHART_SPEC_ATTRIBUTE = "data-chart-spec";
9
+ /** Options for {@link createChartNodeSpec}. */
10
+ interface ChartNodeSpecOptions {
11
+ /** The node group. Defaults to 'block'. */
12
+ readonly group?: string | undefined;
13
+ /** Extra class for the rendered DOM element. */
14
+ readonly className?: string | undefined;
15
+ }
16
+ /** The attrs shape of a chart node. */
17
+ interface ChartNodeAttrs {
18
+ /** The portable chart spec, or null for an empty/placeholder chart. */
19
+ readonly spec: ChartSpec | null;
20
+ }
21
+ /**
22
+ * Creates the ProseMirror NodeSpec for a chart node.
23
+ *
24
+ * The chart is an atom (leaf) node whose only attr is the portable chart
25
+ * spec — pure JSON, so document persistence (toJSON / fromJSON) carries the
26
+ * spec with zero extra work. DOM serialisation stores the spec JSON in a
27
+ * data attribute so copy/paste and HTML round-trips preserve it too.
28
+ */
29
+ export declare function createChartNodeSpec(options?: ChartNodeSpecOptions): NodeSpec;
30
+ /** Resolves a data reference to rows. The host (CMS) owns how refs are fetched. */
31
+ type DataResolver = (spec: ChartSpec) => DataRows | Promise<DataRows>;
32
+ /** Options for {@link ChartNodeView} / {@link chartNodeViewFactory}. */
33
+ interface ChartNodeViewOptions {
34
+ /**
35
+ * The box used to compute geometry when the editor element has no
36
+ * measurable width yet. The SVG scales to its container via viewBox,
37
+ * so this only sets the aspect ratio and coordinate precision.
38
+ */
39
+ readonly defaultBox?: Box | undefined;
40
+ /** Resolves data refs at edit time. Without it, ref-data charts show a placeholder. */
41
+ readonly resolveData?: DataResolver | undefined;
42
+ /** Extra class for the wrapper element. */
43
+ readonly className?: string | undefined;
44
+ }
45
+ /**
46
+ * Edit-time chart rendering inside ProseMirror.
47
+ *
48
+ * A NodeView is imperative DOM — exactly why the pure Layer 2 core pays off:
49
+ * this class owns a plain <div>, computes geometry via the `/core` entry, and sets
50
+ * SVG markup. No framework, no reactivity.
51
+ */
52
+ export declare class ChartNodeView implements NodeView {
53
+ /** The wrapper element ProseMirror mounts; it holds the SVG or a placeholder message. */
54
+ readonly dom: HTMLElement;
55
+ private node;
56
+ private readonly options;
57
+ /** Guards against stale async data resolutions racing a newer render. */
58
+ private renderToken;
59
+ constructor(node: Node, _view: EditorView, options?: ChartNodeViewOptions);
60
+ /** ProseMirror calls this when the node updates (e.g. the spec attr changes). */
61
+ update(node: Node): boolean;
62
+ /** The NodeView owns its DOM; editor mutations inside it are ignored. */
63
+ ignoreMutation(): boolean;
64
+ /** Invalidates any in-flight async data resolution so it cannot render after removal. */
65
+ destroy(): void;
66
+ private render;
67
+ private renderRefData;
68
+ private renderChart;
69
+ private renderMessage;
70
+ private measureBox;
71
+ }
72
+ /**
73
+ * Creates a NodeView factory for ProseMirror's `nodeViews` editor prop:
74
+ *
75
+ * ```ts
76
+ * new EditorView(place, {
77
+ * state,
78
+ * nodeViews: { chart: chartNodeViewFactory({ resolveData }) },
79
+ * });
80
+ * ```
81
+ */
82
+ export declare function chartNodeViewFactory(options?: ChartNodeViewOptions): (node: Node, view: EditorView, getPos: () => number | undefined) => ChartNodeView;
83
+ /** Options for {@link renderContextToSvg}. */
84
+ interface RenderSvgOptions {
85
+ /** Chart title rendered above the plot. */
86
+ readonly title?: string | undefined;
87
+ /** Class attribute for the root <svg> element. */
88
+ readonly className?: string | undefined;
89
+ }
90
+ /**
91
+ * Renders a computed ChartContext to an SVG string.
92
+ *
93
+ * Pure string building — no DOM required — so it works in the ProseMirror
94
+ * NodeView (via innerHTML), in server-side rendering, and in headless tests.
95
+ * The SVG uses a viewBox so it scales to its container via CSS.
96
+ */
97
+ export declare function renderContextToSvg(context: ChartContext, options?: RenderSvgOptions): string;
98
+ /**
99
+ * A tree's text alternative as an HTML string: the hierarchy as a nested
100
+ * list in depth-first order, visually hidden. Place it after the SVG from
101
+ * {@link renderContextToSvg}: the SVG is `role="img"`, whose children are
102
+ * presentational. Empty for every other mark.
103
+ */
104
+ export declare function renderTreeOutline(context: ChartContext): string;
105
+ export type { ChartNodeAttrs, ChartNodeSpecOptions, ChartNodeViewOptions, DataResolver, RenderSvgOptions };
106
+ //# sourceMappingURL=prosemirror.d.mts.map
@@ -0,0 +1,245 @@
1
+ import { a as isDataRef, i as validateSpec, n as encodeSpec } from "./spec-FF2Tds2a.mjs";
2
+ import { d as CATEGORICAL_PALETTE, l as outlineText, n as computeContext, s as VISUALLY_HIDDEN_STYLE } from "./core-BVbfW7N6.mjs";
3
+ // src/prosemirror/node-spec.ts
4
+ /** The canonical name for the chart node in a ProseMirror schema. */
5
+ const CHART_NODE_NAME = "chart";
6
+ /** The DOM attribute carrying the serialised spec. */
7
+ const CHART_SPEC_ATTRIBUTE = "data-chart-spec";
8
+ /**
9
+ * Creates the ProseMirror NodeSpec for a chart node.
10
+ *
11
+ * The chart is an atom (leaf) node whose only attr is the portable chart
12
+ * spec — pure JSON, so document persistence (toJSON / fromJSON) carries the
13
+ * spec with zero extra work. DOM serialisation stores the spec JSON in a
14
+ * data attribute so copy/paste and HTML round-trips preserve it too.
15
+ */
16
+ function createChartNodeSpec(options) {
17
+ const className = options?.className ?? "chart-node";
18
+ return {
19
+ group: options?.group ?? "block",
20
+ atom: true,
21
+ selectable: true,
22
+ draggable: true,
23
+ attrs: { spec: { default: null } },
24
+ parseDOM: [{
25
+ tag: `div[${CHART_SPEC_ATTRIBUTE}]`,
26
+ getAttrs: (dom) => {
27
+ const raw = dom.getAttribute(CHART_SPEC_ATTRIBUTE);
28
+ if (!raw) return { spec: null };
29
+ const parsed = parseJson(raw);
30
+ if (parsed === void 0) return false;
31
+ const result = validateSpec(parsed);
32
+ return result.ok ? { spec: result.value } : false;
33
+ }
34
+ }],
35
+ toDOM: (node) => {
36
+ const attrs = node.attrs;
37
+ return ["div", {
38
+ class: className,
39
+ [CHART_SPEC_ATTRIBUTE]: attrs.spec ? JSON.stringify(encodeSpec(attrs.spec)) : ""
40
+ }];
41
+ }
42
+ };
43
+ }
44
+ function parseJson(raw) {
45
+ try {
46
+ return JSON.parse(raw);
47
+ } catch {
48
+ return;
49
+ }
50
+ }
51
+ // src/prosemirror/render-svg.ts
52
+ const AXIS_COLOR = "#9ca3af";
53
+ const LABEL_COLOR = "#4b5563";
54
+ const TITLE_COLOR = "#111827";
55
+ const FONT = "font-family=\"system-ui, sans-serif\"";
56
+ const DEFAULT_COLOR = CATEGORICAL_PALETTE[0];
57
+ /**
58
+ * Renders a computed ChartContext to an SVG string.
59
+ *
60
+ * Pure string building — no DOM required — so it works in the ProseMirror
61
+ * NodeView (via innerHTML), in server-side rendering, and in headless tests.
62
+ * The SVG uses a viewBox so it scales to its container via CSS.
63
+ */
64
+ function renderContextToSvg(context, options) {
65
+ const { outer, margins } = context.dimensions;
66
+ const parts = [];
67
+ const classAttr = options?.className ? ` class="${escapeXml(options.className)}"` : "";
68
+ parts.push(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${outer.width} ${outer.height}"${classAttr} role="img">`);
69
+ if (options?.title) parts.push(`<text x="${outer.width / 2}" y="${margins.top / 2}" text-anchor="middle" dominant-baseline="middle" ${FONT} font-size="14" font-weight="600" fill="${TITLE_COLOR}">${escapeXml(options.title)}</text>`);
70
+ parts.push(`<g transform="translate(${margins.left},${margins.top})">`);
71
+ if (context.geometry.mark !== "tree") parts.push(renderAxes(context));
72
+ parts.push(renderGeometry(context));
73
+ parts.push("</g>");
74
+ parts.push("</svg>");
75
+ return parts.filter((part) => part.length > 0).join("");
76
+ }
77
+ function renderAxes(context) {
78
+ const { plot } = context.dimensions;
79
+ const parts = [];
80
+ parts.push(`<line x1="0" y1="${plot.height}" x2="${plot.width}" y2="${plot.height}" stroke="${AXIS_COLOR}"/>`);
81
+ parts.push(`<line x1="0" y1="0" x2="0" y2="${plot.height}" stroke="${AXIS_COLOR}"/>`);
82
+ for (const tick of context.ticks.x ?? []) parts.push(renderXTick(tick, plot.height));
83
+ for (const tick of context.ticks.y ?? []) parts.push(renderYTick(tick));
84
+ return parts.join("");
85
+ }
86
+ function renderXTick(tick, plotHeight) {
87
+ return `<g transform="translate(${tick.position},${plotHeight})"><line y2="6" stroke="${AXIS_COLOR}"/><text y="18" text-anchor="middle" ${FONT} font-size="11" fill="${LABEL_COLOR}">${escapeXml(tick.label)}</text></g>`;
88
+ }
89
+ function renderYTick(tick) {
90
+ return `<g transform="translate(0,${tick.position})"><line x2="-6" stroke="${AXIS_COLOR}"/><text x="-10" text-anchor="end" dominant-baseline="middle" ${FONT} font-size="11" fill="${LABEL_COLOR}">${escapeXml(tick.label)}</text></g>`;
91
+ }
92
+ function renderGeometry(context) {
93
+ const geometry = context.geometry;
94
+ switch (geometry.mark) {
95
+ case "line": return geometry.series.filter((series) => series.path.length > 0).map((series) => `<path d="${series.path}" fill="none" stroke="${series.color ?? DEFAULT_COLOR}" stroke-width="2"/>`).join("");
96
+ case "area": return geometry.series.filter((series) => series.path.length > 0).map((series) => `<path d="${series.path}" fill="${series.color ?? DEFAULT_COLOR}" fill-opacity="0.7" stroke="none"/>`).join("");
97
+ case "bar": return geometry.bars.map((bar) => `<rect x="${bar.x}" y="${bar.y}" width="${bar.width}" height="${bar.height}" fill="${bar.color ?? DEFAULT_COLOR}"/>`).join("");
98
+ case "tree": return renderTree(geometry);
99
+ case "point": return geometry.points.map((point) => `<circle cx="${point.cx}" cy="${point.cy}" r="${point.r}" fill="${point.color ?? DEFAULT_COLOR}"/>`).join("");
100
+ }
101
+ }
102
+ const LINK_COLOR = "#9ca3af";
103
+ /** A `tree` mark (spec 1.1): links under the node boxes, each box labelled. */
104
+ function renderTree(geometry) {
105
+ const links = geometry.links.map((link) => `<path d="${link.path}" fill="none" stroke="${LINK_COLOR}"/>`);
106
+ const nodes = geometry.nodes.map((node) => `<g><rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="4" fill="#ffffff" stroke="${node.color ?? DEFAULT_COLOR}" stroke-width="1.5"/><text x="${node.x + node.width / 2}" y="${node.y + node.height / 2}" text-anchor="middle" dominant-baseline="middle" ${FONT} font-size="11" fill="${TITLE_COLOR}">${escapeXml(node.label)}</text></g>`);
107
+ return [...links, ...nodes].join("");
108
+ }
109
+ function outlineList(nodes) {
110
+ return `<ul>${nodes.map((node) => `<li>${escapeXml(outlineText(node))}${node.children.length > 0 ? outlineList(node.children) : ""}</li>`).join("")}</ul>`;
111
+ }
112
+ /**
113
+ * A tree's text alternative as an HTML string: the hierarchy as a nested
114
+ * list in depth-first order, visually hidden. Place it after the SVG from
115
+ * {@link renderContextToSvg}: the SVG is `role="img"`, whose children are
116
+ * presentational. Empty for every other mark.
117
+ */
118
+ function renderTreeOutline(context) {
119
+ if (context.geometry.mark !== "tree") return "";
120
+ return `<div style="${VISUALLY_HIDDEN_STYLE}" data-chart-outline="">${outlineList(context.geometry.outline)}</div>`;
121
+ }
122
+ function escapeXml(value) {
123
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
124
+ }
125
+ // src/prosemirror/node-view.ts
126
+ const DEFAULT_BOX = {
127
+ width: 640,
128
+ height: 360
129
+ };
130
+ /**
131
+ * Edit-time chart rendering inside ProseMirror.
132
+ *
133
+ * A NodeView is imperative DOM — exactly why the pure Layer 2 core pays off:
134
+ * this class owns a plain <div>, computes geometry via the `/core` entry, and sets
135
+ * SVG markup. No framework, no reactivity.
136
+ */
137
+ var ChartNodeView = class {
138
+ /** The wrapper element ProseMirror mounts; it holds the SVG or a placeholder message. */
139
+ dom;
140
+ node;
141
+ options;
142
+ /** Guards against stale async data resolutions racing a newer render. */
143
+ renderToken = 0;
144
+ constructor(node, _view, options) {
145
+ this.node = node;
146
+ this.options = options ?? {};
147
+ this.dom = document.createElement("div");
148
+ this.dom.className = this.options.className ?? "chart-node-view";
149
+ this.render();
150
+ }
151
+ /** ProseMirror calls this when the node updates (e.g. the spec attr changes). */
152
+ update(node) {
153
+ if (node.type !== this.node.type) return false;
154
+ this.node = node;
155
+ this.render();
156
+ return true;
157
+ }
158
+ /** The NodeView owns its DOM; editor mutations inside it are ignored. */
159
+ ignoreMutation() {
160
+ return true;
161
+ }
162
+ /** Invalidates any in-flight async data resolution so it cannot render after removal. */
163
+ destroy() {
164
+ this.renderToken += 1;
165
+ }
166
+ render() {
167
+ const token = ++this.renderToken;
168
+ const spec = this.node.attrs.spec;
169
+ if (!spec) {
170
+ this.renderMessage("No chart configured");
171
+ return;
172
+ }
173
+ const validation = validateSpec(spec);
174
+ if (!validation.ok) {
175
+ this.renderMessage("Invalid chart spec");
176
+ return;
177
+ }
178
+ if (isDataRef(validation.value.data)) {
179
+ this.renderRefData(validation.value, token);
180
+ return;
181
+ }
182
+ this.renderChart(validation.value, void 0);
183
+ }
184
+ renderRefData(spec, token) {
185
+ const resolve = this.options.resolveData;
186
+ if (!resolve) {
187
+ this.renderMessage("Chart data is stored externally");
188
+ return;
189
+ }
190
+ const resolved = resolve(spec);
191
+ if (isPromise(resolved)) {
192
+ this.renderMessage("Loading chart data…");
193
+ resolved.then((rows) => {
194
+ if (token === this.renderToken) this.renderChart(spec, rows);
195
+ }).catch(() => {
196
+ if (token === this.renderToken) this.renderMessage("Failed to load chart data");
197
+ });
198
+ return;
199
+ }
200
+ this.renderChart(spec, resolved);
201
+ }
202
+ renderChart(spec, rows) {
203
+ const box = this.measureBox();
204
+ try {
205
+ const context = computeContext(spec, box, rows);
206
+ this.dom.innerHTML = renderContextToSvg(context, { title: spec.config?.title }) + renderTreeOutline(context);
207
+ } catch (error) {
208
+ this.renderMessage(error instanceof Error ? error.message : "Failed to render chart");
209
+ }
210
+ }
211
+ renderMessage(message) {
212
+ const placeholder = document.createElement("div");
213
+ placeholder.className = "chart-node-view__placeholder";
214
+ placeholder.textContent = message;
215
+ this.dom.replaceChildren(placeholder);
216
+ }
217
+ measureBox() {
218
+ const fallback = this.options.defaultBox ?? DEFAULT_BOX;
219
+ const width = this.dom.clientWidth;
220
+ if (width > 0) return {
221
+ width,
222
+ height: Math.round(width * fallback.height / fallback.width)
223
+ };
224
+ return fallback;
225
+ }
226
+ };
227
+ /**
228
+ * Creates a NodeView factory for ProseMirror's `nodeViews` editor prop:
229
+ *
230
+ * ```ts
231
+ * new EditorView(place, {
232
+ * state,
233
+ * nodeViews: { chart: chartNodeViewFactory({ resolveData }) },
234
+ * });
235
+ * ```
236
+ */
237
+ function chartNodeViewFactory(options) {
238
+ return (node, view) => new ChartNodeView(node, view, options);
239
+ }
240
+ function isPromise(value) {
241
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
242
+ }
243
+ export { CHART_NODE_NAME, CHART_SPEC_ATTRIBUTE, ChartNodeView, chartNodeViewFactory, createChartNodeSpec, renderContextToSvg, renderTreeOutline };
244
+
245
+ //# sourceMappingURL=prosemirror.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prosemirror.mjs","names":[],"sources":["../src/prosemirror/node-spec.ts","../src/prosemirror/render-svg.ts","../src/prosemirror/node-view.ts"],"sourcesContent":["import { encodeSpec, validateSpec, type ChartSpec } from '../spec/index.ts';\nimport type { NodeSpec } from 'prosemirror-model';\n\n/** The canonical name for the chart node in a ProseMirror schema. */\nexport const CHART_NODE_NAME = 'chart';\n\n/** The DOM attribute carrying the serialised spec. */\nexport const CHART_SPEC_ATTRIBUTE = 'data-chart-spec';\n\n/** Options for {@link createChartNodeSpec}. */\nexport interface ChartNodeSpecOptions {\n /** The node group. Defaults to 'block'. */\n readonly group?: string | undefined;\n /** Extra class for the rendered DOM element. */\n readonly className?: string | undefined;\n}\n\n/** The attrs shape of a chart node. */\nexport interface ChartNodeAttrs {\n /** The portable chart spec, or null for an empty/placeholder chart. */\n readonly spec: ChartSpec | null;\n}\n\n/**\n * Creates the ProseMirror NodeSpec for a chart node.\n *\n * The chart is an atom (leaf) node whose only attr is the portable chart\n * spec — pure JSON, so document persistence (toJSON / fromJSON) carries the\n * spec with zero extra work. DOM serialisation stores the spec JSON in a\n * data attribute so copy/paste and HTML round-trips preserve it too.\n */\nexport function createChartNodeSpec(options?: ChartNodeSpecOptions): NodeSpec {\n const className = options?.className ?? 'chart-node';\n\n return {\n group: options?.group ?? 'block',\n atom: true,\n // Charts are self-contained data; they have no editable text content.\n selectable: true,\n draggable: true,\n attrs: {\n spec: { default: null },\n },\n parseDOM: [\n {\n tag: `div[${CHART_SPEC_ATTRIBUTE}]`,\n getAttrs: (dom) => {\n const raw = dom.getAttribute(CHART_SPEC_ATTRIBUTE);\n if (!raw) return { spec: null };\n const parsed = parseJson(raw);\n if (parsed === undefined) return false;\n const result = validateSpec(parsed);\n // An unparseable spec is not a chart node; let other rules try.\n return result.ok ? { spec: result.value } : false;\n },\n },\n ],\n toDOM: (node) => {\n const attrs = node.attrs as ChartNodeAttrs;\n return [\n 'div',\n {\n class: className,\n [CHART_SPEC_ATTRIBUTE]: attrs.spec ? JSON.stringify(encodeSpec(attrs.spec)) : '',\n },\n ];\n },\n };\n}\n\nfunction parseJson(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n return undefined;\n }\n}\n","import {\n CATEGORICAL_PALETTE,\n outlineText,\n VISUALLY_HIDDEN_STYLE,\n type ChartContext,\n type Tick,\n type TreeGeometry,\n type TreeOutlineNode,\n} from '../core/index.ts';\n\n/** Options for {@link renderContextToSvg}. */\nexport interface RenderSvgOptions {\n /** Chart title rendered above the plot. */\n readonly title?: string | undefined;\n /** Class attribute for the root <svg> element. */\n readonly className?: string | undefined;\n}\n\nconst AXIS_COLOR = '#9ca3af';\nconst LABEL_COLOR = '#4b5563';\nconst TITLE_COLOR = '#111827';\nconst FONT = 'font-family=\"system-ui, sans-serif\"';\nconst DEFAULT_COLOR = CATEGORICAL_PALETTE[0]!;\n\n/**\n * Renders a computed ChartContext to an SVG string.\n *\n * Pure string building — no DOM required — so it works in the ProseMirror\n * NodeView (via innerHTML), in server-side rendering, and in headless tests.\n * The SVG uses a viewBox so it scales to its container via CSS.\n */\nexport function renderContextToSvg(context: ChartContext, options?: RenderSvgOptions): string {\n const { outer, margins } = context.dimensions;\n const parts: string[] = [];\n\n const classAttr = options?.className ? ` class=\"${escapeXml(options.className)}\"` : '';\n parts.push(\n `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 ${outer.width} ${outer.height}\"${classAttr} role=\"img\">`,\n );\n\n if (options?.title) {\n parts.push(\n `<text x=\"${outer.width / 2}\" y=\"${margins.top / 2}\" text-anchor=\"middle\" dominant-baseline=\"middle\" ` +\n `${FONT} font-size=\"14\" font-weight=\"600\" fill=\"${TITLE_COLOR}\">${escapeXml(options.title)}</text>`,\n );\n }\n\n // Plot group: geometry and axes are in plot coordinates.\n parts.push(`<g transform=\"translate(${margins.left},${margins.top})\">`);\n if (context.geometry.mark !== 'tree') parts.push(renderAxes(context));\n parts.push(renderGeometry(context));\n parts.push('</g>');\n\n parts.push('</svg>');\n return parts.filter((part) => part.length > 0).join('');\n}\n\nfunction renderAxes(context: ChartContext): string {\n const { plot } = context.dimensions;\n const parts: string[] = [];\n\n // Axis lines.\n parts.push(\n `<line x1=\"0\" y1=\"${plot.height}\" x2=\"${plot.width}\" y2=\"${plot.height}\" stroke=\"${AXIS_COLOR}\"/>`,\n );\n parts.push(`<line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"${plot.height}\" stroke=\"${AXIS_COLOR}\"/>`);\n\n // X ticks: below the plot.\n for (const tick of context.ticks.x ?? []) {\n parts.push(renderXTick(tick, plot.height));\n }\n // Y ticks: left of the plot.\n for (const tick of context.ticks.y ?? []) {\n parts.push(renderYTick(tick));\n }\n\n return parts.join('');\n}\n\nfunction renderXTick(tick: Tick, plotHeight: number): string {\n return (\n `<g transform=\"translate(${tick.position},${plotHeight})\">` +\n `<line y2=\"6\" stroke=\"${AXIS_COLOR}\"/>` +\n `<text y=\"18\" text-anchor=\"middle\" ${FONT} font-size=\"11\" fill=\"${LABEL_COLOR}\">${escapeXml(tick.label)}</text>` +\n '</g>'\n );\n}\n\nfunction renderYTick(tick: Tick): string {\n return (\n `<g transform=\"translate(0,${tick.position})\">` +\n `<line x2=\"-6\" stroke=\"${AXIS_COLOR}\"/>` +\n `<text x=\"-10\" text-anchor=\"end\" dominant-baseline=\"middle\" ${FONT} font-size=\"11\" fill=\"${LABEL_COLOR}\">${escapeXml(tick.label)}</text>` +\n '</g>'\n );\n}\n\nfunction renderGeometry(context: ChartContext): string {\n const geometry = context.geometry;\n switch (geometry.mark) {\n case 'line':\n return geometry.series\n .filter((series) => series.path.length > 0)\n .map(\n (series) =>\n `<path d=\"${series.path}\" fill=\"none\" stroke=\"${series.color ?? DEFAULT_COLOR}\" stroke-width=\"2\"/>`,\n )\n .join('');\n case 'area':\n return geometry.series\n .filter((series) => series.path.length > 0)\n .map(\n (series) =>\n `<path d=\"${series.path}\" fill=\"${series.color ?? DEFAULT_COLOR}\" fill-opacity=\"0.7\" stroke=\"none\"/>`,\n )\n .join('');\n case 'bar':\n return geometry.bars\n .map(\n (bar) =>\n `<rect x=\"${bar.x}\" y=\"${bar.y}\" width=\"${bar.width}\" height=\"${bar.height}\" fill=\"${bar.color ?? DEFAULT_COLOR}\"/>`,\n )\n .join('');\n case 'tree':\n return renderTree(geometry);\n case 'point':\n return geometry.points\n .map(\n (point) =>\n `<circle cx=\"${point.cx}\" cy=\"${point.cy}\" r=\"${point.r}\" fill=\"${point.color ?? DEFAULT_COLOR}\"/>`,\n )\n .join('');\n }\n}\n\nconst LINK_COLOR = '#9ca3af';\n\n/** A `tree` mark (spec 1.1): links under the node boxes, each box labelled. */\nfunction renderTree(geometry: TreeGeometry): string {\n const links = geometry.links.map(\n (link) => `<path d=\"${link.path}\" fill=\"none\" stroke=\"${LINK_COLOR}\"/>`,\n );\n const nodes = geometry.nodes.map(\n (node) =>\n `<g><rect x=\"${node.x}\" y=\"${node.y}\" width=\"${node.width}\" height=\"${node.height}\" rx=\"4\" fill=\"#ffffff\" ` +\n `stroke=\"${node.color ?? DEFAULT_COLOR}\" stroke-width=\"1.5\"/>` +\n `<text x=\"${node.x + node.width / 2}\" y=\"${node.y + node.height / 2}\" text-anchor=\"middle\" dominant-baseline=\"middle\" ` +\n `${FONT} font-size=\"11\" fill=\"${TITLE_COLOR}\">${escapeXml(node.label)}</text></g>`,\n );\n return [...links, ...nodes].join('');\n}\n\nfunction outlineList(nodes: ReadonlyArray<TreeOutlineNode>): string {\n const items = nodes.map(\n (node) =>\n `<li>${escapeXml(outlineText(node))}${node.children.length > 0 ? outlineList(node.children) : ''}</li>`,\n );\n return `<ul>${items.join('')}</ul>`;\n}\n\n/**\n * A tree's text alternative as an HTML string: the hierarchy as a nested\n * list in depth-first order, visually hidden. Place it after the SVG from\n * {@link renderContextToSvg}: the SVG is `role=\"img\"`, whose children are\n * presentational. Empty for every other mark.\n */\nexport function renderTreeOutline(context: ChartContext): string {\n if (context.geometry.mark !== 'tree') return '';\n return `<div style=\"${VISUALLY_HIDDEN_STYLE}\" data-chart-outline=\"\">${outlineList(context.geometry.outline)}</div>`;\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&apos;');\n}\n","import { computeContext, type Box, type DataRows } from '../core/index.ts';\nimport { isDataRef, validateSpec, type ChartSpec } from '../spec/index.ts';\nimport type { Node as ProseMirrorNode } from 'prosemirror-model';\nimport type { EditorView, NodeView } from 'prosemirror-view';\nimport type { ChartNodeAttrs } from './node-spec.ts';\nimport { renderContextToSvg, renderTreeOutline } from './render-svg.ts';\n\n/** Resolves a data reference to rows. The host (CMS) owns how refs are fetched. */\nexport type DataResolver = (spec: ChartSpec) => DataRows | Promise<DataRows>;\n\n/** Options for {@link ChartNodeView} / {@link chartNodeViewFactory}. */\nexport interface ChartNodeViewOptions {\n /**\n * The box used to compute geometry when the editor element has no\n * measurable width yet. The SVG scales to its container via viewBox,\n * so this only sets the aspect ratio and coordinate precision.\n */\n readonly defaultBox?: Box | undefined;\n /** Resolves data refs at edit time. Without it, ref-data charts show a placeholder. */\n readonly resolveData?: DataResolver | undefined;\n /** Extra class for the wrapper element. */\n readonly className?: string | undefined;\n}\n\nconst DEFAULT_BOX: Box = { width: 640, height: 360 };\n\n/**\n * Edit-time chart rendering inside ProseMirror.\n *\n * A NodeView is imperative DOM — exactly why the pure Layer 2 core pays off:\n * this class owns a plain <div>, computes geometry via the `/core` entry, and sets\n * SVG markup. No framework, no reactivity.\n */\nexport class ChartNodeView implements NodeView {\n /** The wrapper element ProseMirror mounts; it holds the SVG or a placeholder message. */\n readonly dom: HTMLElement;\n\n private node: ProseMirrorNode;\n private readonly options: ChartNodeViewOptions;\n /** Guards against stale async data resolutions racing a newer render. */\n private renderToken = 0;\n\n constructor(node: ProseMirrorNode, _view: EditorView, options?: ChartNodeViewOptions) {\n this.node = node;\n this.options = options ?? {};\n this.dom = document.createElement('div');\n this.dom.className = this.options.className ?? 'chart-node-view';\n this.render();\n }\n\n /** ProseMirror calls this when the node updates (e.g. the spec attr changes). */\n update(node: ProseMirrorNode): boolean {\n if (node.type !== this.node.type) return false;\n this.node = node;\n this.render();\n return true;\n }\n\n /** The NodeView owns its DOM; editor mutations inside it are ignored. */\n ignoreMutation(): boolean {\n return true;\n }\n\n /** Invalidates any in-flight async data resolution so it cannot render after removal. */\n destroy(): void {\n // Invalidate any in-flight async render.\n this.renderToken += 1;\n }\n\n private render(): void {\n const token = ++this.renderToken;\n const spec = (this.node.attrs as ChartNodeAttrs).spec;\n\n if (!spec) {\n this.renderMessage('No chart configured');\n return;\n }\n\n // Re-validate: node attrs may come from collaborative peers or pasted docs.\n const validation = validateSpec(spec);\n if (!validation.ok) {\n this.renderMessage('Invalid chart spec');\n return;\n }\n\n if (isDataRef(validation.value.data)) {\n this.renderRefData(validation.value, token);\n return;\n }\n\n this.renderChart(validation.value, undefined);\n }\n\n private renderRefData(spec: ChartSpec, token: number): void {\n const resolve = this.options.resolveData;\n if (!resolve) {\n this.renderMessage('Chart data is stored externally');\n return;\n }\n\n const resolved = resolve(spec);\n if (isPromise(resolved)) {\n this.renderMessage('Loading chart data…');\n resolved\n .then((rows) => {\n if (token === this.renderToken) this.renderChart(spec, rows);\n })\n .catch(() => {\n if (token === this.renderToken) this.renderMessage('Failed to load chart data');\n });\n return;\n }\n\n this.renderChart(spec, resolved);\n }\n\n private renderChart(spec: ChartSpec, rows: DataRows | undefined): void {\n const box = this.measureBox();\n try {\n const context = computeContext(spec, box, rows);\n this.dom.innerHTML =\n renderContextToSvg(context, { title: spec.config?.title }) + renderTreeOutline(context);\n } catch (error) {\n this.renderMessage(error instanceof Error ? error.message : 'Failed to render chart');\n }\n }\n\n private renderMessage(message: string): void {\n const placeholder = document.createElement('div');\n placeholder.className = 'chart-node-view__placeholder';\n placeholder.textContent = message;\n this.dom.replaceChildren(placeholder);\n }\n\n private measureBox(): Box {\n const fallback = this.options.defaultBox ?? DEFAULT_BOX;\n const width = this.dom.clientWidth;\n if (width > 0) {\n // Preserve the configured aspect ratio at the measured width.\n return { width, height: Math.round((width * fallback.height) / fallback.width) };\n }\n return fallback;\n }\n}\n\n/**\n * Creates a NodeView factory for ProseMirror's `nodeViews` editor prop:\n *\n * ```ts\n * new EditorView(place, {\n * state,\n * nodeViews: { chart: chartNodeViewFactory({ resolveData }) },\n * });\n * ```\n */\nexport function chartNodeViewFactory(\n options?: ChartNodeViewOptions,\n): (node: ProseMirrorNode, view: EditorView, getPos: () => number | undefined) => ChartNodeView {\n return (node, view) => new ChartNodeView(node, view, options);\n}\n\nfunction isPromise<T>(value: T | Promise<T>): value is Promise<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'then' in value &&\n typeof (value as { then: unknown }).then === 'function'\n );\n}\n"],"mappings":";;;;AAIA,MAAa,kBAAkB;;AAG/B,MAAa,uBAAuB;;;;;;;;;AAwBpC,SAAgB,oBAAoB,SAA0C;CAC5E,MAAM,YAAY,SAAS,aAAa;CAExC,OAAO;EACL,OAAO,SAAS,SAAS;EACzB,MAAM;EAEN,YAAY;EACZ,WAAW;EACX,OAAO,EACL,MAAM,EAAE,SAAS,KAAK,EACxB;EACA,UAAU,CACR;GACE,KAAK,OAAO,qBAAqB;GACjC,WAAW,QAAQ;IACjB,MAAM,MAAM,IAAI,aAAa,oBAAoB;IACjD,IAAI,CAAC,KAAK,OAAO,EAAE,MAAM,KAAK;IAC9B,MAAM,SAAS,UAAU,GAAG;IAC5B,IAAI,WAAW,KAAA,GAAW,OAAO;IACjC,MAAM,SAAS,aAAa,MAAM;IAElC,OAAO,OAAO,KAAK,EAAE,MAAM,OAAO,MAAM,IAAI;GAC9C;EACF,CACF;EACA,QAAQ,SAAS;GACf,MAAM,QAAQ,KAAK;GACnB,OAAO,CACL,OACA;IACE,OAAO;KACN,uBAAuB,MAAM,OAAO,KAAK,UAAU,WAAW,MAAM,IAAI,CAAC,IAAI;GAChF,CACF;EACF;CACF;AACF;AAEA,SAAS,UAAU,KAAsB;CACvC,IAAI;EACF,OAAO,KAAK,MAAM,GAAG;CACvB,QAAQ;EACN;CACF;AACF;;AC1DA,MAAM,aAAa;AACnB,MAAM,cAAc;AACpB,MAAM,cAAc;AACpB,MAAM,OAAO;AACb,MAAM,gBAAgB,oBAAoB;;;;;;;;AAS1C,SAAgB,mBAAmB,SAAuB,SAAoC;CAC5F,MAAM,EAAE,OAAO,YAAY,QAAQ;CACnC,MAAM,QAAkB,CAAC;CAEzB,MAAM,YAAY,SAAS,YAAY,WAAW,UAAU,QAAQ,SAAS,EAAE,KAAK;CACpF,MAAM,KACJ,wDAAwD,MAAM,MAAM,GAAG,MAAM,OAAO,GAAG,UAAU,aACnG;CAEA,IAAI,SAAS,OACX,MAAM,KACJ,YAAY,MAAM,QAAQ,EAAE,OAAO,QAAQ,MAAM,EAAE,oDAC9C,KAAK,0CAA0C,YAAY,IAAI,UAAU,QAAQ,KAAK,EAAE,QAC/F;CAIF,MAAM,KAAK,2BAA2B,QAAQ,KAAK,GAAG,QAAQ,IAAI,IAAI;CACtE,IAAI,QAAQ,SAAS,SAAS,QAAQ,MAAM,KAAK,WAAW,OAAO,CAAC;CACpE,MAAM,KAAK,eAAe,OAAO,CAAC;CAClC,MAAM,KAAK,MAAM;CAEjB,MAAM,KAAK,QAAQ;CACnB,OAAO,MAAM,QAAQ,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE;AACxD;AAEA,SAAS,WAAW,SAA+B;CACjD,MAAM,EAAE,SAAS,QAAQ;CACzB,MAAM,QAAkB,CAAC;CAGzB,MAAM,KACJ,oBAAoB,KAAK,OAAO,QAAQ,KAAK,MAAM,QAAQ,KAAK,OAAO,YAAY,WAAW,IAChG;CACA,MAAM,KAAK,kCAAkC,KAAK,OAAO,YAAY,WAAW,IAAI;CAGpF,KAAK,MAAM,QAAQ,QAAQ,MAAM,KAAK,CAAC,GACrC,MAAM,KAAK,YAAY,MAAM,KAAK,MAAM,CAAC;CAG3C,KAAK,MAAM,QAAQ,QAAQ,MAAM,KAAK,CAAC,GACrC,MAAM,KAAK,YAAY,IAAI,CAAC;CAG9B,OAAO,MAAM,KAAK,EAAE;AACtB;AAEA,SAAS,YAAY,MAAY,YAA4B;CAC3D,OACE,2BAA2B,KAAK,SAAS,GAAG,WAAW,0BAC/B,WAAW,uCACE,KAAK,wBAAwB,YAAY,IAAI,UAAU,KAAK,KAAK,EAAE;AAG5G;AAEA,SAAS,YAAY,MAAoB;CACvC,OACE,6BAA6B,KAAK,SAAS,2BAClB,WAAW,gEAC0B,KAAK,wBAAwB,YAAY,IAAI,UAAU,KAAK,KAAK,EAAE;AAGrI;AAEA,SAAS,eAAe,SAA+B;CACrD,MAAM,WAAW,QAAQ;CACzB,QAAQ,SAAS,MAAjB;EACE,KAAK,QACH,OAAO,SAAS,OACb,QAAQ,WAAW,OAAO,KAAK,SAAS,CAAC,CAAC,CAC1C,KACE,WACC,YAAY,OAAO,KAAK,wBAAwB,OAAO,SAAS,cAAc,qBAClF,CAAC,CACA,KAAK,EAAE;EACZ,KAAK,QACH,OAAO,SAAS,OACb,QAAQ,WAAW,OAAO,KAAK,SAAS,CAAC,CAAC,CAC1C,KACE,WACC,YAAY,OAAO,KAAK,UAAU,OAAO,SAAS,cAAc,qCACpE,CAAC,CACA,KAAK,EAAE;EACZ,KAAK,OACH,OAAO,SAAS,KACb,KACE,QACC,YAAY,IAAI,EAAE,OAAO,IAAI,EAAE,WAAW,IAAI,MAAM,YAAY,IAAI,OAAO,UAAU,IAAI,SAAS,cAAc,IACpH,CAAC,CACA,KAAK,EAAE;EACZ,KAAK,QACH,OAAO,WAAW,QAAQ;EAC5B,KAAK,SACH,OAAO,SAAS,OACb,KACE,UACC,eAAe,MAAM,GAAG,QAAQ,MAAM,GAAG,OAAO,MAAM,EAAE,UAAU,MAAM,SAAS,cAAc,IACnG,CAAC,CACA,KAAK,EAAE;CACd;AACF;AAEA,MAAM,aAAa;;AAGnB,SAAS,WAAW,UAAgC;CAClD,MAAM,QAAQ,SAAS,MAAM,KAC1B,SAAS,YAAY,KAAK,KAAK,wBAAwB,WAAW,IACrE;CACA,MAAM,QAAQ,SAAS,MAAM,KAC1B,SACC,eAAe,KAAK,EAAE,OAAO,KAAK,EAAE,WAAW,KAAK,MAAM,YAAY,KAAK,OAAO,kCACvE,KAAK,SAAS,cAAc,iCAC3B,KAAK,IAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,IAAI,KAAK,SAAS,EAAE,oDACjE,KAAK,wBAAwB,YAAY,IAAI,UAAU,KAAK,KAAK,EAAE,YAC1E;CACA,OAAO,CAAC,GAAG,OAAO,GAAG,KAAK,CAAC,CAAC,KAAK,EAAE;AACrC;AAEA,SAAS,YAAY,OAA+C;CAKlE,OAAO,OAJO,MAAM,KACjB,SACC,OAAO,UAAU,YAAY,IAAI,CAAC,IAAI,KAAK,SAAS,SAAS,IAAI,YAAY,KAAK,QAAQ,IAAI,GAAG,MAEnF,CAAC,CAAC,KAAK,EAAE,EAAE;AAC/B;;;;;;;AAQA,SAAgB,kBAAkB,SAA+B;CAC/D,IAAI,QAAQ,SAAS,SAAS,QAAQ,OAAO;CAC7C,OAAO,eAAe,sBAAsB,0BAA0B,YAAY,QAAQ,SAAS,OAAO,EAAE;AAC9G;AAEA,SAAS,UAAU,OAAuB;CACxC,OAAO,MACJ,WAAW,KAAK,OAAO,CAAC,CACxB,WAAW,KAAK,MAAM,CAAC,CACvB,WAAW,KAAK,MAAM,CAAC,CACvB,WAAW,MAAK,QAAQ,CAAC,CACzB,WAAW,KAAK,QAAQ;AAC7B;;AC1JA,MAAM,cAAmB;CAAE,OAAO;CAAK,QAAQ;AAAI;;;;;;;;AASnD,IAAa,gBAAb,MAA+C;;CAE7C;CAEA;CACA;;CAEA,cAAsB;CAEtB,YAAY,MAAuB,OAAmB,SAAgC;EACpF,KAAK,OAAO;EACZ,KAAK,UAAU,WAAW,CAAC;EAC3B,KAAK,MAAM,SAAS,cAAc,KAAK;EACvC,KAAK,IAAI,YAAY,KAAK,QAAQ,aAAa;EAC/C,KAAK,OAAO;CACd;;CAGA,OAAO,MAAgC;EACrC,IAAI,KAAK,SAAS,KAAK,KAAK,MAAM,OAAO;EACzC,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,OAAO;CACT;;CAGA,iBAA0B;EACxB,OAAO;CACT;;CAGA,UAAgB;EAEd,KAAK,eAAe;CACtB;CAEA,SAAuB;EACrB,MAAM,QAAQ,EAAE,KAAK;EACrB,MAAM,OAAQ,KAAK,KAAK,MAAyB;EAEjD,IAAI,CAAC,MAAM;GACT,KAAK,cAAc,qBAAqB;GACxC;EACF;EAGA,MAAM,aAAa,aAAa,IAAI;EACpC,IAAI,CAAC,WAAW,IAAI;GAClB,KAAK,cAAc,oBAAoB;GACvC;EACF;EAEA,IAAI,UAAU,WAAW,MAAM,IAAI,GAAG;GACpC,KAAK,cAAc,WAAW,OAAO,KAAK;GAC1C;EACF;EAEA,KAAK,YAAY,WAAW,OAAO,KAAA,CAAS;CAC9C;CAEA,cAAsB,MAAiB,OAAqB;EAC1D,MAAM,UAAU,KAAK,QAAQ;EAC7B,IAAI,CAAC,SAAS;GACZ,KAAK,cAAc,iCAAiC;GACpD;EACF;EAEA,MAAM,WAAW,QAAQ,IAAI;EAC7B,IAAI,UAAU,QAAQ,GAAG;GACvB,KAAK,cAAc,qBAAqB;GACxC,SACG,MAAM,SAAS;IACd,IAAI,UAAU,KAAK,aAAa,KAAK,YAAY,MAAM,IAAI;GAC7D,CAAC,CAAC,CACD,YAAY;IACX,IAAI,UAAU,KAAK,aAAa,KAAK,cAAc,2BAA2B;GAChF,CAAC;GACH;EACF;EAEA,KAAK,YAAY,MAAM,QAAQ;CACjC;CAEA,YAAoB,MAAiB,MAAkC;EACrE,MAAM,MAAM,KAAK,WAAW;EAC5B,IAAI;GACF,MAAM,UAAU,eAAe,MAAM,KAAK,IAAI;GAC9C,KAAK,IAAI,YACP,mBAAmB,SAAS,EAAE,OAAO,KAAK,QAAQ,MAAM,CAAC,IAAI,kBAAkB,OAAO;EAC1F,SAAS,OAAO;GACd,KAAK,cAAc,iBAAiB,QAAQ,MAAM,UAAU,wBAAwB;EACtF;CACF;CAEA,cAAsB,SAAuB;EAC3C,MAAM,cAAc,SAAS,cAAc,KAAK;EAChD,YAAY,YAAY;EACxB,YAAY,cAAc;EAC1B,KAAK,IAAI,gBAAgB,WAAW;CACtC;CAEA,aAA0B;EACxB,MAAM,WAAW,KAAK,QAAQ,cAAc;EAC5C,MAAM,QAAQ,KAAK,IAAI;EACvB,IAAI,QAAQ,GAEV,OAAO;GAAE;GAAO,QAAQ,KAAK,MAAO,QAAQ,SAAS,SAAU,SAAS,KAAK;EAAE;EAEjF,OAAO;CACT;AACF;;;;;;;;;;;AAYA,SAAgB,qBACd,SAC8F;CAC9F,QAAQ,MAAM,SAAS,IAAI,cAAc,MAAM,MAAM,OAAO;AAC9D;AAEA,SAAS,UAAa,OAA4C;CAChE,OACE,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACV,OAAQ,MAA4B,SAAS;AAEjD"}