@grida/svg-editor 1.0.0-alpha.15 → 1.0.0-alpha.16
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 +36 -0
- package/dist/{dom-CK6GlgFF.d.mts → dom-98AUOfsP.d.mts} +44 -2
- package/dist/{dom-CsKXTaNw.d.ts → dom-BO2-E9oK.d.ts} +44 -2
- package/dist/{dom-DILY80j7.mjs → dom-DOvcMvl4.mjs} +67 -2
- package/dist/{dom-Dee6FtgZ.js → dom-U6ae5fQF.js} +72 -1
- 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-F8ckj9X1.js → editor-C6Lj1In-.js} +416 -865
- package/dist/{editor-BKoo9SPL.d.ts → editor-CYoGJ3Hf.d.ts} +311 -5
- package/dist/{editor-Dl7c0q5A.d.mts → editor-D2eQe8lB.d.mts} +311 -5
- package/dist/{editor-CvWpD5mu.mjs → editor-DKQOIKuU.mjs} +415 -865
- 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-CJ1Ctq14.js → model-D0nU_EkL.js} +1176 -62
- package/dist/{model-B2UWgViT.mjs → model-L3t9ixT_.mjs} +1171 -63
- 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 +20 -3
- package/dist/react.d.ts +20 -3
- package/dist/react.js +25 -2
- package/dist/react.mjs +25 -3
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -304,6 +304,25 @@ editor.subscribe_with_selector<T>(
|
|
|
304
304
|
|
|
305
305
|
`state` is a frozen snapshot. Consumers never destructure into internals; if a view they need isn't here or in the purpose-built views below, that's an API gap.
|
|
306
306
|
|
|
307
|
+
### Observation — pick (tap)
|
|
308
|
+
|
|
309
|
+
A **pick** is a discrete tap on the canvas — a press and release within the drag threshold, no drag. It is observe-only and deliberately **separate from selection**: selection answers "what do commands target," a pick answers "what did the user just click, and where." A primary tap on a node both selects it _and_ emits a pick; a tap on empty canvas emits a pick with `node_id: null` (distinguishable from "nothing is selected," which selection alone cannot express); a secondary (right-button) tap emits a pick and does **not** change selection. This is the seam a click-driven host tool needs — a comment / annotation tool anchors UI at `point` and scopes its action to `node_id`, or to the whole document when `null`.
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
type PickEvent = {
|
|
313
|
+
point: Vec2; // document-space — the pointer-DOWN point the tap resolved against
|
|
314
|
+
node_id: NodeId | null; // topmost node under point; null = empty canvas
|
|
315
|
+
button: "primary" | "secondary"; // middle is pan, never taps
|
|
316
|
+
mods: { shift: boolean; alt: boolean; meta: boolean; ctrl: boolean };
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
editor.subscribe_pick(fn: (e: PickEvent) => void): Unsubscribe;
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
The point is document-space and always the pointer-**down** point — so it stays correct even for a tap on an already-selected node (whose selection commits on pointer-up). The channel does **not** bump `state.version`. In React, wire it with `useEditorPick(handler)`.
|
|
323
|
+
|
|
324
|
+
> **Status:** `@unstable` — shipped against one consumer; the shape is open until a second click-driven tool exercises it (P6).
|
|
325
|
+
|
|
307
326
|
### Observation — properties
|
|
308
327
|
|
|
309
328
|
This section is about **property semantics on a single node**, following the CSS / SVG spec. Multi-selection ("mixed values") is a separate concern; see the [Multi-selection](#multi-selection-mixed-values) section below. The two are kept apart on purpose: property semantics is defined by the spec; mixed semantics is an aggregation layer the editor adds because it supports multi-select.
|
|
@@ -608,6 +627,8 @@ editor.commands.{
|
|
|
608
627
|
resize_to(target: { width: number; height: number; anchor?: ResizeAnchor }): void;
|
|
609
628
|
rotate(args: { angle: number; pivot?: { x: number; y: number } }): void;
|
|
610
629
|
rotate_to(args: { angle: number; pivot?: { x: number; y: number } }): void;
|
|
630
|
+
// `matrix` is SVG `matrix(a b c d e f)` order (the `Matrix2D` tuple).
|
|
631
|
+
transform(matrix: Matrix2D, opts?: { ids?: NodeId[]; pivot?: { x: number; y: number } }): boolean;
|
|
611
632
|
flatten_transform(): void; // bake `transform=` into native attrs where possible
|
|
612
633
|
|
|
613
634
|
// alignment (operates on selection of ≥2 nodes against their union bbox)
|
|
@@ -616,12 +637,24 @@ editor.commands.{
|
|
|
616
637
|
// structure
|
|
617
638
|
reorder(direction: "bring_forward" | "send_backward" | "bring_to_front" | "send_to_back"): void;
|
|
618
639
|
group(): void; // wrap selection in a new <g>
|
|
640
|
+
ungroup(opts?: { id?: NodeId }): boolean; // dissolve a plain structural <g>
|
|
641
|
+
// (clean-structural subset only; refuses
|
|
642
|
+
// groups with visual state — see TODO §10)
|
|
619
643
|
remove(): void;
|
|
620
644
|
|
|
621
645
|
// insertion — `tag` is an open string (so paste / RPC can create any element,
|
|
622
646
|
// e.g. "path"); only the closed `InsertableTag` set gets a pointer-driven
|
|
623
647
|
// draw gesture and default paint.
|
|
624
648
|
insert(tag: string, attrs?: Readonly<Record<string, string>>): NodeId;
|
|
649
|
+
// markup-shaped sibling of `insert` — one or more sibling elements, or a
|
|
650
|
+
// full `<svg>` doc (the shell is discarded; its children are the content).
|
|
651
|
+
// Subtrees adopted verbatim; ONE history step; returns root ids in
|
|
652
|
+
// document order. Authored ids are NEVER rewritten (dedup is Tidy's job);
|
|
653
|
+
// undeclared `xlink:` / shell-declared prefixes are hoisted onto the root
|
|
654
|
+
// in the same step. Position is authored content: wrap the fragment in
|
|
655
|
+
// `<g transform="translate(x y)">` to land it at a point — same single
|
|
656
|
+
// undo step, no placement opt.
|
|
657
|
+
insert_fragment(svg: string, opts?: { parent?: NodeId; index?: number; select?: boolean }): NodeId[];
|
|
625
658
|
insert_preview(tag: string, initial?: Readonly<Record<string, string>>): InsertPreviewSession;
|
|
626
659
|
|
|
627
660
|
// content
|
|
@@ -643,6 +676,8 @@ editor.commands.{
|
|
|
643
676
|
|
|
644
677
|
All commands operate on `state.selection` unless they take an explicit target. Commands that can't apply (e.g. `set_text` with no text node selected) are no-ops, not errors.
|
|
645
678
|
|
|
679
|
+
`transform` composes a general 2×3 affine onto the selection **relative** and **in world space about a pivot** (default: the selection union-bbox center) — `E = T(pivot) · matrix · T(-pivot)` — so a bare `[-1, 0, 0, 1, 0, 0]` is an in-place horizontal flip and `[1, 0, 0, -1, 0, 0]` a vertical one. The editor owns the round-trip: `E` is folded onto each member's transform list as a **single leading `matrix` op** (existing `rotate`/`translate` tokens are preserved after it; repeated applies collapse into one matrix; a net-identity leading matrix is dropped). It refuses (returns `false`, no history) on empty selection, no attached surface, or any member that isn't rotatable (matrix / scale / skew / `<text rotate>` / CSS-property / animated transforms — same gate as `rotate`; Flatten Transform is the recovery path). Flat-doc only: nested transformed ancestors are out of scope.
|
|
680
|
+
|
|
646
681
|
(Naming convention for the API surface is `snake_case` to match the SVG / CSS property naming the editor already echoes — `set_property("stroke-width", …)` reads cleanly next to `set_paint("fill", …)`. JavaScript identifiers use `snake_case`; user-facing strings that mirror SVG attribute names stay `kebab-case` exactly as the spec writes them.)
|
|
647
682
|
|
|
648
683
|
### Providers
|
|
@@ -880,6 +915,7 @@ What this editor will never be. Each one is a defensive perimeter for the princi
|
|
|
880
915
|
- **Not customizable in HUD layout.** Style spec only — no overlay slots, no handle replacement, no custom chrome components.
|
|
881
916
|
- **Not a private IR.** SVG is the source of truth. The editor does not maintain an alternative on-disk format, and the bytes are not projected from any in-memory canonical store. (The internal typed element IR described under [Paradigm § Element IR (internal)](#element-ir-internal) is a typed view over the parsed AST, not a store the file is derived from — the AST and the file are the source of truth, and the IR is rebuilt from them on each load.)
|
|
882
917
|
- **Not a serializer playground.** Round-trip rules are fixed (P1). No "compact mode," no "Prettier mode," no consumer-supplied formatter.
|
|
918
|
+
- **Not an input-interception hook.** The pick/tap observation (`subscribe_pick`) reports a click that already happened; it cannot prevent, delay, or replace the editor's own selection and gesture handling. A host that needs to intercept input owns the container and splices its own layer in (the DOM escape hatch) — it does not get a veto through the observation surface.
|
|
883
919
|
|
|
884
920
|
If a consumer needs any of the above, the right answer is "this is the wrong tool." Saying yes to any one is the path that turned the Grida main editor into a 6,800-line god-class.
|
|
885
921
|
|
|
@@ -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-D2eQe8lB.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
|
/**
|
|
@@ -33,6 +62,19 @@ type DomSurfaceOptions = {
|
|
|
33
62
|
* when `fit: true`.
|
|
34
63
|
*/
|
|
35
64
|
initial_camera?: cmath.Transform;
|
|
65
|
+
/**
|
|
66
|
+
* Font-load settle source — the `EventTarget` whose `loadingdone` event
|
|
67
|
+
* signals "web fonts finished loading, text may have reflowed." Defaults
|
|
68
|
+
* to `container.ownerDocument.fonts` (the live `FontFaceSet`). The
|
|
69
|
+
* surface installs a `loadingdone` listener that advances the editor's
|
|
70
|
+
* geometry channel so text bounds re-read at the settled glyph metrics
|
|
71
|
+
* (see ../docs/geometry.md §Limitations "Text bbox depends on font").
|
|
72
|
+
*
|
|
73
|
+
* Injectable as a DOM seam: jsdom's `FontFaceSet` is incomplete, so
|
|
74
|
+
* tests pass a plain `EventTarget` stub and `dispatchEvent(new Event(
|
|
75
|
+
* "loadingdone"))` to simulate a settle without a real font / network.
|
|
76
|
+
*/
|
|
77
|
+
font_load_source?: EventTarget;
|
|
36
78
|
};
|
|
37
79
|
/**
|
|
38
80
|
* Surface handle for the DOM surface. Extends the editor's core
|
|
@@ -111,4 +153,4 @@ declare function inverse_project_rect(rect: {
|
|
|
111
153
|
f: number;
|
|
112
154
|
}, offset: readonly [number, number]): cmath.Rectangle | null;
|
|
113
155
|
//#endregion
|
|
114
|
-
export {
|
|
156
|
+
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 };
|
|
@@ -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-CYoGJ3Hf.js";
|
|
2
2
|
import cmath from "@grida/cmath";
|
|
3
3
|
//#region src/core/snap/options.d.ts
|
|
4
4
|
type SnapOptions = {
|
|
@@ -11,6 +11,35 @@ type SnapOptions = {
|
|
|
11
11
|
declare const DEFAULT_SNAP_OPTIONS: SnapOptions;
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region src/dom.d.ts
|
|
14
|
+
/**
|
|
15
|
+
* Wire a web-font settle source to the editor's geometry channel.
|
|
16
|
+
*
|
|
17
|
+
* The DOM surface re-serializes the `<svg>` on every editor tick, but a
|
|
18
|
+
* `<text>` / `<tspan>` bbox can change with NO attribute write: a web font
|
|
19
|
+
* finishing load AFTER its `font-family` / `font-size` was already written.
|
|
20
|
+
* The IR never sees that reflow, so nothing bumps `geometry_version` and
|
|
21
|
+
* every bounds-keyed consumer (snap, HUD chrome, size meter) stays stuck at
|
|
22
|
+
* the fallback-face metrics until the next real edit.
|
|
23
|
+
*
|
|
24
|
+
* Listens for `loadingdone` on `source` (a `FontFaceSet`, or any injected
|
|
25
|
+
* `EventTarget` in tests) and calls `bump` once per settle. COARSE on
|
|
26
|
+
* purpose: one bump clears the WHOLE bounds cache, not just text nodes —
|
|
27
|
+
* consistent with the package's pessimistic-invalidation stance, and far
|
|
28
|
+
* cheaper than scoping the bump to the (possibly many) reflowed runs.
|
|
29
|
+
*
|
|
30
|
+
* Also bumps once when `source.ready` resolves (when present): fonts that
|
|
31
|
+
* settled before attach — a cache hit, or `font-display` resolving the same
|
|
32
|
+
* tick the surface mounts — never re-fire `loadingdone`, so a document
|
|
33
|
+
* mounted post-settle still needs one bump to re-read at the real metrics.
|
|
34
|
+
*
|
|
35
|
+
* Returns a teardown that removes the listener and neutralizes the pending
|
|
36
|
+
* `ready` bump (leak guard) — call it on surface detach.
|
|
37
|
+
*
|
|
38
|
+
* Factored out of the surface so it can be unit-tested with a fake
|
|
39
|
+
* `EventTarget` in the node-only test env (jsdom's `FontFaceSet` is
|
|
40
|
+
* incomplete); never a real font / network.
|
|
41
|
+
*/
|
|
42
|
+
declare function install_font_load_geometry_bump(source: EventTarget | null, bump: () => void): () => void;
|
|
14
43
|
type DomSurfaceOptions = {
|
|
15
44
|
/** Mount the SVG inside this container. */container: HTMLElement;
|
|
16
45
|
/**
|
|
@@ -31,6 +60,19 @@ type DomSurfaceOptions = {
|
|
|
31
60
|
* when `fit: true`.
|
|
32
61
|
*/
|
|
33
62
|
initial_camera?: cmath.Transform;
|
|
63
|
+
/**
|
|
64
|
+
* Font-load settle source — the `EventTarget` whose `loadingdone` event
|
|
65
|
+
* signals "web fonts finished loading, text may have reflowed." Defaults
|
|
66
|
+
* to `container.ownerDocument.fonts` (the live `FontFaceSet`). The
|
|
67
|
+
* surface installs a `loadingdone` listener that advances the editor's
|
|
68
|
+
* geometry channel so text bounds re-read at the settled glyph metrics
|
|
69
|
+
* (see ../docs/geometry.md §Limitations "Text bbox depends on font").
|
|
70
|
+
*
|
|
71
|
+
* Injectable as a DOM seam: jsdom's `FontFaceSet` is incomplete, so
|
|
72
|
+
* tests pass a plain `EventTarget` stub and `dispatchEvent(new Event(
|
|
73
|
+
* "loadingdone"))` to simulate a settle without a real font / network.
|
|
74
|
+
*/
|
|
75
|
+
font_load_source?: EventTarget;
|
|
34
76
|
};
|
|
35
77
|
/**
|
|
36
78
|
* Surface handle for the DOM surface. Extends the editor's core
|
|
@@ -109,4 +151,4 @@ declare function inverse_project_rect(rect: {
|
|
|
109
151
|
f: number;
|
|
110
152
|
}, offset: readonly [number, number]): cmath.Rectangle | null;
|
|
111
153
|
//#endregion
|
|
112
|
-
export {
|
|
154
|
+
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 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as paint, b as is_text_input_focused, c as hit_shape_svg, d as NudgeDwellWatcher, f as TranslateOrchestrator, h as transform, i as TOOL_CURSOR, l as RotateOrchestrator, m as group, n as insertions, o as ResizeOrchestrator, s as resize_pipeline, t as PathModel, y as array_shallow_equal } from "./model-L3t9ixT_.mjs";
|
|
2
2
|
import cmath from "@grida/cmath";
|
|
3
3
|
import { svg_parse } from "@grida/svg/parse";
|
|
4
4
|
import { SVGShapes } from "@grida/svg/pathdata";
|
|
@@ -1638,6 +1638,48 @@ const IS_MODIFIER_KEY = {
|
|
|
1638
1638
|
* live `<text>` element out from under the about-to-mount text surface. */
|
|
1639
1639
|
const TEXT_EDIT_PENDING = { __pending: true };
|
|
1640
1640
|
/**
|
|
1641
|
+
* Wire a web-font settle source to the editor's geometry channel.
|
|
1642
|
+
*
|
|
1643
|
+
* The DOM surface re-serializes the `<svg>` on every editor tick, but a
|
|
1644
|
+
* `<text>` / `<tspan>` bbox can change with NO attribute write: a web font
|
|
1645
|
+
* finishing load AFTER its `font-family` / `font-size` was already written.
|
|
1646
|
+
* The IR never sees that reflow, so nothing bumps `geometry_version` and
|
|
1647
|
+
* every bounds-keyed consumer (snap, HUD chrome, size meter) stays stuck at
|
|
1648
|
+
* the fallback-face metrics until the next real edit.
|
|
1649
|
+
*
|
|
1650
|
+
* Listens for `loadingdone` on `source` (a `FontFaceSet`, or any injected
|
|
1651
|
+
* `EventTarget` in tests) and calls `bump` once per settle. COARSE on
|
|
1652
|
+
* purpose: one bump clears the WHOLE bounds cache, not just text nodes —
|
|
1653
|
+
* consistent with the package's pessimistic-invalidation stance, and far
|
|
1654
|
+
* cheaper than scoping the bump to the (possibly many) reflowed runs.
|
|
1655
|
+
*
|
|
1656
|
+
* Also bumps once when `source.ready` resolves (when present): fonts that
|
|
1657
|
+
* settled before attach — a cache hit, or `font-display` resolving the same
|
|
1658
|
+
* tick the surface mounts — never re-fire `loadingdone`, so a document
|
|
1659
|
+
* mounted post-settle still needs one bump to re-read at the real metrics.
|
|
1660
|
+
*
|
|
1661
|
+
* Returns a teardown that removes the listener and neutralizes the pending
|
|
1662
|
+
* `ready` bump (leak guard) — call it on surface detach.
|
|
1663
|
+
*
|
|
1664
|
+
* Factored out of the surface so it can be unit-tested with a fake
|
|
1665
|
+
* `EventTarget` in the node-only test env (jsdom's `FontFaceSet` is
|
|
1666
|
+
* incomplete); never a real font / network.
|
|
1667
|
+
*/
|
|
1668
|
+
function install_font_load_geometry_bump(source, bump) {
|
|
1669
|
+
if (!source) return () => {};
|
|
1670
|
+
const on_fonts_settled = () => bump();
|
|
1671
|
+
source.addEventListener("loadingdone", on_fonts_settled);
|
|
1672
|
+
let alive = true;
|
|
1673
|
+
const ready = source.ready;
|
|
1674
|
+
if (ready && typeof ready.then === "function") ready.then(() => {
|
|
1675
|
+
if (alive) bump();
|
|
1676
|
+
});
|
|
1677
|
+
return () => {
|
|
1678
|
+
alive = false;
|
|
1679
|
+
source.removeEventListener("loadingdone", on_fonts_settled);
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1641
1683
|
* Attach a DOM surface to a headless editor. Returns a `DomSurfaceHandle`
|
|
1642
1684
|
* whose `detach()` is the inverse — DOM cleared, listeners removed,
|
|
1643
1685
|
* gestures uninstalled.
|
|
@@ -1777,6 +1819,7 @@ var DomSurface = class DomSurface {
|
|
|
1777
1819
|
shapeOf: (id) => this.shape_of(id),
|
|
1778
1820
|
vectorOf: (id) => this.vector_of(id),
|
|
1779
1821
|
onIntent: (i) => this.commit_intent(i),
|
|
1822
|
+
onTap: (t) => this.handle_tap(t),
|
|
1780
1823
|
style: {
|
|
1781
1824
|
chromeColor: editor.style.chrome_color,
|
|
1782
1825
|
showRotationHandles: true
|
|
@@ -1883,6 +1926,8 @@ var DomSurface = class DomSurface {
|
|
|
1883
1926
|
win.addEventListener("resize", fn);
|
|
1884
1927
|
this.teardown.push(() => win.removeEventListener("resize", fn));
|
|
1885
1928
|
}
|
|
1929
|
+
const detach_font_listener = install_font_load_geometry_bump(options.font_load_source ?? container.ownerDocument.fonts ?? null, () => editor._internal.bump_geometry());
|
|
1930
|
+
this.teardown.push(detach_font_listener);
|
|
1886
1931
|
this.wire_events();
|
|
1887
1932
|
const internal = editor._internal;
|
|
1888
1933
|
this.editor_hover_internal = internal;
|
|
@@ -2966,6 +3011,26 @@ var DomSurface = class DomSurface {
|
|
|
2966
3011
|
if (this.editor.keymap.claims(e)) e.preventDefault();
|
|
2967
3012
|
this.editor.keymap.dispatch(e);
|
|
2968
3013
|
}
|
|
3014
|
+
/**
|
|
3015
|
+
* Re-express a HUD tap as an editor {@link PickEvent} and fan it out on the
|
|
3016
|
+
* editor's pick channel. The HUD already resolved everything that matters —
|
|
3017
|
+
* the pointer-down point, the hit node, and click-vs-drag — so this is a
|
|
3018
|
+
* pure translation (HUD `[x, y]` tuple → editor `{ x, y }` doc-space point)
|
|
3019
|
+
* with NO re-hit-testing. Taking the hit from the HUD (not a fresh
|
|
3020
|
+
* `node_at_point`) guarantees the pick and the selection it accompanies can
|
|
3021
|
+
* never disagree. Observe-only: this mutates no editor state.
|
|
3022
|
+
*/
|
|
3023
|
+
handle_tap(tap) {
|
|
3024
|
+
this.editor._internal.push_pick({
|
|
3025
|
+
point: {
|
|
3026
|
+
x: tap.point[0],
|
|
3027
|
+
y: tap.point[1]
|
|
3028
|
+
},
|
|
3029
|
+
node_id: tap.hit,
|
|
3030
|
+
button: tap.button,
|
|
3031
|
+
mods: tap.mods
|
|
3032
|
+
});
|
|
3033
|
+
}
|
|
2969
3034
|
commit_intent(intent) {
|
|
2970
3035
|
switch (intent.kind) {
|
|
2971
3036
|
case "select":
|
|
@@ -4515,4 +4580,4 @@ var SvgHitShapeDriver = class {
|
|
|
4515
4580
|
}
|
|
4516
4581
|
};
|
|
4517
4582
|
//#endregion
|
|
4518
|
-
export {
|
|
4583
|
+
export { project_point_through_ctm as a, MemoizedGeometryProvider as c, project_delta_inverse_ctm as i, Camera as l, install_font_load_geometry_bump as n, Gestures as o, inverse_project_rect as r, DEFAULT_SNAP_OPTIONS as s, attach_dom_surface as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_model = require("./model-
|
|
1
|
+
const require_model = require("./model-D0nU_EkL.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");
|
|
@@ -1640,6 +1640,48 @@ const IS_MODIFIER_KEY = {
|
|
|
1640
1640
|
* live `<text>` element out from under the about-to-mount text surface. */
|
|
1641
1641
|
const TEXT_EDIT_PENDING = { __pending: true };
|
|
1642
1642
|
/**
|
|
1643
|
+
* Wire a web-font settle source to the editor's geometry channel.
|
|
1644
|
+
*
|
|
1645
|
+
* The DOM surface re-serializes the `<svg>` on every editor tick, but a
|
|
1646
|
+
* `<text>` / `<tspan>` bbox can change with NO attribute write: a web font
|
|
1647
|
+
* finishing load AFTER its `font-family` / `font-size` was already written.
|
|
1648
|
+
* The IR never sees that reflow, so nothing bumps `geometry_version` and
|
|
1649
|
+
* every bounds-keyed consumer (snap, HUD chrome, size meter) stays stuck at
|
|
1650
|
+
* the fallback-face metrics until the next real edit.
|
|
1651
|
+
*
|
|
1652
|
+
* Listens for `loadingdone` on `source` (a `FontFaceSet`, or any injected
|
|
1653
|
+
* `EventTarget` in tests) and calls `bump` once per settle. COARSE on
|
|
1654
|
+
* purpose: one bump clears the WHOLE bounds cache, not just text nodes —
|
|
1655
|
+
* consistent with the package's pessimistic-invalidation stance, and far
|
|
1656
|
+
* cheaper than scoping the bump to the (possibly many) reflowed runs.
|
|
1657
|
+
*
|
|
1658
|
+
* Also bumps once when `source.ready` resolves (when present): fonts that
|
|
1659
|
+
* settled before attach — a cache hit, or `font-display` resolving the same
|
|
1660
|
+
* tick the surface mounts — never re-fire `loadingdone`, so a document
|
|
1661
|
+
* mounted post-settle still needs one bump to re-read at the real metrics.
|
|
1662
|
+
*
|
|
1663
|
+
* Returns a teardown that removes the listener and neutralizes the pending
|
|
1664
|
+
* `ready` bump (leak guard) — call it on surface detach.
|
|
1665
|
+
*
|
|
1666
|
+
* Factored out of the surface so it can be unit-tested with a fake
|
|
1667
|
+
* `EventTarget` in the node-only test env (jsdom's `FontFaceSet` is
|
|
1668
|
+
* incomplete); never a real font / network.
|
|
1669
|
+
*/
|
|
1670
|
+
function install_font_load_geometry_bump(source, bump) {
|
|
1671
|
+
if (!source) return () => {};
|
|
1672
|
+
const on_fonts_settled = () => bump();
|
|
1673
|
+
source.addEventListener("loadingdone", on_fonts_settled);
|
|
1674
|
+
let alive = true;
|
|
1675
|
+
const ready = source.ready;
|
|
1676
|
+
if (ready && typeof ready.then === "function") ready.then(() => {
|
|
1677
|
+
if (alive) bump();
|
|
1678
|
+
});
|
|
1679
|
+
return () => {
|
|
1680
|
+
alive = false;
|
|
1681
|
+
source.removeEventListener("loadingdone", on_fonts_settled);
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1643
1685
|
* Attach a DOM surface to a headless editor. Returns a `DomSurfaceHandle`
|
|
1644
1686
|
* whose `detach()` is the inverse — DOM cleared, listeners removed,
|
|
1645
1687
|
* gestures uninstalled.
|
|
@@ -1779,6 +1821,7 @@ var DomSurface = class DomSurface {
|
|
|
1779
1821
|
shapeOf: (id) => this.shape_of(id),
|
|
1780
1822
|
vectorOf: (id) => this.vector_of(id),
|
|
1781
1823
|
onIntent: (i) => this.commit_intent(i),
|
|
1824
|
+
onTap: (t) => this.handle_tap(t),
|
|
1782
1825
|
style: {
|
|
1783
1826
|
chromeColor: editor.style.chrome_color,
|
|
1784
1827
|
showRotationHandles: true
|
|
@@ -1885,6 +1928,8 @@ var DomSurface = class DomSurface {
|
|
|
1885
1928
|
win.addEventListener("resize", fn);
|
|
1886
1929
|
this.teardown.push(() => win.removeEventListener("resize", fn));
|
|
1887
1930
|
}
|
|
1931
|
+
const detach_font_listener = install_font_load_geometry_bump(options.font_load_source ?? container.ownerDocument.fonts ?? null, () => editor._internal.bump_geometry());
|
|
1932
|
+
this.teardown.push(detach_font_listener);
|
|
1888
1933
|
this.wire_events();
|
|
1889
1934
|
const internal = editor._internal;
|
|
1890
1935
|
this.editor_hover_internal = internal;
|
|
@@ -2968,6 +3013,26 @@ var DomSurface = class DomSurface {
|
|
|
2968
3013
|
if (this.editor.keymap.claims(e)) e.preventDefault();
|
|
2969
3014
|
this.editor.keymap.dispatch(e);
|
|
2970
3015
|
}
|
|
3016
|
+
/**
|
|
3017
|
+
* Re-express a HUD tap as an editor {@link PickEvent} and fan it out on the
|
|
3018
|
+
* editor's pick channel. The HUD already resolved everything that matters —
|
|
3019
|
+
* the pointer-down point, the hit node, and click-vs-drag — so this is a
|
|
3020
|
+
* pure translation (HUD `[x, y]` tuple → editor `{ x, y }` doc-space point)
|
|
3021
|
+
* with NO re-hit-testing. Taking the hit from the HUD (not a fresh
|
|
3022
|
+
* `node_at_point`) guarantees the pick and the selection it accompanies can
|
|
3023
|
+
* never disagree. Observe-only: this mutates no editor state.
|
|
3024
|
+
*/
|
|
3025
|
+
handle_tap(tap) {
|
|
3026
|
+
this.editor._internal.push_pick({
|
|
3027
|
+
point: {
|
|
3028
|
+
x: tap.point[0],
|
|
3029
|
+
y: tap.point[1]
|
|
3030
|
+
},
|
|
3031
|
+
node_id: tap.hit,
|
|
3032
|
+
button: tap.button,
|
|
3033
|
+
mods: tap.mods
|
|
3034
|
+
});
|
|
3035
|
+
}
|
|
2971
3036
|
commit_intent(intent) {
|
|
2972
3037
|
switch (intent.kind) {
|
|
2973
3038
|
case "select":
|
|
@@ -4547,6 +4612,12 @@ Object.defineProperty(exports, "attach_dom_surface", {
|
|
|
4547
4612
|
return attach_dom_surface;
|
|
4548
4613
|
}
|
|
4549
4614
|
});
|
|
4615
|
+
Object.defineProperty(exports, "install_font_load_geometry_bump", {
|
|
4616
|
+
enumerable: true,
|
|
4617
|
+
get: function() {
|
|
4618
|
+
return install_font_load_geometry_bump;
|
|
4619
|
+
}
|
|
4620
|
+
});
|
|
4550
4621
|
Object.defineProperty(exports, "inverse_project_rect", {
|
|
4551
4622
|
enumerable: true,
|
|
4552
4623
|
get: function() {
|
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-D2eQe8lB.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-98AUOfsP.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-CYoGJ3Hf.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-BO2-E9oK.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-U6ae5fQF.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-DOvcMvl4.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 };
|