@grida/svg-editor 1.0.0-alpha.4 → 1.0.0-alpha.5

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.
@@ -1,4 +1,4 @@
1
- import { a as SurfaceHandle, d as Gestures, g as Camera, o as SvgEditor } from "./editor-Uu6dZX4y.mjs";
1
+ import { _ as Camera, f as Gestures, o as SurfaceHandle, s as SvgEditor } from "./editor-KRAmUodY.mjs";
2
2
  import cmath from "@grida/cmath";
3
3
 
4
4
  //#region src/dom.d.ts
@@ -29,7 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  enumerable: true
30
30
  }) : target, mod));
31
31
  //#endregion
32
- const require_paint = require("./paint-dDV-Trt9.js");
32
+ const require_paint = require("./paint-cTjePy5e.js");
33
33
  let _grida_text_editor_dom = require("@grida/text-editor/dom");
34
34
  let _grida_hud = require("@grida/hud");
35
35
  let _grida_cmath__measurement = require("@grida/cmath/_measurement");
@@ -564,7 +564,9 @@ var SvgTextSurface = class {
564
564
  this.last_sel_end = -1;
565
565
  this.textEl = textEl;
566
566
  const ownerDoc = textEl.ownerDocument;
567
- const parent = textEl.parentNode;
567
+ let mountAnchor = textEl;
568
+ while (mountAnchor.parentElement instanceof SVGElement && (mountAnchor.localName === "tspan" || mountAnchor.localName === "textPath")) mountAnchor = mountAnchor.parentElement;
569
+ const parent = mountAnchor.parentNode;
568
570
  if (!parent) throw new Error("text element has no parent");
569
571
  const computedWhitespace = ownerDoc.defaultView?.getComputedStyle(textEl).whiteSpace;
570
572
  if (!(computedWhitespace === "pre" || computedWhitespace === "pre-wrap" || computedWhitespace === "break-spaces")) {
@@ -579,14 +581,14 @@ var SvgTextSurface = class {
579
581
  selection.setAttribute("pointer-events", "none");
580
582
  selection.setAttribute("data-svg-text-edit-selection", "");
581
583
  selection.style.display = "none";
582
- parent.insertBefore(selection, textEl);
584
+ parent.insertBefore(selection, mountAnchor);
583
585
  this.selectionRect = selection;
584
586
  const caret = ownerDoc.createElementNS(SVG_NS, "rect");
585
587
  caret.setAttribute("fill", "#2563eb");
586
588
  caret.setAttribute("pointer-events", "none");
587
589
  caret.setAttribute("data-svg-text-edit-caret", "");
588
590
  caret.style.display = "none";
589
- parent.insertBefore(caret, textEl.nextSibling);
591
+ parent.insertBefore(caret, mountAnchor.nextSibling);
590
592
  this.caretRect = caret;
591
593
  }
592
594
  setText(text) {
@@ -1329,7 +1331,7 @@ var DomSurface = class {
1329
1331
  commit_intent(intent) {
1330
1332
  switch (intent.kind) {
1331
1333
  case "select":
1332
- this.editor.commands.select(intent.ids, { additive: intent.mode !== "replace" });
1334
+ this.editor.commands.select(intent.ids, { mode: intent.mode });
1333
1335
  return;
1334
1336
  case "deselect_all":
1335
1337
  this.editor.commands.deselect();
@@ -1556,13 +1558,15 @@ var DomSurface = class {
1556
1558
  if (!intent.additive) this.editor.commands.deselect();
1557
1559
  return;
1558
1560
  }
1559
- this.editor.commands.select(ids, { additive: intent.additive });
1561
+ this.editor.commands.select(ids, { mode: intent.additive ? "add" : "replace" });
1560
1562
  }
1561
1563
  enter_content_edit(id) {
1562
1564
  if (this.text_edit) return false;
1563
1565
  const el = this.element_index.get(id);
1564
- if (!(el instanceof SVGTextElement)) return false;
1566
+ if (!(el instanceof SVGElement)) return false;
1565
1567
  const doc = this.editor._internal;
1568
+ if (!doc.doc.is_text_edit_target(id)) return false;
1569
+ if (!(el instanceof SVGTextContentElement)) return false;
1566
1570
  this.text_edit_target = id;
1567
1571
  this.text_edit_original = doc.doc.text_of(id);
1568
1572
  this.text_edit = TEXT_EDIT_PENDING;
@@ -1,4 +1,4 @@
1
- import { a as SurfaceHandle, d as Gestures, g as Camera, o as SvgEditor } from "./editor-D2l_CDr0.js";
1
+ import { _ as Camera, f as Gestures, o as SurfaceHandle, s as SvgEditor } from "./editor-DdgqLDC9.js";
2
2
  import cmath from "@grida/cmath";
3
3
 
4
4
  //#region src/dom.d.ts
@@ -532,7 +532,9 @@ var SvgTextSurface = class {
532
532
  this.last_sel_end = -1;
533
533
  this.textEl = textEl;
534
534
  const ownerDoc = textEl.ownerDocument;
535
- const parent = textEl.parentNode;
535
+ let mountAnchor = textEl;
536
+ while (mountAnchor.parentElement instanceof SVGElement && (mountAnchor.localName === "tspan" || mountAnchor.localName === "textPath")) mountAnchor = mountAnchor.parentElement;
537
+ const parent = mountAnchor.parentNode;
536
538
  if (!parent) throw new Error("text element has no parent");
537
539
  const computedWhitespace = ownerDoc.defaultView?.getComputedStyle(textEl).whiteSpace;
538
540
  if (!(computedWhitespace === "pre" || computedWhitespace === "pre-wrap" || computedWhitespace === "break-spaces")) {
@@ -547,14 +549,14 @@ var SvgTextSurface = class {
547
549
  selection.setAttribute("pointer-events", "none");
548
550
  selection.setAttribute("data-svg-text-edit-selection", "");
549
551
  selection.style.display = "none";
550
- parent.insertBefore(selection, textEl);
552
+ parent.insertBefore(selection, mountAnchor);
551
553
  this.selectionRect = selection;
552
554
  const caret = ownerDoc.createElementNS(SVG_NS, "rect");
553
555
  caret.setAttribute("fill", "#2563eb");
554
556
  caret.setAttribute("pointer-events", "none");
555
557
  caret.setAttribute("data-svg-text-edit-caret", "");
556
558
  caret.style.display = "none";
557
- parent.insertBefore(caret, textEl.nextSibling);
559
+ parent.insertBefore(caret, mountAnchor.nextSibling);
558
560
  this.caretRect = caret;
559
561
  }
560
562
  setText(text) {
@@ -1297,7 +1299,7 @@ var DomSurface = class {
1297
1299
  commit_intent(intent) {
1298
1300
  switch (intent.kind) {
1299
1301
  case "select":
1300
- this.editor.commands.select(intent.ids, { additive: intent.mode !== "replace" });
1302
+ this.editor.commands.select(intent.ids, { mode: intent.mode });
1301
1303
  return;
1302
1304
  case "deselect_all":
1303
1305
  this.editor.commands.deselect();
@@ -1524,13 +1526,15 @@ var DomSurface = class {
1524
1526
  if (!intent.additive) this.editor.commands.deselect();
1525
1527
  return;
1526
1528
  }
1527
- this.editor.commands.select(ids, { additive: intent.additive });
1529
+ this.editor.commands.select(ids, { mode: intent.additive ? "add" : "replace" });
1528
1530
  }
1529
1531
  enter_content_edit(id) {
1530
1532
  if (this.text_edit) return false;
1531
1533
  const el = this.element_index.get(id);
1532
- if (!(el instanceof SVGTextElement)) return false;
1534
+ if (!(el instanceof SVGElement)) return false;
1533
1535
  const doc = this.editor._internal;
1536
+ if (!doc.doc.is_text_edit_target(id)) return false;
1537
+ if (!(el instanceof SVGTextContentElement)) return false;
1534
1538
  this.text_edit_target = id;
1535
1539
  this.text_edit_original = doc.doc.text_of(id);
1536
1540
  this.text_edit = TEXT_EDIT_PENDING;
package/dist/dom.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as DomSurfaceOptions, r as attach_dom_surface, t as DomSurfaceHandle } from "./dom-DJnZhtOd.mjs";
1
+ import { n as DomSurfaceOptions, r as attach_dom_surface, t as DomSurfaceHandle } from "./dom-CMXNUMjP.mjs";
2
2
  export { DomSurfaceHandle, DomSurfaceOptions, attach_dom_surface };
package/dist/dom.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as DomSurfaceOptions, r as attach_dom_surface, t as DomSurfaceHandle } from "./dom-Cn-RtjRL.js";
1
+ import { n as DomSurfaceOptions, r as attach_dom_surface, t as DomSurfaceHandle } from "./dom-eIgcZ4JC.js";
2
2
  export { DomSurfaceHandle, DomSurfaceOptions, attach_dom_surface };
package/dist/dom.js CHANGED
@@ -1,3 +1,3 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dom = require("./dom-CoVZzFqy.js");
2
+ const require_dom = require("./dom-DyJy1H6Q.js");
3
3
  exports.attach_dom_surface = require_dom.attach_dom_surface;
package/dist/dom.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { t as attach_dom_surface } from "./dom-CmOu0HvI.mjs";
1
+ import { t as attach_dom_surface } from "./dom-l5Y1Wf8C.mjs";
2
2
  export { attach_dom_surface };
@@ -489,7 +489,7 @@ var GradientsRegistry = class {
489
489
  };
490
490
  }
491
491
  write_gradient(node, def) {
492
- for (const c of [...this.doc.children_of(node)]) this.doc.remove(c);
492
+ for (const c of this.doc.children_of(node).slice()) this.doc.remove(c);
493
493
  const set_num = (name, v) => {
494
494
  this.doc.set_attr(node, name, v === void 0 ? null : String(v));
495
495
  };
@@ -730,7 +730,6 @@ function parse_attrs(src, from) {
730
730
  const c = src[i];
731
731
  if (c === "/") {
732
732
  if (src[i + 1] !== ">") throw new Error("expected '/>' at " + i);
733
- pre + "";
734
733
  return {
735
734
  attrs,
736
735
  end_index: i + 2,
@@ -1012,6 +1011,25 @@ var SvgDocument = class SvgDocument {
1012
1011
  if (!style) return [];
1013
1012
  return parse_inline_style(style);
1014
1013
  }
1014
+ /**
1015
+ * Whether `id` can be opened in the flat-string text editor.
1016
+ *
1017
+ * v1 contract: the editor only operates on a *single flat text run*. That
1018
+ * means the target must be a `<text>` or `<tspan>` whose direct children
1019
+ * are all text nodes (or it has no children). A `<text>` containing a
1020
+ * `<tspan>` is *not* honestly editable — `text_of` would drop the tspan
1021
+ * content from the editor's view, and a flat-text write would leave the
1022
+ * tspan dangling. Tspan-as-target is fine and well-defined when it's a
1023
+ * leaf; only the host decides whether to route double-click to a tspan
1024
+ * or its parent text.
1025
+ */
1026
+ is_text_edit_target(id) {
1027
+ const n = this.nodes.get(id);
1028
+ if (!n || n.kind !== "element") return false;
1029
+ if (n.local !== "text" && n.local !== "tspan") return false;
1030
+ for (const c of n.children) if (this.nodes.get(c)?.kind !== "text") return false;
1031
+ return true;
1032
+ }
1015
1033
  text_of(id) {
1016
1034
  const n = this.nodes.get(id);
1017
1035
  if (!n || n.kind !== "element") return "";
@@ -1327,7 +1345,7 @@ function createSvgEditor(opts) {
1327
1345
  let load_version = 0;
1328
1346
  let style = {
1329
1347
  ...DEFAULT_STYLE,
1330
- ...opts.style ?? {}
1348
+ ...opts.style
1331
1349
  };
1332
1350
  const providers = opts.providers ?? {};
1333
1351
  const listeners = /* @__PURE__ */ new Set();
@@ -1380,11 +1398,16 @@ function createSvgEditor(opts) {
1380
1398
  }
1381
1399
  function select(target, opts) {
1382
1400
  const ids = typeof target === "string" ? [target] : [...target];
1383
- if (opts?.additive) {
1384
- const merged = new Set(selection);
1385
- for (const id of ids) merged.add(id);
1386
- set_selection([...merged]);
1387
- } else set_selection(ids);
1401
+ const mode = opts?.mode ?? "replace";
1402
+ if (mode === "replace") {
1403
+ set_selection(ids);
1404
+ return;
1405
+ }
1406
+ const next = new Set(selection);
1407
+ if (mode === "add") for (const id of ids) next.add(id);
1408
+ else for (const id of ids) if (next.has(id)) next.delete(id);
1409
+ else next.add(id);
1410
+ set_selection([...next]);
1388
1411
  }
1389
1412
  function deselect() {
1390
1413
  set_selection([]);
@@ -1597,7 +1620,7 @@ function createSvgEditor(opts) {
1597
1620
  function set_text(value) {
1598
1621
  if (selection.length !== 1) return;
1599
1622
  const target = selection[0];
1600
- if (doc.tag_of(target) !== "text") return;
1623
+ if (!doc.is_text_edit_target(target)) return;
1601
1624
  const original = doc.text_of(target);
1602
1625
  if (original === value) return;
1603
1626
  const apply = () => {
@@ -1640,7 +1663,7 @@ function createSvgEditor(opts) {
1640
1663
  function enter_content_edit(target) {
1641
1664
  const id = target ?? (selection.length === 1 ? selection[0] : null);
1642
1665
  if (!id) return false;
1643
- if (doc.tag_of(id) !== "text") return false;
1666
+ if (!doc.is_text_edit_target(id)) return false;
1644
1667
  if (!content_edit_driver) return false;
1645
1668
  return content_edit_driver(id);
1646
1669
  }
@@ -1,6 +1,6 @@
1
+ import cmath from "@grida/cmath";
1
2
  import * as _$_grida_history0 from "@grida/history";
2
3
  import { Keybinding, Platform } from "@grida/keybinding";
3
- import cmath from "@grida/cmath";
4
4
 
5
5
  //#region src/types.d.ts
6
6
  /**
@@ -321,113 +321,6 @@ declare class Camera {
321
321
  private notify;
322
322
  }
323
323
  //#endregion
324
- //#region src/commands/registry.d.ts
325
- /**
326
- * Command registry.
327
- *
328
- * A passive id-keyed registry of handlers. Built so that:
329
- *
330
- * - keybindings (in `src/keymap`) can address commands by stable id;
331
- * - new commands can be added in ONE place (`src/commands/defaults.ts`)
332
- * without growing the public surface of the editor;
333
- * - "one key, many meanings" can be expressed via chain semantics: a
334
- * handler returns `true` if it consumed, `false`/`void` otherwise,
335
- * and the dispatcher tries the next candidate in the chain.
336
- *
337
- * Handlers are plain closures — they capture whatever editor reference
338
- * they need. The registry itself stays unaware of the editor's type,
339
- * which avoids a circular type dependency between editor and registry.
340
- */
341
- /** Stable, dotted id for a command, e.g. `"history.undo"`. */
342
- type CommandId = string;
343
- /**
344
- * A command handler.
345
- *
346
- * Return `true` if the handler consumed the invocation. Return `false`
347
- * or `undefined` to signal "did not apply" — the dispatcher will try
348
- * the next candidate registered for the same key.
349
- *
350
- * Handlers are closures: they capture their editor reference. No
351
- * editor parameter is passed — keep handlers self-contained.
352
- */
353
- type CommandHandler = (args?: unknown) => boolean | void;
354
- declare class CommandRegistry {
355
- private readonly map;
356
- /**
357
- * Register a command. Returns an unregister function. Re-registering
358
- * the same id replaces the previous handler (last writer wins).
359
- */
360
- register(id: CommandId, handler: CommandHandler): () => void;
361
- /**
362
- * Invoke a command by id. Returns `true` if the handler consumed,
363
- * `false` otherwise (including unknown ids and handlers that returned
364
- * `false`/`undefined`).
365
- */
366
- invoke(id: CommandId, args?: unknown): boolean;
367
- has(id: CommandId): boolean;
368
- /** All registered ids, for debugging / introspection. */
369
- ids(): readonly CommandId[];
370
- }
371
- //#endregion
372
- //#region src/keymap/keymap.d.ts
373
- type KeymapBinding = {
374
- /** Declarative key combination. Build with `kb()` / `c()` / `seq()`. */keybinding: Keybinding; /** Command id to invoke on match. */
375
- command: CommandId; /** Forwarded as the `args` parameter to the command handler. */
376
- args?: unknown; /** Higher priorities run first in the chain. Default 0. */
377
- priority?: number;
378
- /**
379
- * Reserved for V2; not honored by the V1 dispatcher. When added, this
380
- * will be evaluated before the handler runs; if false, the binding is
381
- * skipped without invoking the handler.
382
- */
383
- when?: (ctx: unknown) => boolean;
384
- };
385
- declare class Keymap {
386
- private readonly commands;
387
- private readonly platformGetter;
388
- /**
389
- * Bindings bucketed by canonical chunk-key hash, computed per
390
- * `@grida/keybinding`'s `chunkKey`. Each list is the chain for that
391
- * key, sorted in dispatch order (priority desc, then registration
392
- * order).
393
- */
394
- private readonly buckets;
395
- /** Insert order, so ties on priority are deterministic. */
396
- private seq;
397
- constructor(commands: CommandRegistry, platformGetter?: () => Platform);
398
- /**
399
- * Bind a key combination to a command. Returns an unbind function.
400
- * The same `Keybinding` can be bound to multiple commands — they will
401
- * all be tried in chain order on dispatch.
402
- */
403
- bind(binding: KeymapBinding): () => void;
404
- /**
405
- * Remove bindings matching the spec. If both filters are passed, only
406
- * bindings that match BOTH are removed.
407
- */
408
- unbind(spec: {
409
- keybinding?: Keybinding;
410
- command?: CommandId;
411
- }): void;
412
- /** All registered bindings, for introspection. Order is not guaranteed. */
413
- bindings(): readonly KeymapBinding[];
414
- /**
415
- * Match the event against bound chunks, then run candidates in chain
416
- * order. Returns `true` and calls `preventDefault()` on the first
417
- * handler that consumes; returns `false` if nothing matched or all
418
- * matches fell through.
419
- */
420
- dispatch(event: KeyboardEvent): boolean;
421
- /**
422
- * Compute the set of canonical hashes a `Keybinding` lights up. A
423
- * binding with aliases (multiple sequences) contributes one hash per
424
- * single-chunk alias; multi-chunk sequences (chords) are skipped in
425
- * V1.
426
- */
427
- private chunkKeysFor;
428
- private has_safe_mod;
429
- }
430
- //#endregion
431
324
  //#region src/core/parser.d.ts
432
325
  type AttrToken = {
433
326
  /** Verbatim source name including any prefix, e.g. "xlink:href". */raw_name: string; /** Prefix or null. */
@@ -557,6 +450,19 @@ declare class SvgDocument implements DocumentEvents {
557
450
  property: string;
558
451
  value: string;
559
452
  }>;
453
+ /**
454
+ * Whether `id` can be opened in the flat-string text editor.
455
+ *
456
+ * v1 contract: the editor only operates on a *single flat text run*. That
457
+ * means the target must be a `<text>` or `<tspan>` whose direct children
458
+ * are all text nodes (or it has no children). A `<text>` containing a
459
+ * `<tspan>` is *not* honestly editable — `text_of` would drop the tspan
460
+ * content from the editor's view, and a flat-text write would leave the
461
+ * tspan dangling. Tspan-as-target is fine and well-defined when it's a
462
+ * leaf; only the host decides whether to route double-click to a tspan
463
+ * or its parent text.
464
+ */
465
+ is_text_edit_target(id: NodeId): boolean;
560
466
  text_of(id: NodeId): string;
561
467
  /** Replace all direct text children with a single text node carrying `value`. */
562
468
  set_text(id: NodeId, value: string): void;
@@ -586,6 +492,113 @@ type Defs = {
586
492
  gradients: GradientsApi;
587
493
  };
588
494
  //#endregion
495
+ //#region src/commands/registry.d.ts
496
+ /**
497
+ * Command registry.
498
+ *
499
+ * A passive id-keyed registry of handlers. Built so that:
500
+ *
501
+ * - keybindings (in `src/keymap`) can address commands by stable id;
502
+ * - new commands can be added in ONE place (`src/commands/defaults.ts`)
503
+ * without growing the public surface of the editor;
504
+ * - "one key, many meanings" can be expressed via chain semantics: a
505
+ * handler returns `true` if it consumed, `false`/`void` otherwise,
506
+ * and the dispatcher tries the next candidate in the chain.
507
+ *
508
+ * Handlers are plain closures — they capture whatever editor reference
509
+ * they need. The registry itself stays unaware of the editor's type,
510
+ * which avoids a circular type dependency between editor and registry.
511
+ */
512
+ /** Stable, dotted id for a command, e.g. `"history.undo"`. */
513
+ type CommandId = string;
514
+ /**
515
+ * A command handler.
516
+ *
517
+ * Return `true` if the handler consumed the invocation. Return `false`
518
+ * or `undefined` to signal "did not apply" — the dispatcher will try
519
+ * the next candidate registered for the same key.
520
+ *
521
+ * Handlers are closures: they capture their editor reference. No
522
+ * editor parameter is passed — keep handlers self-contained.
523
+ */
524
+ type CommandHandler = (args?: unknown) => boolean | void;
525
+ declare class CommandRegistry {
526
+ private readonly map;
527
+ /**
528
+ * Register a command. Returns an unregister function. Re-registering
529
+ * the same id replaces the previous handler (last writer wins).
530
+ */
531
+ register(id: CommandId, handler: CommandHandler): () => void;
532
+ /**
533
+ * Invoke a command by id. Returns `true` if the handler consumed,
534
+ * `false` otherwise (including unknown ids and handlers that returned
535
+ * `false`/`undefined`).
536
+ */
537
+ invoke(id: CommandId, args?: unknown): boolean;
538
+ has(id: CommandId): boolean;
539
+ /** All registered ids, for debugging / introspection. */
540
+ ids(): readonly CommandId[];
541
+ }
542
+ //#endregion
543
+ //#region src/keymap/keymap.d.ts
544
+ type KeymapBinding = {
545
+ /** Declarative key combination. Build with `kb()` / `c()` / `seq()`. */keybinding: Keybinding; /** Command id to invoke on match. */
546
+ command: CommandId; /** Forwarded as the `args` parameter to the command handler. */
547
+ args?: unknown; /** Higher priorities run first in the chain. Default 0. */
548
+ priority?: number;
549
+ /**
550
+ * Reserved for V2; not honored by the V1 dispatcher. When added, this
551
+ * will be evaluated before the handler runs; if false, the binding is
552
+ * skipped without invoking the handler.
553
+ */
554
+ when?: (ctx: unknown) => boolean;
555
+ };
556
+ declare class Keymap {
557
+ private readonly commands;
558
+ private readonly platformGetter;
559
+ /**
560
+ * Bindings bucketed by canonical chunk-key hash, computed per
561
+ * `@grida/keybinding`'s `chunkKey`. Each list is the chain for that
562
+ * key, sorted in dispatch order (priority desc, then registration
563
+ * order).
564
+ */
565
+ private readonly buckets;
566
+ /** Insert order, so ties on priority are deterministic. */
567
+ private seq;
568
+ constructor(commands: CommandRegistry, platformGetter?: () => Platform);
569
+ /**
570
+ * Bind a key combination to a command. Returns an unbind function.
571
+ * The same `Keybinding` can be bound to multiple commands — they will
572
+ * all be tried in chain order on dispatch.
573
+ */
574
+ bind(binding: KeymapBinding): () => void;
575
+ /**
576
+ * Remove bindings matching the spec. If both filters are passed, only
577
+ * bindings that match BOTH are removed.
578
+ */
579
+ unbind(spec: {
580
+ keybinding?: Keybinding;
581
+ command?: CommandId;
582
+ }): void;
583
+ /** All registered bindings, for introspection. Order is not guaranteed. */
584
+ bindings(): readonly KeymapBinding[];
585
+ /**
586
+ * Match the event against bound chunks, then run candidates in chain
587
+ * order. Returns `true` and calls `preventDefault()` on the first
588
+ * handler that consumes; returns `false` if nothing matched or all
589
+ * matches fell through.
590
+ */
591
+ dispatch(event: KeyboardEvent): boolean;
592
+ /**
593
+ * Compute the set of canonical hashes a `Keybinding` lights up. A
594
+ * binding with aliases (multiple sequences) contributes one hash per
595
+ * single-chunk alias; multi-chunk sequences (chords) are skipped in
596
+ * V1.
597
+ */
598
+ private chunkKeysFor;
599
+ private has_safe_mod;
600
+ }
601
+ //#endregion
589
602
  //#region src/core/properties.d.ts
590
603
  declare function read_property(doc: SvgDocument, id: NodeId, property: string): PropertyValue;
591
604
  //#endregion
@@ -675,9 +688,18 @@ type Surface = {
675
688
  type SurfaceHandle = {
676
689
  detach(): void;
677
690
  };
691
+ /**
692
+ * Mode for `commands.select`. Matches the HUD's `SelectMode` vocabulary so
693
+ * intents can be threaded through without lossy collapsing.
694
+ *
695
+ * - `"replace"` (default) — set selection to `ids`, discarding the previous set.
696
+ * - `"add"` — union: each id in `ids` is added; existing members stay.
697
+ * - `"toggle"` — flip each id's membership (present → removed; absent → added).
698
+ */
699
+ type SelectMode = "replace" | "add" | "toggle";
678
700
  type Commands = {
679
701
  select(target: NodeId | ReadonlyArray<NodeId>, opts?: {
680
- additive?: boolean;
702
+ mode?: SelectMode;
681
703
  }): void;
682
704
  deselect(): void;
683
705
  enter_scope(group: NodeId): void;
@@ -815,4 +837,4 @@ declare function createSvgEditor(opts: CreateSvgEditorOptions): {
815
837
  keymap: Keymap;
816
838
  };
817
839
  //#endregion
818
- export { LinearGradientDefinition as A, Providers as B, EditorStyle as C, GradientEntry as D, GradientDefinition as E, PaintPreviewSession as F, Vec2 as G, Rect as H, PaintValue as I, PreviewSession as L, NodeId as M, Paint as N, GradientStop as O, PaintFallback as P, PropertyValue as R, EditorState as S, FontResolver as T, ReorderDirection as U, RadialGradientDefinition as V, Unsubscribe as W, CameraConstraints as _, SurfaceHandle as a, Color as b, GestureBinding as c, Gestures as d, KeymapBinding as f, Camera as g, BoundsResolver as h, DomComputedResolver as i, Mode as j, InvalidComputedValue as k, GestureContext as l, CommandId as m, CreateSvgEditorOptions as n, SvgEditor as o, CommandHandler as p, DomComputedPaint as r, createSvgEditor as s, Commands as t, GestureId as u, CameraOptions as v, FileIOProvider as w, DEFAULT_STYLE as x, ClipboardProvider as y, Provenance as z };
840
+ export { InvalidComputedValue as A, Provenance as B, EditorState as C, GradientDefinition as D, FontResolver as E, PaintFallback as F, Unsubscribe as G, RadialGradientDefinition as H, PaintPreviewSession as I, Vec2 as K, PaintValue as L, Mode as M, NodeId as N, GradientEntry as O, Paint as P, PreviewSession as R, DEFAULT_STYLE as S, FileIOProvider as T, Rect as U, Providers as V, ReorderDirection as W, Camera as _, SelectMode as a, ClipboardProvider as b, createSvgEditor as c, GestureId as d, Gestures as f, BoundsResolver as g, CommandId as h, DomComputedResolver as i, LinearGradientDefinition as j, GradientStop as k, GestureBinding as l, CommandHandler as m, CreateSvgEditorOptions as n, SurfaceHandle as o, KeymapBinding as p, DomComputedPaint as r, SvgEditor as s, Commands as t, GestureContext as u, CameraConstraints as v, EditorStyle as w, Color as x, CameraOptions as y, PropertyValue as z };
@@ -1,5 +1,5 @@
1
- require("./dom-CoVZzFqy.js");
2
- const require_paint = require("./paint-dDV-Trt9.js");
1
+ require("./dom-DyJy1H6Q.js");
2
+ const require_paint = require("./paint-cTjePy5e.js");
3
3
  let _grida_history = require("@grida/history");
4
4
  let _grida_keybinding = require("@grida/keybinding");
5
5
  //#region src/commands/registry.ts
@@ -490,7 +490,7 @@ var GradientsRegistry = class {
490
490
  };
491
491
  }
492
492
  write_gradient(node, def) {
493
- for (const c of [...this.doc.children_of(node)]) this.doc.remove(c);
493
+ for (const c of this.doc.children_of(node).slice()) this.doc.remove(c);
494
494
  const set_num = (name, v) => {
495
495
  this.doc.set_attr(node, name, v === void 0 ? null : String(v));
496
496
  };
@@ -731,7 +731,6 @@ function parse_attrs(src, from) {
731
731
  const c = src[i];
732
732
  if (c === "/") {
733
733
  if (src[i + 1] !== ">") throw new Error("expected '/>' at " + i);
734
- pre + "";
735
734
  return {
736
735
  attrs,
737
736
  end_index: i + 2,
@@ -1013,6 +1012,25 @@ var SvgDocument = class SvgDocument {
1013
1012
  if (!style) return [];
1014
1013
  return parse_inline_style(style);
1015
1014
  }
1015
+ /**
1016
+ * Whether `id` can be opened in the flat-string text editor.
1017
+ *
1018
+ * v1 contract: the editor only operates on a *single flat text run*. That
1019
+ * means the target must be a `<text>` or `<tspan>` whose direct children
1020
+ * are all text nodes (or it has no children). A `<text>` containing a
1021
+ * `<tspan>` is *not* honestly editable — `text_of` would drop the tspan
1022
+ * content from the editor's view, and a flat-text write would leave the
1023
+ * tspan dangling. Tspan-as-target is fine and well-defined when it's a
1024
+ * leaf; only the host decides whether to route double-click to a tspan
1025
+ * or its parent text.
1026
+ */
1027
+ is_text_edit_target(id) {
1028
+ const n = this.nodes.get(id);
1029
+ if (!n || n.kind !== "element") return false;
1030
+ if (n.local !== "text" && n.local !== "tspan") return false;
1031
+ for (const c of n.children) if (this.nodes.get(c)?.kind !== "text") return false;
1032
+ return true;
1033
+ }
1016
1034
  text_of(id) {
1017
1035
  const n = this.nodes.get(id);
1018
1036
  if (!n || n.kind !== "element") return "";
@@ -1328,7 +1346,7 @@ function createSvgEditor(opts) {
1328
1346
  let load_version = 0;
1329
1347
  let style = {
1330
1348
  ...DEFAULT_STYLE,
1331
- ...opts.style ?? {}
1349
+ ...opts.style
1332
1350
  };
1333
1351
  const providers = opts.providers ?? {};
1334
1352
  const listeners = /* @__PURE__ */ new Set();
@@ -1381,11 +1399,16 @@ function createSvgEditor(opts) {
1381
1399
  }
1382
1400
  function select(target, opts) {
1383
1401
  const ids = typeof target === "string" ? [target] : [...target];
1384
- if (opts?.additive) {
1385
- const merged = new Set(selection);
1386
- for (const id of ids) merged.add(id);
1387
- set_selection([...merged]);
1388
- } else set_selection(ids);
1402
+ const mode = opts?.mode ?? "replace";
1403
+ if (mode === "replace") {
1404
+ set_selection(ids);
1405
+ return;
1406
+ }
1407
+ const next = new Set(selection);
1408
+ if (mode === "add") for (const id of ids) next.add(id);
1409
+ else for (const id of ids) if (next.has(id)) next.delete(id);
1410
+ else next.add(id);
1411
+ set_selection([...next]);
1389
1412
  }
1390
1413
  function deselect() {
1391
1414
  set_selection([]);
@@ -1598,7 +1621,7 @@ function createSvgEditor(opts) {
1598
1621
  function set_text(value) {
1599
1622
  if (selection.length !== 1) return;
1600
1623
  const target = selection[0];
1601
- if (doc.tag_of(target) !== "text") return;
1624
+ if (!doc.is_text_edit_target(target)) return;
1602
1625
  const original = doc.text_of(target);
1603
1626
  if (original === value) return;
1604
1627
  const apply = () => {
@@ -1641,7 +1664,7 @@ function createSvgEditor(opts) {
1641
1664
  function enter_content_edit(target) {
1642
1665
  const id = target ?? (selection.length === 1 ? selection[0] : null);
1643
1666
  if (!id) return false;
1644
- if (doc.tag_of(id) !== "text") return false;
1667
+ if (!doc.is_text_edit_target(id)) return false;
1645
1668
  if (!content_edit_driver) return false;
1646
1669
  return content_edit_driver(id);
1647
1670
  }
@@ -1,6 +1,6 @@
1
- import cmath from "@grida/cmath";
2
1
  import * as _$_grida_history0 from "@grida/history";
3
2
  import { Keybinding, Platform } from "@grida/keybinding";
3
+ import cmath from "@grida/cmath";
4
4
 
5
5
  //#region src/types.d.ts
6
6
  /**
@@ -321,113 +321,6 @@ declare class Camera {
321
321
  private notify;
322
322
  }
323
323
  //#endregion
324
- //#region src/commands/registry.d.ts
325
- /**
326
- * Command registry.
327
- *
328
- * A passive id-keyed registry of handlers. Built so that:
329
- *
330
- * - keybindings (in `src/keymap`) can address commands by stable id;
331
- * - new commands can be added in ONE place (`src/commands/defaults.ts`)
332
- * without growing the public surface of the editor;
333
- * - "one key, many meanings" can be expressed via chain semantics: a
334
- * handler returns `true` if it consumed, `false`/`void` otherwise,
335
- * and the dispatcher tries the next candidate in the chain.
336
- *
337
- * Handlers are plain closures — they capture whatever editor reference
338
- * they need. The registry itself stays unaware of the editor's type,
339
- * which avoids a circular type dependency between editor and registry.
340
- */
341
- /** Stable, dotted id for a command, e.g. `"history.undo"`. */
342
- type CommandId = string;
343
- /**
344
- * A command handler.
345
- *
346
- * Return `true` if the handler consumed the invocation. Return `false`
347
- * or `undefined` to signal "did not apply" — the dispatcher will try
348
- * the next candidate registered for the same key.
349
- *
350
- * Handlers are closures: they capture their editor reference. No
351
- * editor parameter is passed — keep handlers self-contained.
352
- */
353
- type CommandHandler = (args?: unknown) => boolean | void;
354
- declare class CommandRegistry {
355
- private readonly map;
356
- /**
357
- * Register a command. Returns an unregister function. Re-registering
358
- * the same id replaces the previous handler (last writer wins).
359
- */
360
- register(id: CommandId, handler: CommandHandler): () => void;
361
- /**
362
- * Invoke a command by id. Returns `true` if the handler consumed,
363
- * `false` otherwise (including unknown ids and handlers that returned
364
- * `false`/`undefined`).
365
- */
366
- invoke(id: CommandId, args?: unknown): boolean;
367
- has(id: CommandId): boolean;
368
- /** All registered ids, for debugging / introspection. */
369
- ids(): readonly CommandId[];
370
- }
371
- //#endregion
372
- //#region src/keymap/keymap.d.ts
373
- type KeymapBinding = {
374
- /** Declarative key combination. Build with `kb()` / `c()` / `seq()`. */keybinding: Keybinding; /** Command id to invoke on match. */
375
- command: CommandId; /** Forwarded as the `args` parameter to the command handler. */
376
- args?: unknown; /** Higher priorities run first in the chain. Default 0. */
377
- priority?: number;
378
- /**
379
- * Reserved for V2; not honored by the V1 dispatcher. When added, this
380
- * will be evaluated before the handler runs; if false, the binding is
381
- * skipped without invoking the handler.
382
- */
383
- when?: (ctx: unknown) => boolean;
384
- };
385
- declare class Keymap {
386
- private readonly commands;
387
- private readonly platformGetter;
388
- /**
389
- * Bindings bucketed by canonical chunk-key hash, computed per
390
- * `@grida/keybinding`'s `chunkKey`. Each list is the chain for that
391
- * key, sorted in dispatch order (priority desc, then registration
392
- * order).
393
- */
394
- private readonly buckets;
395
- /** Insert order, so ties on priority are deterministic. */
396
- private seq;
397
- constructor(commands: CommandRegistry, platformGetter?: () => Platform);
398
- /**
399
- * Bind a key combination to a command. Returns an unbind function.
400
- * The same `Keybinding` can be bound to multiple commands — they will
401
- * all be tried in chain order on dispatch.
402
- */
403
- bind(binding: KeymapBinding): () => void;
404
- /**
405
- * Remove bindings matching the spec. If both filters are passed, only
406
- * bindings that match BOTH are removed.
407
- */
408
- unbind(spec: {
409
- keybinding?: Keybinding;
410
- command?: CommandId;
411
- }): void;
412
- /** All registered bindings, for introspection. Order is not guaranteed. */
413
- bindings(): readonly KeymapBinding[];
414
- /**
415
- * Match the event against bound chunks, then run candidates in chain
416
- * order. Returns `true` and calls `preventDefault()` on the first
417
- * handler that consumes; returns `false` if nothing matched or all
418
- * matches fell through.
419
- */
420
- dispatch(event: KeyboardEvent): boolean;
421
- /**
422
- * Compute the set of canonical hashes a `Keybinding` lights up. A
423
- * binding with aliases (multiple sequences) contributes one hash per
424
- * single-chunk alias; multi-chunk sequences (chords) are skipped in
425
- * V1.
426
- */
427
- private chunkKeysFor;
428
- private has_safe_mod;
429
- }
430
- //#endregion
431
324
  //#region src/core/parser.d.ts
432
325
  type AttrToken = {
433
326
  /** Verbatim source name including any prefix, e.g. "xlink:href". */raw_name: string; /** Prefix or null. */
@@ -557,6 +450,19 @@ declare class SvgDocument implements DocumentEvents {
557
450
  property: string;
558
451
  value: string;
559
452
  }>;
453
+ /**
454
+ * Whether `id` can be opened in the flat-string text editor.
455
+ *
456
+ * v1 contract: the editor only operates on a *single flat text run*. That
457
+ * means the target must be a `<text>` or `<tspan>` whose direct children
458
+ * are all text nodes (or it has no children). A `<text>` containing a
459
+ * `<tspan>` is *not* honestly editable — `text_of` would drop the tspan
460
+ * content from the editor's view, and a flat-text write would leave the
461
+ * tspan dangling. Tspan-as-target is fine and well-defined when it's a
462
+ * leaf; only the host decides whether to route double-click to a tspan
463
+ * or its parent text.
464
+ */
465
+ is_text_edit_target(id: NodeId): boolean;
560
466
  text_of(id: NodeId): string;
561
467
  /** Replace all direct text children with a single text node carrying `value`. */
562
468
  set_text(id: NodeId, value: string): void;
@@ -586,6 +492,113 @@ type Defs = {
586
492
  gradients: GradientsApi;
587
493
  };
588
494
  //#endregion
495
+ //#region src/commands/registry.d.ts
496
+ /**
497
+ * Command registry.
498
+ *
499
+ * A passive id-keyed registry of handlers. Built so that:
500
+ *
501
+ * - keybindings (in `src/keymap`) can address commands by stable id;
502
+ * - new commands can be added in ONE place (`src/commands/defaults.ts`)
503
+ * without growing the public surface of the editor;
504
+ * - "one key, many meanings" can be expressed via chain semantics: a
505
+ * handler returns `true` if it consumed, `false`/`void` otherwise,
506
+ * and the dispatcher tries the next candidate in the chain.
507
+ *
508
+ * Handlers are plain closures — they capture whatever editor reference
509
+ * they need. The registry itself stays unaware of the editor's type,
510
+ * which avoids a circular type dependency between editor and registry.
511
+ */
512
+ /** Stable, dotted id for a command, e.g. `"history.undo"`. */
513
+ type CommandId = string;
514
+ /**
515
+ * A command handler.
516
+ *
517
+ * Return `true` if the handler consumed the invocation. Return `false`
518
+ * or `undefined` to signal "did not apply" — the dispatcher will try
519
+ * the next candidate registered for the same key.
520
+ *
521
+ * Handlers are closures: they capture their editor reference. No
522
+ * editor parameter is passed — keep handlers self-contained.
523
+ */
524
+ type CommandHandler = (args?: unknown) => boolean | void;
525
+ declare class CommandRegistry {
526
+ private readonly map;
527
+ /**
528
+ * Register a command. Returns an unregister function. Re-registering
529
+ * the same id replaces the previous handler (last writer wins).
530
+ */
531
+ register(id: CommandId, handler: CommandHandler): () => void;
532
+ /**
533
+ * Invoke a command by id. Returns `true` if the handler consumed,
534
+ * `false` otherwise (including unknown ids and handlers that returned
535
+ * `false`/`undefined`).
536
+ */
537
+ invoke(id: CommandId, args?: unknown): boolean;
538
+ has(id: CommandId): boolean;
539
+ /** All registered ids, for debugging / introspection. */
540
+ ids(): readonly CommandId[];
541
+ }
542
+ //#endregion
543
+ //#region src/keymap/keymap.d.ts
544
+ type KeymapBinding = {
545
+ /** Declarative key combination. Build with `kb()` / `c()` / `seq()`. */keybinding: Keybinding; /** Command id to invoke on match. */
546
+ command: CommandId; /** Forwarded as the `args` parameter to the command handler. */
547
+ args?: unknown; /** Higher priorities run first in the chain. Default 0. */
548
+ priority?: number;
549
+ /**
550
+ * Reserved for V2; not honored by the V1 dispatcher. When added, this
551
+ * will be evaluated before the handler runs; if false, the binding is
552
+ * skipped without invoking the handler.
553
+ */
554
+ when?: (ctx: unknown) => boolean;
555
+ };
556
+ declare class Keymap {
557
+ private readonly commands;
558
+ private readonly platformGetter;
559
+ /**
560
+ * Bindings bucketed by canonical chunk-key hash, computed per
561
+ * `@grida/keybinding`'s `chunkKey`. Each list is the chain for that
562
+ * key, sorted in dispatch order (priority desc, then registration
563
+ * order).
564
+ */
565
+ private readonly buckets;
566
+ /** Insert order, so ties on priority are deterministic. */
567
+ private seq;
568
+ constructor(commands: CommandRegistry, platformGetter?: () => Platform);
569
+ /**
570
+ * Bind a key combination to a command. Returns an unbind function.
571
+ * The same `Keybinding` can be bound to multiple commands — they will
572
+ * all be tried in chain order on dispatch.
573
+ */
574
+ bind(binding: KeymapBinding): () => void;
575
+ /**
576
+ * Remove bindings matching the spec. If both filters are passed, only
577
+ * bindings that match BOTH are removed.
578
+ */
579
+ unbind(spec: {
580
+ keybinding?: Keybinding;
581
+ command?: CommandId;
582
+ }): void;
583
+ /** All registered bindings, for introspection. Order is not guaranteed. */
584
+ bindings(): readonly KeymapBinding[];
585
+ /**
586
+ * Match the event against bound chunks, then run candidates in chain
587
+ * order. Returns `true` and calls `preventDefault()` on the first
588
+ * handler that consumes; returns `false` if nothing matched or all
589
+ * matches fell through.
590
+ */
591
+ dispatch(event: KeyboardEvent): boolean;
592
+ /**
593
+ * Compute the set of canonical hashes a `Keybinding` lights up. A
594
+ * binding with aliases (multiple sequences) contributes one hash per
595
+ * single-chunk alias; multi-chunk sequences (chords) are skipped in
596
+ * V1.
597
+ */
598
+ private chunkKeysFor;
599
+ private has_safe_mod;
600
+ }
601
+ //#endregion
589
602
  //#region src/core/properties.d.ts
590
603
  declare function read_property(doc: SvgDocument, id: NodeId, property: string): PropertyValue;
591
604
  //#endregion
@@ -675,9 +688,18 @@ type Surface = {
675
688
  type SurfaceHandle = {
676
689
  detach(): void;
677
690
  };
691
+ /**
692
+ * Mode for `commands.select`. Matches the HUD's `SelectMode` vocabulary so
693
+ * intents can be threaded through without lossy collapsing.
694
+ *
695
+ * - `"replace"` (default) — set selection to `ids`, discarding the previous set.
696
+ * - `"add"` — union: each id in `ids` is added; existing members stay.
697
+ * - `"toggle"` — flip each id's membership (present → removed; absent → added).
698
+ */
699
+ type SelectMode = "replace" | "add" | "toggle";
678
700
  type Commands = {
679
701
  select(target: NodeId | ReadonlyArray<NodeId>, opts?: {
680
- additive?: boolean;
702
+ mode?: SelectMode;
681
703
  }): void;
682
704
  deselect(): void;
683
705
  enter_scope(group: NodeId): void;
@@ -815,4 +837,4 @@ declare function createSvgEditor(opts: CreateSvgEditorOptions): {
815
837
  keymap: Keymap;
816
838
  };
817
839
  //#endregion
818
- export { LinearGradientDefinition as A, Providers as B, EditorStyle as C, GradientEntry as D, GradientDefinition as E, PaintPreviewSession as F, Vec2 as G, Rect as H, PaintValue as I, PreviewSession as L, NodeId as M, Paint as N, GradientStop as O, PaintFallback as P, PropertyValue as R, EditorState as S, FontResolver as T, ReorderDirection as U, RadialGradientDefinition as V, Unsubscribe as W, CameraConstraints as _, SurfaceHandle as a, Color as b, GestureBinding as c, Gestures as d, KeymapBinding as f, Camera as g, BoundsResolver as h, DomComputedResolver as i, Mode as j, InvalidComputedValue as k, GestureContext as l, CommandId as m, CreateSvgEditorOptions as n, SvgEditor as o, CommandHandler as p, DomComputedPaint as r, createSvgEditor as s, Commands as t, GestureId as u, CameraOptions as v, FileIOProvider as w, DEFAULT_STYLE as x, ClipboardProvider as y, Provenance as z };
840
+ export { InvalidComputedValue as A, Provenance as B, EditorState as C, GradientDefinition as D, FontResolver as E, PaintFallback as F, Unsubscribe as G, RadialGradientDefinition as H, PaintPreviewSession as I, Vec2 as K, PaintValue as L, Mode as M, NodeId as N, GradientEntry as O, Paint as P, PreviewSession as R, DEFAULT_STYLE as S, FileIOProvider as T, Rect as U, Providers as V, ReorderDirection as W, Camera as _, SelectMode as a, ClipboardProvider as b, createSvgEditor as c, GestureId as d, Gestures as f, BoundsResolver as g, CommandId as h, DomComputedResolver as i, LinearGradientDefinition as j, GradientStop as k, GestureBinding as l, CommandHandler as m, CreateSvgEditorOptions as n, SurfaceHandle as o, KeymapBinding as p, DomComputedPaint as r, SvgEditor as s, Commands as t, GestureContext as u, CameraConstraints as v, EditorStyle as w, Color as x, CameraOptions as y, PropertyValue as z };
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { A as LinearGradientDefinition, B as Providers, C as EditorStyle, D as GradientEntry, E as GradientDefinition, F as PaintPreviewSession, G as Vec2, H as Rect, I as PaintValue, L as PreviewSession, M as NodeId, N as Paint, O as GradientStop, P as PaintFallback, R as PropertyValue, S as EditorState, T as FontResolver, U as ReorderDirection, V as RadialGradientDefinition, W as Unsubscribe, _ as CameraConstraints, a as SurfaceHandle, b as Color, c as GestureBinding, d as Gestures, f as KeymapBinding, g as Camera, h as BoundsResolver, i as DomComputedResolver, j as Mode, k as InvalidComputedValue, l as GestureContext, m as CommandId, n as CreateSvgEditorOptions, o as SvgEditor, p as CommandHandler, r as DomComputedPaint, s as createSvgEditor, t as Commands, u as GestureId, v as CameraOptions, w as FileIOProvider, x as DEFAULT_STYLE, y as ClipboardProvider, z as Provenance } from "./editor-Uu6dZX4y.mjs";
2
- export { BoundsResolver, Camera, CameraConstraints, CameraOptions, ClipboardProvider, Color, CommandHandler, CommandId, Commands, CreateSvgEditorOptions, DEFAULT_STYLE, DomComputedPaint, DomComputedResolver, EditorState, EditorStyle, FileIOProvider, FontResolver, GestureBinding, GestureContext, GestureId, Gestures, GradientDefinition, GradientEntry, GradientStop, InvalidComputedValue, KeymapBinding, LinearGradientDefinition, Mode, NodeId, Paint, PaintFallback, PaintPreviewSession, PaintValue, PreviewSession, PropertyValue, Provenance, Providers, RadialGradientDefinition, Rect, ReorderDirection, SurfaceHandle, SvgEditor, Unsubscribe, Vec2, createSvgEditor };
1
+ import { A as InvalidComputedValue, B as Provenance, C as EditorState, D as GradientDefinition, E as FontResolver, F as PaintFallback, G as Unsubscribe, H as RadialGradientDefinition, I as PaintPreviewSession, K as Vec2, L as PaintValue, M as Mode, N as NodeId, O as GradientEntry, P as Paint, R as PreviewSession, S as DEFAULT_STYLE, T as FileIOProvider, U as Rect, V as Providers, W as ReorderDirection, _ as Camera, a as SelectMode, b as ClipboardProvider, c as createSvgEditor, d as GestureId, f as Gestures, g as BoundsResolver, h as CommandId, i as DomComputedResolver, j as LinearGradientDefinition, k as GradientStop, l as GestureBinding, m as CommandHandler, n as CreateSvgEditorOptions, o as SurfaceHandle, p as KeymapBinding, r as DomComputedPaint, s as SvgEditor, t as Commands, u as GestureContext, v as CameraConstraints, w as EditorStyle, x as Color, y as CameraOptions, z as PropertyValue } from "./editor-KRAmUodY.mjs";
2
+ export { BoundsResolver, Camera, CameraConstraints, CameraOptions, ClipboardProvider, Color, CommandHandler, CommandId, Commands, CreateSvgEditorOptions, DEFAULT_STYLE, DomComputedPaint, DomComputedResolver, EditorState, EditorStyle, FileIOProvider, FontResolver, GestureBinding, GestureContext, GestureId, Gestures, GradientDefinition, GradientEntry, GradientStop, InvalidComputedValue, KeymapBinding, LinearGradientDefinition, Mode, NodeId, Paint, PaintFallback, PaintPreviewSession, PaintValue, PreviewSession, PropertyValue, Provenance, Providers, RadialGradientDefinition, Rect, ReorderDirection, SelectMode, SurfaceHandle, SvgEditor, Unsubscribe, Vec2, createSvgEditor };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { A as LinearGradientDefinition, B as Providers, C as EditorStyle, D as GradientEntry, E as GradientDefinition, F as PaintPreviewSession, G as Vec2, H as Rect, I as PaintValue, L as PreviewSession, M as NodeId, N as Paint, O as GradientStop, P as PaintFallback, R as PropertyValue, S as EditorState, T as FontResolver, U as ReorderDirection, V as RadialGradientDefinition, W as Unsubscribe, _ as CameraConstraints, a as SurfaceHandle, b as Color, c as GestureBinding, d as Gestures, f as KeymapBinding, g as Camera, h as BoundsResolver, i as DomComputedResolver, j as Mode, k as InvalidComputedValue, l as GestureContext, m as CommandId, n as CreateSvgEditorOptions, o as SvgEditor, p as CommandHandler, r as DomComputedPaint, s as createSvgEditor, t as Commands, u as GestureId, v as CameraOptions, w as FileIOProvider, x as DEFAULT_STYLE, y as ClipboardProvider, z as Provenance } from "./editor-D2l_CDr0.js";
2
- export { BoundsResolver, Camera, CameraConstraints, CameraOptions, ClipboardProvider, Color, CommandHandler, CommandId, Commands, CreateSvgEditorOptions, DEFAULT_STYLE, DomComputedPaint, DomComputedResolver, EditorState, EditorStyle, FileIOProvider, FontResolver, GestureBinding, GestureContext, GestureId, Gestures, GradientDefinition, GradientEntry, GradientStop, InvalidComputedValue, KeymapBinding, LinearGradientDefinition, Mode, NodeId, Paint, PaintFallback, PaintPreviewSession, PaintValue, PreviewSession, PropertyValue, Provenance, Providers, RadialGradientDefinition, Rect, ReorderDirection, SurfaceHandle, SvgEditor, Unsubscribe, Vec2, createSvgEditor };
1
+ import { A as InvalidComputedValue, B as Provenance, C as EditorState, D as GradientDefinition, E as FontResolver, F as PaintFallback, G as Unsubscribe, H as RadialGradientDefinition, I as PaintPreviewSession, K as Vec2, L as PaintValue, M as Mode, N as NodeId, O as GradientEntry, P as Paint, R as PreviewSession, S as DEFAULT_STYLE, T as FileIOProvider, U as Rect, V as Providers, W as ReorderDirection, _ as Camera, a as SelectMode, b as ClipboardProvider, c as createSvgEditor, d as GestureId, f as Gestures, g as BoundsResolver, h as CommandId, i as DomComputedResolver, j as LinearGradientDefinition, k as GradientStop, l as GestureBinding, m as CommandHandler, n as CreateSvgEditorOptions, o as SurfaceHandle, p as KeymapBinding, r as DomComputedPaint, s as SvgEditor, t as Commands, u as GestureContext, v as CameraConstraints, w as EditorStyle, x as Color, y as CameraOptions, z as PropertyValue } from "./editor-DdgqLDC9.js";
2
+ export { BoundsResolver, Camera, CameraConstraints, CameraOptions, ClipboardProvider, Color, CommandHandler, CommandId, Commands, CreateSvgEditorOptions, DEFAULT_STYLE, DomComputedPaint, DomComputedResolver, EditorState, EditorStyle, FileIOProvider, FontResolver, GestureBinding, GestureContext, GestureId, Gestures, GradientDefinition, GradientEntry, GradientStop, InvalidComputedValue, KeymapBinding, LinearGradientDefinition, Mode, NodeId, Paint, PaintFallback, PaintPreviewSession, PaintValue, PreviewSession, PropertyValue, Provenance, Providers, RadialGradientDefinition, Rect, ReorderDirection, SelectMode, SurfaceHandle, SvgEditor, Unsubscribe, Vec2, createSvgEditor };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_editor = require("./editor-D2zZAyny.js");
2
+ const require_editor = require("./editor-Ds47eN37.js");
3
3
  exports.DEFAULT_STYLE = require_editor.DEFAULT_STYLE;
4
4
  exports.createSvgEditor = require_editor.createSvgEditor;
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { n as DEFAULT_STYLE, t as createSvgEditor } from "./editor-CjK56cgb.mjs";
1
+ import { n as DEFAULT_STYLE, t as createSvgEditor } from "./editor-CRflVqEz.mjs";
2
2
  export { DEFAULT_STYLE, createSvgEditor };
@@ -1,4 +1,4 @@
1
- require("./dom-CoVZzFqy.js");
1
+ require("./dom-DyJy1H6Q.js");
2
2
  let svg_pathdata = require("svg-pathdata");
3
3
  //#region src/util/dom.ts
4
4
  /**
@@ -1,5 +1,5 @@
1
- import { o as SvgEditor } from "./editor-Uu6dZX4y.mjs";
2
- import { n as DomSurfaceOptions, t as DomSurfaceHandle } from "./dom-DJnZhtOd.mjs";
1
+ import { s as SvgEditor } from "./editor-KRAmUodY.mjs";
2
+ import { n as DomSurfaceOptions, t as DomSurfaceHandle } from "./dom-CMXNUMjP.mjs";
3
3
  declare namespace keynote_d_exports {
4
4
  export { KeynoteAttachOptions, KeynoteSurfaceHandle, attach };
5
5
  }
package/dist/presets.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { o as SvgEditor } from "./editor-D2l_CDr0.js";
2
- import { n as DomSurfaceOptions, t as DomSurfaceHandle } from "./dom-Cn-RtjRL.js";
1
+ import { s as SvgEditor } from "./editor-DdgqLDC9.js";
2
+ import { n as DomSurfaceOptions, t as DomSurfaceHandle } from "./dom-eIgcZ4JC.js";
3
3
  declare namespace keynote_d_exports {
4
4
  export { KeynoteAttachOptions, KeynoteSurfaceHandle, attach };
5
5
  }
package/dist/presets.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dom = require("./dom-CoVZzFqy.js");
2
+ const require_dom = require("./dom-DyJy1H6Q.js");
3
3
  //#region src/presets/keynote.ts
4
4
  var keynote_exports = /* @__PURE__ */ require_dom.__exportAll({ attach: () => attach });
5
5
  /**
@@ -16,28 +16,25 @@ var keynote_exports = /* @__PURE__ */ require_dom.__exportAll({ attach: () => at
16
16
  * additionally tears down the load subscription.
17
17
  */
18
18
  function attach(editor, opts) {
19
- let padding = opts.padding ?? 80;
20
19
  const inner = require_dom.attach_dom_surface(editor, {
21
20
  ...opts.surface,
22
21
  container: opts.container,
23
22
  fit: true
24
23
  });
25
- inner.camera.constraints = {
26
- type: "cover",
27
- bounds: "<root>",
28
- padding
24
+ const apply = (p) => {
25
+ inner.camera.constraints = {
26
+ type: "cover",
27
+ bounds: "<root>",
28
+ padding: p
29
+ };
29
30
  };
30
- const unsub_load = editor.subscribe_with_selector((s) => s.load_version, () => inner.camera.fit("<root>", { margin: padding }));
31
+ apply(opts.padding ?? 80);
32
+ const unsub_load = editor.subscribe_with_selector((s) => s.load_version, () => inner.camera.fit("<root>", { margin: inner.camera.constraints?.padding ?? 0 }));
31
33
  return {
32
34
  camera: inner.camera,
33
35
  gestures: inner.gestures,
34
36
  set_padding(p) {
35
- padding = p;
36
- inner.camera.constraints = {
37
- type: "cover",
38
- bounds: "<root>",
39
- padding: p
40
- };
37
+ apply(p);
41
38
  inner.camera.fit("<root>", { margin: p });
42
39
  },
43
40
  detach: () => {
package/dist/presets.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
2
- import { t as attach_dom_surface } from "./dom-CmOu0HvI.mjs";
2
+ import { t as attach_dom_surface } from "./dom-l5Y1Wf8C.mjs";
3
3
  //#region src/presets/keynote.ts
4
4
  var keynote_exports = /* @__PURE__ */ __exportAll({ attach: () => attach });
5
5
  /**
@@ -16,28 +16,25 @@ var keynote_exports = /* @__PURE__ */ __exportAll({ attach: () => attach });
16
16
  * additionally tears down the load subscription.
17
17
  */
18
18
  function attach(editor, opts) {
19
- let padding = opts.padding ?? 80;
20
19
  const inner = attach_dom_surface(editor, {
21
20
  ...opts.surface,
22
21
  container: opts.container,
23
22
  fit: true
24
23
  });
25
- inner.camera.constraints = {
26
- type: "cover",
27
- bounds: "<root>",
28
- padding
24
+ const apply = (p) => {
25
+ inner.camera.constraints = {
26
+ type: "cover",
27
+ bounds: "<root>",
28
+ padding: p
29
+ };
29
30
  };
30
- const unsub_load = editor.subscribe_with_selector((s) => s.load_version, () => inner.camera.fit("<root>", { margin: padding }));
31
+ apply(opts.padding ?? 80);
32
+ const unsub_load = editor.subscribe_with_selector((s) => s.load_version, () => inner.camera.fit("<root>", { margin: inner.camera.constraints?.padding ?? 0 }));
31
33
  return {
32
34
  camera: inner.camera,
33
35
  gestures: inner.gestures,
34
36
  set_padding(p) {
35
- padding = p;
36
- inner.camera.constraints = {
37
- type: "cover",
38
- bounds: "<root>",
39
- padding: p
40
- };
37
+ apply(p);
41
38
  inner.camera.fit("<root>", { margin: p });
42
39
  },
43
40
  detach: () => {
package/dist/react.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as Providers, C as EditorStyle, S as EditorState, o as SvgEditor, t as Commands } from "./editor-Uu6dZX4y.mjs";
2
- import { t as DomSurfaceHandle } from "./dom-DJnZhtOd.mjs";
1
+ import { C as EditorState, V as Providers, s as SvgEditor, t as Commands, w as EditorStyle } from "./editor-KRAmUodY.mjs";
2
+ import { t as DomSurfaceHandle } from "./dom-CMXNUMjP.mjs";
3
3
  import cmath from "@grida/cmath";
4
4
  import { ReactNode } from "react";
5
5
  import * as _$react_jsx_runtime0 from "react/jsx-runtime";
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as Providers, C as EditorStyle, S as EditorState, o as SvgEditor, t as Commands } from "./editor-D2l_CDr0.js";
2
- import { t as DomSurfaceHandle } from "./dom-Cn-RtjRL.js";
1
+ import { C as EditorState, V as Providers, s as SvgEditor, t as Commands, w as EditorStyle } from "./editor-DdgqLDC9.js";
2
+ import { t as DomSurfaceHandle } from "./dom-eIgcZ4JC.js";
3
3
  import cmath from "@grida/cmath";
4
4
  import * as _$react_jsx_runtime0 from "react/jsx-runtime";
5
5
  import { ReactNode } from "react";
package/dist/react.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_dom = require("./dom-CoVZzFqy.js");
4
- const require_editor = require("./editor-D2zZAyny.js");
3
+ const require_dom = require("./dom-DyJy1H6Q.js");
4
+ const require_editor = require("./editor-Ds47eN37.js");
5
5
  let react = require("react");
6
6
  let react_jsx_runtime = require("react/jsx-runtime");
7
7
  //#region src/react.tsx
package/dist/react.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { t as createSvgEditor } from "./editor-CjK56cgb.mjs";
3
- import { t as attach_dom_surface } from "./dom-CmOu0HvI.mjs";
2
+ import { t as createSvgEditor } from "./editor-CRflVqEz.mjs";
3
+ import { t as attach_dom_surface } from "./dom-l5Y1Wf8C.mjs";
4
4
  import { createContext, useContext, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  //#region src/react.tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grida/svg-editor",
3
- "version": "1.0.0-alpha.4",
3
+ "version": "1.0.0-alpha.5",
4
4
  "description": "Headless SVG editor (experimental).",
5
5
  "license": "MIT",
6
6
  "author": "Grida",
@@ -39,11 +39,11 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "svg-pathdata": "^7.2.0",
42
+ "@grida/cmath": "0.1.0",
42
43
  "@grida/history": "0.1.0",
43
44
  "@grida/hud": "0.1.0",
44
- "@grida/text-editor": "0.1.0",
45
45
  "@grida/keybinding": "0.1.0",
46
- "@grida/cmath": "0.1.0"
46
+ "@grida/text-editor": "0.1.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/react": "^19",