@grida/svg-editor 1.0.0-alpha.2 → 1.0.0-alpha.4
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 +213 -5
- package/dist/chunk-CfYAbeIz.mjs +13 -0
- package/dist/{dom-kA8NDuVh.mjs → dom-CmOu0HvI.mjs} +701 -7
- package/dist/dom-Cn-RtjRL.d.ts +48 -0
- package/dist/{dom-CfP_ZURh.js → dom-CoVZzFqy.js} +716 -7
- package/dist/dom-DJnZhtOd.d.mts +48 -0
- package/dist/dom.d.mts +2 -16
- package/dist/dom.d.ts +2 -16
- package/dist/dom.js +1 -1
- package/dist/dom.mjs +1 -1
- package/dist/{editor-M6j8XGO5.mjs → editor-CjK56cgb.mjs} +11 -11
- package/dist/{editor-BryibVvr.d.mts → editor-D2l_CDr0.d.ts} +314 -108
- package/dist/{editor-DllAMsDu.js → editor-D2zZAyny.js} +13 -13
- package/dist/{editor-klT8wu-x.d.ts → editor-Uu6dZX4y.d.mts} +314 -108
- package/dist/index-CHiXYO9-.d.ts +1 -0
- package/dist/index-ThDLM4Am.d.mts +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{paint-DuCg6Y-K.mjs → paint-Cfiw4g_J.mjs} +17 -1
- package/dist/{paint-DHq_3iwU.js → paint-dDV-Trt9.js} +23 -1
- package/dist/presets.d.mts +46 -0
- package/dist/presets.d.ts +46 -0
- package/dist/presets.js +55 -0
- package/dist/presets.mjs +50 -0
- package/dist/react.d.mts +49 -6
- package/dist/react.d.ts +49 -6
- package/dist/react.js +49 -9
- package/dist/react.mjs +49 -10
- package/package.json +9 -4
package/dist/presets.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_dom = require("./dom-CoVZzFqy.js");
|
|
3
|
+
//#region src/presets/keynote.ts
|
|
4
|
+
var keynote_exports = /* @__PURE__ */ require_dom.__exportAll({ attach: () => attach });
|
|
5
|
+
/**
|
|
6
|
+
* Attach a keynote-shaped DOM surface:
|
|
7
|
+
* - Mounts via `attach_dom_surface` with `fit: true` (slide is visible on
|
|
8
|
+
* first frame).
|
|
9
|
+
* - Installs a `'cover'` camera constraint bound to the document root, so
|
|
10
|
+
* the user can't zoom out past the slide or pan past its edges.
|
|
11
|
+
* - Subscribes to `editor.state.load_version` so every `editor.load(svg)`
|
|
12
|
+
* re-fits the camera to the new document.
|
|
13
|
+
*
|
|
14
|
+
* Returns a `KeynoteSurfaceHandle` — same shape as `DomSurfaceHandle` plus
|
|
15
|
+
* `set_padding` for present-mode toggles. The returned `detach()`
|
|
16
|
+
* additionally tears down the load subscription.
|
|
17
|
+
*/
|
|
18
|
+
function attach(editor, opts) {
|
|
19
|
+
let padding = opts.padding ?? 80;
|
|
20
|
+
const inner = require_dom.attach_dom_surface(editor, {
|
|
21
|
+
...opts.surface,
|
|
22
|
+
container: opts.container,
|
|
23
|
+
fit: true
|
|
24
|
+
});
|
|
25
|
+
inner.camera.constraints = {
|
|
26
|
+
type: "cover",
|
|
27
|
+
bounds: "<root>",
|
|
28
|
+
padding
|
|
29
|
+
};
|
|
30
|
+
const unsub_load = editor.subscribe_with_selector((s) => s.load_version, () => inner.camera.fit("<root>", { margin: padding }));
|
|
31
|
+
return {
|
|
32
|
+
camera: inner.camera,
|
|
33
|
+
gestures: inner.gestures,
|
|
34
|
+
set_padding(p) {
|
|
35
|
+
padding = p;
|
|
36
|
+
inner.camera.constraints = {
|
|
37
|
+
type: "cover",
|
|
38
|
+
bounds: "<root>",
|
|
39
|
+
padding: p
|
|
40
|
+
};
|
|
41
|
+
inner.camera.fit("<root>", { margin: p });
|
|
42
|
+
},
|
|
43
|
+
detach: () => {
|
|
44
|
+
unsub_load();
|
|
45
|
+
inner.detach();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
Object.defineProperty(exports, "keynote", {
|
|
51
|
+
enumerable: true,
|
|
52
|
+
get: function() {
|
|
53
|
+
return keynote_exports;
|
|
54
|
+
}
|
|
55
|
+
});
|
package/dist/presets.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
|
|
2
|
+
import { t as attach_dom_surface } from "./dom-CmOu0HvI.mjs";
|
|
3
|
+
//#region src/presets/keynote.ts
|
|
4
|
+
var keynote_exports = /* @__PURE__ */ __exportAll({ attach: () => attach });
|
|
5
|
+
/**
|
|
6
|
+
* Attach a keynote-shaped DOM surface:
|
|
7
|
+
* - Mounts via `attach_dom_surface` with `fit: true` (slide is visible on
|
|
8
|
+
* first frame).
|
|
9
|
+
* - Installs a `'cover'` camera constraint bound to the document root, so
|
|
10
|
+
* the user can't zoom out past the slide or pan past its edges.
|
|
11
|
+
* - Subscribes to `editor.state.load_version` so every `editor.load(svg)`
|
|
12
|
+
* re-fits the camera to the new document.
|
|
13
|
+
*
|
|
14
|
+
* Returns a `KeynoteSurfaceHandle` — same shape as `DomSurfaceHandle` plus
|
|
15
|
+
* `set_padding` for present-mode toggles. The returned `detach()`
|
|
16
|
+
* additionally tears down the load subscription.
|
|
17
|
+
*/
|
|
18
|
+
function attach(editor, opts) {
|
|
19
|
+
let padding = opts.padding ?? 80;
|
|
20
|
+
const inner = attach_dom_surface(editor, {
|
|
21
|
+
...opts.surface,
|
|
22
|
+
container: opts.container,
|
|
23
|
+
fit: true
|
|
24
|
+
});
|
|
25
|
+
inner.camera.constraints = {
|
|
26
|
+
type: "cover",
|
|
27
|
+
bounds: "<root>",
|
|
28
|
+
padding
|
|
29
|
+
};
|
|
30
|
+
const unsub_load = editor.subscribe_with_selector((s) => s.load_version, () => inner.camera.fit("<root>", { margin: padding }));
|
|
31
|
+
return {
|
|
32
|
+
camera: inner.camera,
|
|
33
|
+
gestures: inner.gestures,
|
|
34
|
+
set_padding(p) {
|
|
35
|
+
padding = p;
|
|
36
|
+
inner.camera.constraints = {
|
|
37
|
+
type: "cover",
|
|
38
|
+
bounds: "<root>",
|
|
39
|
+
padding: p
|
|
40
|
+
};
|
|
41
|
+
inner.camera.fit("<root>", { margin: p });
|
|
42
|
+
},
|
|
43
|
+
detach: () => {
|
|
44
|
+
unsub_load();
|
|
45
|
+
inner.detach();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { keynote_exports as keynote };
|
package/dist/react.d.mts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { B as Providers, C as EditorStyle, S as EditorState, o as SvgEditor, t as Commands } from "./editor-Uu6dZX4y.mjs";
|
|
2
|
+
import { t as DomSurfaceHandle } from "./dom-DJnZhtOd.mjs";
|
|
3
|
+
import cmath from "@grida/cmath";
|
|
2
4
|
import { ReactNode } from "react";
|
|
3
5
|
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
4
6
|
|
|
@@ -22,17 +24,40 @@ declare function SvgEditorProvider({
|
|
|
22
24
|
type SvgEditorCanvasProps = {
|
|
23
25
|
className?: string;
|
|
24
26
|
style?: React.CSSProperties;
|
|
27
|
+
/**
|
|
28
|
+
* Install the default gesture set. Default `true`. See
|
|
29
|
+
* `DomSurfaceOptions.gestures`.
|
|
30
|
+
*/
|
|
31
|
+
gestures?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Auto-fit the document on initial attach. Default `false`. See
|
|
34
|
+
* `DomSurfaceOptions.fit`.
|
|
35
|
+
*/
|
|
36
|
+
fit?: boolean; /** Initial camera transform. Default identity. */
|
|
37
|
+
initial_camera?: cmath.Transform;
|
|
38
|
+
/**
|
|
39
|
+
* Receives the `DomSurfaceHandle` once the surface is attached, and
|
|
40
|
+
* `null` on unmount/detach. Use this to thread `handle.camera` /
|
|
41
|
+
* `handle.gestures` into surrounding chrome (toolbars, badges, etc.).
|
|
42
|
+
*/
|
|
43
|
+
onAttach?: (handle: DomSurfaceHandle | null) => void;
|
|
25
44
|
};
|
|
26
45
|
/**
|
|
27
46
|
* Renders the editor's SVG into a `div` and wires it to the DOM surface.
|
|
28
47
|
*
|
|
29
|
-
* Internally calls `attach_dom_surface(editor, { container })` on
|
|
30
|
-
* `handle.detach()` on unmount.
|
|
31
|
-
*
|
|
48
|
+
* Internally calls `attach_dom_surface(editor, { container, ... })` on
|
|
49
|
+
* mount and `handle.detach()` on unmount. Surface-scoped concerns (camera,
|
|
50
|
+
* gestures) are reached via the `onAttach` callback — there is no global
|
|
51
|
+
* context for them, because a host may mount multiple canvases in the
|
|
52
|
+
* same editor session.
|
|
32
53
|
*/
|
|
33
54
|
declare function SvgEditorCanvas({
|
|
34
55
|
className,
|
|
35
|
-
style
|
|
56
|
+
style,
|
|
57
|
+
gestures,
|
|
58
|
+
fit,
|
|
59
|
+
initial_camera,
|
|
60
|
+
onAttach
|
|
36
61
|
}: SvgEditorCanvasProps): _$react_jsx_runtime0.JSX.Element;
|
|
37
62
|
declare function useSvgEditor(): SvgEditor;
|
|
38
63
|
/**
|
|
@@ -45,5 +70,23 @@ declare function useEditorState<T>(selector: (state: EditorState) => T, equals?:
|
|
|
45
70
|
* re-renders (commands themselves don't change identity).
|
|
46
71
|
*/
|
|
47
72
|
declare function useCommands(): Commands;
|
|
73
|
+
/**
|
|
74
|
+
* Subscribe to a slice of `handle.camera` from a `DomSurfaceHandle`. Pass
|
|
75
|
+
* the handle (or null if it isn't attached yet) and a selector that reads
|
|
76
|
+
* what you need from the camera. The returned value updates on every
|
|
77
|
+
* camera mutation — does NOT bump `editor.state.version`.
|
|
78
|
+
*
|
|
79
|
+
* Typical use: zoom badge in a toolbar.
|
|
80
|
+
*
|
|
81
|
+
* ```tsx
|
|
82
|
+
* const zoom = useCameraSnapshot(handle, (c) => c.zoom, 1);
|
|
83
|
+
* return <div>{Math.round(zoom * 100)}%</div>;
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* The `fallback` is what's returned when `handle` is `null` (before mount /
|
|
87
|
+
* after detach). It's also the SSR snapshot value — anything that won't
|
|
88
|
+
* mismatch with the first client render.
|
|
89
|
+
*/
|
|
90
|
+
declare function useCameraSnapshot<T>(handle: DomSurfaceHandle | null, selector: (camera: DomSurfaceHandle["camera"]) => T, fallback: T): T;
|
|
48
91
|
//#endregion
|
|
49
|
-
export { SvgEditorCanvas, SvgEditorCanvasProps, SvgEditorProvider, SvgEditorProviderProps, useCommands, useEditorState, useSvgEditor };
|
|
92
|
+
export { SvgEditorCanvas, SvgEditorCanvasProps, SvgEditorProvider, SvgEditorProviderProps, useCameraSnapshot, useCommands, useEditorState, useSvgEditor };
|
package/dist/react.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { B as Providers, C as EditorStyle, S as EditorState, o as SvgEditor, t as Commands } from "./editor-D2l_CDr0.js";
|
|
2
|
+
import { t as DomSurfaceHandle } from "./dom-Cn-RtjRL.js";
|
|
3
|
+
import cmath from "@grida/cmath";
|
|
2
4
|
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
5
|
import { ReactNode } from "react";
|
|
4
6
|
|
|
@@ -22,17 +24,40 @@ declare function SvgEditorProvider({
|
|
|
22
24
|
type SvgEditorCanvasProps = {
|
|
23
25
|
className?: string;
|
|
24
26
|
style?: React.CSSProperties;
|
|
27
|
+
/**
|
|
28
|
+
* Install the default gesture set. Default `true`. See
|
|
29
|
+
* `DomSurfaceOptions.gestures`.
|
|
30
|
+
*/
|
|
31
|
+
gestures?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Auto-fit the document on initial attach. Default `false`. See
|
|
34
|
+
* `DomSurfaceOptions.fit`.
|
|
35
|
+
*/
|
|
36
|
+
fit?: boolean; /** Initial camera transform. Default identity. */
|
|
37
|
+
initial_camera?: cmath.Transform;
|
|
38
|
+
/**
|
|
39
|
+
* Receives the `DomSurfaceHandle` once the surface is attached, and
|
|
40
|
+
* `null` on unmount/detach. Use this to thread `handle.camera` /
|
|
41
|
+
* `handle.gestures` into surrounding chrome (toolbars, badges, etc.).
|
|
42
|
+
*/
|
|
43
|
+
onAttach?: (handle: DomSurfaceHandle | null) => void;
|
|
25
44
|
};
|
|
26
45
|
/**
|
|
27
46
|
* Renders the editor's SVG into a `div` and wires it to the DOM surface.
|
|
28
47
|
*
|
|
29
|
-
* Internally calls `attach_dom_surface(editor, { container })` on
|
|
30
|
-
* `handle.detach()` on unmount.
|
|
31
|
-
*
|
|
48
|
+
* Internally calls `attach_dom_surface(editor, { container, ... })` on
|
|
49
|
+
* mount and `handle.detach()` on unmount. Surface-scoped concerns (camera,
|
|
50
|
+
* gestures) are reached via the `onAttach` callback — there is no global
|
|
51
|
+
* context for them, because a host may mount multiple canvases in the
|
|
52
|
+
* same editor session.
|
|
32
53
|
*/
|
|
33
54
|
declare function SvgEditorCanvas({
|
|
34
55
|
className,
|
|
35
|
-
style
|
|
56
|
+
style,
|
|
57
|
+
gestures,
|
|
58
|
+
fit,
|
|
59
|
+
initial_camera,
|
|
60
|
+
onAttach
|
|
36
61
|
}: SvgEditorCanvasProps): _$react_jsx_runtime0.JSX.Element;
|
|
37
62
|
declare function useSvgEditor(): SvgEditor;
|
|
38
63
|
/**
|
|
@@ -45,5 +70,23 @@ declare function useEditorState<T>(selector: (state: EditorState) => T, equals?:
|
|
|
45
70
|
* re-renders (commands themselves don't change identity).
|
|
46
71
|
*/
|
|
47
72
|
declare function useCommands(): Commands;
|
|
73
|
+
/**
|
|
74
|
+
* Subscribe to a slice of `handle.camera` from a `DomSurfaceHandle`. Pass
|
|
75
|
+
* the handle (or null if it isn't attached yet) and a selector that reads
|
|
76
|
+
* what you need from the camera. The returned value updates on every
|
|
77
|
+
* camera mutation — does NOT bump `editor.state.version`.
|
|
78
|
+
*
|
|
79
|
+
* Typical use: zoom badge in a toolbar.
|
|
80
|
+
*
|
|
81
|
+
* ```tsx
|
|
82
|
+
* const zoom = useCameraSnapshot(handle, (c) => c.zoom, 1);
|
|
83
|
+
* return <div>{Math.round(zoom * 100)}%</div>;
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* The `fallback` is what's returned when `handle` is `null` (before mount /
|
|
87
|
+
* after detach). It's also the SSR snapshot value — anything that won't
|
|
88
|
+
* mismatch with the first client render.
|
|
89
|
+
*/
|
|
90
|
+
declare function useCameraSnapshot<T>(handle: DomSurfaceHandle | null, selector: (camera: DomSurfaceHandle["camera"]) => T, fallback: T): T;
|
|
48
91
|
//#endregion
|
|
49
|
-
export { SvgEditorCanvas, SvgEditorCanvasProps, SvgEditorProvider, SvgEditorProviderProps, useCommands, useEditorState, useSvgEditor };
|
|
92
|
+
export { SvgEditorCanvas, SvgEditorCanvasProps, SvgEditorProvider, SvgEditorProviderProps, useCameraSnapshot, useCommands, useEditorState, useSvgEditor };
|
package/dist/react.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const require_dom = require("./dom-
|
|
4
|
-
const require_editor = require("./editor-
|
|
3
|
+
const require_dom = require("./dom-CoVZzFqy.js");
|
|
4
|
+
const require_editor = require("./editor-D2zZAyny.js");
|
|
5
5
|
let react = require("react");
|
|
6
6
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
7
7
|
//#region src/react.tsx
|
|
@@ -38,19 +38,38 @@ function SvgEditorProvider({ svg, providers, style, children }) {
|
|
|
38
38
|
/**
|
|
39
39
|
* Renders the editor's SVG into a `div` and wires it to the DOM surface.
|
|
40
40
|
*
|
|
41
|
-
* Internally calls `attach_dom_surface(editor, { container })` on
|
|
42
|
-
* `handle.detach()` on unmount.
|
|
43
|
-
*
|
|
41
|
+
* Internally calls `attach_dom_surface(editor, { container, ... })` on
|
|
42
|
+
* mount and `handle.detach()` on unmount. Surface-scoped concerns (camera,
|
|
43
|
+
* gestures) are reached via the `onAttach` callback — there is no global
|
|
44
|
+
* context for them, because a host may mount multiple canvases in the
|
|
45
|
+
* same editor session.
|
|
44
46
|
*/
|
|
45
|
-
function SvgEditorCanvas({ className, style }) {
|
|
47
|
+
function SvgEditorCanvas({ className, style, gestures, fit, initial_camera, onAttach }) {
|
|
46
48
|
const editor = useSvgEditor();
|
|
47
49
|
const ref = (0, react.useRef)(null);
|
|
50
|
+
const on_attach_ref = (0, react.useRef)(onAttach);
|
|
51
|
+
on_attach_ref.current = onAttach;
|
|
52
|
+
const initial_camera_ref = (0, react.useRef)(initial_camera);
|
|
53
|
+
initial_camera_ref.current = initial_camera;
|
|
48
54
|
(0, react.useEffect)(() => {
|
|
49
55
|
const container = ref.current;
|
|
50
56
|
if (!container) return;
|
|
51
|
-
const handle = require_dom.attach_dom_surface(editor, {
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
const handle = require_dom.attach_dom_surface(editor, {
|
|
58
|
+
container,
|
|
59
|
+
gestures,
|
|
60
|
+
fit,
|
|
61
|
+
initial_camera: initial_camera_ref.current
|
|
62
|
+
});
|
|
63
|
+
on_attach_ref.current?.(handle);
|
|
64
|
+
return () => {
|
|
65
|
+
on_attach_ref.current?.(null);
|
|
66
|
+
handle.detach();
|
|
67
|
+
};
|
|
68
|
+
}, [
|
|
69
|
+
editor,
|
|
70
|
+
gestures,
|
|
71
|
+
fit
|
|
72
|
+
]);
|
|
54
73
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
55
74
|
ref,
|
|
56
75
|
className,
|
|
@@ -89,9 +108,30 @@ function useCommands() {
|
|
|
89
108
|
const editor = useSvgEditor();
|
|
90
109
|
return (0, react.useMemo)(() => editor.commands, [editor]);
|
|
91
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Subscribe to a slice of `handle.camera` from a `DomSurfaceHandle`. Pass
|
|
113
|
+
* the handle (or null if it isn't attached yet) and a selector that reads
|
|
114
|
+
* what you need from the camera. The returned value updates on every
|
|
115
|
+
* camera mutation — does NOT bump `editor.state.version`.
|
|
116
|
+
*
|
|
117
|
+
* Typical use: zoom badge in a toolbar.
|
|
118
|
+
*
|
|
119
|
+
* ```tsx
|
|
120
|
+
* const zoom = useCameraSnapshot(handle, (c) => c.zoom, 1);
|
|
121
|
+
* return <div>{Math.round(zoom * 100)}%</div>;
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* The `fallback` is what's returned when `handle` is `null` (before mount /
|
|
125
|
+
* after detach). It's also the SSR snapshot value — anything that won't
|
|
126
|
+
* mismatch with the first client render.
|
|
127
|
+
*/
|
|
128
|
+
function useCameraSnapshot(handle, selector, fallback) {
|
|
129
|
+
return (0, react.useSyncExternalStore)((cb) => handle?.camera.subscribe(cb) ?? (() => {}), () => handle ? selector(handle.camera) : fallback, () => fallback);
|
|
130
|
+
}
|
|
92
131
|
//#endregion
|
|
93
132
|
exports.SvgEditorCanvas = SvgEditorCanvas;
|
|
94
133
|
exports.SvgEditorProvider = SvgEditorProvider;
|
|
134
|
+
exports.useCameraSnapshot = useCameraSnapshot;
|
|
95
135
|
exports.useCommands = useCommands;
|
|
96
136
|
exports.useEditorState = useEditorState;
|
|
97
137
|
exports.useSvgEditor = useSvgEditor;
|
package/dist/react.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { t as createSvgEditor } from "./editor-
|
|
3
|
-
import { t as attach_dom_surface } from "./dom-
|
|
2
|
+
import { t as createSvgEditor } from "./editor-CjK56cgb.mjs";
|
|
3
|
+
import { t as attach_dom_surface } from "./dom-CmOu0HvI.mjs";
|
|
4
4
|
import { createContext, useContext, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
//#region src/react.tsx
|
|
@@ -37,19 +37,38 @@ function SvgEditorProvider({ svg, providers, style, children }) {
|
|
|
37
37
|
/**
|
|
38
38
|
* Renders the editor's SVG into a `div` and wires it to the DOM surface.
|
|
39
39
|
*
|
|
40
|
-
* Internally calls `attach_dom_surface(editor, { container })` on
|
|
41
|
-
* `handle.detach()` on unmount.
|
|
42
|
-
*
|
|
40
|
+
* Internally calls `attach_dom_surface(editor, { container, ... })` on
|
|
41
|
+
* mount and `handle.detach()` on unmount. Surface-scoped concerns (camera,
|
|
42
|
+
* gestures) are reached via the `onAttach` callback — there is no global
|
|
43
|
+
* context for them, because a host may mount multiple canvases in the
|
|
44
|
+
* same editor session.
|
|
43
45
|
*/
|
|
44
|
-
function SvgEditorCanvas({ className, style }) {
|
|
46
|
+
function SvgEditorCanvas({ className, style, gestures, fit, initial_camera, onAttach }) {
|
|
45
47
|
const editor = useSvgEditor();
|
|
46
48
|
const ref = useRef(null);
|
|
49
|
+
const on_attach_ref = useRef(onAttach);
|
|
50
|
+
on_attach_ref.current = onAttach;
|
|
51
|
+
const initial_camera_ref = useRef(initial_camera);
|
|
52
|
+
initial_camera_ref.current = initial_camera;
|
|
47
53
|
useEffect(() => {
|
|
48
54
|
const container = ref.current;
|
|
49
55
|
if (!container) return;
|
|
50
|
-
const handle = attach_dom_surface(editor, {
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
const handle = attach_dom_surface(editor, {
|
|
57
|
+
container,
|
|
58
|
+
gestures,
|
|
59
|
+
fit,
|
|
60
|
+
initial_camera: initial_camera_ref.current
|
|
61
|
+
});
|
|
62
|
+
on_attach_ref.current?.(handle);
|
|
63
|
+
return () => {
|
|
64
|
+
on_attach_ref.current?.(null);
|
|
65
|
+
handle.detach();
|
|
66
|
+
};
|
|
67
|
+
}, [
|
|
68
|
+
editor,
|
|
69
|
+
gestures,
|
|
70
|
+
fit
|
|
71
|
+
]);
|
|
53
72
|
return /* @__PURE__ */ jsx("div", {
|
|
54
73
|
ref,
|
|
55
74
|
className,
|
|
@@ -88,5 +107,25 @@ function useCommands() {
|
|
|
88
107
|
const editor = useSvgEditor();
|
|
89
108
|
return useMemo(() => editor.commands, [editor]);
|
|
90
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Subscribe to a slice of `handle.camera` from a `DomSurfaceHandle`. Pass
|
|
112
|
+
* the handle (or null if it isn't attached yet) and a selector that reads
|
|
113
|
+
* what you need from the camera. The returned value updates on every
|
|
114
|
+
* camera mutation — does NOT bump `editor.state.version`.
|
|
115
|
+
*
|
|
116
|
+
* Typical use: zoom badge in a toolbar.
|
|
117
|
+
*
|
|
118
|
+
* ```tsx
|
|
119
|
+
* const zoom = useCameraSnapshot(handle, (c) => c.zoom, 1);
|
|
120
|
+
* return <div>{Math.round(zoom * 100)}%</div>;
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* The `fallback` is what's returned when `handle` is `null` (before mount /
|
|
124
|
+
* after detach). It's also the SSR snapshot value — anything that won't
|
|
125
|
+
* mismatch with the first client render.
|
|
126
|
+
*/
|
|
127
|
+
function useCameraSnapshot(handle, selector, fallback) {
|
|
128
|
+
return useSyncExternalStore((cb) => handle?.camera.subscribe(cb) ?? (() => {}), () => handle ? selector(handle.camera) : fallback, () => fallback);
|
|
129
|
+
}
|
|
91
130
|
//#endregion
|
|
92
|
-
export { SvgEditorCanvas, SvgEditorProvider, useCommands, useEditorState, useSvgEditor };
|
|
131
|
+
export { SvgEditorCanvas, SvgEditorProvider, useCameraSnapshot, useCommands, useEditorState, useSvgEditor };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grida/svg-editor",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.4",
|
|
4
4
|
"description": "Headless SVG editor (experimental).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Grida",
|
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
"types": "./dist/react.d.ts",
|
|
27
27
|
"import": "./dist/react.mjs",
|
|
28
28
|
"require": "./dist/react.js"
|
|
29
|
+
},
|
|
30
|
+
"./presets": {
|
|
31
|
+
"types": "./dist/presets.d.ts",
|
|
32
|
+
"import": "./dist/presets.mjs",
|
|
33
|
+
"require": "./dist/presets.js"
|
|
29
34
|
}
|
|
30
35
|
},
|
|
31
36
|
"publishConfig": {
|
|
@@ -34,11 +39,11 @@
|
|
|
34
39
|
},
|
|
35
40
|
"dependencies": {
|
|
36
41
|
"svg-pathdata": "^7.2.0",
|
|
37
|
-
"@grida/cmath": "0.1.0",
|
|
38
42
|
"@grida/history": "0.1.0",
|
|
39
|
-
"@grida/text-editor": "0.1.0",
|
|
40
43
|
"@grida/hud": "0.1.0",
|
|
41
|
-
"@grida/
|
|
44
|
+
"@grida/text-editor": "0.1.0",
|
|
45
|
+
"@grida/keybinding": "0.1.0",
|
|
46
|
+
"@grida/cmath": "0.1.0"
|
|
42
47
|
},
|
|
43
48
|
"devDependencies": {
|
|
44
49
|
"@types/react": "^19",
|