@grida/svg-editor 1.0.0-alpha.16 → 1.0.0-alpha.17

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.
@@ -154,10 +154,20 @@ const GEOMETRY_ATTRS = new Set([
154
154
  ]);
155
155
  /** `transform:` CSS property at the start of a declaration list or after `;`. */
156
156
  const CSS_TRANSFORM_PROPERTY = /(?:^|;)\s*transform\s*:/i;
157
+ /** Trivia shape for editor-synthesized attribute tokens — parser-authored
158
+ * tokens carry their source trivia verbatim; tokens the editor appends get
159
+ * this one canonical shape (` name="value"`). */
160
+ const SYNTH_ATTR_TRIVIA = {
161
+ pre: " ",
162
+ eq_trivia: "",
163
+ eq_trailing: "",
164
+ quote: "\""
165
+ };
157
166
  var SvgDocument = class SvgDocument {
158
167
  constructor(svg) {
159
168
  this.listeners = /* @__PURE__ */ new Set();
160
169
  this._structure_version = 0;
170
+ this._revision = 0;
161
171
  this._geometry_version = 0;
162
172
  if (typeof svg !== "string") throw new TypeError(`new SvgDocument(svg) requires a string source, got ${svg === null ? "null" : typeof svg}`);
163
173
  this.source = svg;
@@ -205,6 +215,20 @@ var SvgDocument = class SvgDocument {
205
215
  get structure_version() {
206
216
  return this._structure_version;
207
217
  }
218
+ /**
219
+ * Total mutation counter — advances on EVERY listener-visible mutation
220
+ * (attribute, style, text, topology, load/reset), unlike the selective
221
+ * `structure_version` / `geometry_version` channels. The single
222
+ * edit-version source: anything derived from this document — the
223
+ * editor's `content_version` / `dirty`, memoized reads, a rendered
224
+ * projection — answers "am I current?" by comparing values, with no
225
+ * event-ordering dependence. Advances BEFORE listeners fire, so a
226
+ * read issued from inside a change listener already sees the new
227
+ * value.
228
+ */
229
+ get revision() {
230
+ return this._revision;
231
+ }
208
232
  /** See `_geometry_version` for what this counter signals. */
209
233
  get geometry_version() {
210
234
  return this._geometry_version;
@@ -221,15 +245,15 @@ var SvgDocument = class SvgDocument {
221
245
  * settled glyph metrics. See ../../docs/geometry.md §Limitations.
222
246
  *
223
247
  * Deliberately does NOT call `emit()`: this is not a document edit, so
224
- * it must not bump `doc_version` / mark the doc dirty / touch undo
225
- * (the editor's `on_change` handler does all three). The editor's
226
- * `_internal.bump_geometry` advances `geometry_version` here and fans
227
- * out the geometry listeners itself.
248
+ * `revision` must not advance no dirty flag, no undo, no render
249
+ * flush. The editor's `_internal.bump_geometry` advances
250
+ * `geometry_version` here and fans out the geometry listeners itself.
228
251
  */
229
252
  bump_geometry() {
230
253
  this._geometry_version++;
231
254
  }
232
255
  emit() {
256
+ this._revision++;
233
257
  for (const fn of this.listeners) fn();
234
258
  }
235
259
  /** Notify subscribers — for callers that mutate directly via setAttr/etc. */
@@ -380,9 +404,7 @@ var SvgDocument = class SvgDocument {
380
404
  local: name,
381
405
  ns,
382
406
  value,
383
- pre: " ",
384
- eq_trivia: "",
385
- quote: "\""
407
+ ...SYNTH_ATTR_TRIVIA
386
408
  });
387
409
  if (structural) this._structure_version++;
388
410
  if (geometry) this._geometry_version++;
@@ -638,9 +660,7 @@ var SvgDocument = class SvgDocument {
638
660
  local: "d",
639
661
  ns: null,
640
662
  value: d,
641
- pre: " ",
642
- eq_trivia: "",
643
- quote: "\""
663
+ ...SYNTH_ATTR_TRIVIA
644
664
  });
645
665
  let added_fill_none = false;
646
666
  if (prev_local === "line" && this.get_attr(id, "fill") === null && this.get_style(id, "fill") === null) {
@@ -650,9 +670,7 @@ var SvgDocument = class SvgDocument {
650
670
  local: "fill",
651
671
  ns: null,
652
672
  value: "none",
653
- pre: " ",
654
- eq_trivia: "",
655
- quote: "\""
673
+ ...SYNTH_ATTR_TRIVIA
656
674
  });
657
675
  added_fill_none = true;
658
676
  }
@@ -1019,7 +1037,7 @@ var SvgDocument = class SvgDocument {
1019
1037
  }
1020
1038
  }
1021
1039
  emit_attr(a) {
1022
- return `${a.pre}${a.raw_name}${a.eq_trivia}=${a.quote}${(0, _grida_svg_parser.encode_attr_value)(a.value, a.quote)}${a.quote}`;
1040
+ return `${a.pre}${a.raw_name}${a.eq_trivia}=${a.eq_trailing}${a.quote}${(0, _grida_svg_parser.encode_attr_value)(a.value, a.quote)}${a.quote}`;
1023
1041
  }
1024
1042
  };
1025
1043
  function parse_inline_style(s) {
@@ -1037,6 +1055,131 @@ function parse_inline_style(s) {
1037
1055
  }
1038
1056
  return out;
1039
1057
  }
1058
+ /**
1059
+ * Namespace prefixes resolvable without a source declaration. ONE table,
1060
+ * shared by the paste-side hoist (`insert_fragment`'s xmlns plan) and the
1061
+ * copy-side shell repair (clipboard payload extraction) — the two sides
1062
+ * form a round-trip and must agree: a prefix only one side knows would
1063
+ * produce payloads the other can't honor.
1064
+ */
1065
+ const WELL_KNOWN_NS_PREFIXES = new Map([["xlink", _grida_svg_parser.XLINK_NS]]);
1066
+ //#endregion
1067
+ //#region src/core/subtree.ts
1068
+ /**
1069
+ * The selection → subtree algebra the two extraction operations share,
1070
+ * plus the clone-specific member (`clone_plan`).
1071
+ *
1072
+ * The clipboard FRD
1073
+ * ([docs/wg/feat-svg-editor/clipboard.md](../../../../docs/wg/feat-svg-editor/clipboard.md)
1074
+ * §Two extraction operations) names two operations over a normalized
1075
+ * selection: **payload extraction** (copy — `core/clipboard.ts`) and
1076
+ * **subtree clone** (duplicate / clone-drag — this module; design note:
1077
+ * [docs/wg/feat-svg-editor/subtree-clone.md](../../../../docs/wg/feat-svg-editor/subtree-clone.md)).
1078
+ * They share exactly two things — selection normalization
1079
+ * ({@link subtree.normalize_roots}) and verbatim subtree serialization
1080
+ * (`SvgDocument.serialize_node`) — and nothing else.
1081
+ *
1082
+ * Unlike copy's payload, a clone carries **no reference closure and no
1083
+ * namespace shell**: the destination is the source document, every
1084
+ * `url(#…)` / `href` reference still resolves against it, and carrying
1085
+ * definitions would deposit duplicate defs on every duplicate.
1086
+ *
1087
+ * Consumers: `commands.duplicate` (⌘D) and the translate orchestrator's
1088
+ * Alt-drag clone session (gridaco/grida#817).
1089
+ *
1090
+ * Verbatim-id policy: authored `id=""` attributes are cloned verbatim,
1091
+ * NEVER rewritten — same stance as `insert_fragment`. A clone of a node
1092
+ * carrying `id="x"` yields a second `id="x"`; reference resolution follows
1093
+ * the host renderer's first-in-document-order rule (a cloned subtree's
1094
+ * internal self-reference resolves to the ORIGINAL), and deduplication is
1095
+ * the explicit Tidy command's job.
1096
+ */
1097
+ let subtree;
1098
+ (function(_subtree) {
1099
+ function by_document_order(doc) {
1100
+ const index = /* @__PURE__ */ new Map();
1101
+ let i = 0;
1102
+ for (const id of doc.all_nodes()) index.set(id, i++);
1103
+ return (a, b) => (index.get(a) ?? 0) - (index.get(b) ?? 0);
1104
+ }
1105
+ _subtree.by_document_order = by_document_order;
1106
+ function normalize_roots(doc, selection, order) {
1107
+ const live = [...new Set(selection)].filter((id) => doc.is_element(id) && doc.contains(doc.root, id));
1108
+ const roots = doc.prune_nested_nodes(live);
1109
+ if (roots.length > 1) roots.sort(order ?? by_document_order(doc));
1110
+ return roots;
1111
+ }
1112
+ _subtree.normalize_roots = normalize_roots;
1113
+ function clone_plan(doc, selection) {
1114
+ const out = [];
1115
+ for (const origin of normalize_roots(doc, selection)) {
1116
+ const parent = doc.parent_of(origin);
1117
+ if (parent === null) continue;
1118
+ if (doc.tag_of(origin) === "svg") continue;
1119
+ const { roots } = doc.create_fragment(doc.serialize_node(origin));
1120
+ if (roots.length !== 1) throw new Error(`subtree.clone_plan: cloning ${JSON.stringify(origin)} yielded ${roots.length} roots`);
1121
+ out.push({
1122
+ origin,
1123
+ clone: roots[0],
1124
+ parent,
1125
+ before: doc.next_sibling_of(origin)
1126
+ });
1127
+ }
1128
+ return out;
1129
+ }
1130
+ _subtree.clone_plan = clone_plan;
1131
+ function insert_plan(doc, plan) {
1132
+ for (const p of plan) doc.insert(p.clone, p.parent, p.before);
1133
+ }
1134
+ _subtree.insert_plan = insert_plan;
1135
+ function remove_plan(doc, plan) {
1136
+ for (const p of plan) doc.remove(p.clone);
1137
+ }
1138
+ _subtree.remove_plan = remove_plan;
1139
+ /** Per-member rigidity tolerance for the rigid-translate witness in
1140
+ * {@link repeat_delta} — position drift from the shared delta, and
1141
+ * size drift from the origin. Generous against float noise from
1142
+ * re-encoded path data / `getBBox`, far below anything a user would
1143
+ * call a move or a resize. */
1144
+ const REPEAT_RIGID_EPSILON = .01;
1145
+ function repeat_delta(record, targets, bounds_of) {
1146
+ if (record === null) return null;
1147
+ if (record.origins.length === 0) return null;
1148
+ if (record.origins.length !== record.clones.length) return null;
1149
+ if (!array_shallow_equal(record.clones, targets)) return null;
1150
+ const origin_bounds = collect_bounds(record.origins, bounds_of);
1151
+ if (origin_bounds === null) return null;
1152
+ const clone_bounds = collect_bounds(record.clones, bounds_of);
1153
+ if (clone_bounds === null) return null;
1154
+ const a = _grida_cmath.default.rect.union(origin_bounds);
1155
+ const b = _grida_cmath.default.rect.union(clone_bounds);
1156
+ const dx = b.x - a.x;
1157
+ const dy = b.y - a.y;
1158
+ for (let i = 0; i < origin_bounds.length; i++) {
1159
+ const o = origin_bounds[i];
1160
+ const c = clone_bounds[i];
1161
+ if (Math.abs(c.x - o.x - dx) > REPEAT_RIGID_EPSILON || Math.abs(c.y - o.y - dy) > REPEAT_RIGID_EPSILON || Math.abs(c.width - o.width) > REPEAT_RIGID_EPSILON || Math.abs(c.height - o.height) > REPEAT_RIGID_EPSILON) return null;
1162
+ }
1163
+ if (Math.abs(dx) <= REPEAT_RIGID_EPSILON && Math.abs(dy) <= REPEAT_RIGID_EPSILON) return null;
1164
+ return {
1165
+ x: dx,
1166
+ y: dy
1167
+ };
1168
+ }
1169
+ _subtree.repeat_delta = repeat_delta;
1170
+ /** All-or-nothing bounds gather for {@link repeat_delta} — one
1171
+ * unmeasurable member and the record can't witness a rigid
1172
+ * translate. */
1173
+ function collect_bounds(ids, bounds_of) {
1174
+ const out = [];
1175
+ for (const id of ids) {
1176
+ const r = bounds_of(id);
1177
+ if (r === null) return null;
1178
+ out.push(r);
1179
+ }
1180
+ return out;
1181
+ }
1182
+ })(subtree || (subtree = {}));
1040
1183
  //#endregion
1041
1184
  //#region src/core/transform.ts
1042
1185
  let transform;
@@ -1804,6 +1947,8 @@ let translate_pipeline;
1804
1947
  })(translate_pipeline || (translate_pipeline = {}));
