zoom-canvas-engine 0.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 ADDED
@@ -0,0 +1,171 @@
1
+ # zoom-canvas-engine
2
+
3
+ A high-performance, zero-dependency\* zoom & pan engine for [Konva.js](https://konvajs.org/) + React.
4
+
5
+ > \*Peer dependencies: `react`, `konva`, `react-konva`
6
+
7
+ ## Features
8
+
9
+ - 🎯 **Pointer-aware zooming** — zooms toward the cursor position, not the origin
10
+ - 🔄 **Controlled & uncontrolled modes** — use it your way
11
+ - 📐 **Fit-to-content** — auto-center and scale content to fill the viewport
12
+ - 🖱️ **Natural panning** — scroll wheel, shift+scroll for horizontal, drag-to-pan
13
+ - ⚙️ **Fully configurable** — zoom speed, limits, modifier keys, padding, etc.
14
+ - 🎨 **Standalone ZoomControls** — no external UI library needed
15
+ - 📦 **TypeScript-first** — fully typed API
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install zoom-canvas-engine
21
+ ```
22
+
23
+ ## Peer Dependencies
24
+
25
+ ```bash
26
+ npm install react react-dom konva react-konva
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ### 1. Zero-config (uncontrolled)
32
+
33
+ ```tsx
34
+ import { ZoomCanvas, ZoomControls, useZoomCanvas } from 'zoom-canvas-engine';
35
+ import { Layer, Rect } from 'react-konva';
36
+
37
+ function App() {
38
+ const engine = useZoomCanvas();
39
+
40
+ return (
41
+ <div style={{ width: '100vw', height: '100vh', position: 'relative' }}>
42
+ <ZoomCanvas width={800} height={600} engine={engine}>
43
+ <Layer>
44
+ <Rect x={100} y={100} width={200} height={200} fill="coral" />
45
+ </Layer>
46
+ </ZoomCanvas>
47
+
48
+ <ZoomControls
49
+ zoom={engine.zoom}
50
+ onZoom={engine.setZoom}
51
+ onZoomIn={engine.zoomIn}
52
+ onZoomOut={engine.zoomOut}
53
+ onFit={() => engine.fitToContent(800, 600, 1000, 1000)}
54
+ onReset={engine.reset}
55
+ showReset
56
+ style={{ position: 'absolute', bottom: 24, left: '50%', transform: 'translateX(-50%)' }}
57
+ />
58
+ </div>
59
+ );
60
+ }
61
+ ```
62
+
63
+ ### 2. Controlled mode
64
+
65
+ ```tsx
66
+ function App() {
67
+ const [zoom, setZoom] = useState(1);
68
+ const [position, setPosition] = useState({ x: 0, y: 0 });
69
+
70
+ return (
71
+ <ZoomCanvas
72
+ width={800}
73
+ height={600}
74
+ zoom={zoom}
75
+ position={position}
76
+ onZoom={setZoom}
77
+ onPosition={setPosition}
78
+ >
79
+ <Layer>
80
+ <Rect x={0} y={0} width={500} height={500} fill="steelblue" />
81
+ </Layer>
82
+ </ZoomCanvas>
83
+ );
84
+ }
85
+ ```
86
+
87
+ ### 3. With custom config
88
+
89
+ ```tsx
90
+ const engine = useZoomCanvas({
91
+ initialZoom: 0.5,
92
+ minZoom: 0.1,
93
+ maxZoom: 10,
94
+ zoomSpeed: 1.15,
95
+ requireModifierToZoom: true, // Ctrl/Cmd + scroll to zoom
96
+ fitPadding: 60,
97
+ });
98
+ ```
99
+
100
+ ## API Reference
101
+
102
+ ### `useZoomCanvas(options?)`
103
+
104
+ Returns an engine object with:
105
+
106
+ | Property | Type | Description |
107
+ |---|---|---|
108
+ | `zoom` | `number` | Current zoom level |
109
+ | `position` | `Vector2d` | Current canvas position `{ x, y }` |
110
+ | `setZoom(z)` | `(number) => void` | Set zoom (clamped to min/max) |
111
+ | `setPosition(p)` | `(Vector2d) => void` | Set position |
112
+ | `zoomToPoint(point, zoom)` | | Zoom toward a specific screen coordinate |
113
+ | `zoomToCenter(vw, vh, zoom)` | | Zoom toward center of viewport |
114
+ | `zoomIn()` / `zoomOut()` | | Step zoom by `zoomSpeed` |
115
+ | `fitToContent(vw, vh, cw, ch)` | | Fit & center content in viewport |
116
+ | `centerContent(vw, vh, cw, ch)` | | Center without changing zoom |
117
+ | `reset()` | | Reset to initial state |
118
+ | `config` | `ZoomCanvasConfig` | Resolved configuration |
119
+
120
+ ### `<ZoomCanvas>`
121
+
122
+ | Prop | Type | Default | Description |
123
+ |---|---|---|---|
124
+ | `width` | `number` | **required** | Canvas width |
125
+ | `height` | `number` | **required** | Canvas height |
126
+ | `engine` | `UseZoomCanvasReturn` | — | Pass hook return for full control |
127
+ | `zoom` | `number` | — | Controlled zoom |
128
+ | `position` | `Vector2d` | — | Controlled position |
129
+ | `onZoom` | `(z) => void` | — | Zoom change callback |
130
+ | `onPosition` | `(p) => void` | — | Position change callback |
131
+ | `draggable` | `boolean` | `config.dragPan` | Override drag behavior |
132
+ | `stageRef` | `RefObject` | — | Access Konva Stage |
133
+ | `onStageClick` | `(e) => void` | — | Stage background click |
134
+ | `config` | `Partial<ZoomCanvasConfig>` | — | Config overrides |
135
+
136
+ ### `<ZoomControls>`
137
+
138
+ Self-contained zoom UI with +/- buttons, slider, percentage label, fit & reset.
139
+
140
+ | Prop | Type | Default | Description |
141
+ |---|---|---|---|
142
+ | `zoom` | `number` | **required** | Current zoom |
143
+ | `onZoom` | `(z) => void` | **required** | Zoom change |
144
+ | `onZoomIn` / `onZoomOut` | `() => void` | — | Custom step handlers |
145
+ | `onFit` | `() => void` | — | Fit button handler |
146
+ | `onReset` | `() => void` | — | Reset button handler |
147
+ | `showFit` | `boolean` | `true` | Show fit button |
148
+ | `showReset` | `boolean` | `false` | Show reset button |
149
+ | `showLabel` | `boolean` | `true` | Show percentage |
150
+ | `showSlider` | `boolean` | `true` | Show range slider |
151
+
152
+ ## Configuration Defaults
153
+
154
+ ```ts
155
+ {
156
+ minZoom: 0.05,
157
+ maxZoom: 20,
158
+ zoomSpeed: 1.08,
159
+ wheelZoom: true,
160
+ wheelPan: true,
161
+ dragPan: true,
162
+ requireModifierToZoom: false,
163
+ fitPadding: 40,
164
+ initialZoom: 1,
165
+ initialPosition: { x: 0, y: 0 },
166
+ }
167
+ ```
168
+
169
+ ## License
170
+
171
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,374 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var reactKonva = require('react-konva');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var React__default = /*#__PURE__*/_interopDefault(React);
10
+
11
+ // src/ZoomCanvas.tsx
12
+
13
+ // src/types.ts
14
+ var DEFAULT_CONFIG = {
15
+ minZoom: 0.05,
16
+ maxZoom: 20,
17
+ zoomSpeed: 1.08,
18
+ wheelZoom: true,
19
+ wheelPan: true,
20
+ dragPan: true,
21
+ requireModifierToZoom: false,
22
+ fitPadding: 40,
23
+ initialZoom: 1,
24
+ initialPosition: { x: 0, y: 0 }
25
+ };
26
+
27
+ // src/useZoomCanvas.ts
28
+ var clamp = (value, min, max) => Math.min(Math.max(value, min), max);
29
+ var useZoomCanvas = (options = {}) => {
30
+ const config = React.useMemo(
31
+ () => ({ ...DEFAULT_CONFIG, ...options }),
32
+ // We intentionally spread — config rarely changes
33
+ // eslint-disable-next-line react-hooks/exhaustive-deps
34
+ [
35
+ options.minZoom,
36
+ options.maxZoom,
37
+ options.zoomSpeed,
38
+ options.wheelZoom,
39
+ options.wheelPan,
40
+ options.dragPan,
41
+ options.requireModifierToZoom,
42
+ options.fitPadding,
43
+ options.initialZoom,
44
+ options.initialPosition
45
+ ]
46
+ );
47
+ const [internalZoom, setInternalZoom] = React.useState(config.initialZoom);
48
+ const [internalPosition, setInternalPosition] = React.useState(config.initialPosition);
49
+ const isZoomControlled = options.zoom !== void 0;
50
+ const isPositionControlled = options.position !== void 0;
51
+ const zoom = isZoomControlled ? options.zoom : internalZoom;
52
+ const position = isPositionControlled ? options.position : internalPosition;
53
+ const callbacksRef = React.useRef(options);
54
+ callbacksRef.current = options;
55
+ const viewportRef = React.useRef({ width: 0, height: 0 });
56
+ const setViewportSize = React.useCallback((size) => {
57
+ viewportRef.current = size;
58
+ }, []);
59
+ const updateZoom = React.useCallback(
60
+ (newZoom) => {
61
+ const clamped = clamp(newZoom, config.minZoom, config.maxZoom);
62
+ const vp = viewportRef.current;
63
+ const cx = vp.width / 2;
64
+ const cy = vp.height / 2;
65
+ const canvasX = (cx - position.x) / zoom;
66
+ const canvasY = (cy - position.y) / zoom;
67
+ const newPos = {
68
+ x: cx - canvasX * clamped,
69
+ y: cy - canvasY * clamped
70
+ };
71
+ if (!isZoomControlled) setInternalZoom(clamped);
72
+ if (!isPositionControlled) setInternalPosition(newPos);
73
+ callbacksRef.current.onZoomChange?.(clamped);
74
+ callbacksRef.current.onPositionChange?.(newPos);
75
+ callbacksRef.current.onChange?.({ zoom: clamped, position: newPos });
76
+ },
77
+ [config.minZoom, config.maxZoom, isZoomControlled, isPositionControlled, position, zoom]
78
+ );
79
+ const updatePosition = React.useCallback(
80
+ (newPos) => {
81
+ if (!isPositionControlled) setInternalPosition(newPos);
82
+ callbacksRef.current.onPositionChange?.(newPos);
83
+ callbacksRef.current.onChange?.({ zoom, position: newPos });
84
+ },
85
+ [isPositionControlled, zoom]
86
+ );
87
+ const updateBoth = React.useCallback(
88
+ (newZoom, newPos) => {
89
+ const clamped = clamp(newZoom, config.minZoom, config.maxZoom);
90
+ if (!isZoomControlled) setInternalZoom(clamped);
91
+ if (!isPositionControlled) setInternalPosition(newPos);
92
+ callbacksRef.current.onZoomChange?.(clamped);
93
+ callbacksRef.current.onPositionChange?.(newPos);
94
+ callbacksRef.current.onChange?.({ zoom: clamped, position: newPos });
95
+ },
96
+ [config.minZoom, config.maxZoom, isZoomControlled, isPositionControlled]
97
+ );
98
+ const zoomToPoint = React.useCallback(
99
+ (point, newZoom) => {
100
+ const clamped = clamp(newZoom, config.minZoom, config.maxZoom);
101
+ const canvasPoint = {
102
+ x: (point.x - position.x) / zoom,
103
+ y: (point.y - position.y) / zoom
104
+ };
105
+ const newPos = {
106
+ x: point.x - canvasPoint.x * clamped,
107
+ y: point.y - canvasPoint.y * clamped
108
+ };
109
+ updateBoth(clamped, newPos);
110
+ },
111
+ [zoom, position, config.minZoom, config.maxZoom, updateBoth]
112
+ );
113
+ const zoomToCenter = React.useCallback(
114
+ (viewportWidth, viewportHeight, newZoom) => {
115
+ zoomToPoint({ x: viewportWidth / 2, y: viewportHeight / 2 }, newZoom);
116
+ },
117
+ [zoomToPoint]
118
+ );
119
+ const zoomIn = React.useCallback(() => {
120
+ updateZoom(zoom * config.zoomSpeed);
121
+ }, [zoom, config.zoomSpeed, updateZoom]);
122
+ const zoomOut = React.useCallback(() => {
123
+ updateZoom(zoom / config.zoomSpeed);
124
+ }, [zoom, config.zoomSpeed, updateZoom]);
125
+ const fitToContent = React.useCallback(
126
+ (vw, vh, cw, ch) => {
127
+ const pad = config.fitPadding;
128
+ const scaleX = (vw - pad * 2) / cw;
129
+ const scaleY = (vh - pad * 2) / ch;
130
+ const newZoom = clamp(Math.min(scaleX, scaleY), config.minZoom, config.maxZoom);
131
+ const newPos = {
132
+ x: (vw - cw * newZoom) / 2,
133
+ y: (vh - ch * newZoom) / 2
134
+ };
135
+ updateBoth(newZoom, newPos);
136
+ },
137
+ [config.fitPadding, config.minZoom, config.maxZoom, updateBoth]
138
+ );
139
+ const centerContent = React.useCallback(
140
+ (vw, vh, cw, ch) => {
141
+ updatePosition({
142
+ x: (vw - cw * zoom) / 2,
143
+ y: (vh - ch * zoom) / 2
144
+ });
145
+ },
146
+ [zoom, updatePosition]
147
+ );
148
+ const reset = React.useCallback(() => {
149
+ updateBoth(config.initialZoom, config.initialPosition);
150
+ }, [config.initialZoom, config.initialPosition, updateBoth]);
151
+ const _handleWheel = React.useCallback(
152
+ (e, stage) => {
153
+ e.evt.preventDefault();
154
+ const hasModifier = e.evt.ctrlKey || e.evt.metaKey;
155
+ const shouldZoom = config.requireModifierToZoom ? hasModifier : !e.evt.shiftKey;
156
+ if (shouldZoom && config.wheelZoom) {
157
+ const pointer = stage.getPointerPosition();
158
+ if (!pointer) return;
159
+ const direction = e.evt.deltaY > 0 ? -1 : 1;
160
+ const newZoom = direction > 0 ? zoom * config.zoomSpeed : zoom / config.zoomSpeed;
161
+ zoomToPoint(pointer, newZoom);
162
+ } else if (config.wheelPan) {
163
+ const dx = e.evt.shiftKey ? -e.evt.deltaY : -e.evt.deltaX;
164
+ const dy = e.evt.shiftKey ? 0 : -e.evt.deltaY;
165
+ updatePosition({ x: position.x + dx, y: position.y + dy });
166
+ }
167
+ },
168
+ [zoom, position, config, zoomToPoint, updatePosition]
169
+ );
170
+ const _handleDragEnd = React.useCallback(
171
+ (e, stage) => {
172
+ if (e.target === stage) {
173
+ updatePosition({ x: e.target.x(), y: e.target.y() });
174
+ }
175
+ },
176
+ [updatePosition]
177
+ );
178
+ return {
179
+ zoom,
180
+ position,
181
+ setZoom: updateZoom,
182
+ setPosition: updatePosition,
183
+ zoomToPoint,
184
+ zoomToCenter,
185
+ zoomIn,
186
+ zoomOut,
187
+ fitToContent,
188
+ centerContent,
189
+ reset,
190
+ config,
191
+ _handleWheel,
192
+ _handleDragEnd,
193
+ /** @internal used by ZoomCanvas to track viewport size */
194
+ _setViewportSize: setViewportSize
195
+ };
196
+ };
197
+ var ZoomCanvas = ({
198
+ width,
199
+ height,
200
+ children,
201
+ stageRef: externalStageRef,
202
+ onStageClick,
203
+ draggable,
204
+ className,
205
+ style,
206
+ zoom: controlledZoom,
207
+ position: controlledPosition,
208
+ onZoom,
209
+ onPosition,
210
+ engine: externalEngine,
211
+ config: configOverrides
212
+ }) => {
213
+ const internalStageRef = React.useRef(null);
214
+ const stageRef = externalStageRef || internalStageRef;
215
+ const internalEngine = useZoomCanvas({
216
+ ...configOverrides,
217
+ zoom: controlledZoom,
218
+ position: controlledPosition,
219
+ onZoomChange: onZoom,
220
+ onPositionChange: onPosition
221
+ });
222
+ const engine = externalEngine || internalEngine;
223
+ React__default.default.useEffect(() => {
224
+ engine._setViewportSize({ width, height });
225
+ }, [width, height, engine._setViewportSize]);
226
+ const isDragEnabled = draggable ?? engine.config.dragPan;
227
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: { overflow: "hidden", ...style }, children: /* @__PURE__ */ jsxRuntime.jsx(
228
+ reactKonva.Stage,
229
+ {
230
+ ref: stageRef,
231
+ width,
232
+ height,
233
+ scaleX: engine.zoom,
234
+ scaleY: engine.zoom,
235
+ x: engine.position.x,
236
+ y: engine.position.y,
237
+ draggable: isDragEnabled,
238
+ onWheel: (e) => {
239
+ engine._handleWheel(e, stageRef.current || e.target.getStage());
240
+ },
241
+ onClick: onStageClick,
242
+ onTap: onStageClick,
243
+ onDragEnd: (e) => {
244
+ engine._handleDragEnd(e, stageRef.current || e.target.getStage());
245
+ },
246
+ children
247
+ }
248
+ ) });
249
+ };
250
+ var ZoomControls = ({
251
+ zoom,
252
+ onZoom,
253
+ onZoomIn,
254
+ onZoomOut,
255
+ onFit,
256
+ onReset,
257
+ minZoom = 5,
258
+ maxZoom = 500,
259
+ className = "",
260
+ style,
261
+ showFit = true,
262
+ showReset = false,
263
+ showLabel = true,
264
+ showSlider = true
265
+ }) => {
266
+ const zoomPercent = Math.round(zoom * 100);
267
+ const btnStyle = {
268
+ display: "inline-flex",
269
+ alignItems: "center",
270
+ justifyContent: "center",
271
+ width: 28,
272
+ height: 28,
273
+ border: "none",
274
+ borderRadius: "50%",
275
+ background: "transparent",
276
+ cursor: "pointer",
277
+ fontSize: 16,
278
+ fontWeight: 600,
279
+ color: "inherit",
280
+ transition: "background 0.15s"
281
+ };
282
+ const containerStyle = {
283
+ display: "flex",
284
+ alignItems: "center",
285
+ gap: 8,
286
+ padding: "6px 16px",
287
+ borderRadius: 9999,
288
+ background: "white",
289
+ boxShadow: "0 4px 24px rgba(0,0,0,0.08), 0 0 0 1px rgba(0,0,0,0.06)",
290
+ fontSize: 12,
291
+ fontFamily: "system-ui, -apple-system, sans-serif",
292
+ userSelect: "none",
293
+ ...style
294
+ };
295
+ const sliderStyle = {
296
+ width: 80,
297
+ height: 4,
298
+ appearance: "none",
299
+ background: "#e5e5e5",
300
+ borderRadius: 2,
301
+ outline: "none",
302
+ cursor: "pointer"
303
+ };
304
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: containerStyle, children: [
305
+ /* @__PURE__ */ jsxRuntime.jsx(
306
+ "button",
307
+ {
308
+ style: btnStyle,
309
+ onClick: () => onZoomOut ? onZoomOut() : onZoom(zoom / 1.15),
310
+ title: "Zoom out",
311
+ onMouseEnter: (e) => e.currentTarget.style.background = "#f3f3f3",
312
+ onMouseLeave: (e) => e.currentTarget.style.background = "transparent",
313
+ children: "\u2212"
314
+ }
315
+ ),
316
+ showSlider && /* @__PURE__ */ jsxRuntime.jsx(
317
+ "input",
318
+ {
319
+ type: "range",
320
+ min: minZoom,
321
+ max: maxZoom,
322
+ value: zoomPercent,
323
+ onChange: (e) => onZoom(Number(e.target.value) / 100),
324
+ style: sliderStyle,
325
+ onMouseDown: (e) => e.stopPropagation()
326
+ }
327
+ ),
328
+ /* @__PURE__ */ jsxRuntime.jsx(
329
+ "button",
330
+ {
331
+ style: btnStyle,
332
+ onClick: () => onZoomIn ? onZoomIn() : onZoom(zoom * 1.15),
333
+ title: "Zoom in",
334
+ onMouseEnter: (e) => e.currentTarget.style.background = "#f3f3f3",
335
+ onMouseLeave: (e) => e.currentTarget.style.background = "transparent",
336
+ children: "+"
337
+ }
338
+ ),
339
+ showLabel && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { width: 40, textAlign: "center", fontWeight: 700, fontSize: 11 }, children: [
340
+ zoomPercent,
341
+ "%"
342
+ ] }),
343
+ showFit && onFit && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
344
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 1, height: 16, background: "#e5e5e5" } }),
345
+ /* @__PURE__ */ jsxRuntime.jsx(
346
+ "button",
347
+ {
348
+ style: { ...btnStyle, width: "auto", borderRadius: 4, padding: "2px 8px", fontSize: 10, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.05em" },
349
+ onClick: onFit,
350
+ onMouseEnter: (e) => e.currentTarget.style.background = "#f3f3f3",
351
+ onMouseLeave: (e) => e.currentTarget.style.background = "transparent",
352
+ children: "Fit"
353
+ }
354
+ )
355
+ ] }),
356
+ showReset && onReset && /* @__PURE__ */ jsxRuntime.jsx(
357
+ "button",
358
+ {
359
+ style: { ...btnStyle, width: "auto", borderRadius: 4, padding: "2px 8px", fontSize: 10, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.05em" },
360
+ onClick: onReset,
361
+ onMouseEnter: (e) => e.currentTarget.style.background = "#f3f3f3",
362
+ onMouseLeave: (e) => e.currentTarget.style.background = "transparent",
363
+ children: "Reset"
364
+ }
365
+ )
366
+ ] });
367
+ };
368
+
369
+ exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
370
+ exports.ZoomCanvas = ZoomCanvas;
371
+ exports.ZoomControls = ZoomControls;
372
+ exports.useZoomCanvas = useZoomCanvas;
373
+ //# sourceMappingURL=index.cjs.map
374
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/useZoomCanvas.ts","../src/ZoomCanvas.tsx","../src/ZoomControls.tsx"],"names":["useMemo","useState","useRef","useCallback","React","jsx","Stage","jsxs","Fragment"],"mappings":"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","file":"index.cjs","sourcesContent":["import Konva from 'konva';\r\n\r\n// ─── Core Types ──────────────────────────────────────────────────────────────\r\n\r\nexport interface Vector2d {\r\n x: number;\r\n y: number;\r\n}\r\n\r\nexport interface ZoomCanvasState {\r\n zoom: number;\r\n position: Vector2d;\r\n}\r\n\r\n// ─── Configuration ───────────────────────────────────────────────────────────\r\n\r\nexport interface ZoomCanvasConfig {\r\n /** Minimum allowed zoom level. @default 0.05 */\r\n minZoom: number;\r\n /** Maximum allowed zoom level. @default 20 */\r\n maxZoom: number;\r\n /** Zoom speed multiplier per wheel tick. @default 1.08 */\r\n zoomSpeed: number;\r\n /** Whether wheel zooming is enabled. @default true */\r\n wheelZoom: boolean;\r\n /** Whether panning via scroll wheel is enabled. @default true */\r\n wheelPan: boolean;\r\n /** Whether drag-to-pan is enabled. @default true */\r\n dragPan: boolean;\r\n /** Whether Ctrl/Cmd must be held to zoom (false = zoom without modifier). @default false */\r\n requireModifierToZoom: boolean;\r\n /** Padding used in fitToContent calculations. @default 40 */\r\n fitPadding: number;\r\n /** Initial zoom level. @default 1 */\r\n initialZoom: number;\r\n /** Initial position. @default { x: 0, y: 0 } */\r\n initialPosition: Vector2d;\r\n}\r\n\r\nexport const DEFAULT_CONFIG: ZoomCanvasConfig = {\r\n minZoom: 0.05,\r\n maxZoom: 20,\r\n zoomSpeed: 1.08,\r\n wheelZoom: true,\r\n wheelPan: true,\r\n dragPan: true,\r\n requireModifierToZoom: false,\r\n fitPadding: 40,\r\n initialZoom: 1,\r\n initialPosition: { x: 0, y: 0 },\r\n};\r\n\r\n// ─── Hook Options & Return ───────────────────────────────────────────────────\r\n\r\nexport interface UseZoomCanvasOptions extends Partial<ZoomCanvasConfig> {\r\n /** Controlled zoom value. */\r\n zoom?: number;\r\n /** Controlled position value. */\r\n position?: Vector2d;\r\n /** Callback fired when zoom changes. */\r\n onZoomChange?: (zoom: number) => void;\r\n /** Callback fired when position changes. */\r\n onPositionChange?: (position: Vector2d) => void;\r\n /** Callback fired on any state change. */\r\n onChange?: (state: ZoomCanvasState) => void;\r\n}\r\n\r\nexport interface UseZoomCanvasReturn {\r\n zoom: number;\r\n position: Vector2d;\r\n setZoom: (zoom: number) => void;\r\n setPosition: (pos: Vector2d) => void;\r\n /** Zoom to a specific point on the canvas (point in screen/viewport coordinates). */\r\n zoomToPoint: (point: Vector2d, newZoom: number) => void;\r\n /** Zoom to center of the given viewport dimensions. */\r\n zoomToCenter: (viewportWidth: number, viewportHeight: number, newZoom: number) => void;\r\n zoomIn: () => void;\r\n zoomOut: () => void;\r\n /** Fit content rect into viewport, centering it. */\r\n fitToContent: (viewportWidth: number, viewportHeight: number, contentWidth: number, contentHeight: number) => void;\r\n /** Center content in viewport without changing zoom. */\r\n centerContent: (viewportWidth: number, viewportHeight: number, contentWidth: number, contentHeight: number) => void;\r\n /** Reset to initial zoom and position. */\r\n reset: () => void;\r\n config: ZoomCanvasConfig;\r\n /** @internal wheel handler for ZoomCanvas */\r\n _handleWheel: (e: Konva.KonvaEventObject<WheelEvent>, stage: Konva.Stage) => void;\r\n /** @internal drag handler for ZoomCanvas */\r\n _handleDragEnd: (e: Konva.KonvaEventObject<DragEvent>, stage: Konva.Stage) => void;\r\n /** @internal viewport size tracker for center-based zoom */\r\n _setViewportSize: (size: { width: number; height: number }) => void;\r\n}\r\n\r\n// ─── Component Props ─────────────────────────────────────────────────────────\r\n\r\nexport interface ZoomCanvasProps {\r\n width: number;\r\n height: number;\r\n children?: React.ReactNode;\r\n stageRef?: React.RefObject<Konva.Stage | null>;\r\n onStageClick?: (e: Konva.KonvaEventObject<MouseEvent | TouchEvent>) => void;\r\n /** Override drag-to-pan. When undefined, uses config.dragPan. */\r\n draggable?: boolean;\r\n className?: string;\r\n style?: React.CSSProperties;\r\n\r\n // ── Controlled mode ──\r\n zoom?: number;\r\n position?: Vector2d;\r\n onZoom?: (zoom: number) => void;\r\n onPosition?: (pos: Vector2d) => void;\r\n\r\n // ── Or pass engine instance for zero-config ──\r\n engine?: UseZoomCanvasReturn;\r\n\r\n /** Config overrides (only used when engine is NOT provided). */\r\n config?: Partial<ZoomCanvasConfig>;\r\n}\r\n\r\nexport interface ZoomControlsProps {\r\n zoom: number;\r\n onZoom: (zoom: number) => void;\r\n onZoomIn?: () => void;\r\n onZoomOut?: () => void;\r\n onFit?: () => void;\r\n onReset?: () => void;\r\n minZoom?: number;\r\n maxZoom?: number;\r\n className?: string;\r\n style?: React.CSSProperties;\r\n showFit?: boolean;\r\n showReset?: boolean;\r\n showLabel?: boolean;\r\n showSlider?: boolean;\r\n}\r\n","import { useState, useCallback, useMemo, useRef } from 'react';\r\nimport Konva from 'konva';\r\nimport {\r\n Vector2d,\r\n ZoomCanvasConfig,\r\n DEFAULT_CONFIG,\r\n UseZoomCanvasOptions,\r\n UseZoomCanvasReturn,\r\n} from './types';\r\n\r\n/** @internal viewport size tracked by ZoomCanvas */\r\nexport interface ViewportSize { width: number; height: number; }\r\n\r\n/**\r\n * Clamp a value between min and max.\r\n */\r\nconst clamp = (value: number, min: number, max: number) =>\r\n Math.min(Math.max(value, min), max);\r\n\r\n/**\r\n * Core hook for zoom & pan state management.\r\n *\r\n * Supports both **controlled** and **uncontrolled** modes:\r\n * - Uncontrolled: just call `useZoomCanvas()` — it manages its own state.\r\n * - Controlled: pass `zoom`, `position`, `onZoomChange`, `onPositionChange`.\r\n *\r\n * @example\r\n * ```tsx\r\n * // Uncontrolled (simplest)\r\n * const engine = useZoomCanvas({ initialZoom: 0.8 });\r\n *\r\n * // Controlled\r\n * const [zoom, setZoom] = useState(1);\r\n * const [pos, setPos] = useState({ x: 0, y: 0 });\r\n * const engine = useZoomCanvas({ zoom, position: pos, onZoomChange: setZoom, onPositionChange: setPos });\r\n * ```\r\n */\r\nexport const useZoomCanvas = (options: UseZoomCanvasOptions = {}): UseZoomCanvasReturn => {\r\n const config = useMemo<ZoomCanvasConfig>(\r\n () => ({ ...DEFAULT_CONFIG, ...options }),\r\n // We intentionally spread — config rarely changes\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [\r\n options.minZoom, options.maxZoom, options.zoomSpeed,\r\n options.wheelZoom, options.wheelPan, options.dragPan,\r\n options.requireModifierToZoom, options.fitPadding,\r\n options.initialZoom, options.initialPosition,\r\n ],\r\n );\r\n\r\n // ── Internal state (used in uncontrolled mode) ──────────────────────────\r\n const [internalZoom, setInternalZoom] = useState(config.initialZoom);\r\n const [internalPosition, setInternalPosition] = useState<Vector2d>(config.initialPosition);\r\n\r\n // ── Determine controlled vs uncontrolled ────────────────────────────────\r\n const isZoomControlled = options.zoom !== undefined;\r\n const isPositionControlled = options.position !== undefined;\r\n\r\n const zoom = isZoomControlled ? options.zoom! : internalZoom;\r\n const position = isPositionControlled ? options.position! : internalPosition;\r\n\r\n // Use refs for latest callbacks to avoid stale closures\r\n const callbacksRef = useRef(options);\r\n callbacksRef.current = options;\r\n\r\n // ── Viewport tracking (set by ZoomCanvas component) ─────────────────────\r\n const viewportRef = useRef<ViewportSize>({ width: 0, height: 0 });\r\n const setViewportSize = useCallback((size: ViewportSize) => {\r\n viewportRef.current = size;\r\n }, []);\r\n\r\n // ── State updaters ──────────────────────────────────────────────────────\r\n // setZoom always zooms from the viewport center so content stays centered.\r\n const updateZoom = useCallback(\r\n (newZoom: number) => {\r\n const clamped = clamp(newZoom, config.minZoom, config.maxZoom);\r\n const vp = viewportRef.current;\r\n // Zoom toward center of viewport\r\n const cx = vp.width / 2;\r\n const cy = vp.height / 2;\r\n const canvasX = (cx - position.x) / zoom;\r\n const canvasY = (cy - position.y) / zoom;\r\n const newPos = {\r\n x: cx - canvasX * clamped,\r\n y: cy - canvasY * clamped,\r\n };\r\n if (!isZoomControlled) setInternalZoom(clamped);\r\n if (!isPositionControlled) setInternalPosition(newPos);\r\n callbacksRef.current.onZoomChange?.(clamped);\r\n callbacksRef.current.onPositionChange?.(newPos);\r\n callbacksRef.current.onChange?.({ zoom: clamped, position: newPos });\r\n },\r\n [config.minZoom, config.maxZoom, isZoomControlled, isPositionControlled, position, zoom],\r\n );\r\n\r\n const updatePosition = useCallback(\r\n (newPos: Vector2d) => {\r\n if (!isPositionControlled) setInternalPosition(newPos);\r\n callbacksRef.current.onPositionChange?.(newPos);\r\n callbacksRef.current.onChange?.({ zoom, position: newPos });\r\n },\r\n [isPositionControlled, zoom],\r\n );\r\n\r\n const updateBoth = useCallback(\r\n (newZoom: number, newPos: Vector2d) => {\r\n const clamped = clamp(newZoom, config.minZoom, config.maxZoom);\r\n if (!isZoomControlled) setInternalZoom(clamped);\r\n if (!isPositionControlled) setInternalPosition(newPos);\r\n callbacksRef.current.onZoomChange?.(clamped);\r\n callbacksRef.current.onPositionChange?.(newPos);\r\n callbacksRef.current.onChange?.({ zoom: clamped, position: newPos });\r\n },\r\n [config.minZoom, config.maxZoom, isZoomControlled, isPositionControlled],\r\n );\r\n\r\n // ── Zoom to a specific screen-space point ───────────────────────────────\r\n const zoomToPoint = useCallback(\r\n (point: Vector2d, newZoom: number) => {\r\n const clamped = clamp(newZoom, config.minZoom, config.maxZoom);\r\n // Convert pointer position to canvas-space before zoom\r\n const canvasPoint = {\r\n x: (point.x - position.x) / zoom,\r\n y: (point.y - position.y) / zoom,\r\n };\r\n // After zoom, the same canvas point should stay under the pointer\r\n const newPos = {\r\n x: point.x - canvasPoint.x * clamped,\r\n y: point.y - canvasPoint.y * clamped,\r\n };\r\n updateBoth(clamped, newPos);\r\n },\r\n [zoom, position, config.minZoom, config.maxZoom, updateBoth],\r\n );\r\n\r\n // ── Zoom to center of viewport ──────────────────────────────────────────\r\n const zoomToCenter = useCallback(\r\n (viewportWidth: number, viewportHeight: number, newZoom: number) => {\r\n zoomToPoint({ x: viewportWidth / 2, y: viewportHeight / 2 }, newZoom);\r\n },\r\n [zoomToPoint],\r\n );\r\n\r\n // ── Step zoom ───────────────────────────────────────────────────────────\r\n const zoomIn = useCallback(() => {\r\n updateZoom(zoom * config.zoomSpeed);\r\n }, [zoom, config.zoomSpeed, updateZoom]);\r\n\r\n const zoomOut = useCallback(() => {\r\n updateZoom(zoom / config.zoomSpeed);\r\n }, [zoom, config.zoomSpeed, updateZoom]);\r\n\r\n // ── Fit content to viewport ─────────────────────────────────────────────\r\n const fitToContent = useCallback(\r\n (vw: number, vh: number, cw: number, ch: number) => {\r\n const pad = config.fitPadding;\r\n const scaleX = (vw - pad * 2) / cw;\r\n const scaleY = (vh - pad * 2) / ch;\r\n const newZoom = clamp(Math.min(scaleX, scaleY), config.minZoom, config.maxZoom);\r\n const newPos = {\r\n x: (vw - cw * newZoom) / 2,\r\n y: (vh - ch * newZoom) / 2,\r\n };\r\n updateBoth(newZoom, newPos);\r\n },\r\n [config.fitPadding, config.minZoom, config.maxZoom, updateBoth],\r\n );\r\n\r\n // ── Center without changing zoom ────────────────────────────────────────\r\n const centerContent = useCallback(\r\n (vw: number, vh: number, cw: number, ch: number) => {\r\n updatePosition({\r\n x: (vw - cw * zoom) / 2,\r\n y: (vh - ch * zoom) / 2,\r\n });\r\n },\r\n [zoom, updatePosition],\r\n );\r\n\r\n // ── Reset ───────────────────────────────────────────────────────────────\r\n const reset = useCallback(() => {\r\n updateBoth(config.initialZoom, config.initialPosition);\r\n }, [config.initialZoom, config.initialPosition, updateBoth]);\r\n\r\n // ── Internal: wheel handler (used by ZoomCanvas) ────────────────────────\r\n const _handleWheel = useCallback(\r\n (e: Konva.KonvaEventObject<WheelEvent>, stage: Konva.Stage) => {\r\n e.evt.preventDefault();\r\n\r\n const hasModifier = e.evt.ctrlKey || e.evt.metaKey;\r\n const shouldZoom = config.requireModifierToZoom ? hasModifier : !e.evt.shiftKey;\r\n\r\n if (shouldZoom && config.wheelZoom) {\r\n const pointer = stage.getPointerPosition();\r\n if (!pointer) return;\r\n\r\n const direction = e.evt.deltaY > 0 ? -1 : 1;\r\n const newZoom = direction > 0 ? zoom * config.zoomSpeed : zoom / config.zoomSpeed;\r\n zoomToPoint(pointer, newZoom);\r\n } else if (config.wheelPan) {\r\n // Pan mode\r\n const dx = e.evt.shiftKey ? -e.evt.deltaY : -e.evt.deltaX;\r\n const dy = e.evt.shiftKey ? 0 : -e.evt.deltaY;\r\n updatePosition({ x: position.x + dx, y: position.y + dy });\r\n }\r\n },\r\n [zoom, position, config, zoomToPoint, updatePosition],\r\n );\r\n\r\n // ── Internal: drag end handler ──────────────────────────────────────────\r\n const _handleDragEnd = useCallback(\r\n (e: Konva.KonvaEventObject<DragEvent>, stage: Konva.Stage) => {\r\n if (e.target === stage) {\r\n updatePosition({ x: e.target.x(), y: e.target.y() });\r\n }\r\n },\r\n [updatePosition],\r\n );\r\n\r\n return {\r\n zoom,\r\n position,\r\n setZoom: updateZoom,\r\n setPosition: updatePosition,\r\n zoomToPoint,\r\n zoomToCenter,\r\n zoomIn,\r\n zoomOut,\r\n fitToContent,\r\n centerContent,\r\n reset,\r\n config,\r\n _handleWheel,\r\n _handleDragEnd,\r\n /** @internal used by ZoomCanvas to track viewport size */\r\n _setViewportSize: setViewportSize,\r\n };\r\n};\r\n","import React, { useRef } from 'react';\r\nimport { Stage } from 'react-konva';\r\nimport Konva from 'konva';\r\nimport { ZoomCanvasProps, DEFAULT_CONFIG } from './types';\r\nimport { useZoomCanvas } from './useZoomCanvas';\r\n\r\n/**\r\n * A fully self-contained zoom & pan canvas powered by Konva.\r\n *\r\n * **Three usage modes:**\r\n *\r\n * 1. **Engine mode** (recommended): pass `engine={useZoomCanvas()}` for full control.\r\n * 2. **Controlled mode**: pass `zoom`, `position`, `onZoom`, `onPosition`.\r\n * 3. **Uncontrolled mode**: pass nothing — the component manages its own state.\r\n *\r\n * @example\r\n * ```tsx\r\n * // Engine mode (recommended)\r\n * const engine = useZoomCanvas({ initialZoom: 0.5 });\r\n * <ZoomCanvas width={800} height={600} engine={engine}>\r\n * <Layer>...</Layer>\r\n * </ZoomCanvas>\r\n *\r\n * // Uncontrolled mode (zero config)\r\n * <ZoomCanvas width={800} height={600}>\r\n * <Layer>...</Layer>\r\n * </ZoomCanvas>\r\n * ```\r\n */\r\nexport const ZoomCanvas: React.FC<ZoomCanvasProps> = ({\r\n width,\r\n height,\r\n children,\r\n stageRef: externalStageRef,\r\n onStageClick,\r\n draggable,\r\n className,\r\n style,\r\n zoom: controlledZoom,\r\n position: controlledPosition,\r\n onZoom,\r\n onPosition,\r\n engine: externalEngine,\r\n config: configOverrides,\r\n}) => {\r\n const internalStageRef = useRef<Konva.Stage>(null);\r\n const stageRef = externalStageRef || internalStageRef;\r\n\r\n // If no engine is provided, create an internal one\r\n const internalEngine = useZoomCanvas({\r\n ...configOverrides,\r\n zoom: controlledZoom,\r\n position: controlledPosition,\r\n onZoomChange: onZoom,\r\n onPositionChange: onPosition,\r\n });\r\n\r\n const engine = externalEngine || internalEngine;\r\n\r\n // Keep engine aware of viewport dimensions for center-based zoom\r\n React.useEffect(() => {\r\n engine._setViewportSize({ width, height });\r\n }, [width, height, engine._setViewportSize]);\r\n\r\n const isDragEnabled = draggable ?? engine.config.dragPan;\r\n\r\n return (\r\n <div className={className} style={{ overflow: 'hidden', ...style }}>\r\n <Stage\r\n ref={stageRef}\r\n width={width}\r\n height={height}\r\n scaleX={engine.zoom}\r\n scaleY={engine.zoom}\r\n x={engine.position.x}\r\n y={engine.position.y}\r\n draggable={isDragEnabled}\r\n onWheel={(e) => {\r\n engine._handleWheel(e, stageRef.current || e.target.getStage()!);\r\n }}\r\n onClick={onStageClick}\r\n onTap={onStageClick}\r\n onDragEnd={(e) => {\r\n engine._handleDragEnd(e, stageRef.current || e.target.getStage()!);\r\n }}\r\n >\r\n {children}\r\n </Stage>\r\n </div>\r\n );\r\n};\r\n","import React from 'react';\r\n\r\n/**\r\n * Standalone zoom controls — no external UI library dependencies.\r\n * Renders +/- buttons, a range slider, percentage label, fit & reset buttons.\r\n * Fully styleable via className / style.\r\n */\r\nexport const ZoomControls: React.FC<{\r\n zoom: number;\r\n onZoom: (zoom: number) => void;\r\n onZoomIn?: () => void;\r\n onZoomOut?: () => void;\r\n onFit?: () => void;\r\n onReset?: () => void;\r\n minZoom?: number;\r\n maxZoom?: number;\r\n className?: string;\r\n style?: React.CSSProperties;\r\n showFit?: boolean;\r\n showReset?: boolean;\r\n showLabel?: boolean;\r\n showSlider?: boolean;\r\n}> = ({\r\n zoom,\r\n onZoom,\r\n onZoomIn,\r\n onZoomOut,\r\n onFit,\r\n onReset,\r\n minZoom = 5,\r\n maxZoom = 500,\r\n className = '',\r\n style,\r\n showFit = true,\r\n showReset = false,\r\n showLabel = true,\r\n showSlider = true,\r\n}) => {\r\n const zoomPercent = Math.round(zoom * 100);\r\n\r\n const btnStyle: React.CSSProperties = {\r\n display: 'inline-flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n width: 28,\r\n height: 28,\r\n border: 'none',\r\n borderRadius: '50%',\r\n background: 'transparent',\r\n cursor: 'pointer',\r\n fontSize: 16,\r\n fontWeight: 600,\r\n color: 'inherit',\r\n transition: 'background 0.15s',\r\n };\r\n\r\n const containerStyle: React.CSSProperties = {\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: 8,\r\n padding: '6px 16px',\r\n borderRadius: 9999,\r\n background: 'white',\r\n boxShadow: '0 4px 24px rgba(0,0,0,0.08), 0 0 0 1px rgba(0,0,0,0.06)',\r\n fontSize: 12,\r\n fontFamily: 'system-ui, -apple-system, sans-serif',\r\n userSelect: 'none',\r\n ...style,\r\n };\r\n\r\n const sliderStyle: React.CSSProperties = {\r\n width: 80,\r\n height: 4,\r\n appearance: 'none' as const,\r\n background: '#e5e5e5',\r\n borderRadius: 2,\r\n outline: 'none',\r\n cursor: 'pointer',\r\n };\r\n\r\n return (\r\n <div className={className} style={containerStyle}>\r\n <button\r\n style={btnStyle}\r\n onClick={() => (onZoomOut ? onZoomOut() : onZoom(zoom / 1.15))}\r\n title=\"Zoom out\"\r\n onMouseEnter={(e) => (e.currentTarget.style.background = '#f3f3f3')}\r\n onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}\r\n >\r\n −\r\n </button>\r\n\r\n {showSlider && (\r\n <input\r\n type=\"range\"\r\n min={minZoom}\r\n max={maxZoom}\r\n value={zoomPercent}\r\n onChange={(e) => onZoom(Number(e.target.value) / 100)}\r\n style={sliderStyle}\r\n onMouseDown={(e) => e.stopPropagation()}\r\n />\r\n )}\r\n\r\n <button\r\n style={btnStyle}\r\n onClick={() => (onZoomIn ? onZoomIn() : onZoom(zoom * 1.15))}\r\n title=\"Zoom in\"\r\n onMouseEnter={(e) => (e.currentTarget.style.background = '#f3f3f3')}\r\n onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}\r\n >\r\n +\r\n </button>\r\n\r\n {showLabel && (\r\n <span style={{ width: 40, textAlign: 'center', fontWeight: 700, fontSize: 11 }}>\r\n {zoomPercent}%\r\n </span>\r\n )}\r\n\r\n {showFit && onFit && (\r\n <>\r\n <div style={{ width: 1, height: 16, background: '#e5e5e5' }} />\r\n <button\r\n style={{ ...btnStyle, width: 'auto', borderRadius: 4, padding: '2px 8px', fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em' }}\r\n onClick={onFit}\r\n onMouseEnter={(e) => (e.currentTarget.style.background = '#f3f3f3')}\r\n onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}\r\n >\r\n Fit\r\n </button>\r\n </>\r\n )}\r\n\r\n {showReset && onReset && (\r\n <button\r\n style={{ ...btnStyle, width: 'auto', borderRadius: 4, padding: '2px 8px', fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em' }}\r\n onClick={onReset}\r\n onMouseEnter={(e) => (e.currentTarget.style.background = '#f3f3f3')}\r\n onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}\r\n >\r\n Reset\r\n </button>\r\n )}\r\n </div>\r\n );\r\n};\r\n"]}