@grida/svg-editor 1.0.0-alpha.15 → 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.
- package/README.md +69 -0
- package/dist/{dom-CsKXTaNw.d.ts → dom-BMzX1CXZ.d.ts} +56 -2
- package/dist/{dom-DILY80j7.mjs → dom-Bjj9xySE.mjs} +171 -13
- package/dist/{dom-Dee6FtgZ.js → dom-CaByuo6C.js} +176 -12
- package/dist/{dom-CK6GlgFF.d.mts → dom-TctdgRnn.d.mts} +56 -2
- package/dist/dom.d.mts +3 -3
- package/dist/dom.d.ts +3 -3
- package/dist/dom.js +2 -1
- package/dist/dom.mjs +2 -2
- package/dist/{editor-CvWpD5mu.mjs → editor-BLsELHSZ.mjs} +769 -866
- package/dist/{editor-BKoo9SPL.d.ts → editor-BSxTUsW_.d.ts} +553 -5
- package/dist/{editor-Dl7c0q5A.d.mts → editor-KqpIW1qm.d.mts} +553 -5
- package/dist/{editor-F8ckj9X1.js → editor-N9af0JD2.js} +769 -866
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{model-B2UWgViT.mjs → model-DMaN5GnH.mjs} +1442 -72
- package/dist/{model-CJ1Ctq14.js → model-GpysNbOv.js} +1459 -71
- package/dist/presets.d.mts +2 -2
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +2 -2
- package/dist/presets.mjs +1 -1
- package/dist/react.d.mts +28 -4
- package/dist/react.d.ts +28 -4
- package/dist/react.js +29 -4
- package/dist/react.mjs +29 -5
- package/package.json +9 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_model = require("./model-
|
|
1
|
+
const require_model = require("./model-GpysNbOv.js");
|
|
2
2
|
let _grida_cmath = require("@grida/cmath");
|
|
3
3
|
_grida_cmath = require_model.__toESM(_grida_cmath);
|
|
4
4
|
let _grida_svg_parse = require("@grida/svg/parse");
|
|
@@ -1007,15 +1007,18 @@ function create_attention_tracker(container) {
|
|
|
1007
1007
|
};
|
|
1008
1008
|
container.addEventListener("pointerenter", on_enter);
|
|
1009
1009
|
container.addEventListener("pointerleave", on_leave);
|
|
1010
|
-
const
|
|
1010
|
+
const is_focus_within = () => {
|
|
1011
1011
|
const owner = container.ownerDocument;
|
|
1012
|
-
if (!owner) return
|
|
1012
|
+
if (!owner) return false;
|
|
1013
1013
|
const active = owner.activeElement;
|
|
1014
|
-
|
|
1015
|
-
|
|
1014
|
+
return !!active && active !== owner.body && container.contains(active);
|
|
1015
|
+
};
|
|
1016
|
+
const is_attended = () => {
|
|
1017
|
+
return is_focus_within() || pointer_over;
|
|
1016
1018
|
};
|
|
1017
1019
|
return {
|
|
1018
1020
|
is_attended,
|
|
1021
|
+
is_focus_within,
|
|
1019
1022
|
dispose: () => {
|
|
1020
1023
|
container.removeEventListener("pointerenter", on_enter);
|
|
1021
1024
|
container.removeEventListener("pointerleave", on_leave);
|
|
@@ -1640,6 +1643,48 @@ const IS_MODIFIER_KEY = {
|
|
|
1640
1643
|
* live `<text>` element out from under the about-to-mount text surface. */
|
|
1641
1644
|
const TEXT_EDIT_PENDING = { __pending: true };
|
|
1642
1645
|
/**
|
|
1646
|
+
* Wire a web-font settle source to the editor's geometry channel.
|
|
1647
|
+
*
|
|
1648
|
+
* The DOM surface re-serializes the `<svg>` on every editor tick, but a
|
|
1649
|
+
* `<text>` / `<tspan>` bbox can change with NO attribute write: a web font
|
|
1650
|
+
* finishing load AFTER its `font-family` / `font-size` was already written.
|
|
1651
|
+
* The IR never sees that reflow, so nothing bumps `geometry_version` and
|
|
1652
|
+
* every bounds-keyed consumer (snap, HUD chrome, size meter) stays stuck at
|
|
1653
|
+
* the fallback-face metrics until the next real edit.
|
|
1654
|
+
*
|
|
1655
|
+
* Listens for `loadingdone` on `source` (a `FontFaceSet`, or any injected
|
|
1656
|
+
* `EventTarget` in tests) and calls `bump` once per settle. COARSE on
|
|
1657
|
+
* purpose: one bump clears the WHOLE bounds cache, not just text nodes —
|
|
1658
|
+
* consistent with the package's pessimistic-invalidation stance, and far
|
|
1659
|
+
* cheaper than scoping the bump to the (possibly many) reflowed runs.
|
|
1660
|
+
*
|
|
1661
|
+
* Also bumps once when `source.ready` resolves (when present): fonts that
|
|
1662
|
+
* settled before attach — a cache hit, or `font-display` resolving the same
|
|
1663
|
+
* tick the surface mounts — never re-fire `loadingdone`, so a document
|
|
1664
|
+
* mounted post-settle still needs one bump to re-read at the real metrics.
|
|
1665
|
+
*
|
|
1666
|
+
* Returns a teardown that removes the listener and neutralizes the pending
|
|
1667
|
+
* `ready` bump (leak guard) — call it on surface detach.
|
|
1668
|
+
*
|
|
1669
|
+
* Factored out of the surface so it can be unit-tested with a fake
|
|
1670
|
+
* `EventTarget` in the node-only test env (jsdom's `FontFaceSet` is
|
|
1671
|
+
* incomplete); never a real font / network.
|
|
1672
|
+
*/
|
|
1673
|
+
function install_font_load_geometry_bump(source, bump) {
|
|
1674
|
+
if (!source) return () => {};
|
|
1675
|
+
const on_fonts_settled = () => bump();
|
|
1676
|
+
source.addEventListener("loadingdone", on_fonts_settled);
|
|
1677
|
+
let alive = true;
|
|
1678
|
+
const ready = source.ready;
|
|
1679
|
+
if (ready && typeof ready.then === "function") ready.then(() => {
|
|
1680
|
+
if (alive) bump();
|
|
1681
|
+
});
|
|
1682
|
+
return () => {
|
|
1683
|
+
alive = false;
|
|
1684
|
+
source.removeEventListener("loadingdone", on_fonts_settled);
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1643
1688
|
* Attach a DOM surface to a headless editor. Returns a `DomSurfaceHandle`
|
|
1644
1689
|
* whose `detach()` is the inverse — DOM cleared, listeners removed,
|
|
1645
1690
|
* gestures uninstalled.
|
|
@@ -1665,6 +1710,7 @@ var DomSurface = class DomSurface {
|
|
|
1665
1710
|
this.svg_root = null;
|
|
1666
1711
|
this.teardown = [];
|
|
1667
1712
|
this.element_index = /* @__PURE__ */ new Map();
|
|
1713
|
+
this.rendered_doc_revision = -1;
|
|
1668
1714
|
this.last_pointer = {
|
|
1669
1715
|
x: 0,
|
|
1670
1716
|
y: 0
|
|
@@ -1689,6 +1735,7 @@ var DomSurface = class DomSurface {
|
|
|
1689
1735
|
this.container = options.container;
|
|
1690
1736
|
const container = this.container;
|
|
1691
1737
|
this.fit_on_attach = options.fit === true;
|
|
1738
|
+
this.clipboard_enabled = options.clipboard !== false;
|
|
1692
1739
|
this.attention = create_attention_tracker(container);
|
|
1693
1740
|
this.teardown.push(() => this.attention.dispose());
|
|
1694
1741
|
if (process.env.NODE_ENV !== "production" && container.children.length > 0) console.warn("@grida/svg-editor: surface container is not empty at attach time. Render chrome (toolbars, layer lists, inspectors) as siblings of the container, not children — otherwise clicks on those children will silently break. See README §Surface.");
|
|
@@ -1696,6 +1743,8 @@ var DomSurface = class DomSurface {
|
|
|
1696
1743
|
container.style.overflow = "hidden";
|
|
1697
1744
|
container.style.userSelect = "none";
|
|
1698
1745
|
container.style.webkitUserSelect = "none";
|
|
1746
|
+
container.tabIndex = -1;
|
|
1747
|
+
container.style.outline = "none";
|
|
1699
1748
|
const translate_options = () => {
|
|
1700
1749
|
const style = this.editor.style;
|
|
1701
1750
|
const zoom = this.camera.zoom || 1;
|
|
@@ -1711,7 +1760,9 @@ var DomSurface = class DomSurface {
|
|
|
1711
1760
|
open_preview: (label) => this.editor_internal().history.preview(label),
|
|
1712
1761
|
open_snap: (ids) => this.open_snap_session_for(ids),
|
|
1713
1762
|
options: translate_options,
|
|
1714
|
-
project_delta: (id, d) => this._geometry_provider?.world_delta_to_local(id, d) ?? d
|
|
1763
|
+
project_delta: (id, d) => this._geometry_provider?.world_delta_to_local(id, d) ?? d,
|
|
1764
|
+
set_selection: (ids) => this.editor.commands.select(ids),
|
|
1765
|
+
on_clone_commit: (record) => this.editor_internal().seed_duplication(record)
|
|
1715
1766
|
});
|
|
1716
1767
|
const resize_options = () => {
|
|
1717
1768
|
const style = this.editor.style;
|
|
@@ -1779,6 +1830,7 @@ var DomSurface = class DomSurface {
|
|
|
1779
1830
|
shapeOf: (id) => this.shape_of(id),
|
|
1780
1831
|
vectorOf: (id) => this.vector_of(id),
|
|
1781
1832
|
onIntent: (i) => this.commit_intent(i),
|
|
1833
|
+
onTap: (t) => this.handle_tap(t),
|
|
1782
1834
|
style: {
|
|
1783
1835
|
chromeColor: editor.style.chrome_color,
|
|
1784
1836
|
showRotationHandles: true
|
|
@@ -1838,7 +1890,7 @@ var DomSurface = class DomSurface {
|
|
|
1838
1890
|
this.current_tool = editor.state.tool;
|
|
1839
1891
|
this.hud.setVectorSelectionMode(this.current_tool.type === "lasso" ? "lasso" : "marquee");
|
|
1840
1892
|
this.hud.setVectorBendMode(this.current_tool.type === "bend" ? "always" : "auto");
|
|
1841
|
-
this.
|
|
1893
|
+
this.flush_dom();
|
|
1842
1894
|
this.sync_surface_selection();
|
|
1843
1895
|
this.hud.setPixelGrid({
|
|
1844
1896
|
enabled: editor.style.pixel_grid,
|
|
@@ -1885,6 +1937,8 @@ var DomSurface = class DomSurface {
|
|
|
1885
1937
|
win.addEventListener("resize", fn);
|
|
1886
1938
|
this.teardown.push(() => win.removeEventListener("resize", fn));
|
|
1887
1939
|
}
|
|
1940
|
+
const detach_font_listener = install_font_load_geometry_bump(options.font_load_source ?? container.ownerDocument.fonts ?? null, () => editor._internal.bump_geometry());
|
|
1941
|
+
this.teardown.push(detach_font_listener);
|
|
1888
1942
|
this.wire_events();
|
|
1889
1943
|
const internal = editor._internal;
|
|
1890
1944
|
this.editor_hover_internal = internal;
|
|
@@ -1892,12 +1946,14 @@ var DomSurface = class DomSurface {
|
|
|
1892
1946
|
this.teardown.push(() => internal.set_content_edit_driver(null));
|
|
1893
1947
|
internal.set_computed_resolver({
|
|
1894
1948
|
computed_property: (id, name) => {
|
|
1949
|
+
this.flush_dom();
|
|
1895
1950
|
const el = this.element_index.get(id);
|
|
1896
1951
|
if (!el) return null;
|
|
1897
1952
|
const value = getComputedStyle(el).getPropertyValue(name);
|
|
1898
1953
|
return value === "" ? null : value;
|
|
1899
1954
|
},
|
|
1900
1955
|
computed_paint: (id, channel) => {
|
|
1956
|
+
this.flush_dom();
|
|
1901
1957
|
const el = this.element_index.get(id);
|
|
1902
1958
|
if (!el) return null;
|
|
1903
1959
|
const computed = getComputedStyle(el).getPropertyValue(channel);
|
|
@@ -1914,7 +1970,8 @@ var DomSurface = class DomSurface {
|
|
|
1914
1970
|
root: () => this.svg_root,
|
|
1915
1971
|
camera: () => this.camera,
|
|
1916
1972
|
container: () => this.container,
|
|
1917
|
-
pick_at_world: (p, allow_root) => this._pick_node_at_world(p, allow_root)
|
|
1973
|
+
pick_at_world: (p, allow_root) => this._pick_node_at_world(p, allow_root),
|
|
1974
|
+
flush: () => this.flush_dom()
|
|
1918
1975
|
}), {
|
|
1919
1976
|
subscribe_structure: (cb) => editor.subscribe_with_selector((s) => s.structure_version, () => cb()),
|
|
1920
1977
|
subscribe_geometry: (cb) => editor.subscribe_geometry(cb)
|
|
@@ -2096,6 +2153,25 @@ var DomSurface = class DomSurface {
|
|
|
2096
2153
|
detach_gestures() {
|
|
2097
2154
|
this.gestures._dispose();
|
|
2098
2155
|
}
|
|
2156
|
+
/**
|
|
2157
|
+
* Bring the live DOM up to date with the doc IR iff it is stale.
|
|
2158
|
+
*
|
|
2159
|
+
* Staleness contract: anything that reads the LIVE DOM as a proxy for
|
|
2160
|
+
* document state — the geometry driver (`getBBox` / `getCTM`), the
|
|
2161
|
+
* computed-style resolver — MUST call this first. Doc listeners (the
|
|
2162
|
+
* geometry channel, editor `subscribe`) fire synchronously inside the
|
|
2163
|
+
* mutation, BEFORE the surface's render listener has projected the new
|
|
2164
|
+
* attrs into the DOM; a read in that window returns the PREVIOUS
|
|
2165
|
+
* geometry, and through `MemoizedGeometryProvider` it would be cached as
|
|
2166
|
+
* if current — every later consumer (align, resize_to, snap) then plans
|
|
2167
|
+
* against one-mutation-stale bounds. Same model as CSS layout: reading
|
|
2168
|
+
* `offsetWidth` flushes pending layout; reading `bounds_of` flushes the
|
|
2169
|
+
* pending render.
|
|
2170
|
+
*/
|
|
2171
|
+
flush_dom() {
|
|
2172
|
+
if (this.rendered_doc_revision === this.editor._internal.doc.revision) return;
|
|
2173
|
+
this.render();
|
|
2174
|
+
}
|
|
2099
2175
|
render() {
|
|
2100
2176
|
if (this.text_edit) return;
|
|
2101
2177
|
const owner_doc = this.container.ownerDocument;
|
|
@@ -2122,6 +2198,7 @@ var DomSurface = class DomSurface {
|
|
|
2122
2198
|
for (let c = el.firstElementChild; c; c = c.nextElementSibling) if (c instanceof SVGElement) tag_walk(c);
|
|
2123
2199
|
};
|
|
2124
2200
|
tag_walk(new_svg);
|
|
2201
|
+
this.rendered_doc_revision = doc.revision;
|
|
2125
2202
|
}
|
|
2126
2203
|
sync_canvas_size() {
|
|
2127
2204
|
const cr = this.container.getBoundingClientRect();
|
|
@@ -2716,6 +2793,60 @@ var DomSurface = class DomSurface {
|
|
|
2716
2793
|
});
|
|
2717
2794
|
on(win, "blur", () => this.sync_modifiers(null));
|
|
2718
2795
|
on(this.container, "contextmenu", (e) => e.preventDefault());
|
|
2796
|
+
if (this.clipboard_enabled) {
|
|
2797
|
+
on(owner_doc, "copy", (e) => this.on_copy_or_cut(e, "copy"));
|
|
2798
|
+
on(owner_doc, "cut", (e) => this.on_copy_or_cut(e, "cut"));
|
|
2799
|
+
on(owner_doc, "paste", (e) => this.on_paste(e));
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
/**
|
|
2803
|
+
* Gate for claiming a native clipboard gesture. Deliberately STRICTER
|
|
2804
|
+
* than the keyboard attention gate: focus-based only — pointer-over is
|
|
2805
|
+
* a sufficient signal for a keystroke (worst case: a stolen scroll) but
|
|
2806
|
+
* not for clipboard (worst case: destroying what the user believed they
|
|
2807
|
+
* copied, or routing a paste meant for a host text field into the
|
|
2808
|
+
* document). A user with text selected in a sibling panel and the
|
|
2809
|
+
* pointer idly over the canvas must get their text copy.
|
|
2810
|
+
*/
|
|
2811
|
+
claims_clipboard(kind) {
|
|
2812
|
+
if (this.text_edit) return false;
|
|
2813
|
+
if (this.editor.state.mode !== "select") return false;
|
|
2814
|
+
if (!this.attention.is_focus_within()) return false;
|
|
2815
|
+
if (require_model.is_text_input_focused()) return false;
|
|
2816
|
+
if (kind !== "paste") {
|
|
2817
|
+
const sel = this.container.ownerDocument.getSelection();
|
|
2818
|
+
if (sel && !sel.isCollapsed) return false;
|
|
2819
|
+
}
|
|
2820
|
+
return true;
|
|
2821
|
+
}
|
|
2822
|
+
/**
|
|
2823
|
+
* Act-then-claim: an empty selection returns without `preventDefault()`,
|
|
2824
|
+
* leaving the browser default (and the OS clipboard) untouched. The
|
|
2825
|
+
* buffer-only `_internal.clipboard` variants are used here — the event's
|
|
2826
|
+
* DataTransfer is this gesture's ONE external channel (the public
|
|
2827
|
+
* commands would additionally write the provider; one gesture, one
|
|
2828
|
+
* external write — FRD §Transport).
|
|
2829
|
+
*/
|
|
2830
|
+
on_copy_or_cut(e, kind) {
|
|
2831
|
+
if (!this.claims_clipboard(kind)) return;
|
|
2832
|
+
if (!e.clipboardData) return;
|
|
2833
|
+
const internal = this.editor_internal();
|
|
2834
|
+
const payload = kind === "copy" ? internal.clipboard.copy() : internal.clipboard.cut();
|
|
2835
|
+
if (payload === null) return;
|
|
2836
|
+
e.clipboardData.setData("text/plain", payload);
|
|
2837
|
+
e.preventDefault();
|
|
2838
|
+
}
|
|
2839
|
+
/**
|
|
2840
|
+
* Claim-then-act (mirrors the keydown claim doctrine: swallow when the
|
|
2841
|
+
* gesture is aimed at the editor, not just when a handler consumed):
|
|
2842
|
+
* a refused paste — junk text — still claims; the suppressed default is
|
|
2843
|
+
* a no-op on a div anyway.
|
|
2844
|
+
*/
|
|
2845
|
+
on_paste(e) {
|
|
2846
|
+
if (!this.claims_clipboard("paste")) return;
|
|
2847
|
+
e.preventDefault();
|
|
2848
|
+
const text = e.clipboardData?.getData("text/plain");
|
|
2849
|
+
if (text) this.editor.commands.paste(text);
|
|
2719
2850
|
}
|
|
2720
2851
|
/**
|
|
2721
2852
|
* Master signal for modifier-driven UX consumers (measurement, future
|
|
@@ -2737,7 +2868,7 @@ var DomSurface = class DomSurface {
|
|
|
2737
2868
|
kind: "modifiers",
|
|
2738
2869
|
mods: next
|
|
2739
2870
|
});
|
|
2740
|
-
if (prev.shift !== next.shift && this.translate_orchestrator.has_active_session()) this.translate_orchestrator.redrive_modifiers(this.current_translate_modifiers());
|
|
2871
|
+
if ((prev.shift !== next.shift || prev.alt !== next.alt) && this.translate_orchestrator.has_active_session()) this.translate_orchestrator.redrive_modifiers(this.current_translate_modifiers());
|
|
2741
2872
|
if (prev.shift !== next.shift && this.resize_orchestrator.has_active_session()) this.resize_orchestrator.redrive_modifiers(this.current_resize_modifiers());
|
|
2742
2873
|
if (prev.shift !== next.shift && this.rotate_orchestrator.has_active_session()) this.rotate_orchestrator.redrive_modifiers(this.current_rotate_modifiers());
|
|
2743
2874
|
this.redraw();
|
|
@@ -2758,6 +2889,7 @@ var DomSurface = class DomSurface {
|
|
|
2758
2889
|
} else if (kind === "pointer_up") this.text_edit.pointerUp();
|
|
2759
2890
|
return;
|
|
2760
2891
|
}
|
|
2892
|
+
if (kind === "pointer_down") this.container.focus({ preventScroll: true });
|
|
2761
2893
|
const cr = this.container.getBoundingClientRect();
|
|
2762
2894
|
const x = e.clientX - cr.left;
|
|
2763
2895
|
const y = e.clientY - cr.top;
|
|
@@ -2968,6 +3100,26 @@ var DomSurface = class DomSurface {
|
|
|
2968
3100
|
if (this.editor.keymap.claims(e)) e.preventDefault();
|
|
2969
3101
|
this.editor.keymap.dispatch(e);
|
|
2970
3102
|
}
|
|
3103
|
+
/**
|
|
3104
|
+
* Re-express a HUD tap as an editor {@link PickEvent} and fan it out on the
|
|
3105
|
+
* editor's pick channel. The HUD already resolved everything that matters —
|
|
3106
|
+
* the pointer-down point, the hit node, and click-vs-drag — so this is a
|
|
3107
|
+
* pure translation (HUD `[x, y]` tuple → editor `{ x, y }` doc-space point)
|
|
3108
|
+
* with NO re-hit-testing. Taking the hit from the HUD (not a fresh
|
|
3109
|
+
* `node_at_point`) guarantees the pick and the selection it accompanies can
|
|
3110
|
+
* never disagree. Observe-only: this mutates no editor state.
|
|
3111
|
+
*/
|
|
3112
|
+
handle_tap(tap) {
|
|
3113
|
+
this.editor._internal.push_pick({
|
|
3114
|
+
point: {
|
|
3115
|
+
x: tap.point[0],
|
|
3116
|
+
y: tap.point[1]
|
|
3117
|
+
},
|
|
3118
|
+
node_id: tap.hit,
|
|
3119
|
+
button: tap.button,
|
|
3120
|
+
mods: tap.mods
|
|
3121
|
+
});
|
|
3122
|
+
}
|
|
2971
3123
|
commit_intent(intent) {
|
|
2972
3124
|
switch (intent.kind) {
|
|
2973
3125
|
case "select":
|
|
@@ -3054,13 +3206,15 @@ var DomSurface = class DomSurface {
|
|
|
3054
3206
|
});
|
|
3055
3207
|
if (intent.phase === "commit") this.request_redraw();
|
|
3056
3208
|
}
|
|
3057
|
-
/** Snapshot of HUD modifier state mapped to
|
|
3209
|
+
/** Snapshot of HUD modifier state mapped to the orchestrator's `GestureModifiers`.
|
|
3058
3210
|
* Pull-at-consume: HUD is the canonical store (see `sync_modifiers`),
|
|
3059
3211
|
* read live so mid-drag Shift press/release reflects on the next pass. */
|
|
3060
3212
|
current_translate_modifiers() {
|
|
3213
|
+
const mods = this.hud.modifiers();
|
|
3061
3214
|
return {
|
|
3062
|
-
axis_lock:
|
|
3063
|
-
force_disable_snap: false
|
|
3215
|
+
axis_lock: mods.shift ? "by_dominance" : "off",
|
|
3216
|
+
force_disable_snap: false,
|
|
3217
|
+
clone: mods.alt
|
|
3064
3218
|
};
|
|
3065
3219
|
}
|
|
3066
3220
|
/** Snapshot of HUD modifier state mapped to `ResizeModifiers`. Same
|
|
@@ -4399,6 +4553,7 @@ var SvgGeometryDriver = class {
|
|
|
4399
4553
|
this.accessors = accessors;
|
|
4400
4554
|
}
|
|
4401
4555
|
bounds_of(id) {
|
|
4556
|
+
this.accessors.flush();
|
|
4402
4557
|
const el = this.accessors.element_for(id);
|
|
4403
4558
|
if (!el) return null;
|
|
4404
4559
|
if (el instanceof SVGSVGElement) return svg_viewport_bounds(el);
|
|
@@ -4450,6 +4605,7 @@ var SvgGeometryDriver = class {
|
|
|
4450
4605
|
return out;
|
|
4451
4606
|
}
|
|
4452
4607
|
nodes_in_rect(rect) {
|
|
4608
|
+
this.accessors.flush();
|
|
4453
4609
|
const root = this.accessors.root();
|
|
4454
4610
|
if (!root) return [];
|
|
4455
4611
|
const hits = [];
|
|
@@ -4462,6 +4618,7 @@ var SvgGeometryDriver = class {
|
|
|
4462
4618
|
return hits;
|
|
4463
4619
|
}
|
|
4464
4620
|
node_at_point(p) {
|
|
4621
|
+
this.accessors.flush();
|
|
4465
4622
|
return this.accessors.pick_at_world(p, true);
|
|
4466
4623
|
}
|
|
4467
4624
|
/** World→local delta projection. The frame an element's position is
|
|
@@ -4477,6 +4634,7 @@ var SvgGeometryDriver = class {
|
|
|
4477
4634
|
* the local delta. Identity (→ delta unchanged) for flat frames,
|
|
4478
4635
|
* top-level nodes, and any degenerate / unavailable matrix. */
|
|
4479
4636
|
world_delta_to_local(id, delta) {
|
|
4637
|
+
this.accessors.flush();
|
|
4480
4638
|
const parent = this.accessors.element_for(id)?.parentNode;
|
|
4481
4639
|
const root = this.accessors.root();
|
|
4482
4640
|
if (!(parent instanceof SVGGraphicsElement) || !root) return delta;
|
|
@@ -4547,6 +4705,12 @@ Object.defineProperty(exports, "attach_dom_surface", {
|
|
|
4547
4705
|
return attach_dom_surface;
|
|
4548
4706
|
}
|
|
4549
4707
|
});
|
|
4708
|
+
Object.defineProperty(exports, "install_font_load_geometry_bump", {
|
|
4709
|
+
enumerable: true,
|
|
4710
|
+
get: function() {
|
|
4711
|
+
return install_font_load_geometry_bump;
|
|
4712
|
+
}
|
|
4713
|
+
});
|
|
4550
4714
|
Object.defineProperty(exports, "inverse_project_rect", {
|
|
4551
4715
|
enumerable: true,
|
|
4552
4716
|
get: function() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as SvgEditor, p as Gestures, s as SurfaceHandle, w as Camera } from "./editor-
|
|
1
|
+
import { c as SvgEditor, p as Gestures, s as SurfaceHandle, w as Camera } from "./editor-KqpIW1qm.mjs";
|
|
2
2
|
import cmath from "@grida/cmath";
|
|
3
3
|
import { guide } from "@grida/cmath/_snap";
|
|
4
4
|
|
|
@@ -13,6 +13,35 @@ type SnapOptions = {
|
|
|
13
13
|
declare const DEFAULT_SNAP_OPTIONS: SnapOptions;
|
|
14
14
|
//#endregion
|
|
15
15
|
//#region src/dom.d.ts
|
|
16
|
+
/**
|
|
17
|
+
* Wire a web-font settle source to the editor's geometry channel.
|
|
18
|
+
*
|
|
19
|
+
* The DOM surface re-serializes the `<svg>` on every editor tick, but a
|
|
20
|
+
* `<text>` / `<tspan>` bbox can change with NO attribute write: a web font
|
|
21
|
+
* finishing load AFTER its `font-family` / `font-size` was already written.
|
|
22
|
+
* The IR never sees that reflow, so nothing bumps `geometry_version` and
|
|
23
|
+
* every bounds-keyed consumer (snap, HUD chrome, size meter) stays stuck at
|
|
24
|
+
* the fallback-face metrics until the next real edit.
|
|
25
|
+
*
|
|
26
|
+
* Listens for `loadingdone` on `source` (a `FontFaceSet`, or any injected
|
|
27
|
+
* `EventTarget` in tests) and calls `bump` once per settle. COARSE on
|
|
28
|
+
* purpose: one bump clears the WHOLE bounds cache, not just text nodes —
|
|
29
|
+
* consistent with the package's pessimistic-invalidation stance, and far
|
|
30
|
+
* cheaper than scoping the bump to the (possibly many) reflowed runs.
|
|
31
|
+
*
|
|
32
|
+
* Also bumps once when `source.ready` resolves (when present): fonts that
|
|
33
|
+
* settled before attach — a cache hit, or `font-display` resolving the same
|
|
34
|
+
* tick the surface mounts — never re-fire `loadingdone`, so a document
|
|
35
|
+
* mounted post-settle still needs one bump to re-read at the real metrics.
|
|
36
|
+
*
|
|
37
|
+
* Returns a teardown that removes the listener and neutralizes the pending
|
|
38
|
+
* `ready` bump (leak guard) — call it on surface detach.
|
|
39
|
+
*
|
|
40
|
+
* Factored out of the surface so it can be unit-tested with a fake
|
|
41
|
+
* `EventTarget` in the node-only test env (jsdom's `FontFaceSet` is
|
|
42
|
+
* incomplete); never a real font / network.
|
|
43
|
+
*/
|
|
44
|
+
declare function install_font_load_geometry_bump(source: EventTarget | null, bump: () => void): () => void;
|
|
16
45
|
type DomSurfaceOptions = {
|
|
17
46
|
/** Mount the SVG inside this container. */container: HTMLElement;
|
|
18
47
|
/**
|
|
@@ -21,6 +50,18 @@ type DomSurfaceOptions = {
|
|
|
21
50
|
* carte via `handle.gestures.bind(...)`.
|
|
22
51
|
*/
|
|
23
52
|
gestures?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Wire native ClipboardEvent transport — `copy` / `cut` / `paste`
|
|
55
|
+
* listeners on the owner document, gated by the clipboard attention
|
|
56
|
+
* discipline. Default `true`. Pass `false` to route ALL clipboard
|
|
57
|
+
* traffic through the `ClipboardProvider` seam instead (the
|
|
58
|
+
* configuration under which a host's paste-time screening governs
|
|
59
|
+
* every path) — see docs/wg/feat-svg-editor/clipboard.md §Transport
|
|
60
|
+
* "Host control over the native path". Focus management (the container
|
|
61
|
+
* focusing on pointerdown) stays either way — it is a general canvas
|
|
62
|
+
* mitigation, not a clipboard feature.
|
|
63
|
+
*/
|
|
64
|
+
clipboard?: boolean;
|
|
24
65
|
/**
|
|
25
66
|
* Auto-fit the document into the viewport on initial attach. Default
|
|
26
67
|
* `false`. Mirrors Excalidraw's `initialData.scrollToContent`.
|
|
@@ -33,6 +74,19 @@ type DomSurfaceOptions = {
|
|
|
33
74
|
* when `fit: true`.
|
|
34
75
|
*/
|
|
35
76
|
initial_camera?: cmath.Transform;
|
|
77
|
+
/**
|
|
78
|
+
* Font-load settle source — the `EventTarget` whose `loadingdone` event
|
|
79
|
+
* signals "web fonts finished loading, text may have reflowed." Defaults
|
|
80
|
+
* to `container.ownerDocument.fonts` (the live `FontFaceSet`). The
|
|
81
|
+
* surface installs a `loadingdone` listener that advances the editor's
|
|
82
|
+
* geometry channel so text bounds re-read at the settled glyph metrics
|
|
83
|
+
* (see ../docs/geometry.md §Limitations "Text bbox depends on font").
|
|
84
|
+
*
|
|
85
|
+
* Injectable as a DOM seam: jsdom's `FontFaceSet` is incomplete, so
|
|
86
|
+
* tests pass a plain `EventTarget` stub and `dispatchEvent(new Event(
|
|
87
|
+
* "loadingdone"))` to simulate a settle without a real font / network.
|
|
88
|
+
*/
|
|
89
|
+
font_load_source?: EventTarget;
|
|
36
90
|
};
|
|
37
91
|
/**
|
|
38
92
|
* Surface handle for the DOM surface. Extends the editor's core
|
|
@@ -111,4 +165,4 @@ declare function inverse_project_rect(rect: {
|
|
|
111
165
|
f: number;
|
|
112
166
|
}, offset: readonly [number, number]): cmath.Rectangle | null;
|
|
113
167
|
//#endregion
|
|
114
|
-
export {
|
|
168
|
+
export { inverse_project_rect as a, DEFAULT_SNAP_OPTIONS as c, install_font_load_geometry_bump as i, SnapOptions as l, DomSurfaceOptions as n, project_delta_inverse_ctm as o, attach_dom_surface as r, project_point_through_ctm as s, DomSurfaceHandle as t };
|
package/dist/dom.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { C as BoundsResolver, E as CameraOptions, T as CameraConstraints, d as GestureContext, f as GestureId, g as MemoizedGeometryProvider, h as GeometrySignals, i as DomComputedResolver, m as GeometryProvider, p as Gestures, r as DomComputedPaint, u as GestureBinding, w as Camera } from "./editor-
|
|
2
|
-
import { a as
|
|
3
|
-
export { type BoundsResolver, Camera, type CameraConstraints, type CameraOptions, DEFAULT_SNAP_OPTIONS, type DomComputedPaint, type DomComputedResolver, DomSurfaceHandle, DomSurfaceOptions, type GeometryProvider, type GeometrySignals, type GestureBinding, type GestureContext, type GestureId, Gestures, MemoizedGeometryProvider, type SnapOptions, attach_dom_surface, inverse_project_rect, project_delta_inverse_ctm, project_point_through_ctm };
|
|
1
|
+
import { C as BoundsResolver, E as CameraOptions, T as CameraConstraints, d as GestureContext, f as GestureId, g as MemoizedGeometryProvider, h as GeometrySignals, i as DomComputedResolver, m as GeometryProvider, p as Gestures, r as DomComputedPaint, u as GestureBinding, w as Camera } from "./editor-KqpIW1qm.mjs";
|
|
2
|
+
import { a as inverse_project_rect, c as DEFAULT_SNAP_OPTIONS, i as install_font_load_geometry_bump, l as SnapOptions, n as DomSurfaceOptions, o as project_delta_inverse_ctm, r as attach_dom_surface, s as project_point_through_ctm, t as DomSurfaceHandle } from "./dom-TctdgRnn.mjs";
|
|
3
|
+
export { type BoundsResolver, Camera, type CameraConstraints, type CameraOptions, DEFAULT_SNAP_OPTIONS, type DomComputedPaint, type DomComputedResolver, DomSurfaceHandle, DomSurfaceOptions, type GeometryProvider, type GeometrySignals, type GestureBinding, type GestureContext, type GestureId, Gestures, MemoizedGeometryProvider, type SnapOptions, attach_dom_surface, install_font_load_geometry_bump, inverse_project_rect, project_delta_inverse_ctm, project_point_through_ctm };
|
package/dist/dom.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { C as BoundsResolver, E as CameraOptions, T as CameraConstraints, d as GestureContext, f as GestureId, g as MemoizedGeometryProvider, h as GeometrySignals, i as DomComputedResolver, m as GeometryProvider, p as Gestures, r as DomComputedPaint, u as GestureBinding, w as Camera } from "./editor-
|
|
2
|
-
import { a as
|
|
3
|
-
export { type BoundsResolver, Camera, type CameraConstraints, type CameraOptions, DEFAULT_SNAP_OPTIONS, type DomComputedPaint, type DomComputedResolver, DomSurfaceHandle, DomSurfaceOptions, type GeometryProvider, type GeometrySignals, type GestureBinding, type GestureContext, type GestureId, Gestures, MemoizedGeometryProvider, type SnapOptions, attach_dom_surface, inverse_project_rect, project_delta_inverse_ctm, project_point_through_ctm };
|
|
1
|
+
import { C as BoundsResolver, E as CameraOptions, T as CameraConstraints, d as GestureContext, f as GestureId, g as MemoizedGeometryProvider, h as GeometrySignals, i as DomComputedResolver, m as GeometryProvider, p as Gestures, r as DomComputedPaint, u as GestureBinding, w as Camera } from "./editor-BSxTUsW_.js";
|
|
2
|
+
import { a as inverse_project_rect, c as DEFAULT_SNAP_OPTIONS, i as install_font_load_geometry_bump, l as SnapOptions, n as DomSurfaceOptions, o as project_delta_inverse_ctm, r as attach_dom_surface, s as project_point_through_ctm, t as DomSurfaceHandle } from "./dom-BMzX1CXZ.js";
|
|
3
|
+
export { type BoundsResolver, Camera, type CameraConstraints, type CameraOptions, DEFAULT_SNAP_OPTIONS, type DomComputedPaint, type DomComputedResolver, DomSurfaceHandle, DomSurfaceOptions, type GeometryProvider, type GeometrySignals, type GestureBinding, type GestureContext, type GestureId, Gestures, MemoizedGeometryProvider, type SnapOptions, attach_dom_surface, install_font_load_geometry_bump, inverse_project_rect, project_delta_inverse_ctm, project_point_through_ctm };
|
package/dist/dom.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_dom = require("./dom-
|
|
2
|
+
const require_dom = require("./dom-CaByuo6C.js");
|
|
3
3
|
exports.Camera = require_dom.Camera;
|
|
4
4
|
exports.DEFAULT_SNAP_OPTIONS = require_dom.DEFAULT_SNAP_OPTIONS;
|
|
5
5
|
exports.Gestures = require_dom.Gestures;
|
|
6
6
|
exports.MemoizedGeometryProvider = require_dom.MemoizedGeometryProvider;
|
|
7
7
|
exports.attach_dom_surface = require_dom.attach_dom_surface;
|
|
8
|
+
exports.install_font_load_geometry_bump = require_dom.install_font_load_geometry_bump;
|
|
8
9
|
exports.inverse_project_rect = require_dom.inverse_project_rect;
|
|
9
10
|
exports.project_delta_inverse_ctm = require_dom.project_delta_inverse_ctm;
|
|
10
11
|
exports.project_point_through_ctm = require_dom.project_point_through_ctm;
|
package/dist/dom.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { Camera, DEFAULT_SNAP_OPTIONS, Gestures, MemoizedGeometryProvider, attach_dom_surface, inverse_project_rect, project_delta_inverse_ctm, project_point_through_ctm };
|
|
1
|
+
import { a as project_point_through_ctm, c as MemoizedGeometryProvider, i as project_delta_inverse_ctm, l as Camera, n as install_font_load_geometry_bump, o as Gestures, r as inverse_project_rect, s as DEFAULT_SNAP_OPTIONS, t as attach_dom_surface } from "./dom-Bjj9xySE.mjs";
|
|
2
|
+
export { Camera, DEFAULT_SNAP_OPTIONS, Gestures, MemoizedGeometryProvider, attach_dom_surface, install_font_load_geometry_bump, inverse_project_rect, project_delta_inverse_ctm, project_point_through_ctm };
|