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

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.
Files changed (41) hide show
  1. package/README.md +365 -384
  2. package/dist/dom-BEjG2bSw.d.mts +320 -0
  3. package/dist/dom-DYD1BHTo.js +6050 -0
  4. package/dist/dom-Y2QR7QSi.d.ts +318 -0
  5. package/dist/dom-tM3Dr1EK.mjs +5971 -0
  6. package/dist/dom.d.mts +3 -2
  7. package/dist/dom.d.ts +3 -2
  8. package/dist/dom.js +13 -1
  9. package/dist/dom.mjs +2 -2
  10. package/dist/editor-CWNtt1vu.mjs +2998 -0
  11. package/dist/editor-CZgwg8BK.d.mts +2537 -0
  12. package/dist/editor-CesShk9o.js +3004 -0
  13. package/dist/editor-kFTYSb_8.d.ts +2537 -0
  14. package/dist/index.d.mts +2 -2
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.js +5 -2
  17. package/dist/index.mjs +3 -2
  18. package/dist/model-D0iEDcg3.mjs +5451 -0
  19. package/dist/model-tywvTGZv.js +5640 -0
  20. package/dist/presets.d.mts +17 -2
  21. package/dist/presets.d.ts +17 -2
  22. package/dist/presets.js +20 -15
  23. package/dist/presets.mjs +19 -15
  24. package/dist/react.d.mts +88 -10
  25. package/dist/react.d.ts +88 -10
  26. package/dist/react.js +169 -14
  27. package/dist/react.mjs +159 -16
  28. package/package.json +35 -9
  29. package/dist/dom-CmOu0HvI.mjs +0 -1623
  30. package/dist/dom-Cn-RtjRL.d.ts +0 -48
  31. package/dist/dom-CoVZzFqy.js +0 -1672
  32. package/dist/dom-DJnZhtOd.d.mts +0 -48
  33. package/dist/editor-CjK56cgb.mjs +0 -1823
  34. package/dist/editor-D2l_CDr0.d.ts +0 -818
  35. package/dist/editor-D2zZAyny.js +0 -1835
  36. package/dist/editor-Uu6dZX4y.d.mts +0 -818
  37. package/dist/index-CHiXYO9-.d.ts +0 -1
  38. package/dist/index-ThDLM4Am.d.mts +0 -1
  39. package/dist/paint-Cfiw4g_J.mjs +0 -477
  40. package/dist/paint-dDV-Trt9.js +0 -531
  41. /package/dist/{chunk-CfYAbeIz.mjs → chunk-D7D4PA-g.mjs} +0 -0