1805
1948
  //#endregion
1806
1949
  //#region src/core/translate-pipeline/orchestrator.ts
1950
+ const movers = (s) => s.clone?.ids ?? s.ids;
1951
+ const mover_baselines = (s) => s.clone?.baselines ?? s.baselines;
1807
1952
  const PROVIDER_ID$2 = "svg-editor";
1808
1953
  var TranslateOrchestrator = class {
1809
1954
  constructor(deps) {
@@ -1826,25 +1971,33 @@ var TranslateOrchestrator = class {
1826
1971
  drive(input, modifiers, opts) {
1827
1972
  if (this.active === null) this.active = this.open(input.ids, opts.snap, opts.label ?? "move");
1828
1973
  const session = this.active;
1974
+ this.reconcile_clone(session, modifiers);
1829
1975
  const stages = opts.stages ?? translate_pipeline.stages.DEFAULT;
1830
1976
  const result = this.run_pass(session, input.movement, modifiers, opts.policy, stages);
1831
1977
  session.last_movement = input.movement;
1832
1978
  session.last_policy = opts.policy;
1833
1979
  session.last_stages = stages;
1834
- this.write_preview_delta(session, result.plan);
1980
+ if (opts.phase === "commit" && session.clone !== null) this.write_commit_composite(session, result.plan);
1981
+ else this.write_preview_delta(session, result.plan);
1835
1982
  if (opts.phase === "commit") {
1983
+ const cloned = session.clone;
1836
1984
  session.preview.commit();
1837
1985
  this.dispose_session();
1986
+ if (cloned !== null) this.deps.on_clone_commit?.({
1987
+ origins: cloned.plan.map((p) => p.origin),
1988
+ clones: cloned.ids
1989
+ });
1838
1990
  }
1839
1991
  return result;
1840
1992
  }
1841
1993
  /** Re-run the current preview frame with new modifiers, reusing the
1842
1994
  * last-known movement / policy / stages. Used when a modifier key
1843
- * changes between pointer-move events (Shift down/up mid-drag).
1844
- * No-op when no session is active. */
1995
+ * changes between pointer-move events (Shift down/up mid-drag,
1996
+ * Alt down/up → clone toggle). No-op when no session is active. */
1845
1997
  redrive_modifiers(modifiers) {
1846
1998
  if (!this.active) return null;
1847
1999
  const session = this.active;
2000
+ this.reconcile_clone(session, modifiers);
1848
2001
  const result = this.run_pass(session, session.last_movement, modifiers, session.last_policy, session.last_stages);
1849
2002
  this.write_preview_delta(session, result.plan);
1850
2003
  return result;
@@ -1852,15 +2005,22 @@ var TranslateOrchestrator = class {
1852
2005
  /** Cancel an in-flight gesture (Escape, programmatic abort). */
1853
2006
  cancel() {
1854
2007
  if (!this.active) return;
1855
- this.active.preview.discard();
2008
+ const session = this.active;
2009
+ session.preview.discard();
2010
+ if (session.clone !== null) {
2011
+ subtree.remove_plan(this.deps.get_doc(), session.clone.plan);
2012
+ this.deps.set_selection?.(session.ids);
2013
+ this.deps.emit();
2014
+ }
1856
2015
  this.dispose_session();
1857
2016
  }
1858
2017
  /** Build a plan + context, run the pipeline, stash guides. Pure
1859
- * computation — does not touch the preview. */
2018
+ * computation — does not touch the preview. The context's modifiers
2019
+ * are the pipeline's own vocabulary: stages never see `clone`. */
1860
2020
  run_pass(session, movement, modifiers, policy, stages) {
1861
2021
  const plan0 = {
1862
- ids: session.ids,
1863
- baselines: session.baselines,
2022
+ ids: movers(session),
2023
+ baselines: mover_baselines(session),
1864
2024
  delta: {
1865
2025
  x: 0,
1866
2026
  y: 0
@@ -1868,7 +2028,7 @@ var TranslateOrchestrator = class {
1868
2028
  };
1869
2029
  const ctx = {
1870
2030
  input: {
1871
- ids: session.ids,
2031
+ ids: plan0.ids,
1872
2032
  movement
1873
2033
  },
1874
2034
  modifiers,
@@ -1880,6 +2040,69 @@ var TranslateOrchestrator = class {
1880
2040
  this._last_guides = result.guides;
1881
2041
  return result;
1882
2042
  }
2043
+ /**
2044
+ * Clone-modifier state machine (Alt-drag translate-with-clone). Runs
2045
+ * before each pipeline pass so a toggle takes effect on the very frame
2046
+ * it happens — lazy clone on the first frame with the modifier held.
2047
+ *
2048
+ * Both edges and the composite commit below depend on one invariant:
2049
+ * `translate_pipeline.*.revert` writes ABSOLUTE baseline values (incl.
2050
+ * tspan's exact-attr restore), so re-reverting an already-reverted set
2051
+ * is a no-op. A future relative-write baseline kind would break this.
2052
+ */
2053
+ reconcile_clone(session, modifiers) {
2054
+ const want = modifiers.clone === true;
2055
+ const cloned = session.clone !== null;
2056
+ if (want && !cloned) this.enter_clone(session);
2057
+ else if (!want && cloned) this.exit_clone(session);
2058
+ }
2059
+ /** Enter the cloned state: origins back to rest, verbatim clones
2060
+ * inserted next to them, gesture + selection + snap retargeted to the
2061
+ * clones. The origin set stays untouched for the rest of the cloned
2062
+ * gesture — the CLONE moves, the origin stays (Figma convention). */
2063
+ enter_clone(session) {
2064
+ const doc = this.deps.get_doc();
2065
+ translate_pipeline.revert(doc, {
2066
+ ids: session.ids,
2067
+ baselines: session.baselines,
2068
+ delta: {
2069
+ x: 0,
2070
+ y: 0
2071
+ }
2072
+ });
2073
+ const plan = subtree.clone_plan(doc, session.ids);
2074
+ if (plan.length === 0) return;
2075
+ subtree.insert_plan(doc, plan);
2076
+ const baselines = /* @__PURE__ */ new Map();
2077
+ for (const p of plan) baselines.set(p.clone, session.baselines.get(p.origin));
2078
+ session.clone = {
2079
+ plan,
2080
+ ids: plan.map((p) => p.clone),
2081
+ baselines
2082
+ };
2083
+ this.retarget(session, session.clone.ids);
2084
+ }
2085
+ /** Exit the cloned state: clones removed, gesture + selection + snap
2086
+ * retargeted back to the origins, which resume following the cursor
2087
+ * on the next pass. Removed clones stay in the document's id map
2088
+ * (standard removed-node policy) and are never serialized; a stale
2089
+ * preview delta auto-reverting onto them later is harmless. Re-press
2090
+ * = fresh enter with new clones. */
2091
+ exit_clone(session) {
2092
+ subtree.remove_plan(this.deps.get_doc(), session.clone.plan);
2093
+ session.clone = null;
2094
+ this.retarget(session, session.ids);
2095
+ }
2096
+ /** Point selection + snap at the new mover set. Selection + emit run
2097
+ * BEFORE the snap reopen: the surface re-renders on notify, so freshly
2098
+ * inserted clones are measurable when the new snap session captures
2099
+ * its neighborhood (where the origins are legitimate snap targets). */
2100
+ retarget(session, ids) {
2101
+ this.deps.set_selection?.(ids);
2102
+ this.deps.emit();
2103
+ session.snap?.dispose();
2104
+ session.snap = session.snap_requested ? this.deps.open_snap(ids) : null;
2105
+ }
1883
2106
  open(ids, snap, label) {
1884
2107
  const doc = this.deps.get_doc();
1885
2108
  const filtered = doc.prune_nested_nodes(ids);
@@ -1887,10 +2110,12 @@ var TranslateOrchestrator = class {
1887
2110
  ids: filtered,
1888
2111
  baselines: translate_pipeline.intent.capture_baselines(doc, filtered),
1889
2112
  snap: snap ? this.deps.open_snap(filtered) : null,
2113
+ snap_requested: snap,
1890
2114
  preview: this.deps.open_preview(label),
1891
2115
  last_movement: [0, 0],
1892
2116
  last_policy: "engine",
1893
- last_stages: translate_pipeline.stages.DEFAULT
2117
+ last_stages: translate_pipeline.stages.DEFAULT,
2118
+ clone: null
1894
2119
  };
1895
2120
  }
1896
2121
  /** Bind a fresh apply/revert pair (closure over `plan`) into the
@@ -1912,6 +2137,43 @@ var TranslateOrchestrator = class {
1912
2137
  }
1913
2138
  });
1914
2139
  }
2140
+ /** Commit-time delta for a CLONED gesture. Per-frame deltas stay
2141
+ * translate-only; the structural insert happened at the toggle edge,
2142
+ * outside the preview. The one delta that gets committed must own the
2143
+ * whole outcome, so a single undo removes clone + move and a redo
2144
+ * restores both:
2145
+ * apply = insert clones (idempotent reposition when live) +
2146
+ * translate + select clones
2147
+ * revert = un-translate + remove clones + select origins
2148
+ * `preview.set` reverts the previous translate-only delta first
2149
+ * (clones back to rest), then runs this apply — the document passes
2150
+ * through exactly the states the closures describe.
2151
+ * Captures locals only (not the session), so the committed delta
2152
+ * retains the minimum undo/redo needs. */
2153
+ write_commit_composite(session, plan) {
2154
+ const doc = this.deps.get_doc();
2155
+ const emit = this.deps.emit;
2156
+ const project = this.deps.project_delta;
2157
+ const set_selection = this.deps.set_selection;
2158
+ const clone_plan = session.clone.plan;
2159
+ const clone_ids = session.clone.ids;
2160
+ const origin_ids = session.ids;
2161
+ session.preview.set({
2162
+ providerId: PROVIDER_ID$2,
2163
+ apply: () => {
2164
+ subtree.insert_plan(doc, clone_plan);
2165
+ translate_pipeline.apply(doc, plan, project);
2166
+ set_selection?.(clone_ids);
2167
+ emit();
2168
+ },
2169
+ revert: () => {
2170
+ translate_pipeline.revert(doc, plan);
2171
+ subtree.remove_plan(doc, clone_plan);
2172
+ set_selection?.(origin_ids);
2173
+ emit();
2174
+ }
2175
+ });
2176
+ }
1915
2177
  dispose_session() {
1916
2178
  if (!this.active) return;
1917
2179
  this.active.snap?.dispose();
@@ -4915,6 +5177,12 @@ Object.defineProperty(exports, "TranslateOrchestrator", {
4915
5177
  return TranslateOrchestrator;
4916
5178
  }
4917
5179
  });
5180
+ Object.defineProperty(exports, "WELL_KNOWN_NS_PREFIXES", {
5181
+ enumerable: true,
5182
+ get: function() {
5183
+ return WELL_KNOWN_NS_PREFIXES;
5184
+ }
5185
+ });
4918
5186
  Object.defineProperty(exports, "__exportAll", {
4919
5187
  enumerable: true,
4920
5188
  get: function() {
@@ -4975,6 +5243,12 @@ Object.defineProperty(exports, "rotate_pipeline", {
4975
5243
  return rotate_pipeline;
4976
5244
  }
4977
5245
  });
5246
+ Object.defineProperty(exports, "subtree", {
5247
+ enumerable: true,
5248
+ get: function() {
5249
+ return subtree;
5250
+ }
5251
+ });
4978
5252
  Object.defineProperty(exports, "transform", {
4979
5253
  enumerable: true,
4980
5254
  get: function() {
@@ -1,5 +1,5 @@
1
- import { c as SvgEditor } from "./editor-D2eQe8lB.mjs";
2
- import { n as DomSurfaceOptions, t as DomSurfaceHandle } from "./dom-98AUOfsP.mjs";
1
+ import { c as SvgEditor } from "./editor-KqpIW1qm.mjs";
2
+ import { n as DomSurfaceOptions, t as DomSurfaceHandle } from "./dom-TctdgRnn.mjs";
3
3
 
4
4
  //#region src/presets/keynote.d.ts
5
5
  declare namespace keynote_d_exports {
package/dist/presets.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { c as SvgEditor } from "./editor-CYoGJ3Hf.js";
2
- import { n as DomSurfaceOptions, t as DomSurfaceHandle } from "./dom-BO2-E9oK.js";
1
+ import { c as SvgEditor } from "./editor-BSxTUsW_.js";
2
+ import { n as DomSurfaceOptions, t as DomSurfaceHandle } from "./dom-BMzX1CXZ.js";
3
3
 
4
4
  //#region \0rolldown/runtime.js
5
5
  declare namespace keynote_d_exports {
package/dist/presets.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_model = require("./model-D0nU_EkL.js");
3
- const require_dom = require("./dom-U6ae5fQF.js");
2
+ const require_model = require("./model-GpysNbOv.js");
3
+ const require_dom = require("./dom-CaByuo6C.js");
4
4
  //#region src/presets/keynote.ts
5
5
  var keynote_exports = /* @__PURE__ */ require_model.__exportAll({ attach: () => attach });
6
6
  /**
package/dist/presets.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./chunk-D7D4PA-g.mjs";
2
- import { t as attach_dom_surface } from "./dom-DOvcMvl4.mjs";
2
+ import { t as attach_dom_surface } from "./dom-Bjj9xySE.mjs";
3
3
  //#region src/presets/keynote.ts
4
4
  var keynote_exports = /* @__PURE__ */ __exportAll({ attach: () => attach });
5
5
  /**
package/dist/react.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as EditorState, H as Mode, J as PickEvent, K as PaintPreviewSession, Q as Providers, U as NodeId, Y as PreviewSession, c as SvgEditor, j as EditorStyle, rt as Tool, t as Commands } from "./editor-D2eQe8lB.mjs";
2
- import { t as DomSurfaceHandle } from "./dom-98AUOfsP.mjs";
1
+ import { A as EditorState, H as Mode, J as PickEvent, K as PaintPreviewSession, Q as Providers, U as NodeId, Y as PreviewSession, c as SvgEditor, j as EditorStyle, rt as Tool, t as Commands } from "./editor-KqpIW1qm.mjs";
2
+ import { t as DomSurfaceHandle } from "./dom-TctdgRnn.mjs";
3
3
  import cmath from "@grida/cmath";
4
4
  import { ReactNode } from "react";
5
5
 
@@ -47,7 +47,13 @@ type SvgEditorCanvasProps = {
47
47
  * Auto-fit the document on initial attach. Default `false`. See
48
48
  * `DomSurfaceOptions.fit`.
49
49
  */
50
- fit?: boolean; /** Initial camera transform. Default identity. */
50
+ fit?: boolean;
51
+ /**
52
+ * Wire native ClipboardEvent transport (copy/cut/paste). Default `true`.
53
+ * Pass `false` to route all clipboard traffic through the
54
+ * `ClipboardProvider` seam. See `DomSurfaceOptions.clipboard`.
55
+ */
56
+ clipboard?: boolean; /** Initial camera transform. Default identity. */
51
57
  initial_camera?: cmath.Transform;
52
58
  /**
53
59
  * Receives the `DomSurfaceHandle` once the surface is attached, and
@@ -70,6 +76,7 @@ declare function SvgEditorCanvas({
70
76
  style,
71
77
  gestures,
72
78
  fit,
79
+ clipboard,
73
80
  initial_camera,
74
81
  onAttach
75
82
  }: SvgEditorCanvasProps): import("react/jsx-runtime").JSX.Element;
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as EditorState, H as Mode, J as PickEvent, K as PaintPreviewSession, Q as Providers, U as NodeId, Y as PreviewSession, c as SvgEditor, j as EditorStyle, rt as Tool, t as Commands } from "./editor-CYoGJ3Hf.js";
2
- import { t as DomSurfaceHandle } from "./dom-BO2-E9oK.js";
1
+ import { A as EditorState, H as Mode, J as PickEvent, K as PaintPreviewSession, Q as Providers, U as NodeId, Y as PreviewSession, c as SvgEditor, j as EditorStyle, rt as Tool, t as Commands } from "./editor-BSxTUsW_.js";
2
+ import { t as DomSurfaceHandle } from "./dom-BMzX1CXZ.js";
3
3
  import cmath from "@grida/cmath";
4
4
  import { ReactNode } from "react";
5
5
 
@@ -47,7 +47,13 @@ type SvgEditorCanvasProps = {
47
47
  * Auto-fit the document on initial attach. Default `false`. See
48
48
  * `DomSurfaceOptions.fit`.
49
49
  */
50
- fit?: boolean; /** Initial camera transform. Default identity. */
50
+ fit?: boolean;
51
+ /**
52
+ * Wire native ClipboardEvent transport (copy/cut/paste). Default `true`.
53
+ * Pass `false` to route all clipboard traffic through the
54
+ * `ClipboardProvider` seam. See `DomSurfaceOptions.clipboard`.
55
+ */
56
+ clipboard?: boolean; /** Initial camera transform. Default identity. */
51
57
  initial_camera?: cmath.Transform;
52
58
  /**
53
59
  * Receives the `DomSurfaceHandle` once the surface is attached, and
@@ -70,6 +76,7 @@ declare function SvgEditorCanvas({
70
76
  style,
71
77
  gestures,
72
78
  fit,
79
+ clipboard,
73
80
  initial_camera,
74
81
  onAttach
75
82
  }: SvgEditorCanvasProps): import("react/jsx-runtime").JSX.Element;
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_editor = require("./editor-C6Lj1In-.js");
4
- const require_dom = require("./dom-U6ae5fQF.js");
3
+ const require_editor = require("./editor-N9af0JD2.js");
4
+ const require_dom = require("./dom-CaByuo6C.js");
5
5
  let react = require("react");
6
6
  let react_jsx_runtime = require("react/jsx-runtime");
7
7
  //#region src/react.tsx
@@ -40,7 +40,7 @@ function SvgEditorProvider({ initialSvg, providers, style, children }) {
40
40
  * context for them, because a host may mount multiple canvases in the
41
41
  * same editor session.
42
42
  */
43
- function SvgEditorCanvas({ className, style, gestures, fit, initial_camera, onAttach }) {
43
+ function SvgEditorCanvas({ className, style, gestures, fit, clipboard, initial_camera, onAttach }) {
44
44
  const editor = useSvgEditor();
45
45
  const ref = (0, react.useRef)(null);
46
46
  const on_attach_ref = (0, react.useRef)(onAttach);
@@ -54,6 +54,7 @@ function SvgEditorCanvas({ className, style, gestures, fit, initial_camera, onAt
54
54
  container,
55
55
  gestures,
56
56
  fit,
57
+ clipboard,
57
58
  initial_camera: initial_camera_ref.current
58
59
  });
59
60
  on_attach_ref.current?.(handle);
@@ -64,7 +65,8 @@ function SvgEditorCanvas({ className, style, gestures, fit, initial_camera, onAt
64
65
  }, [
65
66
  editor,
66
67
  gestures,
67
- fit
68
+ fit,
69
+ clipboard
68
70
  ]);
69
71
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
70
72
  ref,
package/dist/react.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { t as createSvgEditor } from "./editor-DKQOIKuU.mjs";
3
- import { t as attach_dom_surface } from "./dom-DOvcMvl4.mjs";
2
+ import { t as createSvgEditor } from "./editor-BLsELHSZ.mjs";
3
+ import { t as attach_dom_surface } from "./dom-Bjj9xySE.mjs";
4
4
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  //#region src/react.tsx
@@ -39,7 +39,7 @@ function SvgEditorProvider({ initialSvg, providers, style, children }) {
39
39
  * context for them, because a host may mount multiple canvases in the
40
40
  * same editor session.
41
41
  */
42
- function SvgEditorCanvas({ className, style, gestures, fit, initial_camera, onAttach }) {
42
+ function SvgEditorCanvas({ className, style, gestures, fit, clipboard, initial_camera, onAttach }) {
43
43
  const editor = useSvgEditor();
44
44
  const ref = useRef(null);
45
45
  const on_attach_ref = useRef(onAttach);
@@ -53,6 +53,7 @@ function SvgEditorCanvas({ className, style, gestures, fit, initial_camera, onAt
53
53
  container,
54
54
  gestures,
55
55
  fit,
56
+ clipboard,
56
57
  initial_camera: initial_camera_ref.current
57
58
  });
58
59
  on_attach_ref.current?.(handle);
@@ -63,7 +64,8 @@ function SvgEditorCanvas({ className, style, gestures, fit, initial_camera, onAt
63
64
  }, [
64
65
  editor,
65
66
  gestures,
66
- fit
67
+ fit,
68
+ clipboard
67
69
  ]);
68
70
  return /* @__PURE__ */ jsx("div", {
69
71
  ref,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grida/svg-editor",
3
- "version": "1.0.0-alpha.16",
3
+ "version": "1.0.0-alpha.17",
4
4
  "description": "Headless SVG editor (experimental).",
5
5
  "keywords": [
6
6
  "bezier",
@@ -59,12 +59,12 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@grida/cmath": "0.2.3",
62
- "@grida/history": "0.1.1",
62
+ "@grida/hud": "0.2.2",
63
63
  "@grida/keybinding": "0.2.1",
64
- "@grida/svg": "0.1.1",
65
- "@grida/text-editor": "0.1.2",
64
+ "@grida/svg": "0.2.0",
65
+ "@grida/history": "0.1.1",
66
66
  "@grida/vn": "0.1.0",
67
- "@grida/hud": "0.2.1"
67
+ "@grida/text-editor": "0.1.2"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@types/react": "^19",