@vkcha/svg-core 0.1.2 → 1.1.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.
package/README.md CHANGED
@@ -7,8 +7,9 @@ Lightweight **SVG scene rendering core** for the web (TypeScript):
7
7
  - **Viewport culling** (removes offscreen nodes from DOM for performance)
8
8
  - **Hit-testing + node events** (`click`, “double click”, right click)
9
9
  - **SVG fragment utilities** (sanitize/measure/parse fragments)
10
+ - **Smooth animations** (`animateTo` with custom easing)
10
11
 
11
- **Live demo:** `https://vkcha.com`
12
+ **Live demo:** [vkcha.com](https://vkcha.com) | **Docs:** [vkcha.com/#docs](https://vkcha.com/#docs)
12
13
 
13
14
  ---
14
15
 
@@ -111,6 +112,150 @@ export function SvgScene() {
111
112
  }
112
113
  ```
113
114
 
115
+ If you prefer a ready-made component wrapper, use `@vkcha/svg-core-react`:
116
+
117
+ ```tsx
118
+ import { useMemo } from "react";
119
+ import { SvgCoreView } from "@vkcha/svg-core-react";
120
+
121
+ export function SvgScene() {
122
+ const nodes = useMemo(
123
+ () => [
124
+ { id: "a", x: 0, y: 0, fragment: `<rect width="120" height="60" rx="10" fill="#10b981"/>` },
125
+ ],
126
+ [],
127
+ );
128
+
129
+ return (
130
+ <SvgCoreView
131
+ init={{
132
+ panZoom: { wheelMode: "pan", zoomRequiresCtrlKey: true },
133
+ culling: { enabled: true },
134
+ }}
135
+ nodes={nodes}
136
+ style={{ width: "100%", height: "100%" }}
137
+ />
138
+ );
139
+ }
140
+ ```
141
+
142
+ ---
143
+
144
+ ### Pan/zoom only (no nodes)
145
+
146
+ If you just want pan/zoom and plan to manage your own SVG content:
147
+
148
+ ```ts
149
+ import { PanZoomCanvas } from "@vkcha/svg-core";
150
+
151
+ const svg = document.querySelector("#canvas") as SVGSVGElement;
152
+ const canvas = new PanZoomCanvas(svg, { wheelMode: "pan" });
153
+
154
+ // Add your own SVG elements under the world layer:
155
+ const layer = canvas.createLayer("custom");
156
+ layer.innerHTML = `
157
+ <rect x="0" y="0" width="200" height="120" fill="#60a5fa" />
158
+ <circle cx="240" cy="80" r="40" fill="#f59e0b" />
159
+ `;
160
+ ```
161
+
162
+ If you want the same thing in React, use `@vkcha/svg-core-react`:
163
+
164
+ ```tsx
165
+ import { useEffect, useRef } from "react";
166
+ import { PanZoomCanvasView } from "@vkcha/svg-core-react";
167
+
168
+ export function PanZoomOnly() {
169
+ const viewRef = useRef<React.ElementRef<typeof PanZoomCanvasView> | null>(null);
170
+
171
+ useEffect(() => {
172
+ const world = viewRef.current?.getWorld();
173
+ if (!world) return;
174
+ world.innerHTML = `
175
+ <rect x="0" y="0" width="200" height="120" fill="#60a5fa" />
176
+ <circle cx="240" cy="80" r="40" fill="#f59e0b" />
177
+ `;
178
+ }, []);
179
+
180
+ return (
181
+ <PanZoomCanvasView
182
+ ref={viewRef}
183
+ options={{ wheelMode: "pan" }}
184
+ style={{ width: "100%", height: "100%" }}
185
+ />
186
+ );
187
+ }
188
+ ```
189
+
190
+ If you prefer to initialize `SvgCore` and still add custom content:
191
+
192
+ ```ts
193
+ import { SvgCore } from "@vkcha/svg-core";
194
+
195
+ const svg = document.querySelector("#canvas") as SVGSVGElement;
196
+ const core = new SvgCore(svg, { panZoom: { wheelMode: "pan" } });
197
+
198
+ const layer = core.createWorldLayer("custom", { position: "below-nodes" });
199
+ layer.innerHTML = `<path d="M10 10H200V60Z" fill="#34d399" />`;
200
+ ```
201
+
202
+ #### `PanZoomCanvas` API (concise reference)
203
+
204
+ **State**
205
+
206
+ - `canvas.state: { zoom, panX, panY }` — current state
207
+ - `canvas.setState(partial)` — set state immediately
208
+ - `canvas.reset()` — reset to `{ zoom: 1, panX: 0, panY: 0 }`
209
+
210
+ **Animation**
211
+
212
+ - `canvas.animateTo(target, durationMs?, easing?)` — animate to a target state (returns `Promise<void>`)
213
+ - `canvas.stopAnimation()` — cancel any running animation
214
+
215
+ Example: smooth zoom with ease-out cubic:
216
+
217
+ ```ts
218
+ await canvas.animateTo(
219
+ { zoom: 2, panX: 100, panY: 50 },
220
+ 400,
221
+ (t) => 1 - Math.pow(1 - t, 3), // ease-out cubic
222
+ );
223
+ ```
224
+
225
+ **Layers**
226
+
227
+ - `canvas.createLayer(name?, opts?)` — create a `<g>` inside the world
228
+ - `opts.position`: `"front"` (default) or `"back"`
229
+ - `opts.pointerEvents`: CSS `pointer-events` value (e.g., `"none"`)
230
+
231
+ **Subscription**
232
+
233
+ - `canvas.subscribe(fn)` — listen to state changes (returns unsubscribe function)
234
+
235
+ **Options**
236
+
237
+ - `canvas.setOptions(partial)` — update options at runtime
238
+
239
+ **World Group Configuration**
240
+
241
+ Configure the world `<g>` element with custom attributes:
242
+
243
+ ```ts
244
+ const canvas = new PanZoomCanvas(svg, {
245
+ worldGroup: {
246
+ id: "canvas-world",
247
+ attributes: { "data-testid": "world" },
248
+ dynamicAttributes: {
249
+ "data-zoom": (zoom) => String(Math.round(zoom * 100)),
250
+ },
251
+ },
252
+ });
253
+ ```
254
+
255
+ **Cleanup**
256
+
257
+ - `canvas.destroy()` — remove all listeners, cancel animations
258
+
114
259
  ---
115
260
 
116
261
  ### Core concepts
@@ -129,6 +274,7 @@ Useful properties:
129
274
  - `core.world`: a `<g>` for “world space” content
130
275
  - `core.state`: `{ zoom, panX, panY }`
131
276
  - `core.panZoomOptions`: merged pan/zoom options (min/max, wheel mode, etc.)
277
+ - `core.createWorldLayer(...)`: create a custom `<g>` inside the world layer
132
278
 
133
279
  Pan/zoom can be configured **on init** via `new SvgCore(svg, { panZoom: ... })` and **any time later** via `core.configurePanZoom(...)`.
134
280
 
@@ -148,8 +294,10 @@ Pan/zoom can be configured **on init** via `new SvgCore(svg, { panZoom: ... })`
148
294
  - `minZoom: 0.2`
149
295
  - `maxZoom: 8`
150
296
  - `zoomSpeed: 1`
297
+ - `pinchZoomSpeed: 2` — extra speed multiplier for trackpad pinch gestures
151
298
  - `invertZoom: false`
152
299
  - `invertPan: false`
300
+ - `worldGroup: undefined` — optional configuration for the world `<g>` element (id, attributes, dynamic attributes)
153
301
 
154
302
  **Culling defaults**
155
303