@grida/svg-editor 1.0.0-alpha.5 → 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 +16 -8
  23. package/dist/presets.mjs +15 -8
  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-CMXNUMjP.d.mts +0 -48
  30. package/dist/dom-DyJy1H6Q.js +0 -1676
  31. package/dist/dom-eIgcZ4JC.d.ts +0 -48
  32. package/dist/dom-l5Y1Wf8C.mjs +0 -1627
  33. package/dist/editor-CRflVqEz.mjs +0 -1846
  34. package/dist/editor-DdgqLDC9.d.ts +0 -840
  35. package/dist/editor-Ds47eN37.js +0 -1858
  36. package/dist/editor-KRAmUodY.d.mts +0 -840
  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-cTjePy5e.js +0 -531
  41. /package/dist/{chunk-CfYAbeIz.mjs → chunk-D7D4PA-g.mjs} +0 -0
@@ -1,5 +1,7 @@
1
- import { s as SvgEditor } from "./editor-KRAmUodY.mjs";
2
- import { n as DomSurfaceOptions, t as DomSurfaceHandle } from "./dom-CMXNUMjP.mjs";
1
+ import { c as SvgEditor } from "./editor-CZgwg8BK.mjs";
2
+ import { n as DomSurfaceHandle, r as DomSurfaceOptions } from "./dom-BEjG2bSw.mjs";
3
+
4
+ //#region src/presets/keynote.d.ts
3
5
  declare namespace keynote_d_exports {
4
6
  export { KeynoteAttachOptions, KeynoteSurfaceHandle, attach };
5
7
  }
@@ -10,6 +12,13 @@ type KeynoteAttachOptions = {
10
12
  * Used for both initial fit and the cover-constraint clamp. Default 80.
11
13
  */
12
14
  padding?: number;
15
+ /**
16
+ * Screen-pixel scroll slack past the slide edge when zoomed in past fit.
17
+ * Forwarded to the cover constraint's `pan_overshoot`. Default 0 — strict
18
+ * cover behavior (no panning past the slide edge). Applies only when an
19
+ * axis is scrollable; a fitted axis stays locked at center.
20
+ */
21
+ pan_overshoot?: number;
13
22
  /**
14
23
  * Forward additional surface options (e.g. `gestures: false`). `container`,
15
24
  * `fit`, and `initial_camera` are owned by the preset.
@@ -27,6 +36,12 @@ type KeynoteSurfaceHandle = DomSurfaceHandle & {
27
36
  * the captured padding used by load-triggered refits.
28
37
  */
29
38
  set_padding(p: number): void;
39
+ /**
40
+ * Update the scroll-past slack at runtime. Mutates the live constraint;
41
+ * `reenforce()` pulls a previously over-panned transform back into the
42
+ * new range when the value is decreased.
43
+ */
44
+ set_pan_overshoot(o: number): void;
30
45
  };
31
46
  /**
32
47
  * Attach a keynote-shaped DOM surface:
package/dist/presets.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- import { s as SvgEditor } from "./editor-DdgqLDC9.js";
2
- import { n as DomSurfaceOptions, t as DomSurfaceHandle } from "./dom-eIgcZ4JC.js";
1
+ import { c as SvgEditor } from "./editor-kFTYSb_8.js";
2
+ import { n as DomSurfaceHandle, r as DomSurfaceOptions } from "./dom-Y2QR7QSi.js";
3
+
4
+ //#region \0rolldown/runtime.js
3
5
  declare namespace keynote_d_exports {
4
6
  export { KeynoteAttachOptions, KeynoteSurfaceHandle, attach };
5
7
  }
@@ -10,6 +12,13 @@ type KeynoteAttachOptions = {
10
12
  * Used for both initial fit and the cover-constraint clamp. Default 80.
11
13
  */
12
14
  padding?: number;
15
+ /**
16
+ * Screen-pixel scroll slack past the slide edge when zoomed in past fit.
17
+ * Forwarded to the cover constraint's `pan_overshoot`. Default 0 — strict
18
+ * cover behavior (no panning past the slide edge). Applies only when an
19
+ * axis is scrollable; a fitted axis stays locked at center.
20
+ */
21
+ pan_overshoot?: number;
13
22
  /**
14
23
  * Forward additional surface options (e.g. `gestures: false`). `container`,
15
24
  * `fit`, and `initial_camera` are owned by the preset.
@@ -27,6 +36,12 @@ type KeynoteSurfaceHandle = DomSurfaceHandle & {
27
36
  * the captured padding used by load-triggered refits.
28
37
  */
29
38
  set_padding(p: number): void;
