@grida/svg-editor 1.0.0-alpha.2 → 1.0.0-alpha.21

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,963 +0,0 @@
1
- //#region \0rolldown/runtime.js
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
- key = keys[i];
11
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
- get: ((k) => from[k]).bind(null, key),
13
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
- });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
- value: mod,
20
- enumerable: true
21
- }) : target, mod));
22
- //#endregion
23
- const require_paint = require("./paint-DHq_3iwU.js");
24
- let _grida_text_editor_dom = require("@grida/text-editor/dom");
25
- let _grida_hud = require("@grida/hud");
26
- let _grida_cmath__measurement = require("@grida/cmath/_measurement");
27
- let _grida_cmath = require("@grida/cmath");
28
- _grida_cmath = __toESM(_grida_cmath);
29
- //#region src/text-surface.ts
30
- const SVG_NS = "http://www.w3.org/2000/svg";
31
- const XML_NS = "http://www.w3.org/XML/1998/namespace";
32
- var SvgTextSurface = class {
33
- constructor(textEl) {
34
- this.prevXmlSpace = void 0;
35
- this.last_caret_idx = -1;
36
- this.last_caret_visible = false;
37
- this.last_sel_start = -1;
38
- this.last_sel_end = -1;
39
- this.textEl = textEl;
40
- const ownerDoc = textEl.ownerDocument;
41
- const parent = textEl.parentNode;
42
- if (!parent) throw new Error("text element has no parent");
43
- const computedWhitespace = ownerDoc.defaultView?.getComputedStyle(textEl).whiteSpace;
44
- if (!(computedWhitespace === "pre" || computedWhitespace === "pre-wrap" || computedWhitespace === "break-spaces")) {
45
- this.prevXmlSpace = textEl.getAttributeNS(XML_NS, "space");
46
- textEl.setAttributeNS(XML_NS, "xml:space", "preserve");
47
- }
48
- const selection = ownerDoc.createElementNS(SVG_NS, "rect");
49
- selection.setAttribute("fill", "#2563eb");
50
- selection.setAttribute("fill-opacity", "0.25");
51
- selection.setAttribute("pointer-events", "none");
52
- selection.setAttribute("data-svg-text-edit-selection", "");
53
- selection.style.display = "none";
54
- parent.insertBefore(selection, textEl);
55
- this.selectionRect = selection;
56
- const caret = ownerDoc.createElementNS(SVG_NS, "rect");
57
- caret.setAttribute("fill", "#2563eb");
58
- caret.setAttribute("pointer-events", "none");
59
- caret.setAttribute("data-svg-text-edit-caret", "");
60
- caret.style.display = "none";
61
- parent.insertBefore(caret, textEl.nextSibling);
62
- this.caretRect = caret;
63
- }
64
- setText(text) {
65
- if (this.textEl.textContent !== text) this.textEl.textContent = text;
66
- }
67
- setCaret(index, visible) {
68
- if (index === this.last_caret_idx && visible === this.last_caret_visible) return;
69
- this.last_caret_idx = index;
70
- this.last_caret_visible = visible;
71
- if (!visible) {
72
- this.caretRect.style.display = "none";
73
- return;
74
- }
75
- const m = this.metrics();
76
- const x = this.charX(index);
77
- this.caretRect.setAttribute("x", String(x - .75));
78
- this.caretRect.setAttribute("y", String(m.top));
79
- this.caretRect.setAttribute("width", "1.5");
80
- this.caretRect.setAttribute("height", String(m.height));
81
- this.caretRect.style.display = "block";
82
- }
83
- setSelection(start, end) {
84
- if (start === this.last_sel_start && end === this.last_sel_end) return;
85
- this.last_sel_start = start;
86
- this.last_sel_end = end;
87
- if (start === end) {
88
- this.selectionRect.style.display = "none";
89
- return;
90
- }
91
- const m = this.metrics();
92
- const x1 = this.charX(start);
93
- const x2 = this.charX(end);
94
- this.selectionRect.setAttribute("x", String(Math.min(x1, x2)));
95
- this.selectionRect.setAttribute("y", String(m.top));
96
- this.selectionRect.setAttribute("width", String(Math.abs(x2 - x1)));
97
- this.selectionRect.setAttribute("height", String(m.height));
98
- this.selectionRect.style.display = "block";
99
- }
100
- dispose(keepEditMutations = false) {
101
- this.caretRect.remove();
102
- this.selectionRect.remove();
103
- if (this.prevXmlSpace !== void 0 && !keepEditMutations) if (this.prevXmlSpace === null) this.textEl.removeAttributeNS(XML_NS, "space");
104
- else this.textEl.setAttributeNS(XML_NS, "xml:space", this.prevXmlSpace);
105
- this.prevXmlSpace = void 0;
106
- }
107
- positionAtPoint(clientX, clientY) {
108
- const ctm = this.textEl.getScreenCTM();
109
- const svg = this.textEl.ownerSVGElement;
110
- if (!ctm || !svg) return 0;
111
- const pt = svg.createSVGPoint();
112
- pt.x = clientX;
113
- pt.y = clientY;
114
- const local = pt.matrixTransform(ctm.inverse());
115
- return this.localXToCharIndex(local.x);
116
- }
117
- /**
118
- * Single-line `<text>` element: there's no "previous visual line" to move
119
- * to. Cocoa single-line convention: Up/PageUp/line_start → doc start;
120
- * Down/PageDown/line_end → doc end.
121
- */
122
- positionForNavigation(_index, direction) {
123
- const text = this.textEl.textContent ?? "";
124
- switch (direction) {
125
- case "up":
126
- case "page_up":
127
- case "line_start": return 0;
128
- case "down":
129
- case "page_down":
130
- case "line_end": return text.length;
131
- }
132
- }
133
- metrics() {
134
- try {
135
- const b = this.textEl.getBBox();
136
- if (b.height > 0) return {
137
- top: b.y,
138
- height: b.height
139
- };
140
- } catch {}
141
- const fontSize = parseFloat(this.textEl.ownerDocument.defaultView?.getComputedStyle(this.textEl).fontSize ?? "16") || 16;
142
- return {
143
- top: parseFloat(this.textEl.getAttribute("y") ?? "0") - fontSize * .85,
144
- height: fontSize
145
- };
146
- }
147
- charX(i) {
148
- const text = this.textEl.textContent ?? "";
149
- const baseX = parseFloat(this.textEl.getAttribute("x") ?? "0");
150
- if (text.length === 0) return baseX;
151
- if (i <= 0) try {
152
- return this.textEl.getStartPositionOfChar(0).x;
153
- } catch {
154
- return baseX;
155
- }
156
- if (i >= text.length) try {
157
- return this.textEl.getEndPositionOfChar(text.length - 1).x;
158
- } catch {
159
- return baseX;
160
- }
161
- try {
162
- return this.textEl.getStartPositionOfChar(i).x;
163
- } catch {
164
- return baseX;
165
- }
166
- }
167
- localXToCharIndex(localX) {
168
- const text = this.textEl.textContent ?? "";
169
- if (!text) return 0;
170
- for (let i = 0; i < text.length; i++) try {
171
- const ext = this.textEl.getExtentOfChar(i);
172
- if (localX < ext.x + ext.width / 2) return i;
173
- } catch {
174
- break;
175
- }
176
- return text.length;
177
- }
178
- };
179
- //#endregion
180
- //#region src/dom.ts
181
- const ID_ATTR = "data-grida-id";
182
- /** KeyboardEvent.key values for the modifiers the surface tracks. Used to
183
- * short-circuit window-level `keydown`/`keyup` for non-modifier keystrokes. */
184
- const IS_MODIFIER_KEY = {
185
- Shift: true,
186
- Alt: true,
187
- Meta: true,
188
- Control: true
189
- };
190
- /** Sentinel placed in `text_edit` before `createTextEditor` returns, so the
191
- * surface skips render() during the in-flight mount and doesn't yank the
192
- * live `<text>` element out from under the about-to-mount text surface. */
193
- const TEXT_EDIT_PENDING = { __pending: true };
194
- /**
195
- * Attach a DOM surface to a headless editor. Returns a `SurfaceHandle` whose
196
- * `detach()` is the inverse — DOM cleared, listeners removed.
197
- *
198
- * Usage is one-shot per container: the surface owns the container's children
199
- * for its lifetime, and `detach()` restores it to empty.
200
- */
201
- function attach_dom_surface(editor, options) {
202
- const surface = new DomSurface(editor, options.container);
203
- return editor.attach(surface);
204
- }
205
- var DomSurface = class {
206
- constructor(editor, container) {
207
- this.editor = editor;
208
- this.container = container;
209
- this.svg_root = null;
210
- this.teardown = [];
211
- this.element_index = /* @__PURE__ */ new Map();
212
- this.resize_observer = null;
213
- this.active_preview = null;
214
- this.text_edit = null;
215
- this.text_edit_target = null;
216
- this.text_edit_original = "";
217
- this.editor_hover_internal = null;
218
- if (getComputedStyle(container).position === "static") container.style.position = "relative";
219
- container.style.userSelect = "none";
220
- container.style.webkitUserSelect = "none";
221
- this.hud_canvas = container.ownerDocument.createElement("canvas");
222
- Object.assign(this.hud_canvas.style, {
223
- position: "absolute",
224
- left: "0",
225
- top: "0",
226
- pointerEvents: "none"
227
- });
228
- container.appendChild(this.hud_canvas);
229
- this.hud = new _grida_hud.Surface(this.hud_canvas, {
230
- pick: (p) => this.hit_test(p[0], p[1]),
231
- shapeOf: (id) => this.shape_of(id),
232
- onIntent: (i) => this.commit_intent(i),
233
- style: { chromeColor: editor.style.chrome_color }
234
- });
235
- this.render();
236
- this.sync_canvas_size();
237
- this.sync_surface_selection();
238
- this.redraw();
239
- const win = container.ownerDocument.defaultView ?? window;
240
- const raf = win.requestAnimationFrame(() => {
241
- this.sync_canvas_size();
242
- this.redraw();
243
- });
244
- this.teardown.push(() => win.cancelAnimationFrame(raf));
245
- const unsub = editor.subscribe(() => {
246
- this.render();
247
- this.sync_surface_selection();
248
- this.sync_canvas_size();
249
- });
250
- this.teardown.push(unsub);
251
- if (typeof ResizeObserver !== "undefined") {
252
- this.resize_observer = new ResizeObserver(() => this.sync_canvas_size());
253
- this.resize_observer.observe(container);
254
- this.teardown.push(() => this.resize_observer?.disconnect());
255
- } else {
256
- const win = container.ownerDocument.defaultView ?? window;
257
- const fn = () => this.sync_canvas_size();
258
- win.addEventListener("resize", fn);
259
- this.teardown.push(() => win.removeEventListener("resize", fn));
260
- }
261
- this.wire_events();
262
- const internal = editor._internal;
263
- this.editor_hover_internal = internal;
264
- internal.set_content_edit_driver((id) => this.enter_content_edit(id));
265
- this.teardown.push(() => internal.set_content_edit_driver(null));
266
- internal.set_computed_resolver({
267
- computed_property: (id, name) => {
268
- const el = this.element_index.get(id);
269
- if (!el) return null;
270
- const value = getComputedStyle(el).getPropertyValue(name);
271
- return value === "" ? null : value;
272
- },
273
- computed_paint: (id, channel) => {
274
- const el = this.element_index.get(id);
275
- if (!el) return null;
276
- const computed = getComputedStyle(el).getPropertyValue(channel);
277
- if (computed === "") return null;
278
- return {
279
- computed,
280
- resolved_paint: require_paint.parse_paint(computed)
281
- };
282
- }
283
- });
284
- this.teardown.push(() => internal.set_computed_resolver(null));
285
- internal.set_surface_hover_override_driver((id) => {
286
- const response = this.hud.setHoverOverride(id);
287
- if (response.hoverChanged) internal.push_surface_hover(this.hud.hover());
288
- if (response.needsRedraw) this.redraw();
289
- });
290
- this.teardown.push(() => internal.set_surface_hover_override_driver(null));
291
- }
292
- paint(_snapshot) {}
293
- hit_test(x, y) {
294
- const owner_doc = this.container.ownerDocument;
295
- const cr = this.container.getBoundingClientRect();
296
- const target = owner_doc.elementFromPoint(cr.left + x, cr.top + y);
297
- if (!(target instanceof SVGElement)) return null;
298
- const root_id = this.editor.tree().root;
299
- let cur = target;
300
- while (cur instanceof Element) {
301
- const id = cur.getAttribute(ID_ATTR);
302
- if (id) return id === root_id ? null : id;
303
- cur = cur.parentElement;
304
- }
305
- return null;
306
- }
307
- on_input(_listener) {
308
- return () => {};
309
- }
310
- dispose() {
311
- if (this.text_edit) {
312
- this.text_edit.cancel();
313
- this.text_edit = null;
314
- this.text_edit_target = null;
315
- }
316
- for (const fn of this.teardown) fn();
317
- this.teardown = [];
318
- this.hud.dispose();
319
- this.hud_canvas.remove();
320
- if (this.svg_root) this.svg_root.remove();
321
- this.svg_root = null;
322
- this.element_index.clear();
323
- this.active_preview = null;
324
- }
325
- render() {
326
- if (this.text_edit) return;
327
- const owner_doc = this.container.ownerDocument;
328
- const doc = this.editor._internal.doc;
329
- const svg_text = this.editor.serialize();
330
- const wrapper = owner_doc.createElement("div");
331
- wrapper.innerHTML = svg_text;
332
- const new_svg = wrapper.querySelector("svg");
333
- if (!(new_svg instanceof SVGSVGElement)) return;
334
- if (this.svg_root) this.svg_root.replaceWith(new_svg);
335
- else this.container.insertBefore(new_svg, this.hud_canvas);
336
- this.svg_root = new_svg;
337
- this.element_index.clear();
338
- const ids = doc.all_elements();
339
- let i = 0;
340
- const tag_walk = (el) => {
341
- if (i < ids.length) {
342
- const id = ids[i++];
343
- el.setAttribute(ID_ATTR, id);
344
- this.element_index.set(id, el);
345
- }
346
- for (let c = el.firstElementChild; c; c = c.nextElementSibling) if (c instanceof SVGElement) tag_walk(c);
347
- };
348
- tag_walk(new_svg);
349
- }
350
- sync_canvas_size() {
351
- const cr = this.container.getBoundingClientRect();
352
- this.hud.setSize(cr.width, cr.height);
353
- this.redraw();
354
- }
355
- /** Single per-frame draw entry — merges host-fed extras with surface chrome. */
356
- redraw() {
357
- this.hud.draw(this.compute_measurement_extra());
358
- }
359
- /**
360
- * Build the host-fed measurement guide for the current frame, or
361
- * `undefined` if no guide should be drawn.
362
- *
363
- * Master signal: Alt held (read from `surface.modifiers()`). Each
364
- * additional condition is a derivation, not a separate flag — this keeps
365
- * a single source of truth and lets future Alt-consumers (constrained
366
- * resize, axis-lock, …) live next to this one without re-tracking the key.
367
- */
368
- compute_measurement_extra() {
369
- if (!this.hud.modifiers().alt) return void 0;
370
- if (this.hud.gesture().kind !== "idle") return void 0;
371
- const sel = this.editor.state.selection;
372
- if (sel.length === 0) return void 0;
373
- const hover = this.hud.hover();
374
- if (!hover) return void 0;
375
- if (sel.includes(hover)) return void 0;
376
- const a_rects = sel.map((id) => this.container_box(id)).filter((r) => r !== null);
377
- if (a_rects.length === 0) return void 0;
378
- const b_rect = this.container_box(hover);
379
- if (!b_rect) return void 0;
380
- const m = (0, _grida_cmath__measurement.measure)(_grida_cmath.default.rect.union(a_rects), b_rect);
381
- if (!m) return void 0;
382
- return (0, _grida_hud.measurementToHUDDraw)(m, this.editor.style.measurement_color);
383
- }
384
- sync_surface_selection() {
385
- const state = this.editor.state;
386
- if (state.mode === "edit-content") {
387
- this.hud.setSelection([]);
388
- return;
389
- }
390
- this.hud.setSelection(state.selection);
391
- }
392
- /**
393
- * Return the selection shape for a node. Vector `<line>` nodes return
394
- * `{ kind: "line", p1, p2 }` so the HUD lays out endpoint knobs; all
395
- * other nodes return `{ kind: "rect", rect }` using the container-space
396
- * bounding box.
397
- */
398
- shape_of(id) {
399
- if (this.tag_of(id) === "line") {
400
- const line = this.line_endpoints_in_container(id);
401
- if (line) return {
402
- kind: "line",
403
- p1: line.p1,
404
- p2: line.p2
405
- };
406
- }
407
- const rect = this.container_box(id);
408
- if (!rect) return null;
409
- return {
410
- kind: "rect",
411
- rect
412
- };
413
- }
414
- /**
415
- * Project an SVG `<line>`'s `x1,y1,x2,y2` from its own coordinate space
416
- * to the container's coordinate space, where the HUD operates.
417
- */
418
- line_endpoints_in_container(id) {
419
- const el = this.element_index.get(id);
420
- if (!(el instanceof SVGGraphicsElement)) return null;
421
- if (typeof el.getScreenCTM !== "function") return null;
422
- const ctm = el.getScreenCTM();
423
- if (!ctm || !this.svg_root) return null;
424
- const x1 = parseFloat(el.getAttribute("x1") ?? "0");
425
- const y1 = parseFloat(el.getAttribute("y1") ?? "0");
426
- const x2 = parseFloat(el.getAttribute("x2") ?? "0");
427
- const y2 = parseFloat(el.getAttribute("y2") ?? "0");
428
- if (!Number.isFinite(x1) || !Number.isFinite(y1)) return null;
429
- if (!Number.isFinite(x2) || !Number.isFinite(y2)) return null;
430
- const project = (px, py) => {
431
- return [ctm.a * px + ctm.c * py + ctm.e, ctm.b * px + ctm.d * py + ctm.f];
432
- };
433
- const cr = this.container.getBoundingClientRect();
434
- const [s1x, s1y] = project(x1, y1);
435
- const [s2x, s2y] = project(x2, y2);
436
- return {
437
- p1: [s1x - cr.left + this.container.scrollLeft, s1y - cr.top + this.container.scrollTop],
438
- p2: [s2x - cr.left + this.container.scrollLeft, s2y - cr.top + this.container.scrollTop]
439
- };
440
- }
441
- container_box(id) {
442
- const el = this.element_index.get(id);
443
- if (!el) return null;
444
- const ge = el;
445
- if (typeof ge.getBBox !== "function" || typeof ge.getScreenCTM !== "function") return null;
446
- let bbox;
447
- try {
448
- const b = ge.getBBox();
449
- bbox = {
450
- x: b.x,
451
- y: b.y,
452
- width: b.width,
453
- height: b.height
454
- };
455
- } catch {
456
- return null;
457
- }
458
- const ctm = ge.getScreenCTM();
459
- if (!ctm) return null;
460
- const project = (px, py) => ({
461
- x: ctm.a * px + ctm.c * py + ctm.e,
462
- y: ctm.b * px + ctm.d * py + ctm.f
463
- });
464
- const corners = [
465
- project(bbox.x, bbox.y),
466
- project(bbox.x + bbox.width, bbox.y),
467
- project(bbox.x + bbox.width, bbox.y + bbox.height),
468
- project(bbox.x, bbox.y + bbox.height)
469
- ];
470
- const xs = corners.map((c) => c.x);
471
- const ys = corners.map((c) => c.y);
472
- const left = Math.min(...xs);
473
- const top = Math.min(...ys);
474
- const right = Math.max(...xs);
475
- const bottom = Math.max(...ys);
476
- const cr = this.container.getBoundingClientRect();
477
- return {
478
- x: left - cr.left + this.container.scrollLeft,
479
- y: top - cr.top + this.container.scrollTop,
480
- width: right - left,
481
- height: bottom - top
482
- };
483
- }
484
- screen_delta_in_own_frame(id, dx, dy) {
485
- const el = this.element_index.get(id);
486
- if (!el) return {
487
- x: dx,
488
- y: dy
489
- };
490
- return this.unproject_delta(el, dx, dy);
491
- }
492
- screen_delta_in_parent_frame(id, dx, dy) {
493
- const el = this.element_index.get(id);
494
- if (!el) return {
495
- x: dx,
496
- y: dy
497
- };
498
- const parent = el.parentNode ?? el;
499
- return this.unproject_delta(parent, dx, dy);
500
- }
501
- unproject_delta(frame, dx, dy) {
502
- const ctm = typeof frame.getScreenCTM === "function" ? frame.getScreenCTM() : null;
503
- if (!ctm || !this.svg_root) return {
504
- x: dx,
505
- y: dy
506
- };
507
- const inv = ctm.inverse();
508
- const p0 = this.svg_root.createSVGPoint();
509
- p0.x = 0;
510
- p0.y = 0;
511
- const p1 = this.svg_root.createSVGPoint();
512
- p1.x = dx;
513
- p1.y = dy;
514
- const tp0 = p0.matrixTransform(inv);
515
- const tp1 = p1.matrixTransform(inv);
516
- return {
517
- x: tp1.x - tp0.x,
518
- y: tp1.y - tp0.y
519
- };
520
- }
521
- wire_events() {
522
- const owner_doc = this.container.ownerDocument;
523
- const win = owner_doc.defaultView ?? window;
524
- const on = (target, event, handler) => {
525
- target.addEventListener(event, handler);
526
- this.teardown.push(() => target.removeEventListener(event, handler));
527
- };
528
- on(this.container, "pointerdown", (e) => this.dispatch_pointer(e, "pointer_down"));
529
- on(win, "pointermove", (e) => this.dispatch_pointer(e, "pointer_move"));
530
- on(win, "pointerup", (e) => this.dispatch_pointer(e, "pointer_up"));
531
- on(owner_doc, "keydown", (e) => this.on_keydown(e));
532
- on(win, "keydown", (e) => {
533
- if (e.repeat || !IS_MODIFIER_KEY[e.key]) return;
534
- this.sync_modifiers(e);
535
- });
536
- on(win, "keyup", (e) => {
537
- if (!IS_MODIFIER_KEY[e.key]) return;
538
- this.sync_modifiers(e);
539
- });
540
- on(win, "blur", () => this.sync_modifiers(null));
541
- on(this.container, "contextmenu", (e) => e.preventDefault());
542
- }
543
- /**
544
- * Master signal for modifier-driven UX consumers (measurement, future
545
- * constrained-resize, …). Modifier changes aren't on the pointer-event
546
- * path, so derived overlays would otherwise wait for the next pointer
547
- * move; redraw eagerly. `null` means modifiers are forced clear
548
- * (blur / focus-out).
549
- */
550
- sync_modifiers(e) {
551
- const next = e ? {
552
- shift: e.shiftKey,
553
- alt: e.altKey,
554
- meta: e.metaKey,
555
- ctrl: e.ctrlKey
556
- } : _grida_hud.NO_MODS;
557
- const prev = this.hud.modifiers();
558
- if (prev.shift === next.shift && prev.alt === next.alt && prev.meta === next.meta && prev.ctrl === next.ctrl) return;
559
- const response = this.hud.dispatch({
560
- kind: "modifiers",
561
- mods: next
562
- });
563
- this.redraw();
564
- if (response.cursorChanged) this.sync_cursor();
565
- if (response.hoverChanged) this.editor_hover_internal?.push_surface_hover(this.hud.hover());
566
- }
567
- dispatch_pointer(e, kind) {
568
- if (this.text_edit) {
569
- if (kind === "pointer_down") {
570
- const el = this.text_edit_target ? this.element_index.get(this.text_edit_target) : null;
571
- if (el && e.target instanceof Element && (e.target === el || el.contains(e.target))) this.text_edit.pointerDown(e.clientX, e.clientY, e.shiftKey);
572
- } else if (kind === "pointer_move") this.text_edit.pointerMove(e.clientX, e.clientY);
573
- else if (kind === "pointer_up") this.text_edit.pointerUp();
574
- return;
575
- }
576
- const cr = this.container.getBoundingClientRect();
577
- const x = e.clientX - cr.left;
578
- const y = e.clientY - cr.top;
579
- const mods = {
580
- shift: e.shiftKey,
581
- alt: e.altKey,
582
- meta: e.metaKey,
583
- ctrl: e.ctrlKey
584
- };
585
- const button = e.button === 0 ? "primary" : e.button === 2 ? "secondary" : "middle";
586
- let event;
587
- if (kind === "pointer_move") event = {
588
- kind,
589
- x,
590
- y,
591
- mods
592
- };
593
- else {
594
- event = {
595
- kind,
596
- x,
597
- y,
598
- button,
599
- mods
600
- };
601
- if (kind === "pointer_down") try {
602
- this.container.setPointerCapture(e.pointerId);
603
- } catch {}
604
- }
605
- const response = this.hud.dispatch(event);
606
- if (response.needsRedraw) this.redraw();
607
- if (response.cursorChanged) this.sync_cursor();
608
- if (response.hoverChanged) this.editor_hover_internal?.push_surface_hover(this.hud.hover());
609
- }
610
- sync_cursor() {
611
- const c = this.hud.cursor();
612
- let css = "default";
613
- if (typeof c === "string") css = c === "default" ? "default" : c;
614
- else if (c.kind === "resize") css = `${c.direction}-resize`;
615
- else if (c.kind === "rotate") css = "crosshair";
616
- this.container.style.cursor = css;
617
- }
618
- on_keydown(e) {
619
- if (this.text_edit) return;
620
- if (e.code === "Escape" && this.active_preview) {
621
- this.active_preview.session.discard();
622
- this.active_preview = null;
623
- }
624
- this.editor.keymap.dispatch(e);
625
- }
626
- commit_intent(intent) {
627
- switch (intent.kind) {
628
- case "select":
629
- this.editor.commands.select(intent.ids, { additive: intent.mode !== "replace" });
630
- return;
631
- case "deselect_all":
632
- this.editor.commands.deselect();
633
- return;
634
- case "translate":
635
- this.handle_translate(intent);
636
- return;
637
- case "resize":
638
- this.handle_resize(intent);
639
- return;
640
- case "rotate": return;
641
- case "marquee_select":
642
- this.handle_marquee(intent);
643
- return;
644
- case "set_endpoint":
645
- this.handle_set_endpoint(intent);
646
- return;
647
- case "enter_content_edit":
648
- this.editor.commands.select(intent.id);
649
- this.editor.enter_content_edit(intent.id);
650
- return;
651
- case "cancel_gesture":
652
- if (this.active_preview) {
653
- this.active_preview.session.discard();
654
- this.active_preview = null;
655
- }
656
- return;
657
- }
658
- }
659
- handle_translate(intent) {
660
- const ids = intent.ids;
661
- if (ids.length === 0) return;
662
- const internal = this.editor_internal();
663
- const doc = internal.doc;
664
- const emit = internal.emit;
665
- if (!this.active_preview || this.active_preview.kind !== "translate") {
666
- if (this.active_preview) this.active_preview.session.discard();
667
- const baselines = /* @__PURE__ */ new Map();
668
- for (const id of ids) baselines.set(id, require_paint.capture_translate_baseline(doc, id));
669
- this.active_preview = {
670
- kind: "translate",
671
- ids,
672
- baselines,
673
- session: internal.history.preview("move")
674
- };
675
- }
676
- const baselines = this.active_preview.baselines;
677
- const deltas = /* @__PURE__ */ new Map();
678
- for (const id of ids) deltas.set(id, this.screen_delta_in_parent_frame(id, intent.dx, intent.dy));
679
- const apply = () => {
680
- for (const id of ids) {
681
- const baseline = baselines.get(id);
682
- const d = deltas.get(id);
683
- if (!baseline || !d) continue;
684
- require_paint.apply_translate(doc, id, baseline, d.x, d.y);
685
- }
686
- emit();
687
- };
688
- const revert = () => {
689
- for (const id of ids) {
690
- const baseline = baselines.get(id);
691
- if (!baseline) continue;
692
- require_paint.apply_translate(doc, id, baseline, 0, 0);
693
- }
694
- emit();
695
- };
696
- this.active_preview.session.set({
697
- providerId: "svg-editor",
698
- apply,
699
- revert
700
- });
701
- if (intent.phase === "commit") {
702
- this.active_preview.session.commit();
703
- this.active_preview = null;
704
- }
705
- }
706
- handle_resize(intent) {
707
- const id = intent.ids[0];
708
- if (!id || !require_paint.is_resizable(this.tag_of(id))) return;
709
- const internal = this.editor_internal();
710
- const doc = internal.doc;
711
- const emit = internal.emit;
712
- if (!this.active_preview || this.active_preview.kind !== "resize" || this.active_preview.id !== id) {
713
- if (this.active_preview) this.active_preview.session.discard();
714
- const bbox = this.bbox_local(id) ?? {
715
- x: 0,
716
- y: 0,
717
- width: 0,
718
- height: 0
719
- };
720
- const initial_screen = this.container_box(id) ?? {
721
- x: 0,
722
- y: 0,
723
- width: 0,
724
- height: 0
725
- };
726
- this.active_preview = {
727
- kind: "resize",
728
- id,
729
- direction: intent.anchor,
730
- baseline: require_paint.capture_resize_baseline(doc, id, bbox),
731
- initial_screen,
732
- session: internal.history.preview("resize")
733
- };
734
- }
735
- const baseline = this.active_preview.baseline;
736
- const dir = this.active_preview.direction;
737
- const initial = this.active_preview.initial_screen;
738
- const dx_screen = intent.rect.width - initial.width;
739
- const dy_screen = intent.rect.height - initial.height;
740
- const signed_dx = dir === "w" || dir === "nw" || dir === "sw" ? -dx_screen : dx_screen;
741
- const signed_dy = dir === "n" || dir === "ne" || dir === "nw" ? -dy_screen : dy_screen;
742
- const d = this.screen_delta_in_own_frame(id, signed_dx, signed_dy);
743
- const f = require_paint.compute_resize_factors(baseline, dir, d.x, d.y, false);
744
- const apply = () => {
745
- require_paint.apply_resize(doc, id, baseline, f.sx, f.sy, f.origin);
746
- emit();
747
- };
748
- const revert = () => {
749
- require_paint.apply_resize(doc, id, baseline, 1, 1, f.origin);
750
- emit();
751
- };
752
- this.active_preview.session.set({
753
- providerId: "svg-editor",
754
- apply,
755
- revert
756
- });
757
- if (intent.phase === "commit") {
758
- this.active_preview.session.commit();
759
- this.active_preview = null;
760
- }
761
- }
762
- /**
763
- * Apply a `set_endpoint` intent — moving one endpoint of a vector
764
- * `<line>` to a new container-space position. Unprojects to the element's
765
- * own (SVG) coord space and updates the corresponding attribute.
766
- */
767
- handle_set_endpoint(intent) {
768
- const id = intent.id;
769
- if (this.tag_of(id) !== "line") return;
770
- const internal = this.editor_internal();
771
- const doc = internal.doc;
772
- const emit = internal.emit;
773
- if (!this.active_preview || this.active_preview.kind !== "endpoint" || this.active_preview.id !== id || this.active_preview.endpoint !== intent.endpoint) {
774
- if (this.active_preview) this.active_preview.session.discard();
775
- const initial = {
776
- x1: numAttr(doc, id, "x1"),
777
- y1: numAttr(doc, id, "y1"),
778
- x2: numAttr(doc, id, "x2"),
779
- y2: numAttr(doc, id, "y2")
780
- };
781
- this.active_preview = {
782
- kind: "endpoint",
783
- id,
784
- endpoint: intent.endpoint,
785
- initial,
786
- session: internal.history.preview("set-endpoint")
787
- };
788
- }
789
- const initial = this.active_preview.initial;
790
- const endpoint = this.active_preview.endpoint;
791
- const pos_own = this.container_point_in_own_frame(id, intent.pos[0], intent.pos[1]);
792
- if (!pos_own) return;
793
- const target_x = pos_own.x;
794
- const target_y = pos_own.y;
795
- const apply = () => {
796
- if (endpoint === "p1") {
797
- doc.set_attr(id, "x1", String(target_x));
798
- doc.set_attr(id, "y1", String(target_y));
799
- } else {
800
- doc.set_attr(id, "x2", String(target_x));
801
- doc.set_attr(id, "y2", String(target_y));
802
- }
803
- emit();
804
- };
805
- const revert = () => {
806
- doc.set_attr(id, "x1", String(initial.x1));
807
- doc.set_attr(id, "y1", String(initial.y1));
808
- doc.set_attr(id, "x2", String(initial.x2));
809
- doc.set_attr(id, "y2", String(initial.y2));
810
- emit();
811
- };
812
- this.active_preview.session.set({
813
- providerId: "svg-editor",
814
- apply,
815
- revert
816
- });
817
- if (intent.phase === "commit") {
818
- this.active_preview.session.commit();
819
- this.active_preview = null;
820
- }
821
- }
822
- /**
823
- * Convert a container-space point to the element's own SVG coord space.
824
- * Inverse of `line_endpoints_in_container`'s projection.
825
- */
826
- container_point_in_own_frame(id, cx, cy) {
827
- const el = this.element_index.get(id);
828
- if (!(el instanceof SVGGraphicsElement)) return null;
829
- if (typeof el.getScreenCTM !== "function") return null;
830
- const ctm = el.getScreenCTM();
831
- if (!ctm || !this.svg_root) return null;
832
- const cr = this.container.getBoundingClientRect();
833
- const inv = ctm.inverse();
834
- const p = this.svg_root.createSVGPoint();
835
- p.x = cx + cr.left - this.container.scrollLeft;
836
- p.y = cy + cr.top - this.container.scrollTop;
837
- const t = p.matrixTransform(inv);
838
- return {
839
- x: t.x,
840
- y: t.y
841
- };
842
- }
843
- handle_marquee(intent) {
844
- if (intent.phase !== "commit") return;
845
- const ids = [];
846
- for (const [id, el] of this.element_index) {
847
- if (id === this.editor.tree().root) continue;
848
- const box = this.container_box(id);
849
- if (!box) continue;
850
- if (rect_intersects(box, intent.rect)) ids.push(id);
851
- }
852
- if (ids.length === 0) {
853
- if (!intent.additive) this.editor.commands.deselect();
854
- return;
855
- }
856
- this.editor.commands.select(ids, { additive: intent.additive });
857
- }
858
- enter_content_edit(id) {
859
- if (this.text_edit) return false;
860
- const el = this.element_index.get(id);
861
- if (!(el instanceof SVGTextElement)) return false;
862
- const doc = this.editor._internal;
863
- this.text_edit_target = id;
864
- this.text_edit_original = doc.doc.text_of(id);
865
- this.text_edit = TEXT_EDIT_PENDING;
866
- this.editor.commands.set_mode("edit-content");
867
- this.sync_surface_selection();
868
- this.redraw();
869
- const text_surface = new SvgTextSurface(this.element_index.get(id) ?? el);
870
- const is_mac = typeof navigator !== "undefined" && /Mac|iPod|iPhone|iPad/.test(navigator.userAgent);
871
- let settled = false;
872
- const cleanup_after_commit_or_cancel = () => {
873
- this.text_edit = null;
874
- this.text_edit_target = null;
875
- this.editor.commands.set_mode("select");
876
- this.render();
877
- this.sync_surface_selection();
878
- this.redraw();
879
- };
880
- this.text_edit = (0, _grida_text_editor_dom.createTextEditor)({
881
- container: this.container,
882
- initialText: this.text_edit_original,
883
- layout: text_surface,
884
- surface: text_surface,
885
- isMac: is_mac,
886
- ariaLabel: "edit svg text",
887
- requiresMutationsForCommit: (text) => /\s{2,}|^\s|\s$/.test(text),
888
- callbacks: {
889
- onChange: (text) => {
890
- doc.doc.set_text(id, text);
891
- },
892
- onCommit: (final_text) => {
893
- if (settled) return;
894
- settled = true;
895
- doc.doc.set_text(id, this.text_edit_original);
896
- cleanup_after_commit_or_cancel();
897
- if (final_text !== this.text_edit_original) this.editor.commands.set_text(final_text);
898
- },
899
- onCancel: () => {
900
- if (settled) return;
901
- settled = true;
902
- doc.doc.set_text(id, this.text_edit_original);
903
- cleanup_after_commit_or_cancel();
904
- doc.emit();
905
- },
906
- onUndoFallthrough: () => {
907
- this.text_edit?.commit();
908
- this.editor.commands.undo();
909
- },
910
- onRedoFallthrough: () => {
911
- this.text_edit?.commit();
912
- this.editor.commands.redo();
913
- }
914
- }
915
- });
916
- return true;
917
- }
918
- tag_of(id) {
919
- return this.editor.tree().nodes.get(id)?.tag ?? "";
920
- }
921
- bbox_local(id) {
922
- const el = this.element_index.get(id);
923
- if (!el) return null;
924
- const ge = el;
925
- if (typeof ge.getBBox !== "function") return null;
926
- try {
927
- const b = ge.getBBox();
928
- return {
929
- x: b.x,
930
- y: b.y,
931
- width: b.width,
932
- height: b.height
933
- };
934
- } catch {
935
- return null;
936
- }
937
- }
938
- editor_internal() {
939
- return this.editor._internal;
940
- }
941
- };
942
- function numAttr(doc, id, name) {
943
- const v = doc.get_attr(id, name);
944
- if (v === null || v === "") return 0;
945
- const n = parseFloat(v);
946
- return Number.isFinite(n) ? n : 0;
947
- }
948
- function rect_intersects(a, b) {
949
- return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
950
- }
951
- //#endregion
952
- Object.defineProperty(exports, "__toESM", {
953
- enumerable: true,
954
- get: function() {
955
- return __toESM;
956
- }
957
- });
958
- Object.defineProperty(exports, "attach_dom_surface", {
959
- enumerable: true,
960
- get: function() {
961
- return attach_dom_surface;
962
- }
963
- });