@mosaicoo/svg-engine 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.
- package/LICENSE +201 -0
- package/NOTICE +21 -0
- package/README.md +249 -0
- package/fesm2022/mosaicoo-svg-engine-ai-nlu-ui.mjs +459 -0
- package/fesm2022/mosaicoo-svg-engine-ai-nlu-voice-wasm.mjs +1 -0
- package/fesm2022/mosaicoo-svg-engine-ai-nlu.mjs +11 -0
- package/fesm2022/mosaicoo-svg-engine-core.mjs +3 -0
- package/fesm2022/mosaicoo-svg-engine-edit.mjs +2292 -0
- package/fesm2022/mosaicoo-svg-engine-io.mjs +47 -0
- package/fesm2022/mosaicoo-svg-engine-optimize.mjs +1 -0
- package/fesm2022/mosaicoo-svg-engine-render.mjs +301 -0
- package/fesm2022/mosaicoo-svg-engine-ui.mjs +14236 -0
- package/fesm2022/mosaicoo-svg-engine.mjs +1 -0
- package/package.json +105 -0
- package/types/mosaicoo-svg-engine-ai-nlu-ui.d.ts +416 -0
- package/types/mosaicoo-svg-engine-ai-nlu-voice-wasm.d.ts +175 -0
- package/types/mosaicoo-svg-engine-ai-nlu.d.ts +1834 -0
- package/types/mosaicoo-svg-engine-core.d.ts +5139 -0
- package/types/mosaicoo-svg-engine-edit.d.ts +11922 -0
- package/types/mosaicoo-svg-engine-io.d.ts +476 -0
- package/types/mosaicoo-svg-engine-optimize.d.ts +183 -0
- package/types/mosaicoo-svg-engine-render.d.ts +628 -0
- package/types/mosaicoo-svg-engine-ui.d.ts +6861 -0
- package/types/mosaicoo-svg-engine.d.ts +30 -0
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { Type } from '@angular/core';
|
|
3
|
+
import { SvgNode, BoundingBox, SvgDocument, EllipseNode, ImageNode, LineNode, SvgStyle, TextRun, PathNode, PolygonNode, PolylineNode, RectNode, SymbolUseNode, TextNode, Transform, Point } from '@mosaicoo/svg-engine/core';
|
|
4
|
+
import * as _mosaicoo_svg_engine_render from '@mosaicoo/svg-engine/render';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Top-level SVG renderer. Read-only — does **not** handle selection,
|
|
8
|
+
* editing or interaction (those live in `svg-engine/edit`). Suitable as
|
|
9
|
+
* an embedded viewer in third-party applications (D-017 headless boundary:
|
|
10
|
+
* zero deps on `@angular/material`).
|
|
11
|
+
*
|
|
12
|
+
* **Inputs**:
|
|
13
|
+
* - `tree` (required): the {@link SvgNode} to render. Typically a
|
|
14
|
+
* {@link SvgDocument} `root`, but any node is valid (single-node
|
|
15
|
+
* preview, etc.).
|
|
16
|
+
* - `viewBox` (optional): **seed** for {@link ViewportService.contentBox}.
|
|
17
|
+
* The value is mirrored into the viewport on every change. The actual
|
|
18
|
+
* viewBox attribute on the `<svg>` element is **always** derived from
|
|
19
|
+
* `viewport.viewBox()`, so pan/zoom calls on `ViewportService` always
|
|
20
|
+
* take effect regardless of whether this input is provided.
|
|
21
|
+
* - `width` / `height` (optional): CSS pixel dimensions of the rendered
|
|
22
|
+
* `<svg>` element. When both are omitted the SVG is sized by its CSS
|
|
23
|
+
* container (the component sets `:host` and `svg` to `100%/100%` by
|
|
24
|
+
* default).
|
|
25
|
+
* - `ariaLabel` (optional): accessibility label for the `<svg role="img">`.
|
|
26
|
+
*
|
|
27
|
+
* **Pan/zoom semantics**: at the default viewport state (`zoom=1`,
|
|
28
|
+
* `pan=0`), `viewport.viewBox()` equals `viewport.contentBox()`, so the
|
|
29
|
+
* rendered SVG matches the seed exactly. After a `viewport.zoomIn()`
|
|
30
|
+
* (etc.) call, the rendered viewBox reflects the new state — the
|
|
31
|
+
* displayed content scales/pans accordingly.
|
|
32
|
+
*/
|
|
33
|
+
declare class SvgeRenderer {
|
|
34
|
+
private readonly viewport;
|
|
35
|
+
private readonly svgRoot;
|
|
36
|
+
/**
|
|
37
|
+
* **AUDIT-FIX P9** — typed accessor for the rendered `<svg>` element
|
|
38
|
+
* scoped to **this** renderer instance.
|
|
39
|
+
*
|
|
40
|
+
* Returns `null` when the view hasn't been attached yet (early calls
|
|
41
|
+
* during ngOnInit, SSR, jsdom without layout). Consumers should
|
|
42
|
+
* defensively check for null.
|
|
43
|
+
*
|
|
44
|
+
* **Why expose it**: consumers that need to perform DOM measurements
|
|
45
|
+
* on the rendered shapes (bbox, hit-testing, pointer→doc conversion)
|
|
46
|
+
* previously had to call `document.querySelector('svge-renderer svg')`.
|
|
47
|
+
* That global selector breaks the moment a host mounts more than one
|
|
48
|
+
* `<svge-renderer>` on the same page — the first match wins, leaking
|
|
49
|
+
* measurements between editor instances. Using
|
|
50
|
+
* `@ViewChild(SvgeRenderer)` + `svgElement()` keeps the lookup scoped
|
|
51
|
+
* to the component's own tree.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* @Component({ ... })
|
|
56
|
+
* export class MyHost {
|
|
57
|
+
* private readonly renderer = viewChild(SvgeRenderer);
|
|
58
|
+
*
|
|
59
|
+
* measureNode(id: NodeId): BoundingBox | null {
|
|
60
|
+
* const svg = this.renderer()?.svgElement();
|
|
61
|
+
* return svg === null ? null : getRenderedNodeBBox(svg, id);
|
|
62
|
+
* }
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
svgElement(): SVGSVGElement | null;
|
|
67
|
+
readonly tree: _angular_core.InputSignal<SvgNode>;
|
|
68
|
+
readonly viewBox: _angular_core.InputSignal<BoundingBox | null>;
|
|
69
|
+
readonly width: _angular_core.InputSignal<number | null>;
|
|
70
|
+
readonly height: _angular_core.InputSignal<number | null>;
|
|
71
|
+
readonly ariaLabel: _angular_core.InputSignal<string | null>;
|
|
72
|
+
/**
|
|
73
|
+
* Optional reusable-definitions fragment ({@link SvgDocument.defs}).
|
|
74
|
+
* When non-empty, the renderer injects the fragment as the first
|
|
75
|
+
* child of `<svg>` via `insertAdjacentHTML('afterbegin', ...)` so
|
|
76
|
+
* `url(#id)` references inside the tree resolve at paint time.
|
|
77
|
+
*
|
|
78
|
+
* **Why insertAdjacentHTML and not the Angular template**: the
|
|
79
|
+
* fragment can include arbitrary SVG markup (`<linearGradient>`,
|
|
80
|
+
* `<clipPath>`, …) with namespaces and id attributes. Embedding via
|
|
81
|
+
* `[innerHTML]` would run through Angular's sanitizer which can
|
|
82
|
+
* mangle SVG-specific constructs. The fragment was already sanitized
|
|
83
|
+
* at import time (script, event handlers, javascript: hrefs all
|
|
84
|
+
* removed); we trust it as opaque markup here.
|
|
85
|
+
*/
|
|
86
|
+
readonly defs: _angular_core.InputSignal<string | null>;
|
|
87
|
+
/**
|
|
88
|
+
* Rendered viewBox attribute. **Always** derived from
|
|
89
|
+
* `viewport.viewBox()` — which itself is `contentBox` transformed by
|
|
90
|
+
* current zoom and pan. The optional `viewBox` input feeds the
|
|
91
|
+
* viewport's `contentBox` (via the constructor effect), making it the
|
|
92
|
+
* starting point that pan/zoom act upon.
|
|
93
|
+
*
|
|
94
|
+
* This single-source-of-truth approach guarantees that any caller of
|
|
95
|
+
* `ViewportService.zoomIn()`/`pan()`/etc. produces a visible change
|
|
96
|
+
* regardless of whether `viewBox` was supplied as an input.
|
|
97
|
+
*/
|
|
98
|
+
protected readonly viewBoxAttr: _angular_core.Signal<string>;
|
|
99
|
+
/**
|
|
100
|
+
* Sentinel data attribute applied to the injected `<defs>` block so
|
|
101
|
+
* re-runs can find and replace it idempotently instead of stacking
|
|
102
|
+
* duplicates.
|
|
103
|
+
*/
|
|
104
|
+
private static readonly DEFS_MARKER_ATTR;
|
|
105
|
+
constructor();
|
|
106
|
+
/**
|
|
107
|
+
* Idempotently materialize the current `defs()` value as the first
|
|
108
|
+
* child of `<svg>`. Sequence of operations:
|
|
109
|
+
*
|
|
110
|
+
* 1. Locate (and remove) any previous `<defs data-svge-injected-defs="1">`
|
|
111
|
+
* we wrote on an earlier effect tick.
|
|
112
|
+
* 2. If the new fragment is non-empty, build a fresh `<defs>` element
|
|
113
|
+
* and set its inner XML via `insertAdjacentHTML`. The marker attr
|
|
114
|
+
* distinguishes our injection from consumer-provided defs.
|
|
115
|
+
*
|
|
116
|
+
* Safe in jsdom (no-ops when the viewChild ref hasn't resolved yet),
|
|
117
|
+
* SSR (typeof document gate via insertAdjacentHTML availability),
|
|
118
|
+
* and worker contexts (effect simply never runs DOM ops without the
|
|
119
|
+
* viewChild element).
|
|
120
|
+
*/
|
|
121
|
+
private syncDefs;
|
|
122
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvgeRenderer, never>;
|
|
123
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<SvgeRenderer, "svge-renderer", never, { "tree": { "alias": "tree"; "required": true; "isSignal": true; }; "viewBox": { "alias": "viewBox"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "height": { "alias": "height"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "defs": { "alias": "defs"; "required": false; "isSignal": true; }; }, {}, never, ["[svgeBehind]", "*"], true, never>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Convenience helper for consumers that hold a {@link SvgDocument}: returns
|
|
127
|
+
* its `root` and `viewBox` ready to bind to {@link SvgeRenderer}.
|
|
128
|
+
*
|
|
129
|
+
* ```html
|
|
130
|
+
* <svge-renderer [tree]="doc().root" [viewBox]="doc().viewBox" />
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
declare function projectDocumentToRenderer(doc: SvgDocument): {
|
|
134
|
+
readonly tree: SvgNode;
|
|
135
|
+
readonly viewBox: BoundingBox;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/** Apply to `<svg:ellipse>` to bind attributes from an {@link EllipseNode}. */
|
|
139
|
+
declare class SvgeEllipseDirective {
|
|
140
|
+
readonly node: _angular_core.InputSignal<EllipseNode>;
|
|
141
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvgeEllipseDirective, never>;
|
|
142
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<SvgeEllipseDirective, "[svgeEllipse]", never, { "node": { "alias": "svgeEllipse"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Apply to `<svg:image>` to bind attributes from an {@link ImageNode}. */
|
|
146
|
+
declare class SvgeImageDirective {
|
|
147
|
+
readonly node: _angular_core.InputSignal<ImageNode>;
|
|
148
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvgeImageDirective, never>;
|
|
149
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<SvgeImageDirective, "[svgeImage]", never, { "node": { "alias": "svgeImage"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Apply to `<svg:line>` to bind attributes from a {@link LineNode}. */
|
|
153
|
+
declare class SvgeLineDirective {
|
|
154
|
+
readonly node: _angular_core.InputSignal<LineNode>;
|
|
155
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvgeLineDirective, never>;
|
|
156
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<SvgeLineDirective, "[svgeLine]", never, { "node": { "alias": "svgeLine"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Dispatch a single {@link SvgNode} to the correct attribute-binding
|
|
161
|
+
* directive on the appropriate SVG element.
|
|
162
|
+
*
|
|
163
|
+
* **Why an attribute selector** (`g[svgeNode]`):
|
|
164
|
+
*
|
|
165
|
+
* The dispatcher's host is itself a `<svg:g>` (the wrapper that carries
|
|
166
|
+
* `data-node-id` and the node's `transform`). Using a custom-element
|
|
167
|
+
* selector like `<svge-node>` would inject a non-SVG element into the
|
|
168
|
+
* SVG render tree, and SVG painters do not paint through unknown
|
|
169
|
+
* non-SVG elements — geometry inside would silently fail to render.
|
|
170
|
+
* Attribute selector + `<svg:g>` host keeps the entire DOM in the SVG
|
|
171
|
+
* namespace.
|
|
172
|
+
*
|
|
173
|
+
* Built-in dispatch:
|
|
174
|
+
* - 8 leaf types are bound by per-type directives (`[svgeRect]`,
|
|
175
|
+
* `[svgeEllipse]`, …) on their native SVG element.
|
|
176
|
+
* - `group` is handled inline (recursive case) — each child becomes a
|
|
177
|
+
* nested `<svg:g svgeNode>`.
|
|
178
|
+
* - Unknown types fall through to {@link NodeRendererRegistry} (D-020
|
|
179
|
+
* plugin extensibility).
|
|
180
|
+
*
|
|
181
|
+
* Usage (top-level):
|
|
182
|
+
* ```html
|
|
183
|
+
* <svg:g svgeNode [node]="root" />
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
declare class SvgeNodeRenderer {
|
|
187
|
+
private readonly registry;
|
|
188
|
+
readonly node: _angular_core.InputSignal<SvgNode>;
|
|
189
|
+
/** Host-bound transform attr; runs once per input change (OnPush). */
|
|
190
|
+
protected readonly transformAttr: _angular_core.Signal<string | null>;
|
|
191
|
+
/**
|
|
192
|
+
* Custom-component lookup stays computed because the
|
|
193
|
+
* {@link NodeRendererRegistry} signal can change at runtime (plugin
|
|
194
|
+
* install/uninstall) and we want the @switch default branch to react.
|
|
195
|
+
*/
|
|
196
|
+
protected readonly customComponent: _angular_core.Signal<_mosaicoo_svg_engine_render.SvgNodeRendererComponent | null>;
|
|
197
|
+
/**
|
|
198
|
+
* **GROUP-STYLE-FIX (A)** — the node's style ONLY when it's a group,
|
|
199
|
+
* else `null`. Drives the group-wrapper paint/filter/opacity host
|
|
200
|
+
* bindings (see the `host` block). Leaf nodes return `null` here so
|
|
201
|
+
* those bindings emit no attribute on the leaf wrapper (the per-type
|
|
202
|
+
* directive paints the leaf's style on its inner element instead).
|
|
203
|
+
*/
|
|
204
|
+
protected readonly groupStyle: _angular_core.Signal<SvgStyle | null>;
|
|
205
|
+
/**
|
|
206
|
+
* `stroke-dasharray` for the group wrapper, formatted as the SVG
|
|
207
|
+
* space-separated string. `null` (no attribute) when this isn't a
|
|
208
|
+
* group or the group has no dash pattern. Mirrors the exporter's
|
|
209
|
+
* `strokeDasharray.map(fmt).join(' ')`.
|
|
210
|
+
*/
|
|
211
|
+
protected groupDashArray(): string | null;
|
|
212
|
+
/**
|
|
213
|
+
* Text content access for the `@case ('text')` branch. Returns `''`
|
|
214
|
+
* for non-text nodes (never reached at runtime; appeases the template
|
|
215
|
+
* type checker without scattering `$any` for the string interpolation).
|
|
216
|
+
*/
|
|
217
|
+
protected textContent(): string;
|
|
218
|
+
/**
|
|
219
|
+
* **D-100** — `true` when the text node has a non-empty `runs` array.
|
|
220
|
+
* Gates the rich-text (inline styled tspans) render branch. Sits below
|
|
221
|
+
* the `textPathRef` branch in precedence (text on a path with per-run
|
|
222
|
+
* styling is out of scope) and above the multi-line `\n` path.
|
|
223
|
+
*/
|
|
224
|
+
protected textHasRuns(): boolean;
|
|
225
|
+
/** Runs for the rich-text branch; `[]` when the node has none. */
|
|
226
|
+
protected textRuns(): readonly TextRun[];
|
|
227
|
+
/**
|
|
228
|
+
* `true` when the text node's content contains newline characters —
|
|
229
|
+
* the renderer switches to the multi-line tspan path. Single-line
|
|
230
|
+
* text keeps the simpler plain interpolation.
|
|
231
|
+
*/
|
|
232
|
+
protected textIsMultiLine(): boolean;
|
|
233
|
+
/**
|
|
234
|
+
* Split text content into lines for the multi-line tspan path.
|
|
235
|
+
* Returns `[]` for non-text nodes (defensive — branch is gated by
|
|
236
|
+
* `textIsMultiLine`).
|
|
237
|
+
*/
|
|
238
|
+
protected textLines(): readonly string[];
|
|
239
|
+
/**
|
|
240
|
+
* X position of the text node — every tspan inherits this value
|
|
241
|
+
* via its own `x` attribute (the default behaviour of tspan without
|
|
242
|
+
* explicit `x` is to continue from the previous tspan's end, which
|
|
243
|
+
* is NOT what we want for multi-line — we want each line to start
|
|
244
|
+
* at the same horizontal anchor).
|
|
245
|
+
*/
|
|
246
|
+
protected textX(): number;
|
|
247
|
+
/**
|
|
248
|
+
* **D-069** — `dy` value (in `em`s) for non-first tspans in the
|
|
249
|
+
* multi-line text path. Reads `node().lineHeight` and falls back to
|
|
250
|
+
* `1.2` (the pre-D-069 hardcoded default) when undefined — preserves
|
|
251
|
+
* backward compatibility for existing docs/specs.
|
|
252
|
+
*
|
|
253
|
+
* Returned as a CSS-style string with `em` unit so it composes with
|
|
254
|
+
* the `font-size` already on the parent `<text>` (the tspan inherits).
|
|
255
|
+
* Choosing `em` over `px` keeps line spacing proportional when the
|
|
256
|
+
* user later changes the font size — same behavior as CSS
|
|
257
|
+
* `line-height: <number>` (unitless multiplier).
|
|
258
|
+
*/
|
|
259
|
+
protected textLineDy(): string;
|
|
260
|
+
protected textPathHref(): string | null;
|
|
261
|
+
protected textPathStartOffset(): string | null;
|
|
262
|
+
/**
|
|
263
|
+
* `<textPath>` does NOT honor `\n` — embedded newlines render as
|
|
264
|
+
* a single literal space (per SVG spec: whitespace collapse). We
|
|
265
|
+
* pre-collapse so the user sees what they get instead of a stretched
|
|
266
|
+
* " word " gap from the raw newline character.
|
|
267
|
+
*/
|
|
268
|
+
protected textPathFlattened(): string;
|
|
269
|
+
/**
|
|
270
|
+
* Children iteration for the `@case ('group')` branch. Same template-
|
|
271
|
+
* type-checker rationale as {@link textContent}. Empty array for
|
|
272
|
+
* non-group nodes (never reached).
|
|
273
|
+
*/
|
|
274
|
+
protected groupChildren(): readonly SvgNode[];
|
|
275
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvgeNodeRenderer, never>;
|
|
276
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<SvgeNodeRenderer, "g[svgeNode]", never, { "node": { "alias": "node"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Apply to `<svg:path>` to bind attributes from a {@link PathNode}.
|
|
281
|
+
*
|
|
282
|
+
* **D-055 (Live Corners)**: when `node.cornerRadius > 0`, the
|
|
283
|
+
* rendered `d` is the result of {@link roundPathCorners}(authored d,
|
|
284
|
+
* radius) — sharp interior vertices get smoothly rounded with that
|
|
285
|
+
* radius. Authored `d` is left untouched (preserved on the model
|
|
286
|
+
* for later edits / radius slider changes). When `cornerRadius` is
|
|
287
|
+
* `0` or `undefined`, the authored `d` is emitted verbatim — same
|
|
288
|
+
* behavior as before D-055.
|
|
289
|
+
*
|
|
290
|
+
* **D-068 follow-up — `id` host binding**: paths are the only shape
|
|
291
|
+
* type a `<textPath href="#…">` (D-053) can reference, so the path
|
|
292
|
+
* needs a DOM `id` matching `node.id`. Without this, `textPathRef` set
|
|
293
|
+
* by the Inspector resolves to no element and the text silently
|
|
294
|
+
* disappears (bbox in place, zero characters painted). The wrapper
|
|
295
|
+
* `<g>` already carries `data-node-id` for selection lookup, but
|
|
296
|
+
* `data-*` doesn't satisfy `#id` href lookups — only the standard
|
|
297
|
+
* `id` attribute does. Cost is one attribute per path, negligible.
|
|
298
|
+
* No collision risk because `NodeId`s are UUIDs (globally unique
|
|
299
|
+
* across documents and editors). If a future use-case wants to opt
|
|
300
|
+
* out of paint-target ids, gate this behind a flag — keeping it
|
|
301
|
+
* unconditional makes textPath, future `<use href>` fallbacks, and
|
|
302
|
+
* DOM inspector debugging all easier.
|
|
303
|
+
*/
|
|
304
|
+
declare class SvgePathDirective {
|
|
305
|
+
readonly node: _angular_core.InputSignal<PathNode>;
|
|
306
|
+
protected readonly effectiveD: _angular_core.Signal<string>;
|
|
307
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvgePathDirective, never>;
|
|
308
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<SvgePathDirective, "[svgePath]", never, { "node": { "alias": "svgePath"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Apply to `<svg:polygon>` to bind attributes from a {@link PolygonNode}. */
|
|
312
|
+
declare class SvgePolygonDirective {
|
|
313
|
+
readonly node: _angular_core.InputSignal<PolygonNode>;
|
|
314
|
+
protected readonly pointsAttr: _angular_core.Signal<string>;
|
|
315
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvgePolygonDirective, never>;
|
|
316
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<SvgePolygonDirective, "[svgePolygon]", never, { "node": { "alias": "svgePolygon"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Apply to `<svg:polyline>` to bind attributes from a {@link PolylineNode}. */
|
|
320
|
+
declare class SvgePolylineDirective {
|
|
321
|
+
readonly node: _angular_core.InputSignal<PolylineNode>;
|
|
322
|
+
protected readonly pointsAttr: _angular_core.Signal<string>;
|
|
323
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvgePolylineDirective, never>;
|
|
324
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<SvgePolylineDirective, "[svgePolyline]", never, { "node": { "alias": "svgePolyline"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Apply to a `<svg:rect>` element to bind its attributes from a
|
|
329
|
+
* {@link RectNode}. Use the directive as both selector and value binding:
|
|
330
|
+
*
|
|
331
|
+
* ```html
|
|
332
|
+
* <svg:rect [svgeRect]="rectNode" />
|
|
333
|
+
* ```
|
|
334
|
+
*
|
|
335
|
+
* Why a directive (not a component): components inject a host element
|
|
336
|
+
* created by `document.createElement(selector)` which lives in the HTML
|
|
337
|
+
* namespace. Inside an `<svg>`, an HTML wrapper element breaks the SVG
|
|
338
|
+
* render tree — the SVG painter does not traverse non-SVG elements. A
|
|
339
|
+
* directive applied to an actual `<svg:rect>` element keeps the host in
|
|
340
|
+
* the SVG namespace so geometry attributes paint correctly.
|
|
341
|
+
*/
|
|
342
|
+
declare class SvgeRectDirective {
|
|
343
|
+
readonly node: _angular_core.InputSignal<RectNode>;
|
|
344
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvgeRectDirective, never>;
|
|
345
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<SvgeRectDirective, "[svgeRect]", never, { "node": { "alias": "svgeRect"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Apply to `<svg:use>` to bind attributes from a {@link SymbolUseNode}
|
|
350
|
+
* (D-059). The `<symbol id="{symbolId}">` definition that this `<use>`
|
|
351
|
+
* references is contributed to `<defs>` by `ActiveSymbolsService`
|
|
352
|
+
* (in `svg-engine/edit`).
|
|
353
|
+
*
|
|
354
|
+
* **Style inheritance**: `fill`/`stroke`/`opacity` set on the `<use>`
|
|
355
|
+
* cascade into the shadow tree (the master's contents) for any inner
|
|
356
|
+
* element that didn't specify those values explicitly. Editing the
|
|
357
|
+
* master overrides everywhere; editing the instance customizes a
|
|
358
|
+
* single occurrence.
|
|
359
|
+
*
|
|
360
|
+
* **Filter on `<use>`** applies to the rendered output, AFTER the
|
|
361
|
+
* symbol expansion — useful for "glow that one instance" without
|
|
362
|
+
* touching the master.
|
|
363
|
+
*
|
|
364
|
+
* **width/height optional**: when omitted, the symbol renders at its
|
|
365
|
+
* master's natural size (from the `<symbol>`'s `viewBox`).
|
|
366
|
+
*/
|
|
367
|
+
declare class SvgeSymbolUseDirective {
|
|
368
|
+
readonly node: _angular_core.InputSignal<SymbolUseNode>;
|
|
369
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvgeSymbolUseDirective, never>;
|
|
370
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<SvgeSymbolUseDirective, "[svgeSymbolUse]", never, { "node": { "alias": "svgeSymbolUse"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Apply to `<svg:text>` to bind attributes from a {@link TextNode}.
|
|
375
|
+
*
|
|
376
|
+
* The `<svg:text>` element's text **content** is set in the dispatcher's
|
|
377
|
+
* template, because directives cannot inject child nodes. The dispatcher
|
|
378
|
+
* splits `node.content` on `\n` and emits one `<tspan dy>` per line, so
|
|
379
|
+
* multi-line text is rendered by simply embedding line breaks in the
|
|
380
|
+
* `content` field. Explicit `<tspan>` runs with per-run styling are
|
|
381
|
+
* still not modelled at the data layer — that would require an extra
|
|
382
|
+
* `runs` field on `TextNode`.
|
|
383
|
+
*/
|
|
384
|
+
declare class SvgeTextDirective {
|
|
385
|
+
readonly node: _angular_core.InputSignal<TextNode>;
|
|
386
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvgeTextDirective, never>;
|
|
387
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<SvgeTextDirective, "[svgeText]", never, { "node": { "alias": "svgeText"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Viewport state for the SVG renderer: zoom level and pan offset over a
|
|
392
|
+
* base "content" {@link BoundingBox}. Exposed as Angular signals so the
|
|
393
|
+
* renderer can recompute the displayed `viewBox` reactively.
|
|
394
|
+
*
|
|
395
|
+
* Coordinate model:
|
|
396
|
+
* - `contentBox` is the original document space ("what to show at zoom 1").
|
|
397
|
+
* - `zoom` scales the visible window: `>1` zooms in (smaller window),
|
|
398
|
+
* `<1` zooms out (larger window).
|
|
399
|
+
* - `panX`/`panY` translate the visible window in **content units**.
|
|
400
|
+
*
|
|
401
|
+
* The resulting visible {@link BoundingBox} is exposed via {@link viewBox},
|
|
402
|
+
* suitable for binding to `<svg [attr.viewBox]>`.
|
|
403
|
+
*/
|
|
404
|
+
declare class ViewportService {
|
|
405
|
+
private readonly _contentBox;
|
|
406
|
+
private readonly _zoom;
|
|
407
|
+
private readonly _panX;
|
|
408
|
+
private readonly _panY;
|
|
409
|
+
private readonly _minZoom;
|
|
410
|
+
private readonly _maxZoom;
|
|
411
|
+
/**
|
|
412
|
+
* **D-106** — CSS-pixel size of the rendered canvas viewport (the `<svg>`
|
|
413
|
+
* element's client box). `{0,0}` until a consumer reports it (the
|
|
414
|
+
* `[svgeCanvasGestures]` directive does, via a ResizeObserver). Needed to
|
|
415
|
+
* relate the internal `zoom` (which is relative to `contentBox`) to a
|
|
416
|
+
* **physical** on-screen scale — see {@link displayScale}.
|
|
417
|
+
*/
|
|
418
|
+
private readonly _viewportSize;
|
|
419
|
+
readonly contentBox: _angular_core.Signal<BoundingBox>;
|
|
420
|
+
readonly zoom: _angular_core.Signal<number>;
|
|
421
|
+
readonly panX: _angular_core.Signal<number>;
|
|
422
|
+
readonly panY: _angular_core.Signal<number>;
|
|
423
|
+
readonly minZoom: _angular_core.Signal<number>;
|
|
424
|
+
readonly maxZoom: _angular_core.Signal<number>;
|
|
425
|
+
readonly viewportSize: _angular_core.Signal<{
|
|
426
|
+
readonly width: number;
|
|
427
|
+
readonly height: number;
|
|
428
|
+
}>;
|
|
429
|
+
/**
|
|
430
|
+
* Visible window in content coordinates, derived from `contentBox`,
|
|
431
|
+
* `zoom`, `panX`, `panY`. Bind directly to `<svg [attr.viewBox]>`.
|
|
432
|
+
*/
|
|
433
|
+
readonly viewBox: _angular_core.Signal<BoundingBox>;
|
|
434
|
+
/**
|
|
435
|
+
* **D-106** — physical scale (CSS px per document unit) that the WHOLE
|
|
436
|
+
* `contentBox` occupies at `zoom === 1` — i.e. the "fit to window" factor.
|
|
437
|
+
* `preserveAspectRatio="xMidYMid meet"` applies a uniform scale, so it's the
|
|
438
|
+
* limiting (min) of the two axis ratios. `null` when the viewport size or
|
|
439
|
+
* content box is unknown/degenerate (e.g. headless tests with no layout).
|
|
440
|
+
*/
|
|
441
|
+
readonly fitScale: _angular_core.Signal<number | null>;
|
|
442
|
+
/**
|
|
443
|
+
* **D-106** — true on-screen scale: how many CSS pixels one document unit
|
|
444
|
+
* occupies right now. `displayScale === 1` means real 1:1 ("100% / Actual
|
|
445
|
+
* Size", the market convention). Because the SVG renders the visible
|
|
446
|
+
* `viewBox` (`contentBox.size / zoom`) stretched to fill the viewport, the
|
|
447
|
+
* physical scale is `zoom × fitScale`.
|
|
448
|
+
*
|
|
449
|
+
* **Fallback**: when {@link fitScale} is unknown (no measured viewport, e.g.
|
|
450
|
+
* headless rendering) this returns the raw `zoom` — preserving the old
|
|
451
|
+
* "zoom = percent" meaning so non-DOM consumers/tests keep working.
|
|
452
|
+
*/
|
|
453
|
+
readonly displayScale: _angular_core.Signal<number>;
|
|
454
|
+
/** Replace the base content box (e.g., when loading a new document). */
|
|
455
|
+
setContentBox(box: BoundingBox): void;
|
|
456
|
+
/**
|
|
457
|
+
* **D-106** — report the rendered canvas size (CSS px). Drives
|
|
458
|
+
* {@link fitScale}/{@link displayScale}. Ignores non-finite/negative input.
|
|
459
|
+
*/
|
|
460
|
+
setViewportSize(width: number, height: number): void;
|
|
461
|
+
/**
|
|
462
|
+
* **D-106** — set the internal `zoom` so the on-screen {@link displayScale}
|
|
463
|
+
* equals `scale` (e.g. `1` for true 1:1). When the viewport hasn't been
|
|
464
|
+
* measured ({@link fitScale} null), falls back to setting `zoom` directly so
|
|
465
|
+
* the call still does something sensible.
|
|
466
|
+
*/
|
|
467
|
+
setDisplayScale(scale: number): void;
|
|
468
|
+
/** **D-106** — "Actual Size" (100%): pin the on-screen scale to true 1:1. */
|
|
469
|
+
actualSize(): void;
|
|
470
|
+
/**
|
|
471
|
+
* **D-107** — initial framing for a FRESH document: show it at true 100%
|
|
472
|
+
* (1:1) when the whole content box fits the viewport, otherwise fit it to the
|
|
473
|
+
* window. Pan resets to origin (centered). This is the "Fit on Screen"
|
|
474
|
+
* convention (Photoshop): documents that fit open at 100%; larger ones fit so
|
|
475
|
+
* the user isn't dropped into a corner. Falls back to fit (zoom 1) when the
|
|
476
|
+
* viewport hasn't been measured ({@link fitScale} null).
|
|
477
|
+
*
|
|
478
|
+
* (Opening a saved `.svge`/`.svgez` does NOT use this — it restores the
|
|
479
|
+
* viewport persisted in the file.)
|
|
480
|
+
*/
|
|
481
|
+
frameNewDocument(): void;
|
|
482
|
+
/** Replace zoom directly (clamped to `[minZoom, maxZoom]`). */
|
|
483
|
+
setZoom(zoom: number): void;
|
|
484
|
+
/** Multiply current zoom by `factor` (clamped). */
|
|
485
|
+
multiplyZoom(factor: number): void;
|
|
486
|
+
/** Zoom in by the configured step factor. */
|
|
487
|
+
zoomIn(step?: number): void;
|
|
488
|
+
/** Zoom out by the configured step factor. */
|
|
489
|
+
zoomOut(step?: number): void;
|
|
490
|
+
/** Replace pan offset directly (in content units). */
|
|
491
|
+
setPan(x: number, y: number): void;
|
|
492
|
+
/** Translate the current pan by `(dx, dy)` in content units. */
|
|
493
|
+
pan(dx: number, dy: number): void;
|
|
494
|
+
/**
|
|
495
|
+
* Zoom by `factor` while keeping `anchor` (in document coordinates)
|
|
496
|
+
* stationary on screen. Used by wheel-zoom (anchor = cursor) and
|
|
497
|
+
* "zoom to selection" (anchor = selection centre).
|
|
498
|
+
*
|
|
499
|
+
* **Math**: pre-zoom relative position of the anchor inside the
|
|
500
|
+
* visible viewBox must equal post-zoom relative position. Solving
|
|
501
|
+
* for the new pan and applying both `zoom` and `pan` atomically
|
|
502
|
+
* makes the visible viewBox preserve the anchor.
|
|
503
|
+
*
|
|
504
|
+
* **No-op-safe**: if `factor` would push zoom past min/max limits,
|
|
505
|
+
* the clamped zoom may equal the current zoom — pan adjusts to a
|
|
506
|
+
* zero delta, anchor stays put (no observable change).
|
|
507
|
+
*/
|
|
508
|
+
zoomAt(factor: number, anchor: {
|
|
509
|
+
readonly x: number;
|
|
510
|
+
readonly y: number;
|
|
511
|
+
}): void;
|
|
512
|
+
/** Reset zoom to 1 and pan to origin (centered on content). */
|
|
513
|
+
reset(): void;
|
|
514
|
+
/**
|
|
515
|
+
* Reset to fit the content box exactly. Identical to {@link reset}
|
|
516
|
+
* today; reserved for future "fit to selection" / "fit to bounds"
|
|
517
|
+
* variants.
|
|
518
|
+
*/
|
|
519
|
+
fit(): void;
|
|
520
|
+
/**
|
|
521
|
+
* **D-118** — frame `target` (in document/content coordinates) in the visible
|
|
522
|
+
* viewBox: zoom so the box fits with `paddingFraction` margin on each side,
|
|
523
|
+
* and pan so it's centered. Powers `View ▸ Zoom ▸ Fit Selection`.
|
|
524
|
+
*
|
|
525
|
+
* The visible viewBox always keeps the content aspect ratio, so the box is
|
|
526
|
+
* fit "meet"-style — the limiting dimension touches the padded edge, the other
|
|
527
|
+
* has extra room. Zoom is clamped to `[minZoom, maxZoom]`. A degenerate
|
|
528
|
+
* (zero-area) target only re-centers, preserving the current zoom.
|
|
529
|
+
*/
|
|
530
|
+
fitBox(target: BoundingBox, paddingFraction?: number): void;
|
|
531
|
+
/** Configure clamping bounds for zoom. Throws on invalid input. */
|
|
532
|
+
setZoomLimits(min: number, max: number): void;
|
|
533
|
+
private clampZoom;
|
|
534
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ViewportService, never>;
|
|
535
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<ViewportService>;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Renderer component class. The dispatcher (`<svge-node>`) mounts these
|
|
540
|
+
* via `*ngComponentOutlet` and binds `inputs: { node: <SvgNode> }`. The
|
|
541
|
+
* concrete shape of the component is not enforced statically (Angular's
|
|
542
|
+
* `input.required<T>()` returns an `InputSignal<T>`, which does not match
|
|
543
|
+
* a plain `T` field) — the runtime contract is:
|
|
544
|
+
*
|
|
545
|
+
* - The component declares `node` as an Angular `input()` (preferred)
|
|
546
|
+
* or a plain settable property accepting an `SvgNode`.
|
|
547
|
+
* - The component is `standalone: true`.
|
|
548
|
+
*/
|
|
549
|
+
type SvgNodeRendererComponent = Type<unknown>;
|
|
550
|
+
/**
|
|
551
|
+
* Registry of **custom** renderer components, keyed by `SvgNode['type']`.
|
|
552
|
+
*
|
|
553
|
+
* Built-in node types (`rect`, `ellipse`, `line`, `polygon`, `polyline`,
|
|
554
|
+
* `path`, `text`, `image`, `group`) are dispatched by the dispatcher's own
|
|
555
|
+
* `@switch` and do **not** go through this registry. The registry is the
|
|
556
|
+
* extension point for plugins (D-020) that introduce new `type`
|
|
557
|
+
* discriminators (e.g., `'star'`, `'chart-bar'`).
|
|
558
|
+
*
|
|
559
|
+
* Plugin install code:
|
|
560
|
+
* ```ts
|
|
561
|
+
* inject(NodeRendererRegistry).register('star', SvgeStarRenderer);
|
|
562
|
+
* ```
|
|
563
|
+
*
|
|
564
|
+
* The dispatcher resolves the registry's component for unknown types,
|
|
565
|
+
* mounts it via `*ngComponentOutlet` and binds `[node]` automatically.
|
|
566
|
+
*/
|
|
567
|
+
declare class NodeRendererRegistry {
|
|
568
|
+
private readonly entries;
|
|
569
|
+
/**
|
|
570
|
+
* Register a renderer component for a custom node type. Throws when the
|
|
571
|
+
* `type` is already registered (use {@link unregister} first to override).
|
|
572
|
+
*/
|
|
573
|
+
register(type: string, component: SvgNodeRendererComponent): void;
|
|
574
|
+
/** Remove a previously registered renderer. No-op when absent. */
|
|
575
|
+
unregister(type: string): void;
|
|
576
|
+
/** Resolve the renderer for the given type, or `null` if unregistered. */
|
|
577
|
+
resolve(type: string): SvgNodeRendererComponent | null;
|
|
578
|
+
/** All currently registered type discriminators (for inspection/tooling). */
|
|
579
|
+
registeredTypes(): readonly string[];
|
|
580
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<NodeRendererRegistry, never>;
|
|
581
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<NodeRendererRegistry>;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Serialize a {@link Transform} for the SVG `transform` attribute, e.g.
|
|
586
|
+
* `matrix(1 0 0 1 10 20)`. Returns `null` for the identity transform so
|
|
587
|
+
* callers can bind directly with `[attr.transform]` and omit the attribute
|
|
588
|
+
* when it would be a no-op.
|
|
589
|
+
*/
|
|
590
|
+
declare function renderTransformAttr(transform: Transform): string | null;
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Project a client (screen) pixel coordinate into the document's
|
|
594
|
+
* user-space coordinate system by inverting the SVG element's live
|
|
595
|
+
* `getScreenCTM()`.
|
|
596
|
+
*
|
|
597
|
+
* **Why a shared util**: this is the single most duplicated helper
|
|
598
|
+
* across the codebase. Until this util, the same 10-line block lived
|
|
599
|
+
* in 5 different files (`custom-editor` (ex-`playground-home`), `selection-overlay`,
|
|
600
|
+
* `canvas-gestures.directive`, `guides-overlay`, `rulers`) — every new
|
|
601
|
+
* pointer-driven component had to re-implement the CTM dance with the
|
|
602
|
+
* same defensive guards (jsdom missing `getScreenCTM`/`createSVGPoint`,
|
|
603
|
+
* SSR-detached SVG returning a `null` CTM, etc.). Centralizing prevents
|
|
604
|
+
* subtle drift (e.g., one site forgetting the `createSVGPoint` guard
|
|
605
|
+
* and crashing under jsdom).
|
|
606
|
+
*
|
|
607
|
+
* **What it handles**:
|
|
608
|
+
* - `svg === null` → returns `null` (caller didn't have an SVG ref yet)
|
|
609
|
+
* - `getScreenCTM` not implemented → returns `null` (jsdom / SSR)
|
|
610
|
+
* - `getScreenCTM` returns `null` → returns `null` (detached SVG)
|
|
611
|
+
* - `createSVGPoint` not available → returns `null` (older SVG impls)
|
|
612
|
+
*
|
|
613
|
+
* **What it does NOT do**:
|
|
614
|
+
* - Locate the `<svg>` element for you. Callers supply it via
|
|
615
|
+
* `ElementRef.ownerSVGElement`, `document.querySelector`, or a saved
|
|
616
|
+
* `viewChild` ref. Each caller knows the right way to reach its own
|
|
617
|
+
* SVG; baking a strategy here would couple this util to component DI.
|
|
618
|
+
*
|
|
619
|
+
* @param svg The `<svg>` element whose CTM defines the projection.
|
|
620
|
+
* Pass `null` to short-circuit (returns `null`).
|
|
621
|
+
* @param clientX `clientX` from a `MouseEvent` / `PointerEvent`.
|
|
622
|
+
* @param clientY `clientY` from a `MouseEvent` / `PointerEvent`.
|
|
623
|
+
* @returns Doc-space `{ x, y }` or `null` when projection is impossible.
|
|
624
|
+
*/
|
|
625
|
+
declare function screenToDoc(svg: SVGSVGElement | null, clientX: number, clientY: number): Point | null;
|
|
626
|
+
|
|
627
|
+
export { NodeRendererRegistry, SvgeEllipseDirective, SvgeImageDirective, SvgeLineDirective, SvgeNodeRenderer, SvgePathDirective, SvgePolygonDirective, SvgePolylineDirective, SvgeRectDirective, SvgeRenderer, SvgeSymbolUseDirective, SvgeTextDirective, ViewportService, projectDocumentToRenderer, renderTransformAttr, screenToDoc };
|
|
628
|
+
export type { SvgNodeRendererComponent };
|