39
+ /**
40
+ * Update the scroll-past slack at runtime. Mutates the live constraint;
41
+ * `reenforce()` pulls a previously over-panned transform back into the
42
+ * new range when the value is decreased.
43
+ */
44
+ set_pan_overshoot(o: number): void;
30
45
  };
31
46
  /**
32
47
  * Attach a keynote-shaped DOM surface:
package/dist/presets.js CHANGED
@@ -1,7 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dom = require("./dom-DyJy1H6Q.js");
2
+ const require_model = require("./model-tywvTGZv.js");
3
+ const require_dom = require("./dom-DYD1BHTo.js");
3
4
  //#region src/presets/keynote.ts
4
- var keynote_exports = /* @__PURE__ */ require_dom.__exportAll({ attach: () => attach });
5
+ var keynote_exports = /* @__PURE__ */ require_model.__exportAll({ attach: () => attach });
5
6
  /**
6
7
  * Attach a keynote-shaped DOM surface:
7
8
  * - Mounts via `attach_dom_surface` with `fit: true` (slide is visible on
@@ -21,22 +22,29 @@ function attach(editor, opts) {
21
22
  container: opts.container,
22
23
  fit: true
23
24
  });
24
- const apply = (p) => {
25
+ let padding = opts.padding ?? 80;
26
+ let pan_overshoot = opts.pan_overshoot ?? 0;
27
+ const apply = () => {
25
28
  inner.camera.constraints = {
26
29
  type: "cover",
27
30
  bounds: "<root>",
28
- padding: p
31
+ padding,
32
+ pan_overshoot
29
33
  };
30
34
  };
31
- apply(opts.padding ?? 80);
35
+ apply();
32
36
  const unsub_load = editor.subscribe_with_selector((s) => s.load_version, () => inner.camera.fit("<root>", { margin: inner.camera.constraints?.padding ?? 0 }));
33
37
  return {
34
- camera: inner.camera,
35
- gestures: inner.gestures,
38
+ ...inner,
36
39
  set_padding(p) {
37
- apply(p);
40
+ padding = p;
41
+ apply();
38
42
  inner.camera.fit("<root>", { margin: p });
39
43
  },
44
+ set_pan_overshoot(o) {
45
+ pan_overshoot = o;
46
+ apply();
47
+ },
40
48
  detach: () => {
41
49
  unsub_load();
42
50
  inner.detach();
package/dist/presets.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
2
- import { t as attach_dom_surface } from "./dom-l5Y1Wf8C.mjs";
1
+ import { t as __exportAll } from "./chunk-D7D4PA-g.mjs";
2
+ import { t as attach_dom_surface } from "./dom-tM3Dr1EK.mjs";
3
3
  //#region src/presets/keynote.ts
4
4
  var keynote_exports = /* @__PURE__ */ __exportAll({ attach: () => attach });
5
5
  /**
@@ -21,22 +21,29 @@ function attach(editor, opts) {
21
21
  container: opts.container,
22
22
  fit: true
23
23
  });
24
- const apply = (p) => {
24
+ let padding = opts.padding ?? 80;
25
+ let pan_overshoot = opts.pan_overshoot ?? 0;
26
+ const apply = () => {
25
27
  inner.camera.constraints = {
26
28
  type: "cover",
27
29
  bounds: "<root>",
28
- padding: p
30
+ padding,
31
+ pan_overshoot
29
32
  };
30
33
  };
31
- apply(opts.padding ?? 80);
34
+ apply();
32
35
  const unsub_load = editor.subscribe_with_selector((s) => s.load_version, () => inner.camera.fit("<root>", { margin: inner.camera.constraints?.padding ?? 0 }));
33
36
  return {
34
- camera: inner.camera,
35
- gestures: inner.gestures,
37
+ ...inner,
36
38
  set_padding(p) {
37
- apply(p);
39
+ padding = p;
40
+ apply();
38
41
  inner.camera.fit("<root>", { margin: p });
39
42
  },
43
+ set_pan_overshoot(o) {
44
+ pan_overshoot = o;
45
+ apply();
46
+ },
40
47
  detach: () => {
41
48
  unsub_load();
42
49
  inner.detach();
package/dist/react.d.mts CHANGED
@@ -1,26 +1,40 @@
1
- import { C as EditorState, V as Providers, s as SvgEditor, t as Commands, w as EditorStyle } from "./editor-KRAmUodY.mjs";
2
- import { t as DomSurfaceHandle } from "./dom-CMXNUMjP.mjs";
1
+ import { A as EditorState, H as Mode, J as PickEvent, K as PaintPreviewSession, Q as Providers, U as NodeId, Y as PreviewSession, c as SvgEditor, j as EditorStyle, rt as Tool, t as Commands } from "./editor-CZgwg8BK.mjs";
2
+ import { n as DomSurfaceHandle } from "./dom-BEjG2bSw.mjs";
3
3
  import cmath from "@grida/cmath";
4
4
  import { ReactNode } from "react";
5
- import * as _$react_jsx_runtime0 from "react/jsx-runtime";
6
5
 
7
6
  //#region src/react.d.ts
8
7
  type SvgEditorProviderProps = {
9
- svg: string;
8
+ /**
9
+ * Initial document for the editor. Read **once** on first render —
10
+ * subsequent changes to this prop are silently ignored. For live updates
11
+ * (file open, page switch, reset to a snapshot), pull the editor from
12
+ * context with `useSvgEditor()` and call `editor.load(...)` imperatively.
13
+ *
14
+ * This is the same shape Lexical (`initialConfig.editorState`), Slate
15
+ * (`initialValue`), and TipTap (`content` option) settled on for the
16
+ * same reason: a reactive document prop creates a feedback loop with the
17
+ * editor's own emissions. The editor instance is the source of truth
18
+ * for the document during a session; React state is not.
19
+ */
20
+ initialSvg: string;
10
21
  providers?: Providers;
