@grida/svg-editor 1.0.0-alpha.1
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/README.md +831 -0
- package/dist/dom-CfP_ZURh.js +963 -0
- package/dist/dom-kA8NDuVh.mjs +929 -0
- package/dist/dom.d.mts +16 -0
- package/dist/dom.d.ts +16 -0
- package/dist/dom.js +3 -0
- package/dist/dom.mjs +2 -0
- package/dist/editor-B5z-gTML.mjs +1821 -0
- package/dist/editor-CTtU2gu4.d.ts +607 -0
- package/dist/editor-DQWUWrVZ.js +1833 -0
- package/dist/editor-JY7AQrR1.d.mts +607 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/index.mjs +2 -0
- package/dist/paint-DHq_3iwU.js +509 -0
- package/dist/paint-DuCg6Y-K.mjs +461 -0
- package/dist/react.d.mts +49 -0
- package/dist/react.d.ts +49 -0
- package/dist/react.js +97 -0
- package/dist/react.mjs +92 -0
- package/package.json +66 -0
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
import { Keybinding, Platform } from "@grida/keybinding";
|
|
2
|
+
|
|
3
|
+
//#region src/commands/registry.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Command registry.
|
|
6
|
+
*
|
|
7
|
+
* A passive id-keyed registry of handlers. Built so that:
|
|
8
|
+
*
|
|
9
|
+
* - keybindings (in `src/keymap`) can address commands by stable id;
|
|
10
|
+
* - new commands can be added in ONE place (`src/commands/defaults.ts`)
|
|
11
|
+
* without growing the public surface of the editor;
|
|
12
|
+
* - "one key, many meanings" can be expressed via chain semantics: a
|
|
13
|
+
* handler returns `true` if it consumed, `false`/`void` otherwise,
|
|
14
|
+
* and the dispatcher tries the next candidate in the chain.
|
|
15
|
+
*
|
|
16
|
+
* Handlers are plain closures — they capture whatever editor reference
|
|
17
|
+
* they need. The registry itself stays unaware of the editor's type,
|
|
18
|
+
* which avoids a circular type dependency between editor and registry.
|
|
19
|
+
*/
|
|
20
|
+
/** Stable, dotted id for a command, e.g. `"history.undo"`. */
|
|
21
|
+
type CommandId = string;
|
|
22
|
+
/**
|
|
23
|
+
* A command handler.
|
|
24
|
+
*
|
|
25
|
+
* Return `true` if the handler consumed the invocation. Return `false`
|
|
26
|
+
* or `undefined` to signal "did not apply" — the dispatcher will try
|
|
27
|
+
* the next candidate registered for the same key.
|
|
28
|
+
*
|
|
29
|
+
* Handlers are closures: they capture their editor reference. No
|
|
30
|
+
* editor parameter is passed — keep handlers self-contained.
|
|
31
|
+
*/
|
|
32
|
+
type CommandHandler = (args?: unknown) => boolean | void;
|
|
33
|
+
declare class CommandRegistry {
|
|
34
|
+
private readonly map;
|
|
35
|
+
/**
|
|
36
|
+
* Register a command. Returns an unregister function. Re-registering
|
|
37
|
+
* the same id replaces the previous handler (last writer wins).
|
|
38
|
+
*/
|
|
39
|
+
register(id: CommandId, handler: CommandHandler): () => void;
|
|
40
|
+
/**
|
|
41
|
+
* Invoke a command by id. Returns `true` if the handler consumed,
|
|
42
|
+
* `false` otherwise (including unknown ids and handlers that returned
|
|
43
|
+
* `false`/`undefined`).
|
|
44
|
+
*/
|
|
45
|
+
invoke(id: CommandId, args?: unknown): boolean;
|
|
46
|
+
has(id: CommandId): boolean;
|
|
47
|
+
/** All registered ids, for debugging / introspection. */
|
|
48
|
+
ids(): readonly CommandId[];
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/keymap/keymap.d.ts
|
|
52
|
+
type KeymapBinding = {
|
|
53
|
+
/** Declarative key combination. Build with `kb()` / `c()` / `seq()`. */keybinding: Keybinding; /** Command id to invoke on match. */
|
|
54
|
+
command: CommandId; /** Forwarded as the `args` parameter to the command handler. */
|
|
55
|
+
args?: unknown; /** Higher priorities run first in the chain. Default 0. */
|
|
56
|
+
priority?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Reserved for V2; not honored by the V1 dispatcher. When added, this
|
|
59
|
+
* will be evaluated before the handler runs; if false, the binding is
|
|
60
|
+
* skipped without invoking the handler.
|
|
61
|
+
*/
|
|
62
|
+
when?: (ctx: unknown) => boolean;
|
|
63
|
+
};
|
|
64
|
+
declare class Keymap {
|
|
65
|
+
private readonly commands;
|
|
66
|
+
private readonly platformGetter;
|
|
67
|
+
/**
|
|
68
|
+
* Bindings bucketed by canonical chunk-key hash, computed per
|
|
69
|
+
* `@grida/keybinding`'s `chunkKey`. Each list is the chain for that
|
|
70
|
+
* key, sorted in dispatch order (priority desc, then registration
|
|
71
|
+
* order).
|
|
72
|
+
*/
|
|
73
|
+
private readonly buckets;
|
|
74
|
+
/** Insert order, so ties on priority are deterministic. */
|
|
75
|
+
private seq;
|
|
76
|
+
constructor(commands: CommandRegistry, platformGetter?: () => Platform);
|
|
77
|
+
/**
|
|
78
|
+
* Bind a key combination to a command. Returns an unbind function.
|
|
79
|
+
* The same `Keybinding` can be bound to multiple commands — they will
|
|
80
|
+
* all be tried in chain order on dispatch.
|
|
81
|
+
*/
|
|
82
|
+
bind(binding: KeymapBinding): () => void;
|
|
83
|
+
/**
|
|
84
|
+
* Remove bindings matching the spec. If both filters are passed, only
|
|
85
|
+
* bindings that match BOTH are removed.
|
|
86
|
+
*/
|
|
87
|
+
unbind(spec: {
|
|
88
|
+
keybinding?: Keybinding;
|
|
89
|
+
command?: CommandId;
|
|
90
|
+
}): void;
|
|
91
|
+
/** All registered bindings, for introspection. Order is not guaranteed. */
|
|
92
|
+
bindings(): readonly KeymapBinding[];
|
|
93
|
+
/**
|
|
94
|
+
* Match the event against bound chunks, then run candidates in chain
|
|
95
|
+
* order. Returns `true` and calls `preventDefault()` on the first
|
|
96
|
+
* handler that consumes; returns `false` if nothing matched or all
|
|
97
|
+
* matches fell through.
|
|
98
|
+
*/
|
|
99
|
+
dispatch(event: KeyboardEvent): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Compute the set of canonical hashes a `Keybinding` lights up. A
|
|
102
|
+
* binding with aliases (multiple sequences) contributes one hash per
|
|
103
|
+
* single-chunk alias; multi-chunk sequences (chords) are skipped in
|
|
104
|
+
* V1.
|
|
105
|
+
*/
|
|
106
|
+
private chunkKeysFor;
|
|
107
|
+
private has_safe_mod;
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/types.d.ts
|
|
111
|
+
/**
|
|
112
|
+
* Stable identifier for a node in the editor's document model.
|
|
113
|
+
*
|
|
114
|
+
* Independent of any backing representation. Generated when the document is
|
|
115
|
+
* parsed.
|
|
116
|
+
*/
|
|
117
|
+
type NodeId = string;
|
|
118
|
+
type Vec2 = {
|
|
119
|
+
x: number;
|
|
120
|
+
y: number;
|
|
121
|
+
};
|
|
122
|
+
type Rect = {
|
|
123
|
+
x: number;
|
|
124
|
+
y: number;
|
|
125
|
+
width: number;
|
|
126
|
+
height: number;
|
|
127
|
+
};
|
|
128
|
+
type Mode = "select" | "edit-content";
|
|
129
|
+
type Provenance = {
|
|
130
|
+
/** CSS cascade origin (css-cascade-5 §6.2). */origin: "author" | "user_agent"; /** Editor metadata — where in the file the winning declaration lives. */
|
|
131
|
+
carrier: "presentation_attribute" | "inline_style" | "stylesheet" | "inherited" | "defaulted";
|
|
132
|
+
};
|
|
133
|
+
type InvalidComputedValue = {
|
|
134
|
+
error: "invalid_at_computed_value_time";
|
|
135
|
+
reason: string;
|
|
136
|
+
};
|
|
137
|
+
type PropertyValue<T = string | number> = {
|
|
138
|
+
declared: string | null;
|
|
139
|
+
computed: T | InvalidComputedValue | null;
|
|
140
|
+
provenance: Provenance;
|
|
141
|
+
};
|
|
142
|
+
type Color = {
|
|
143
|
+
kind: "rgb";
|
|
144
|
+
value: string;
|
|
145
|
+
} | {
|
|
146
|
+
kind: "current_color";
|
|
147
|
+
};
|
|
148
|
+
type PaintFallback = {
|
|
149
|
+
kind: "none";
|
|
150
|
+
} | {
|
|
151
|
+
kind: "color";
|
|
152
|
+
value: Color;
|
|
153
|
+
};
|
|
154
|
+
type Paint = {
|
|
155
|
+
kind: "none";
|
|
156
|
+
} | {
|
|
157
|
+
kind: "color";
|
|
158
|
+
value: Color;
|
|
159
|
+
} | {
|
|
160
|
+
kind: "ref";
|
|
161
|
+
id: string;
|
|
162
|
+
fallback?: PaintFallback;
|
|
163
|
+
} | {
|
|
164
|
+
kind: "context_fill";
|
|
165
|
+
} | {
|
|
166
|
+
kind: "context_stroke";
|
|
167
|
+
};
|
|
168
|
+
type PaintValue = {
|
|
169
|
+
declared: string | null;
|
|
170
|
+
computed: Paint | InvalidComputedValue | null;
|
|
171
|
+
provenance: Provenance;
|
|
172
|
+
};
|
|
173
|
+
type GradientStop = {
|
|
174
|
+
offset: number;
|
|
175
|
+
color: string;
|
|
176
|
+
opacity?: number;
|
|
177
|
+
};
|
|
178
|
+
type LinearGradientDefinition = {
|
|
179
|
+
kind: "linear";
|
|
180
|
+
stops: GradientStop[];
|
|
181
|
+
x1?: number;
|
|
182
|
+
y1?: number;
|
|
183
|
+
x2?: number;
|
|
184
|
+
y2?: number;
|
|
185
|
+
gradient_units?: "user_space_on_use" | "object_bounding_box";
|
|
186
|
+
spread_method?: "pad" | "reflect" | "repeat";
|
|
187
|
+
};
|
|
188
|
+
type RadialGradientDefinition = {
|
|
189
|
+
kind: "radial";
|
|
190
|
+
stops: GradientStop[];
|
|
191
|
+
cx?: number;
|
|
192
|
+
cy?: number;
|
|
193
|
+
r?: number;
|
|
194
|
+
fx?: number;
|
|
195
|
+
fy?: number;
|
|
196
|
+
gradient_units?: "user_space_on_use" | "object_bounding_box";
|
|
197
|
+
spread_method?: "pad" | "reflect" | "repeat";
|
|
198
|
+
};
|
|
199
|
+
type GradientDefinition = LinearGradientDefinition | RadialGradientDefinition;
|
|
200
|
+
type GradientEntry = {
|
|
201
|
+
id: string;
|
|
202
|
+
definition: GradientDefinition;
|
|
203
|
+
ref_count: number;
|
|
204
|
+
};
|
|
205
|
+
type EditorStyle = {
|
|
206
|
+
chrome_color: string;
|
|
207
|
+
handle_size: number;
|
|
208
|
+
handle_fill: string;
|
|
209
|
+
handle_stroke: string;
|
|
210
|
+
endpoint_dot_radius: number;
|
|
211
|
+
selection_outline_width: number;
|
|
212
|
+
/**
|
|
213
|
+
* Color for measurement guides (distance lines + numeric pills). Distinct
|
|
214
|
+
* from `chrome_color` so the user can tell at a glance whether something
|
|
215
|
+
* is selection chrome or a measurement readout.
|
|
216
|
+
*/
|
|
217
|
+
measurement_color: string;
|
|
218
|
+
};
|
|
219
|
+
declare const DEFAULT_STYLE: EditorStyle;
|
|
220
|
+
type ClipboardProvider = {
|
|
221
|
+
read(): Promise<string | null>;
|
|
222
|
+
write(text: string): Promise<void>;
|
|
223
|
+
};
|
|
224
|
+
type FontResolver = {
|
|
225
|
+
resolve(family: string): Promise<{
|
|
226
|
+
available: boolean;
|
|
227
|
+
metrics?: {
|
|
228
|
+
ascent: number;
|
|
229
|
+
descent: number;
|
|
230
|
+
unitsPerEm: number;
|
|
231
|
+
};
|
|
232
|
+
}>;
|
|
233
|
+
};
|
|
234
|
+
type FileIOProvider = {
|
|
235
|
+
openSvg(): Promise<string | null>;
|
|
236
|
+
saveSvg(svg: string, suggestedName?: string): Promise<void>;
|
|
237
|
+
};
|
|
238
|
+
type Providers = {
|
|
239
|
+
clipboard?: ClipboardProvider;
|
|
240
|
+
fonts?: FontResolver;
|
|
241
|
+
file_io?: FileIOProvider;
|
|
242
|
+
};
|
|
243
|
+
type EditorState = {
|
|
244
|
+
readonly selection: ReadonlyArray<NodeId>;
|
|
245
|
+
readonly scope: NodeId | null;
|
|
246
|
+
readonly mode: Mode;
|
|
247
|
+
readonly dirty: boolean;
|
|
248
|
+
readonly can_undo: boolean;
|
|
249
|
+
readonly can_redo: boolean;
|
|
250
|
+
/**
|
|
251
|
+
* Bumps on every editor emission. Use this when you need to react to
|
|
252
|
+
* any change — selection, history, mutation. NOT a good cache key for
|
|
253
|
+
* tree-shape views because it fires on attribute writes too (e.g. x/y
|
|
254
|
+
* during a drag).
|
|
255
|
+
*/
|
|
256
|
+
readonly version: number;
|
|
257
|
+
/**
|
|
258
|
+
* Bumps only when the document's tree shape or display-label-affecting
|
|
259
|
+
* data changes — node added/removed/reordered, text content, or the
|
|
260
|
+
* `id` attribute. Stable across pure presentation-attribute writes.
|
|
261
|
+
*
|
|
262
|
+
* The right cache key for hierarchy / layers panels: snapshot once per
|
|
263
|
+
* `structure_version` so a drag doesn't invalidate the tree view.
|
|
264
|
+
*/
|
|
265
|
+
readonly structure_version: number;
|
|
266
|
+
};
|
|
267
|
+
type Unsubscribe = () => void;
|
|
268
|
+
type ReorderDirection = "bring_forward" | "send_backward" | "bring_to_front" | "send_to_back";
|
|
269
|
+
type PreviewSession = {
|
|
270
|
+
update(value: string): void;
|
|
271
|
+
commit(): void;
|
|
272
|
+
discard(): void;
|
|
273
|
+
};
|
|
274
|
+
type PaintPreviewSession = {
|
|
275
|
+
update(paint: Paint): void;
|
|
276
|
+
commit(): void;
|
|
277
|
+
discard(): void;
|
|
278
|
+
};
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/core/parser.d.ts
|
|
281
|
+
type AttrToken = {
|
|
282
|
+
/** Verbatim source name including any prefix, e.g. "xlink:href". */raw_name: string; /** Prefix or null. */
|
|
283
|
+
prefix: string | null; /** Local name. */
|
|
284
|
+
local: string; /** Resolved namespace URI, or null if unprefixed and no default ns. */
|
|
285
|
+
ns: string | null; /** Raw attribute value string (entity-decoded). */
|
|
286
|
+
value: string; /** Trivia before the attribute name (whitespace, usually `" "`). */
|
|
287
|
+
pre: string; /** Trivia between `=` and the value (usually empty). */
|
|
288
|
+
eq_trivia: string; /** Quote character (`"` or `'`). */
|
|
289
|
+
quote: '"' | "'";
|
|
290
|
+
};
|
|
291
|
+
type ElementNode = {
|
|
292
|
+
kind: "element";
|
|
293
|
+
id: NodeId;
|
|
294
|
+
parent: NodeId | null; /** Verbatim tag name with prefix. */
|
|
295
|
+
raw_tag: string;
|
|
296
|
+
prefix: string | null;
|
|
297
|
+
local: string;
|
|
298
|
+
ns: string | null;
|
|
299
|
+
attrs: AttrToken[];
|
|
300
|
+
children: NodeId[]; /** True if the source wrote `<tag/>` (no children). */
|
|
301
|
+
self_closing: boolean; /** Trivia inside the tag before `>` or `/>` (e.g. `" "` in `<tag />`). */
|
|
302
|
+
open_tag_trailing: string; /** Trivia between `</` and tag name in the close, e.g. usually empty. */
|
|
303
|
+
close_tag_leading: string; /** Trivia after the closing tag name before `>`, usually empty. */
|
|
304
|
+
close_tag_trailing: string;
|
|
305
|
+
};
|
|
306
|
+
type TextNode = {
|
|
307
|
+
kind: "text";
|
|
308
|
+
id: NodeId;
|
|
309
|
+
parent: NodeId | null; /** Verbatim text, entity-decoded. */
|
|
310
|
+
value: string;
|
|
311
|
+
};
|
|
312
|
+
type CommentNode = {
|
|
313
|
+
kind: "comment";
|
|
314
|
+
id: NodeId;
|
|
315
|
+
parent: NodeId | null; /** Comment body (between `<!--` and `-->`). */
|
|
316
|
+
value: string;
|
|
317
|
+
};
|
|
318
|
+
type CDataNode = {
|
|
319
|
+
kind: "cdata";
|
|
320
|
+
id: NodeId;
|
|
321
|
+
parent: NodeId | null;
|
|
322
|
+
value: string;
|
|
323
|
+
};
|
|
324
|
+
type PiNode = {
|
|
325
|
+
kind: "pi";
|
|
326
|
+
id: NodeId;
|
|
327
|
+
parent: NodeId | null; /** PI target, e.g. "xml" for `<?xml ... ?>`. */
|
|
328
|
+
target: string;
|
|
329
|
+
value: string;
|
|
330
|
+
};
|
|
331
|
+
type DoctypeNode = {
|
|
332
|
+
kind: "doctype";
|
|
333
|
+
id: NodeId;
|
|
334
|
+
parent: NodeId | null; /** Full doctype declaration body, e.g. `svg PUBLIC "..." "..."`. */
|
|
335
|
+
value: string;
|
|
336
|
+
};
|
|
337
|
+
type AnyNode = ElementNode | TextNode | CommentNode | CDataNode | PiNode | DoctypeNode;
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region src/core/document.d.ts
|
|
340
|
+
interface DocumentEvents {
|
|
341
|
+
/** Fires after any structural mutation. */
|
|
342
|
+
on_change(fn: () => void): () => void;
|
|
343
|
+
}
|
|
344
|
+
declare class SvgDocument implements DocumentEvents {
|
|
345
|
+
private nodes;
|
|
346
|
+
private prolog;
|
|
347
|
+
private epilog;
|
|
348
|
+
/** Snapshot of the input string, used for `reset()`. */
|
|
349
|
+
private source;
|
|
350
|
+
/** Original parse result, for `reset()`. */
|
|
351
|
+
private original;
|
|
352
|
+
readonly root: NodeId;
|
|
353
|
+
private listeners;
|
|
354
|
+
/**
|
|
355
|
+
* Counter that bumps ONLY when something the hierarchy view cares about
|
|
356
|
+
* changes — tree topology (`insert`/`remove`), text-node content
|
|
357
|
+
* (`set_text`), or the `id` attribute (which feeds display labels). Pure
|
|
358
|
+
* presentation-attribute writes (x, y, fill, …) do NOT bump it.
|
|
359
|
+
*
|
|
360
|
+
* Why a separate counter: consumers like the layers panel cache snapshots
|
|
361
|
+
* keyed on this. During a drag, x/y writes fire `emit()` repeatedly but
|
|
362
|
+
* `structure_version` stays stable, so the panel's snapshot reference
|
|
363
|
+
* stays the same and React skips the re-render of the whole tree.
|
|
364
|
+
*/
|
|
365
|
+
private _structure_version;
|
|
366
|
+
constructor(svg: string);
|
|
367
|
+
static parse(svg: string): SvgDocument;
|
|
368
|
+
/** Reload from the original parse, discarding all edits. */
|
|
369
|
+
reset_to_original(): void;
|
|
370
|
+
/** Replace document with new svg source (clears edits + history-owned state). */
|
|
371
|
+
load(svg: string): void;
|
|
372
|
+
on_change(fn: () => void): () => void;
|
|
373
|
+
/** See `_structure_version` for what this counter signals. */
|
|
374
|
+
get structure_version(): number;
|
|
375
|
+
private emit;
|
|
376
|
+
/** Notify subscribers — for callers that mutate directly via setAttr/etc. */
|
|
377
|
+
notify(): void;
|
|
378
|
+
get(id: NodeId): AnyNode | null;
|
|
379
|
+
is_element(id: NodeId): boolean;
|
|
380
|
+
parent_of(id: NodeId): NodeId | null;
|
|
381
|
+
children_of(id: NodeId): readonly NodeId[];
|
|
382
|
+
/** Element children only — text/comment/cdata filtered out. */
|
|
383
|
+
element_children_of(id: NodeId): readonly NodeId[];
|
|
384
|
+
next_sibling_of(id: NodeId): NodeId | null;
|
|
385
|
+
next_element_sibling_of(id: NodeId): NodeId | null;
|
|
386
|
+
tag_of(id: NodeId): string;
|
|
387
|
+
contains(ancestor: NodeId, descendant: NodeId): boolean;
|
|
388
|
+
all_nodes(): readonly NodeId[];
|
|
389
|
+
all_elements(): readonly NodeId[];
|
|
390
|
+
find_by_tag(ancestor: NodeId, tag: string): readonly NodeId[];
|
|
391
|
+
/** Read attribute by local name, optionally namespace-filtered. */
|
|
392
|
+
get_attr(id: NodeId, name: string, ns?: string | null): string | null;
|
|
393
|
+
/**
|
|
394
|
+
* Set / remove an attribute. If the attribute exists, it is mutated in place
|
|
395
|
+
* (preserving source position). If it doesn't, it's appended.
|
|
396
|
+
*/
|
|
397
|
+
set_attr(id: NodeId, name: string, value: string | null, ns?: string | null): void;
|
|
398
|
+
attributes_of(id: NodeId): {
|
|
399
|
+
name: string;
|
|
400
|
+
ns: string | null;
|
|
401
|
+
value: string;
|
|
402
|
+
}[];
|
|
403
|
+
get_style(id: NodeId, property: string): string | null;
|
|
404
|
+
set_style(id: NodeId, property: string, value: string | null): void;
|
|
405
|
+
get_all_styles(id: NodeId): Array<{
|
|
406
|
+
property: string;
|
|
407
|
+
value: string;
|
|
408
|
+
}>;
|
|
409
|
+
text_of(id: NodeId): string;
|
|
410
|
+
/** Replace all direct text children with a single text node carrying `value`. */
|
|
411
|
+
set_text(id: NodeId, value: string): void;
|
|
412
|
+
insert(id: NodeId, parent: NodeId, before: NodeId | null): void;
|
|
413
|
+
remove(id: NodeId): void;
|
|
414
|
+
/** Create a new element node and register it (not yet inserted). */
|
|
415
|
+
create_element(local: string, opts?: {
|
|
416
|
+
prefix?: string | null;
|
|
417
|
+
ns?: string | null;
|
|
418
|
+
}): NodeId;
|
|
419
|
+
serialize(): string;
|
|
420
|
+
private emit_node;
|
|
421
|
+
private emit_attr;
|
|
422
|
+
}
|
|
423
|
+
//#endregion
|
|
424
|
+
//#region src/core/defs.d.ts
|
|
425
|
+
interface GradientsApi {
|
|
426
|
+
list(): ReadonlyArray<GradientEntry>;
|
|
427
|
+
get(id: string): GradientEntry | null;
|
|
428
|
+
upsert(definition: GradientDefinition, opts?: {
|
|
429
|
+
id?: string;
|
|
430
|
+
}): string;
|
|
431
|
+
remove(id: string): void;
|
|
432
|
+
subscribe(fn: (entries: ReadonlyArray<GradientEntry>) => void): Unsubscribe;
|
|
433
|
+
}
|
|
434
|
+
type Defs = {
|
|
435
|
+
gradients: GradientsApi;
|
|
436
|
+
};
|
|
437
|
+
//#endregion
|
|
438
|
+
//#region src/core/properties.d.ts
|
|
439
|
+
declare function read_property(doc: SvgDocument, id: NodeId, property: string): PropertyValue;
|
|
440
|
+
//#endregion
|
|
441
|
+
//#region src/core/editor.d.ts
|
|
442
|
+
/** Resolved paint from the DOM-attached cascade. `resolved_paint` mirrors the
|
|
443
|
+
* shape of `PaintValue.computed` so consumers can treat it uniformly with
|
|
444
|
+
* the headless cascade. */
|
|
445
|
+
type DomComputedPaint = {
|
|
446
|
+
computed: string;
|
|
447
|
+
resolved_paint: Paint | InvalidComputedValue | null;
|
|
448
|
+
};
|
|
449
|
+
/** Contract the DOM surface implements to delegate cascade resolution to
|
|
450
|
+
* `getComputedStyle()`. Registered via `editor._internal.set_computed_resolver`
|
|
451
|
+
* on attach. */
|
|
452
|
+
type DomComputedResolver = {
|
|
453
|
+
computed_property(id: NodeId, name: string): string | null;
|
|
454
|
+
computed_paint(id: NodeId, channel: "fill" | "stroke"): DomComputedPaint | null;
|
|
455
|
+
};
|
|
456
|
+
type CreateSvgEditorOptions = {
|
|
457
|
+
svg: string;
|
|
458
|
+
providers?: Providers;
|
|
459
|
+
style?: Partial<EditorStyle>;
|
|
460
|
+
};
|
|
461
|
+
type SvgEditor = ReturnType<typeof createSvgEditor>;
|
|
462
|
+
type Surface = {
|
|
463
|
+
paint(snapshot: unknown): void;
|
|
464
|
+
hit_test(x: number, y: number): NodeId | null;
|
|
465
|
+
on_input(listener: (event: unknown) => void): Unsubscribe;
|
|
466
|
+
dispose(): void;
|
|
467
|
+
};
|
|
468
|
+
type SurfaceHandle = {
|
|
469
|
+
detach(): void;
|
|
470
|
+
};
|
|
471
|
+
type Commands = {
|
|
472
|
+
select(target: NodeId | ReadonlyArray<NodeId>, opts?: {
|
|
473
|
+
additive?: boolean;
|
|
474
|
+
}): void;
|
|
475
|
+
deselect(): void;
|
|
476
|
+
enter_scope(group: NodeId): void;
|
|
477
|
+
exit_scope(): void;
|
|
478
|
+
set_mode(mode: Mode): void;
|
|
479
|
+
set_property(name: string, value: string | null): void;
|
|
480
|
+
preview_property(name: string): PreviewSession;
|
|
481
|
+
set_paint(channel: "fill" | "stroke", paint: Paint): void;
|
|
482
|
+
preview_paint(channel: "fill" | "stroke"): PaintPreviewSession;
|
|
483
|
+
set_paint_from_gradient(channel: "fill" | "stroke", definition: GradientDefinition, opts?: {
|
|
484
|
+
reuse_existing?: boolean;
|
|
485
|
+
}): {
|
|
486
|
+
gradient_id: string;
|
|
487
|
+
};
|
|
488
|
+
translate(delta: {
|
|
489
|
+
dx: number;
|
|
490
|
+
dy: number;
|
|
491
|
+
}): void;
|
|
492
|
+
reorder(direction: ReorderDirection): void;
|
|
493
|
+
remove(): void;
|
|
494
|
+
set_text(value: string): void;
|
|
495
|
+
load_svg(svg: string): void;
|
|
496
|
+
serialize_svg(): string;
|
|
497
|
+
undo(): void;
|
|
498
|
+
redo(): void;
|
|
499
|
+
/**
|
|
500
|
+
* Register a command handler under a stable id. Returns an unregister
|
|
501
|
+
* function. Re-registering the same id replaces the previous handler.
|
|
502
|
+
*
|
|
503
|
+
* Handlers return `true` if they consumed the invocation; `false` or
|
|
504
|
+
* `undefined` signal "did not apply" — the keymap dispatcher will try
|
|
505
|
+
* the next candidate in the chain.
|
|
506
|
+
*/
|
|
507
|
+
register(id: CommandId, handler: CommandHandler): () => void;
|
|
508
|
+
/**
|
|
509
|
+
* Invoke a registered command by id. Returns `true` if a handler
|
|
510
|
+
* consumed the invocation, `false` otherwise (including unknown ids).
|
|
511
|
+
*/
|
|
512
|
+
invoke(id: CommandId, args?: unknown): boolean; /** Whether an id has a registered handler. */
|
|
513
|
+
has(id: CommandId): boolean;
|
|
514
|
+
};
|
|
515
|
+
declare function createSvgEditor(opts: CreateSvgEditorOptions): {
|
|
516
|
+
/**
|
|
517
|
+
* Low-level IR handle. Mutating directly bypasses history; prefer
|
|
518
|
+
* `editor.commands` for app code.
|
|
519
|
+
*/
|
|
520
|
+
document: SvgDocument;
|
|
521
|
+
readonly state: EditorState;
|
|
522
|
+
subscribe: (fn: (state: EditorState) => void) => Unsubscribe;
|
|
523
|
+
subscribe_with_selector: <T>(selector: (s: EditorState) => T, fn: (value: T, prev: T) => void, equals?: (a: T, b: T) => boolean) => Unsubscribe;
|
|
524
|
+
node_properties: (id: NodeId, names: ReadonlyArray<string>) => {
|
|
525
|
+
readonly [name: string]: ReturnType<typeof read_property>;
|
|
526
|
+
};
|
|
527
|
+
node_paint: (id: NodeId, channel: "fill" | "stroke") => PaintValue;
|
|
528
|
+
dom_computed_property: (id: NodeId, name: string) => string | null;
|
|
529
|
+
dom_computed_paint: (id: NodeId, channel: "fill" | "stroke") => DomComputedPaint | null;
|
|
530
|
+
/**
|
|
531
|
+
* Enter content-edit mode on a `<text>` node. Returns `false` (no-op)
|
|
532
|
+
* when no DOM surface is attached.
|
|
533
|
+
*/
|
|
534
|
+
enter_content_edit: (target?: NodeId) => boolean;
|
|
535
|
+
defs: Defs;
|
|
536
|
+
commands: Commands;
|
|
537
|
+
/**
|
|
538
|
+
* Human-readable label for hierarchy panels. SVG has no native "name";
|
|
539
|
+
* this is the package's single source of truth so panels don't reinvent
|
|
540
|
+
* the rule.
|
|
541
|
+
*
|
|
542
|
+
* Rule:
|
|
543
|
+
* - `<text>` → text content, whitespace-collapsed and truncated at
|
|
544
|
+
* ~40 chars (falls back to `"text"` for empty content).
|
|
545
|
+
* - Otherwise → tag name, suffixed with `#id` when the `id` attribute
|
|
546
|
+
* is present (e.g. `"rect #sun"`).
|
|
547
|
+
*
|
|
548
|
+
* `opts.tagLabel` lets callers substitute a friendlier or localized
|
|
549
|
+
* term for the raw tag (e.g. `"rect"` → `"Rectangle"`). Only invoked
|
|
550
|
+
* on the non-text branch.
|
|
551
|
+
*/
|
|
552
|
+
display_label(id: NodeId, opts?: {
|
|
553
|
+
tagLabel?: (tag: string) => string;
|
|
554
|
+
}): string;
|
|
555
|
+
tree(): {
|
|
556
|
+
root: NodeId;
|
|
557
|
+
nodes: ReadonlyMap<NodeId, {
|
|
558
|
+
id: NodeId;
|
|
559
|
+
tag: string;
|
|
560
|
+
name?: string;
|
|
561
|
+
parent: NodeId | null;
|
|
562
|
+
children: ReadonlyArray<NodeId>;
|
|
563
|
+
}>;
|
|
564
|
+
};
|
|
565
|
+
/**
|
|
566
|
+
* The effective hover from the attached HUD surface — what's under the
|
|
567
|
+
* pointer, OR whatever `set_surface_hover_override` last pushed. Used
|
|
568
|
+
* by out-of-canvas UI (layers panel, breadcrumbs) to mirror the canvas
|
|
569
|
+
* highlight. Returns `null` when nothing is hovered.
|
|
570
|
+
*/
|
|
571
|
+
surface_hover(): NodeId | null;
|
|
572
|
+
/**
|
|
573
|
+
* Push a hover override into the HUD surface — e.g. when the user
|
|
574
|
+
* hovers a row in a layers panel. The HUD will render the override's
|
|
575
|
+
* outline and (when applicable) drive measurement to that node.
|
|
576
|
+
* Pass `null` to clear and let the pointer pick take over again.
|
|
577
|
+
*/
|
|
578
|
+
set_surface_hover_override(id: NodeId | null): void;
|
|
579
|
+
/**
|
|
580
|
+
* Subscribe to changes in the effective surface hover. Fires when the
|
|
581
|
+
* HUD reports a new pointer pick AND when an override is set/cleared.
|
|
582
|
+
* Cheap channel — does NOT bump `state.version`.
|
|
583
|
+
*/
|
|
584
|
+
subscribe_surface_hover(cb: () => void): () => void;
|
|
585
|
+
modes: readonly Mode[];
|
|
586
|
+
readonly style: Readonly<EditorStyle>;
|
|
587
|
+
set_style: (partial: Partial<EditorStyle>) => void;
|
|
588
|
+
load: (svg: string) => void;
|
|
589
|
+
serialize: () => string;
|
|
590
|
+
reset: () => void;
|
|
591
|
+
attach: (surface: Surface) => SurfaceHandle;
|
|
592
|
+
detach: () => void;
|
|
593
|
+
dispose: () => void;
|
|
594
|
+
providers: Providers;
|
|
595
|
+
_internal: {
|
|
596
|
+
doc: SvgDocument;
|
|
597
|
+
set_content_edit_driver(fn: ((target: NodeId) => boolean) | null): void;
|
|
598
|
+
/** Fires the driver immediately with the current override so the
|
|
599
|
+
* surface can sync state on attach. */
|
|
600
|
+
set_surface_hover_override_driver(fn: ((id: NodeId | null) => void) | null): void;
|
|
601
|
+
push_surface_hover(id: NodeId | null): void;
|
|
602
|
+
set_computed_resolver(fn: DomComputedResolver | null): void;
|
|
603
|
+
};
|
|
604
|
+
keymap: Keymap;
|
|
605
|
+
};
|
|
606
|
+
//#endregion
|
|
607
|
+
export { RadialGradientDefinition as A, PaintFallback as C, PropertyValue as D, PreviewSession as E, KeymapBinding as F, CommandHandler as I, CommandId as L, ReorderDirection as M, Unsubscribe as N, Provenance as O, Vec2 as P, Paint as S, PaintValue as T, GradientStop as _, SurfaceHandle as a, Mode as b, ClipboardProvider as c, EditorState as d, EditorStyle as f, GradientEntry as g, GradientDefinition as h, DomComputedResolver as i, Rect as j, Providers as k, Color as l, FontResolver as m, CreateSvgEditorOptions as n, SvgEditor as o, FileIOProvider as p, DomComputedPaint as r, createSvgEditor as s, Commands as t, DEFAULT_STYLE as u, InvalidComputedValue as v, PaintPreviewSession as w, NodeId as x, LinearGradientDefinition as y };
|