@@ -0,0 +1,318 @@
1
+ import { c as SvgEditor, p as Gestures, s as SurfaceHandle, w as Camera } from "./editor-kFTYSb_8.js";
2
+ import cmath from "@grida/cmath";
3
+ //#region src/util/attention.d.ts
4
+ /** The runtime handle returned by `create_attention_tracker`. */
5
+ interface AttentionTracker {
6
+ /**
7
+ * `true` iff focus is inside the attention scope (the container's
8
+ * subtree or a registered element's subtree) OR the pointer is
9
+ * currently over any element of the scope. See module doc for the
10
+ * rationale.
11
+ *
12
+ * Pure read; no DOM mutation. Cheap enough to call once per keydown.
13
+ */
14
+ is_attended(): boolean;
15
+ /**
16
+ * `true` iff focus is inside the attention scope — the focus arm of
17
+ * {@link is_attended} alone, WITHOUT the pointer-over arm.
18
+ *
19
+ * Exists for the native clipboard gate (`dom.ts`): pointer-over is a
20
+ * sufficient signal to claim a keystroke (worst case: a stolen scroll)
21
+ * but NOT a copy/cut/paste gesture (worst case: destroying what the
22
+ * user believed they copied, or routing a paste meant for a host text
23
+ * field into the document). See docs/wg/feat-svg-editor/clipboard.md
24
+ * §Transport "Gating the native events".
25
+ */
26
+ is_focus_within(): boolean;
27
+ /**
28
+ * Register a host-chrome element into the attention scope. Editor-
29
+ * adjacent chrome — an inspector, a toolbar, a zoom menu; anything that
30
+ * drives `commands.*` — is a DOM *sibling* of the container (the
31
+ * container is exclusively surface-owned), so without registration the
32
+ * tracker cannot tell it apart from unrelated page surface: clicking
33
+ * its buttons moves focus out of the container, and hovering it fires
34
+ * the container's `pointerleave`, blacking out the whole keymap.
35
+ * Registered elements count for both arms — focus-within and
36
+ * pointer-over. Idempotent; re-adding a registered element is a no-op.
37
+ */
38
+ add(element: Element): void;
39
+ /**
40
+ * Unregister an element added via {@link add}. Also clears any
41
+ * still-latched pointer-over contribution from it (the element may be
42
+ * unmounted mid-hover — e.g. a popover closing under the cursor).
43
+ * Unknown elements are a no-op. The container itself cannot be
44
+ * removed; it is the scope's fixed root, not a registered extra.
45
+ */
46
+ remove(element: Element): void;
47
+ /** Detach the internal pointer-tracking listeners (container and every
48
+ * registered element). */
49
+ dispose(): void;
50
+ }
51
+ //#endregion
52
+ //#region src/core/snap/options.d.ts
53
+ type SnapOptions = {
54
+ /** When false, snap behavior and snap-guide rendering are both off. */enabled: boolean;
55
+ /** Snap threshold in HUD canvas pixels (container CSS px in svg-editor;
56
+ * whatever space the consumer feeds in). Constant across zoom because
57
+ * the input rects are already in screen-equivalent units. */
58
+ threshold_px: number;
59
+ };
60
+ declare const DEFAULT_SNAP_OPTIONS: SnapOptions;
61
+ //#endregion
62
+ //#region src/dom.d.ts
63
+ /**
64
+ * Wire a web-font settle source to the editor's geometry channel.
65
+ *
66
+ * The DOM surface re-serializes the `<svg>` on every editor tick, but a
67
+ * `<text>` / `<tspan>` bbox can change with NO attribute write: a web font
68
+ * finishing load AFTER its `font-family` / `font-size` was already written.
69
+ * The IR never sees that reflow, so nothing bumps `geometry_version` and
70
+ * every bounds-keyed consumer (snap, HUD chrome, size meter) stays stuck at
71
+ * the fallback-face metrics until the next real edit.
72
+ *
73
+ * Listens for `loadingdone` on `source` (a `FontFaceSet`, or any injected
74
+ * `EventTarget` in tests) and calls `bump` once per settle. COARSE on
75
+ * purpose: one bump clears the WHOLE bounds cache, not just text nodes —
76
+ * consistent with the package's pessimistic-invalidation stance, and far
77
+ * cheaper than scoping the bump to the (possibly many) reflowed runs.
78
+ *
79
+ * Also bumps once when `source.ready` resolves (when present): fonts that
80
+ * settled before attach — a cache hit, or `font-display` resolving the same
81
+ * tick the surface mounts — never re-fire `loadingdone`, so a document
82
+ * mounted post-settle still needs one bump to re-read at the real metrics.
83
+ *
84
+ * Returns a teardown that removes the listener and neutralizes the pending
85
+ * `ready` bump (leak guard) — call it on surface detach.
86
+ *
87
+ * Factored out of the surface so it can be unit-tested with a fake
88
+ * `EventTarget` in the node-only test env (jsdom's `FontFaceSet` is
89
+ * incomplete); never a real font / network.
90
+ */
91
+ declare function install_font_load_geometry_bump(source: EventTarget | null, bump: () => void): () => void;
92
+ type DomSurfaceOptions = {
93
+ /** Mount the SVG inside this container. */container: HTMLElement;
94
+ /**
95
+ * Install the default gesture set (wheel-pan/zoom, space-drag, middle-mouse,
96
+ * keyboard zoom). Default `true`. Pass `false` to start blank and bind à la
97
+ * carte via `handle.gestures.bind(...)`.
98
+ */
99
+ gestures?: boolean;
100
+ /**
101
+ * Wire native ClipboardEvent transport — `copy` / `cut` / `paste`
102
+ * listeners on the owner document, gated by the clipboard attention
103
+ * discipline. Default `true`. Pass `false` to route ALL clipboard
104
+ * traffic through the `ClipboardProvider` seam instead (the
105
+ * configuration under which a host's paste-time screening governs
106
+ * every path) — see docs/wg/feat-svg-editor/clipboard.md §Transport
107
+ * "Host control over the native path". Focus management (the container
108
+ * focusing on pointerdown) stays either way — it is a general canvas
109
+ * mitigation, not a clipboard feature.
110
+ */
111
+ clipboard?: boolean;
112
+ /**
113
+ * Auto-fit the document into the viewport on initial attach. Default
114
+ * `false`. Mirrors Excalidraw's `initialData.scrollToContent`.
115
+ * Subsequent `editor.load()` calls do NOT re-fit — call
116
+ * `handle.camera.fit("<root>")` yourself if you want that behavior.
117
+ */
118
+ fit?: boolean;
119
+ /**
120
+ * Initial camera transform. Default `cmath.transform.identity`. Ignored
121
+ * when `fit: true`.
122
+ */
123
+ initial_camera?: cmath.Transform;
124
+ /**
125
+ * Font-load settle source — the `EventTarget` whose `loadingdone` event
126
+ * signals "web fonts finished loading, text may have reflowed." Defaults
127
+ * to `container.ownerDocument.fonts` (the live `FontFaceSet`). The
128
+ * surface installs a `loadingdone` listener that advances the editor's
129
+ * geometry channel so text bounds re-read at the settled glyph metrics
130
+ * (see ../docs/geometry.md §Limitations "Text bbox depends on font").
131
+ *
132
+ * Injectable as a DOM seam: jsdom's `FontFaceSet` is incomplete, so
133
+ * tests pass a plain `EventTarget` stub and `dispatchEvent(new Event(
134
+ * "loadingdone"))` to simulate a settle without a real font / network.
135
+ */
136
+ font_load_source?: EventTarget;
137
+ };
138
+ /**
139
+ * Host-extendable attention scope — public via `handle.attention`.
140
+ *
141
+ * The document-level keymap (undo / redo / delete / tool keys) is gated on
142
+ * attention: focus inside the scope, or pointer over it. The scope starts
143
+ * as the container alone, which makes editor-adjacent host chrome — an
144
+ * inspector, a toolbar, a zoom menu; anything that drives `commands.*` —
145
+ * indistinguishable from unrelated page surface (chrome must be a DOM
146
+ * *sibling* of the container, never a child). Registering a chrome element
147
+ * keeps the full keymap live while the user works in it, with
148
+ * text-input focus still excluded by the keymap's own guard. The native
149
+ * clipboard gate (deliberately stricter: focus-only, never pointer-over)
150
+ * honors the registered set's focus arm the same way.
151
+ *
152
+ * `add` is idempotent; `remove` of an unregistered element is a no-op.
153
+ * Registrations live for the surface's lifetime — `detach()` drops them
154
+ * with the tracker. Hosts with mounting/unmounting chrome (popovers,
155
+ * panels) pair `add` on mount with `remove` on unmount.
156
+ */
157
+ type AttentionScope = Pick<AttentionTracker, "add" | "remove">;
158
+ /**
159
+ * Surface handle for the DOM surface. Extends the editor's core
160
+ * `SurfaceHandle` with the viewport-scoped concerns: pan/zoom (`camera`),
161
+ * pointer/wheel/keyboard gesture bindings (`gestures`), and the
162
+ * host-extendable attention scope (`attention`).
163
+ *
164
+ * Camera, gestures, and attention are **surface-scoped**: detaching the
165
+ * surface drops all three. They never appear on the headless `SvgEditor`.
166
+ */
167
+ type DomSurfaceHandle = SurfaceHandle & {
168
+ camera: Camera;
169
+ gestures: Gestures;
170
+ attention: AttentionScope;
171
+ };
172
+ /**
173
+ * Attach a DOM surface to a headless editor. Returns a `DomSurfaceHandle`
174
+ * whose `detach()` is the inverse — DOM cleared, listeners removed,
175
+ * gestures uninstalled.
176
+ *
177
+ * Usage is one-shot per container: the surface owns the container's children
178
+ * for its lifetime, and `detach()` restores it to empty.
179
+ */
180
+ declare function attach_dom_surface(editor: SvgEditor, options: DomSurfaceOptions): DomSurfaceHandle;
181
+ /**
182
+ * Affine projection of a point through a 2×3 CTM, then offset by a
183
+ * container origin (in page CSS-px). Mirrors how `line_endpoints_in_container`
184
+ * and `shape_of` (transformed branch) bridge from local SVG coords to the
185
+ * HUD's container-CSS-px space (HUD keeps its own transform at identity;
186
+ * the SVG carries the camera as a CSS transform, which getScreenCTM
187
+ * folds in).
188
+ *
189
+ * Exported for headless test coverage — pure function, no DOM types.
190
+ */
191
+ declare function project_point_through_ctm(px: number, py: number, ctm: {
192
+ a: number;
193
+ b: number;
194
+ c: number;
195
+ d: number;
196
+ e: number;
197
+ f: number;
198
+ }, container_offset: readonly [number, number]): [number, number];
199
+ /**
200
+ * The matrix form of {@link project_point_through_ctm}: the full
201
+ * `local → container-px` affine for a CTM + container offset, as a
202
+ * `cmath.Transform`. Use when handing the transform to a downstream projector
203
+ * (e.g. HUD chrome) instead of projecting a single point.
204
+ *
205
+ * Pure function, no DOM types.
206
+ */
207
+ declare function ctm_to_container_transform(ctm: {
208
+ a: number;
209
+ b: number;
210
+ c: number;
211
+ d: number;
212
+ e: number;
213
+ f: number;
214
+ }, container_offset: readonly [number, number]): cmath.Transform;
215
+ /**
216
+ * Inverse of the CTM's linear part applied to a delta vector. Drops
217
+ * translation. Used to convert a HUD-reported container-space drag delta
218
+ * back to the path's local coord space for `PathModel.translateVertices`.
219
+ *
220
+ * Throws on a degenerate (det = 0) matrix — the caller is expected to
221
+ * have a non-singular CTM for any visible element.
222
+ */
223
+ declare function project_delta_inverse_ctm(dx: number, dy: number, ctm: {
224
+ a: number;
225
+ b: number;
226
+ c: number;
227
+ d: number;
228
+ }): [number, number];
229
+ /**
230
+ * Map a transform-box reduced affine (box-relative, as emitted by the HUD
231
+ * `transform_box` intent) into the element's path-LOCAL coordinate space,
232
+ * ready to feed {@link PathModel.transformVertices} (Vertex Transform Box,
233
+ * gridaco/grida#881).
234
+ *
235
+ * The transform box lives in container CSS-px: a rect at `box_origin` of
236
+ * dimensions `box_size`, rotated by `box_rotation` degrees (CCW) about
237
+ * `box_origin` — the box's own frame, which may be non-axis-aligned once the
238
+ * session has rotated it. The HUD reports its gesture as a box-relative 2×3
239
+ * whose translation column is normalized to `[0..1]` against `box_size` (see
240
+ * `@grida/hud` transform-box). The mapping composes three stages — right-to-
241
+ * left, so a local point is taken to container space first:
242
+ *
243
+ * M_local = CTM⁻¹ · ( B · P · B⁻¹ ) · CTM, B = T(o) · R(box_rotation)
244
+ *
245
+ * - `CTM` = `local_to_container` (the element's CTM folded with the
246
+ * container offset; see {@link ctm_to_container_transform}).
247
+ * - `P` = `box_transform` with its translation de-normalized back to
248
+ * container-px (`tx·w`, `ty·h`) — the box's transform in its own un-rotated
249
+ * "box pixel" space (origin at the box's `[0,0]`).
250
+ * - `B` re-anchors that box-pixel transform to the box's placement in
251
+ * container space: translate to `box_origin`, rotated by `box_rotation`.
252
+ * The HUD de-rotates the cursor by `-box_rotation` before reducing, so a
253
+ * scale on a rotated box scales along the box's rotated axes.
254
+ *
255
+ * The result is the world→local round-trip of the box affine, so a rotate /
256
+ * scale composes correctly into local geometry through a scaled, rotated, or
257
+ * y-flipped element CTM. Pure, no DOM types. A zero-`box_size` axis is harmless
258
+ * (its translation contribution is `0`); guarding the gesture against a
259
+ * collapsed axis is the caller's job. `local_to_container` must be non-singular
260
+ * (any visible element's CTM is).
261
+ */
262
+ declare function box_transform_to_local_affine(box_transform: cmath.Transform, box_origin: readonly [number, number], box_size: readonly [number, number], local_to_container: cmath.Transform, box_rotation?: number): cmath.Transform;
263
+ /**
264
+ * Derive an oriented-bbox frame `{ origin, size, rotation }` (HUD transform-box
265
+ * shape; `rotation` in degrees CCW) from a rectangle's four container-px
266
+ * corners, given in `[nw, ne, se, sw]` order (Vertex Transform Box,
267
+ * gridaco/grida#881). The box's width axis is `ne − nw` and its height axis is
268
+ * `sw − nw`; assumes a (near-)rectangular quad — true under a similarity CTM,
269
+ * an approximation under a sheared one. Pure, no DOM types.
270
+ */
271
+ declare function obb_frame_from_corners(corners: {
272
+ nw: readonly [number, number];
273
+ ne: readonly [number, number];
274
+ sw: readonly [number, number];
275
+ }): {
276
+ origin: [number, number];
277
+ size: [number, number];
278
+ rotation: number;
279
+ };
280
+ /**
281
+ * Grow a baseline (local-space) bbox so each axis spans at least `min_px`
282
+ * container pixels, given the current local→container per-axis scale. A
283
+ * degenerate axis (a line, where one axis collapses) becomes a grabbable,
284
+ * rotatable strip; padding is centered so the strip stays on the points.
285
+ *
286
+ * Returns `null` when BOTH axes are below `min_px` (a point — no box).
287
+ *
288
+ * Pure, and evaluated against the CURRENT scale at projection time (NOT baked
289
+ * into the persisted session), so a line box stays a stable container-px strip
290
+ * across zoom / CTM changes rather than going stale at the establishment zoom
291
+ * (gridaco/grida#881 review).
292
+ */
293
+ declare function pad_box_min_axis(bbox: cmath.Rectangle, scale_x: number, scale_y: number, min_px: number): cmath.Rectangle | null;
294
+ /**
295
+ * Inverse-project a doc-space rect through a CTM + container offset back
296
+ * into the element's local frame. The output is the AABB of the four
297
+ * inverse-projected corners — when the CTM has a rotation component the
298
+ * AABB is an approximation, but it matches what the user visually
299
+ * expects from a screen-aligned marquee drag.
300
+ *
301
+ * Returns `null` when the CTM's linear part is singular (degenerate
302
+ * camera) — the caller should skip any test that needs the local rect.
303
+ */
304
+ declare function inverse_project_rect(rect: {
305
+ x: number;
306
+ y: number;
307
+ width: number;
308
+ height: number;
309
+ }, ctm: {
310
+ a: number;
311
+ b: number;
312
+ c: number;
313
+ d: number;
314
+ e: number;
315
+ f: number;
316
+ }, offset: readonly [number, number]): cmath.Rectangle | null;
317
+ //#endregion
318
+ export { box_transform_to_local_affine as a, inverse_project_rect as c, project_delta_inverse_ctm as d, project_point_through_ctm as f, attach_dom_surface as i, obb_frame_from_corners as l, SnapOptions as m, DomSurfaceHandle as n, ctm_to_container_transform as o, DEFAULT_SNAP_OPTIONS as p, DomSurfaceOptions as r, install_font_load_geometry_bump as s, AttentionScope as t, pad_box_min_axis as u };