11
22
  style?: Partial<EditorStyle>;
12
23
  children: ReactNode;
13
24
  };
14
25
  /**
15
26
  * Owns the headless editor and exposes it via context. The editor is created
16
- * once on first render; subsequent prop changes to `svg` call `editor.load()`.
27
+ * once on first render with `initialSvg`; subsequent changes to that prop are
28
+ * silently ignored. To replace the document at runtime, call
29
+ * `useSvgEditor().load(...)` imperatively, or remount the provider with a
30
+ * different `key`.
17
31
  */
18
32
  declare function SvgEditorProvider({
19
- svg,
33
+ initialSvg,
20
34
  providers,
21
35
  style,
22
36
  children
23
- }: SvgEditorProviderProps): _$react_jsx_runtime0.JSX.Element;
37
+ }: SvgEditorProviderProps): import("react/jsx-runtime").JSX.Element;
24
38
  type SvgEditorCanvasProps = {
25
39
  className?: string;
26
40
  style?: React.CSSProperties;
@@ -33,7 +47,13 @@ type SvgEditorCanvasProps = {
33
47
  * Auto-fit the document on initial attach. Default `false`. See
34
48
  * `DomSurfaceOptions.fit`.
35
49
  */
36
- fit?: boolean; /** Initial camera transform. Default identity. */
50
+ fit?: boolean;
51
+ /**
52
+ * Wire native ClipboardEvent transport (copy/cut/paste). Default `true`.
53
+ * Pass `false` to route all clipboard traffic through the
54
+ * `ClipboardProvider` seam. See `DomSurfaceOptions.clipboard`.
55
+ */
56
+ clipboard?: boolean; /** Initial camera transform. Default identity. */
37
57
  initial_camera?: cmath.Transform;
38
58
  /**
39
59
  * Receives the `DomSurfaceHandle` once the surface is attached, and
@@ -56,9 +76,10 @@ declare function SvgEditorCanvas({
56
76
  style,
57
77
  gestures,
58
78
  fit,
79
+ clipboard,
59
80
  initial_camera,
60
81
  onAttach
61
- }: SvgEditorCanvasProps): _$react_jsx_runtime0.JSX.Element;
82
+ }: SvgEditorCanvasProps): import("react/jsx-runtime").JSX.Element;
62
83
  declare function useSvgEditor(): SvgEditor;
63
84
  /**
64
85
  * Subscribe to a slice of `editor.state`. Re-renders when the selected slice
@@ -88,5 +109,62 @@ declare function useCommands(): Commands;
88
109
  * mismatch with the first client render.
89
110
  */
90
111
  declare function useCameraSnapshot<T>(handle: DomSurfaceHandle | null, selector: (camera: DomSurfaceHandle["camera"]) => T, fallback: T): T;
112
+ /** Current selection (frozen, identity-stable across no-op emits). */
113
+ declare function useSelection(): readonly NodeId[];
114
+ /** Active tool. Identity-stable when `set_tool` is a no-op. */
115
+ declare function useTool(): Tool;
116
+ /** Current mode (`"select"` | `"edit-content"`). */
117
+ declare function useMode(): Mode;
118
+ /**
119
+ * What kind of content-edit is active, or `null` when not in content-edit.
120
+ *
121
+ * Symmetric with `useMode()` but at a finer grain — resolves whether the
122
+ * single selected node is a path or a text node so consumers (e.g. the
123
+ * vector-edit toolbar) can render the right affordances. Mirrors the
124
+ * dispatch logic in the host's `enter_content_edit` router which checks
125
+ * `tag_of(id) === "path"` vs `"text" / "tspan"`.
126
+ *
127
+ * Returns `null` for the (defensive) case of `edit-content` with no
128
+ * selection, and for any tag that's neither path nor text.
129
+ */
130
+ declare function useContentEditKind(): "path" | "text" | null;
131
+ /** Whether the history stack has an undoable entry. */
132
+ declare function useCanUndo(): boolean;
133
+ /** Whether the history stack has a redoable entry. */
134
+ declare function useCanRedo(): boolean;
135
+ /** Hook-owned `PaintPreviewSession`. See block comment above. */
136
+ declare function usePaintPreview(channel: "fill" | "stroke"): PaintPreviewSession;
137
+ /** Hook-owned `PreviewSession` for a CSS/SVG property. See block comment above. */
138
+ declare function usePropertyPreview(name: string): PreviewSession;
139
+ /** Bound `editor.load(svg)`. Stable across renders. */
140
+ declare function useEditorLoad(): (svg: string) => void;
141
+ /** Bound `editor.serialize()`. Stable across renders. */
142
+ declare function useEditorSerialize(): () => string;
143
+ /**
144
+ * Push a hover override into the HUD surface — e.g. when the user hovers
145
+ * a row in a layers panel. The HUD will render the override's outline.
146
+ *
147
+ * Pass `null` to clear and let the pointer pick take over again. On
148
+ * unmount, the hook clears any override it set last so the canvas
149
+ * doesn't stay highlighted on a node that no longer has a panel row.
150
+ */
151
+ declare function useHoverOverride(): (id: NodeId | null) => void;
152
+ /**
153
+ * Observe pick (tap) outcomes — a discrete click on the canvas, reporting the
154
+ * document-space `point`, the `node_id` under it (`null` for empty canvas),
155
+ * the `button`, and `mods`. The handler fires once per tap, after the editor's
156
+ * own selection handling. Observe-only: it cannot prevent or alter selection.
157
+ *
158
+ * This is the React edge-wire over `editor.subscribe_pick`. The handler is
159
+ * kept in a ref so re-subscription is never triggered by handler identity —
160
+ * pass an inline closure freely. Pick is editor-scoped (it survives surface
161
+ * detach), so this is a hook, not a `SvgEditorCanvas` prop.
162
+ *
163
+ * Typical use: a comment / annotation tool anchors a popover at `e.point` and
164
+ * scopes its action to `e.node_id` (or to the whole document when `null`).
165
+ *
166
+ * @unstable See {@link PickEvent}.
167
+ */
168
+ declare function useEditorPick(handler: (e: PickEvent) => void): void;
91
169
  //#endregion
92
- export { SvgEditorCanvas, SvgEditorCanvasProps, SvgEditorProvider, SvgEditorProviderProps, useCameraSnapshot, useCommands, useEditorState, useSvgEditor };
170
+ export { SvgEditorCanvas, SvgEditorCanvasProps, SvgEditorProvider, SvgEditorProviderProps, useCameraSnapshot, useCanRedo, useCanUndo, useCommands, useContentEditKind, useEditorLoad, useEditorPick, useEditorSerialize, useEditorState, useHoverOverride, useMode, usePaintPreview, usePropertyPreview, useSelection, useSvgEditor, useTool };
package/dist/react.d.ts CHANGED
@@ -1,26 +1,40 @@
1
- import { C as EditorState, V as Providers, s as SvgEditor, t as Commands, w as EditorStyle } from "./editor-DdgqLDC9.js";
2
- import { t as DomSurfaceHandle } from "./dom-eIgcZ4JC.js";
1
+ import { A as EditorState, H as Mode, J as PickEvent, K as PaintPreviewSession, Q as Providers, U as NodeId, Y as PreviewSession, c as SvgEditor, j as EditorStyle, rt as Tool, t as Commands } from "./editor-kFTYSb_8.js";
2
+ import { n as DomSurfaceHandle } from "./dom-Y2QR7QSi.js";
3
3
  import cmath from "@grida/cmath";
4
- import * as _$react_jsx_runtime0 from "react/jsx-runtime";
5
4
  import { ReactNode } from "react";
6
5
 
7
6
  //#region src/react.d.ts
8
7
  type SvgEditorProviderProps = {
9
- svg: string;
8
+ /**
9
+ * Initial document for the editor. Read **once** on first render —
10
+ * subsequent changes to this prop are silently ignored. For live updates
11
+ * (file open, page switch, reset to a snapshot), pull the editor from
12
+ * context with `useSvgEditor()` and call `editor.load(...)` imperatively.
13
+ *
14
+ * This is the same shape Lexical (`initialConfig.editorState`), Slate
15
+ * (`initialValue`), and TipTap (`content` option) settled on for the
16
+ * same reason: a reactive document prop creates a feedback loop with the
17
+ * editor's own emissions. The editor instance is the source of truth
18
+ * for the document during a session; React state is not.
19
+ */
20
+ initialSvg: string;
10
21
  providers?: Providers;
11
22
  style?: Partial<EditorStyle>;
12
23
  children: ReactNode;
13
24
  };
14
25
  /**
15
26
  * Owns the headless editor and exposes it via context. The editor is created
16
- * once on first render; subsequent prop changes to `svg` call `editor.load()`.
27
+ * once on first render with `initialSvg`; subsequent changes to that prop are
28
+ * silently ignored. To replace the document at runtime, call
29
+ * `useSvgEditor().load(...)` imperatively, or remount the provider with a
30
+ * different `key`.
17
31
  */
18
32
  declare function SvgEditorProvider({
19
- svg,
33
+ initialSvg,
20
34
  providers,
21
35
  style,
22
36
  children
23
- }: SvgEditorProviderProps): _$react_jsx_runtime0.JSX.Element;
37
+ }: SvgEditorProviderProps): import("react/jsx-runtime").JSX.Element;
24
38
  type SvgEditorCanvasProps = {
25
39
  className?: string;
26
40
  style?: React.CSSProperties;
@@ -33,7 +47,13 @@ type SvgEditorCanvasProps = {
33
47
  * Auto-fit the document on initial attach. Default `false`. See
34
48
  * `DomSurfaceOptions.fit`.
35
49
  */
36
- fit?: boolean; /** Initial camera transform. Default identity. */
50
+ fit?: boolean;
51
+ /**
52
+ * Wire native ClipboardEvent transport (copy/cut/paste). Default `true`.
53
+ * Pass `false` to route all clipboard traffic through the
54
+ * `ClipboardProvider` seam. See `DomSurfaceOptions.clipboard`.
55
+ */
56
+ clipboard?: boolean; /** Initial camera transform. Default identity. */
37
57
  initial_camera?: cmath.Transform;
38
58
  /**
39
59
  * Receives the `DomSurfaceHandle` once the surface is attached, and
@@ -56,9 +76,10 @@ declare function SvgEditorCanvas({
56
76
  style,
57
77
  gestures,
58
78
  fit,
79
+ clipboard,
59
80
  initial_camera,
60
81
  onAttach
61
- }: SvgEditorCanvasProps): _$react_jsx_runtime0.JSX.Element;
82
+ }: SvgEditorCanvasProps): import("react/jsx-runtime").JSX.Element;
62
83
  declare function useSvgEditor(): SvgEditor;
63
84
  /**
64
85
  * Subscribe to a slice of `editor.state`. Re-renders when the selected slice
@@ -88,5 +109,62 @@ declare function useCommands(): Commands;
88
109
  * mismatch with the first client render.
89
110
  */
90
111
  declare function useCameraSnapshot<T>(handle: DomSurfaceHandle | null, selector: (camera: DomSurfaceHandle["camera"]) => T, fallback: T): T;
112
+ /** Current selection (frozen, identity-stable across no-op emits). */
113
+ declare function useSelection(): readonly NodeId[];
114
+ /** Active tool. Identity-stable when `set_tool` is a no-op. */
115
+ declare function useTool(): Tool;
116
+ /** Current mode (`"select"` | `"edit-content"`). */
117
+ declare function useMode(): Mode;
118
+ /**
119
+ * What kind of content-edit is active, or `null` when not in content-edit.
120
+ *
121
+ * Symmetric with `useMode()` but at a finer grain — resolves whether the
122
+ * single selected node is a path or a text node so consumers (e.g. the
123
+ * vector-edit toolbar) can render the right affordances. Mirrors the
124
+ * dispatch logic in the host's `enter_content_edit` router which checks
125
+ * `tag_of(id) === "path"` vs `"text" / "tspan"`.
126
+ *
127
+ * Returns `null` for the (defensive) case of `edit-content` with no
128
+ * selection, and for any tag that's neither path nor text.
129
+ */
130
+ declare function useContentEditKind(): "path" | "text" | null;
131
+ /** Whether the history stack has an undoable entry. */
132
+ declare function useCanUndo(): boolean;
133
+ /** Whether the history stack has a redoable entry. */
134
+ declare function useCanRedo(): boolean;
135
+ /** Hook-owned `PaintPreviewSession`. See block comment above. */
136
+ declare function usePaintPreview(channel: "fill" | "stroke"): PaintPreviewSession;
137
+ /** Hook-owned `PreviewSession` for a CSS/SVG property. See block comment above. */
138
+ declare function usePropertyPreview(name: string): PreviewSession;
139
+ /** Bound `editor.load(svg)`. Stable across renders. */
140
+ declare function useEditorLoad(): (svg: string) => void;
141
+ /** Bound `editor.serialize()`. Stable across renders. */
142
+ declare function useEditorSerialize(): () => string;
143
+ /**
144
+ * Push a hover override into the HUD surface — e.g. when the user hovers
145
+ * a row in a layers panel. The HUD will render the override's outline.
146
+ *
147
+ * Pass `null` to clear and let the pointer pick take over again. On
148
+ * unmount, the hook clears any override it set last so the canvas
149
+ * doesn't stay highlighted on a node that no longer has a panel row.
150
+ */
151
+ declare function useHoverOverride(): (id: NodeId | null) => void;
152
+ /**
153
+ * Observe pick (tap) outcomes — a discrete click on the canvas, reporting the
154
+ * document-space `point`, the `node_id` under it (`null` for empty canvas),
155
+ * the `button`, and `mods`. The handler fires once per tap, after the editor's
156
+ * own selection handling. Observe-only: it cannot prevent or alter selection.
157
+ *
158
+ * This is the React edge-wire over `editor.subscribe_pick`. The handler is
159
+ * kept in a ref so re-subscription is never triggered by handler identity —
160
+ * pass an inline closure freely. Pick is editor-scoped (it survives surface
161
+ * detach), so this is a hook, not a `SvgEditorCanvas` prop.
162
+ *
163
+ * Typical use: a comment / annotation tool anchors a popover at `e.point` and
164
+ * scopes its action to `e.node_id` (or to the whole document when `null`).
165
+ *
166
+ * @unstable See {@link PickEvent}.
167
+ */
168
+ declare function useEditorPick(handler: (e: PickEvent) => void): void;
91
169
  //#endregion
92
- export { SvgEditorCanvas, SvgEditorCanvasProps, SvgEditorProvider, SvgEditorProviderProps, useCameraSnapshot, useCommands, useEditorState, useSvgEditor };
170
+ export { SvgEditorCanvas, SvgEditorCanvasProps, SvgEditorProvider, SvgEditorProviderProps, useCameraSnapshot, useCanRedo, useCanUndo, useCommands, useContentEditKind, useEditorLoad, useEditorPick, useEditorSerialize, useEditorState, useHoverOverride, useMode, usePaintPreview, usePropertyPreview, useSelection, useSvgEditor, useTool };
package/dist/react.js CHANGED
@@ -1,30 +1,26 @@
1
1
  "use client";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_dom = require("./dom-DyJy1H6Q.js");
4
- const require_editor = require("./editor-Ds47eN37.js");
3
+ const require_editor = require("./editor-CesShk9o.js");
4
+ const require_dom = require("./dom-DYD1BHTo.js");
5
5
  let react = require("react");
6
6
  let react_jsx_runtime = require("react/jsx-runtime");
7
7
  //#region src/react.tsx
8
8
  const SvgEditorContext = (0, react.createContext)(null);
9
9
  /**
10
10
  * Owns the headless editor and exposes it via context. The editor is created
11
- * once on first render; subsequent prop changes to `svg` call `editor.load()`.
11
+ * once on first render with `initialSvg`; subsequent changes to that prop are
12
+ * silently ignored. To replace the document at runtime, call
13
+ * `useSvgEditor().load(...)` imperatively, or remount the provider with a
14
+ * different `key`.
12
15
  */
13
- function SvgEditorProvider({ svg, providers, style, children }) {
16
+ function SvgEditorProvider({ initialSvg, providers, style, children }) {
14
17
  const editor_ref = (0, react.useRef)(null);
15
18
  if (editor_ref.current === null) editor_ref.current = require_editor.createSvgEditor({
16
- svg,
19
+ svg: initialSvg,
17
20
  providers,
18
21
  style
19
22
  });
20
23
  const editor = editor_ref.current;
21
- const last_svg = (0, react.useRef)(svg);
22
- (0, react.useEffect)(() => {
23
- if (last_svg.current !== svg) {
24
- editor.load(svg);
25
- last_svg.current = svg;
26
- }
27
- }, [svg, editor]);
28
24
  (0, react.useEffect)(() => {
29
25
  return () => {
30
26
  editor.dispose();
@@ -44,7 +40,7 @@ function SvgEditorProvider({ svg, providers, style, children }) {
44
40
  * context for them, because a host may mount multiple canvases in the
45
41
  * same editor session.
46
42
  */
47
- function SvgEditorCanvas({ className, style, gestures, fit, initial_camera, onAttach }) {
43
+ function SvgEditorCanvas({ className, style, gestures, fit, clipboard, initial_camera, onAttach }) {
48
44
  const editor = useSvgEditor();
49
45
  const ref = (0, react.useRef)(null);
50
46
  const on_attach_ref = (0, react.useRef)(onAttach);
@@ -58,6 +54,7 @@ function SvgEditorCanvas({ className, style, gestures, fit, initial_camera, onAt
58
54
  container,
59
55
  gestures,
60
56
  fit,
57
+ clipboard,
61
58
  initial_camera: initial_camera_ref.current
62
59
  });
63
60
  on_attach_ref.current?.(handle);
@@ -68,7 +65,8 @@ function SvgEditorCanvas({ className, style, gestures, fit, initial_camera, onAt
68
65
  }, [
69
66
  editor,
70
67
  gestures,
71
- fit
68
+ fit,
69
+ clipboard
72
70
  ]);
73
71
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
74
72
  ref,
@@ -128,10 +126,167 @@ function useCommands() {
128
126
  function useCameraSnapshot(handle, selector, fallback) {
129
127
  return (0, react.useSyncExternalStore)((cb) => handle?.camera.subscribe(cb) ?? (() => {}), () => handle ? selector(handle.camera) : fallback, () => fallback);
130
128
  }
129
+ /** Current selection (frozen, identity-stable across no-op emits). */
130
+ function useSelection() {
131
+ return useEditorState((s) => s.selection);
132
+ }
133
+ /** Active tool. Identity-stable when `set_tool` is a no-op. */
134
+ function useTool() {
135
+ return useEditorState((s) => s.tool);
136
+ }
137
+ /** Current mode (`"select"` | `"edit-content"`). */
138
+ function useMode() {
139
+ return useEditorState((s) => s.mode);
140
+ }
141
+ /**
142
+ * What kind of content-edit is active, or `null` when not in content-edit.
143
+ *
144
+ * Symmetric with `useMode()` but at a finer grain — resolves whether the
145
+ * single selected node is a path or a text node so consumers (e.g. the
146
+ * vector-edit toolbar) can render the right affordances. Mirrors the
147
+ * dispatch logic in the host's `enter_content_edit` router which checks
148
+ * `tag_of(id) === "path"` vs `"text" / "tspan"`.
149
+ *
150
+ * Returns `null` for the (defensive) case of `edit-content` with no
151
+ * selection, and for any tag that's neither path nor text.
152
+ */
153
+ function useContentEditKind() {
154
+ const editor = useSvgEditor();
155
+ const [mode, id] = useEditorState((s) => `${s.mode}::${s.selection[0] ?? ""}`, (a, b) => a === b).split("::");
156
+ if (mode !== "edit-content" || !id) return null;
157
+ const tag = editor.tree().nodes.get(id)?.tag;
158
+ if (tag === "path") return "path";
159
+ if (tag === "text" || tag === "tspan") return "text";
160
+ return null;
161
+ }
162
+ /** Whether the history stack has an undoable entry. */
163
+ function useCanUndo() {
164
+ return useEditorState((s) => s.can_undo);
165
+ }
166
+ /** Whether the history stack has a redoable entry. */
167
+ function useCanRedo() {
168
+ return useEditorState((s) => s.can_redo);
169
+ }
170
+ function use_lifecycle_session(open, deps) {
171
+ const sessionRef = (0, react.useRef)(null);
172
+ const ops = (0, react.useMemo)(() => ({
173
+ ensure() {
174
+ if (sessionRef.current?.live === false) sessionRef.current = null;
175
+ if (!sessionRef.current) sessionRef.current = open();
176
+ return sessionRef.current;
177
+ },
178
+ /** Non-creating read: is a gesture currently open underneath? */
179
+ live() {
180
+ return sessionRef.current?.live === true;
181
+ },
182
+ finalize(action, commit) {
183
+ const s = sessionRef.current;
184
+ if (!s) return;
185
+ sessionRef.current = null;
186
+ if (action === "commit") commit(s);
187
+ else s.discard();
188
+ }
189
+ }), deps);
190
+ (0, react.useEffect)(() => {
191
+ return () => ops.finalize("discard", () => {});
192
+ }, [ops]);
193
+ return ops;
194
+ }
195
+ /** Hook-owned `PaintPreviewSession`. See block comment above. */
196
+ function usePaintPreview(channel) {
197
+ const editor = useSvgEditor();
198
+ const lc = use_lifecycle_session(() => editor.commands.preview_paint(channel), [editor, channel]);
199
+ return (0, react.useMemo)(() => ({
200
+ get live() {
201
+ return lc.live();
202
+ },
203
+ update: (paint) => lc.ensure().update(paint),
204
+ commit: () => lc.finalize("commit", (s) => s.commit()),
205
+ discard: () => lc.finalize("discard", () => {})
206
+ }), [lc]);
207
+ }
208
+ /** Hook-owned `PreviewSession` for a CSS/SVG property. See block comment above. */
209
+ function usePropertyPreview(name) {
210
+ const editor = useSvgEditor();
211
+ const lc = use_lifecycle_session(() => editor.commands.preview_property(name), [editor, name]);
212
+ return (0, react.useMemo)(() => ({
213
+ get live() {
214
+ return lc.live();
215
+ },
216
+ update: (value) => lc.ensure().update(value),
217
+ commit: () => lc.finalize("commit", (s) => s.commit()),
218
+ discard: () => lc.finalize("discard", () => {})
219
+ }), [lc]);
220
+ }
221
+ /** Bound `editor.load(svg)`. Stable across renders. */
222
+ function useEditorLoad() {
223
+ const editor = useSvgEditor();
224
+ return (0, react.useCallback)((svg) => editor.load(svg), [editor]);
225
+ }
226
+ /** Bound `editor.serialize()`. Stable across renders. */
227
+ function useEditorSerialize() {
228
+ const editor = useSvgEditor();
229
+ return (0, react.useCallback)(() => editor.serialize(), [editor]);
230
+ }
231
+ /**
232
+ * Push a hover override into the HUD surface — e.g. when the user hovers
233
+ * a row in a layers panel. The HUD will render the override's outline.
234
+ *
235
+ * Pass `null` to clear and let the pointer pick take over again. On
236
+ * unmount, the hook clears any override it set last so the canvas
237
+ * doesn't stay highlighted on a node that no longer has a panel row.
238
+ */
239
+ function useHoverOverride() {
240
+ const editor = useSvgEditor();
241
+ const lastSetRef = (0, react.useRef)(null);
242
+ (0, react.useEffect)(() => {
243
+ return () => {
244
+ if (lastSetRef.current !== null && editor.surface_hover() === lastSetRef.current) editor.set_surface_hover_override(null);
245
+ };
246
+ }, [editor]);
247
+ return (0, react.useCallback)((id) => {
248
+ lastSetRef.current = id;
249
+ editor.set_surface_hover_override(id);
250
+ }, [editor]);
251
+ }
252
+ /**
253
+ * Observe pick (tap) outcomes — a discrete click on the canvas, reporting the
254
+ * document-space `point`, the `node_id` under it (`null` for empty canvas),
255
+ * the `button`, and `mods`. The handler fires once per tap, after the editor's
256
+ * own selection handling. Observe-only: it cannot prevent or alter selection.
257
+ *
258
+ * This is the React edge-wire over `editor.subscribe_pick`. The handler is
259
+ * kept in a ref so re-subscription is never triggered by handler identity —
260
+ * pass an inline closure freely. Pick is editor-scoped (it survives surface
261
+ * detach), so this is a hook, not a `SvgEditorCanvas` prop.
262
+ *
263
+ * Typical use: a comment / annotation tool anchors a popover at `e.point` and
264
+ * scopes its action to `e.node_id` (or to the whole document when `null`).
265
+ *
266
+ * @unstable See {@link PickEvent}.
267
+ */
268
+ function useEditorPick(handler) {
269
+ const editor = useSvgEditor();
270
+ const handler_ref = (0, react.useRef)(handler);
271
+ handler_ref.current = handler;
272
+ (0, react.useEffect)(() => editor.subscribe_pick((e) => handler_ref.current(e)), [editor]);
273
+ }
131
274
  //#endregion
132
275
  exports.SvgEditorCanvas = SvgEditorCanvas;
133
276
  exports.SvgEditorProvider = SvgEditorProvider;
134
277
  exports.useCameraSnapshot = useCameraSnapshot;
278
+ exports.useCanRedo = useCanRedo;
279
+ exports.useCanUndo = useCanUndo;
135
280
  exports.useCommands = useCommands;
281
+ exports.useContentEditKind = useContentEditKind;
282
+ exports.useEditorLoad = useEditorLoad;
283
+ exports.useEditorPick = useEditorPick;
284
+ exports.useEditorSerialize = useEditorSerialize;
136
285
  exports.useEditorState = useEditorState;
286
+ exports.useHoverOverride = useHoverOverride;
287
+ exports.useMode = useMode;
288
+ exports.usePaintPreview = usePaintPreview;
289
+ exports.usePropertyPreview = usePropertyPreview;
290
+ exports.useSelection = useSelection;
137
291
  exports.useSvgEditor = useSvgEditor;
292
+ exports.useTool = useTool;