@omer-x/svg-viewport 0.3.1 → 2.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.
- package/dist/index.cjs +167 -0
- package/dist/index.d.cts +57 -0
- package/dist/index.d.ts +42 -12
- package/dist/index.js +129 -94
- package/package.json +27 -23
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
default: () => index_default
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/components/SvgViewport.tsx
|
|
29
|
+
var import_react = require("react");
|
|
30
|
+
|
|
31
|
+
// src/core/matrix.ts
|
|
32
|
+
function transform(matrix) {
|
|
33
|
+
if (!matrix) return void 0;
|
|
34
|
+
const { a, b, c, d, e, f } = matrix;
|
|
35
|
+
return `matrix(${a}, ${b}, ${c}, ${d}, ${e}, ${f})`;
|
|
36
|
+
}
|
|
37
|
+
function focusTo(matrix, { x, y }, width, height, zoom) {
|
|
38
|
+
return matrix.translate(
|
|
39
|
+
width / 2 / zoom - x - matrix.m41 / zoom,
|
|
40
|
+
height / 2 / zoom - y - matrix.m42 / zoom
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
function adjustWithZoom(matrix, scale, svgElement, eX, eY) {
|
|
44
|
+
const rect = svgElement.getBoundingClientRect();
|
|
45
|
+
const focusPoint = new DOMPoint(eX - rect.left, eY - rect.top);
|
|
46
|
+
const relativePoint = focusPoint.matrixTransform(matrix.inverse());
|
|
47
|
+
const modifier = new DOMMatrix().translate(relativePoint.x, relativePoint.y).scale(scale).translate(-relativePoint.x, -relativePoint.y);
|
|
48
|
+
return matrix.multiply(modifier);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/core/initial-focus.ts
|
|
52
|
+
function getFocusedMatrix(focusPoint, width, height) {
|
|
53
|
+
switch (focusPoint) {
|
|
54
|
+
case "center":
|
|
55
|
+
return focusTo(new DOMMatrix(), { x: 0, y: 0 }, width, height, 1);
|
|
56
|
+
case "top-left":
|
|
57
|
+
return focusTo(new DOMMatrix(), { x: 0, y: 0 }, 0, 0, 1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/components/SvgViewport.tsx
|
|
62
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
63
|
+
var SvgViewport = ({
|
|
64
|
+
width,
|
|
65
|
+
height,
|
|
66
|
+
pannable = false,
|
|
67
|
+
zoomable = false,
|
|
68
|
+
minZoom = 0.5,
|
|
69
|
+
maxZoom = 2,
|
|
70
|
+
transformation: externalTransformation,
|
|
71
|
+
onTransformationChange,
|
|
72
|
+
initialFocusPoint = "center",
|
|
73
|
+
style,
|
|
74
|
+
children,
|
|
75
|
+
...otherProps
|
|
76
|
+
}) => {
|
|
77
|
+
const isControlled = externalTransformation !== void 0;
|
|
78
|
+
const [internalTransformation, setInternalTransformation] = (0, import_react.useState)(() => {
|
|
79
|
+
if (isControlled) return null;
|
|
80
|
+
return {
|
|
81
|
+
zoom: 1,
|
|
82
|
+
matrix: getFocusedMatrix(initialFocusPoint, width, height)
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
const transformation = isControlled ? externalTransformation : internalTransformation;
|
|
86
|
+
const transformationRef = (0, import_react.useRef)(transformation);
|
|
87
|
+
(0, import_react.useEffect)(() => {
|
|
88
|
+
transformationRef.current = transformation;
|
|
89
|
+
}, [transformation]);
|
|
90
|
+
const pointer = (0, import_react.useRef)({ x: 0, y: 0 });
|
|
91
|
+
const [grabbing, setGrabbing] = (0, import_react.useState)(false);
|
|
92
|
+
const [isPanning, setIsPanning] = (0, import_react.useState)(false);
|
|
93
|
+
const setTransformation = (0, import_react.useCallback)((value) => {
|
|
94
|
+
if (!isControlled) {
|
|
95
|
+
setInternalTransformation(value);
|
|
96
|
+
}
|
|
97
|
+
onTransformationChange?.(value);
|
|
98
|
+
}, [isControlled, onTransformationChange]);
|
|
99
|
+
const stopGrabbing = () => {
|
|
100
|
+
setGrabbing(false);
|
|
101
|
+
};
|
|
102
|
+
const down = (e) => {
|
|
103
|
+
if (e.button !== 0) return;
|
|
104
|
+
pointer.current = { x: e.clientX, y: e.clientY };
|
|
105
|
+
setIsPanning(true);
|
|
106
|
+
setGrabbing(true);
|
|
107
|
+
};
|
|
108
|
+
const move = (0, import_react.useCallback)((e) => {
|
|
109
|
+
const currentTrans = transformationRef.current;
|
|
110
|
+
if (currentTrans) {
|
|
111
|
+
const x = (e.clientX - pointer.current.x) / currentTrans.zoom;
|
|
112
|
+
const y = (e.clientY - pointer.current.y) / currentTrans.zoom;
|
|
113
|
+
pointer.current = { x: e.clientX, y: e.clientY };
|
|
114
|
+
setTransformation({
|
|
115
|
+
...currentTrans,
|
|
116
|
+
matrix: currentTrans.matrix.translate(x, y)
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}, [setTransformation]);
|
|
120
|
+
const up = (0, import_react.useCallback)(() => {
|
|
121
|
+
setIsPanning(false);
|
|
122
|
+
setGrabbing(false);
|
|
123
|
+
}, []);
|
|
124
|
+
(0, import_react.useEffect)(() => {
|
|
125
|
+
if (isPanning) {
|
|
126
|
+
document.addEventListener("mousemove", move);
|
|
127
|
+
document.addEventListener("mouseup", up);
|
|
128
|
+
}
|
|
129
|
+
return () => {
|
|
130
|
+
document.removeEventListener("mousemove", move);
|
|
131
|
+
document.removeEventListener("mouseup", up);
|
|
132
|
+
};
|
|
133
|
+
}, [isPanning, move, up]);
|
|
134
|
+
const adjustZoom = (e) => {
|
|
135
|
+
if (!zoomable) return;
|
|
136
|
+
e.preventDefault();
|
|
137
|
+
const scale = e.deltaY < 0 ? 1.25 : 0.8;
|
|
138
|
+
const eventTarget = e.currentTarget;
|
|
139
|
+
const { clientX, clientY } = e;
|
|
140
|
+
if (transformation && transformation.zoom * scale > minZoom && transformation.zoom * scale < maxZoom) {
|
|
141
|
+
setTransformation({
|
|
142
|
+
zoom: transformation.zoom * scale,
|
|
143
|
+
matrix: adjustWithZoom(transformation.matrix, scale, eventTarget, clientX, clientY)
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const cursor = pannable ? grabbing ? "grabbing" : "grab" : "auto";
|
|
148
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
149
|
+
"svg",
|
|
150
|
+
{
|
|
151
|
+
width,
|
|
152
|
+
height,
|
|
153
|
+
onMouseDown: pannable ? down : void 0,
|
|
154
|
+
onMouseUp: pannable ? stopGrabbing : void 0,
|
|
155
|
+
onMouseLeave: pannable ? stopGrabbing : void 0,
|
|
156
|
+
onWheel: zoomable ? adjustZoom : void 0,
|
|
157
|
+
onContextMenu: (e) => e.preventDefault(),
|
|
158
|
+
style: { ...style, cursor },
|
|
159
|
+
...otherProps,
|
|
160
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("g", { transform: transform(transformation?.matrix), children: transformation && children })
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
var SvgViewport_default = SvgViewport;
|
|
165
|
+
|
|
166
|
+
// src/index.ts
|
|
167
|
+
var index_default = SvgViewport_default;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
|
|
4
|
+
type FocusPoint = "center" | "top-left";
|
|
5
|
+
|
|
6
|
+
type ViewportTransformation = {
|
|
7
|
+
zoom: number;
|
|
8
|
+
matrix: DOMMatrix;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Props for the SvgViewport component.
|
|
13
|
+
*/
|
|
14
|
+
type SvgViewportProps = ComponentProps<"svg"> & {
|
|
15
|
+
/**
|
|
16
|
+
* Width of the SVG viewport.
|
|
17
|
+
*/
|
|
18
|
+
width: number;
|
|
19
|
+
/**
|
|
20
|
+
* Height of the SVG viewport.
|
|
21
|
+
*/
|
|
22
|
+
height: number;
|
|
23
|
+
/**
|
|
24
|
+
* Enable or disable panning functionality.
|
|
25
|
+
*/
|
|
26
|
+
pannable?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Enable or disable zooming functionality.
|
|
29
|
+
*/
|
|
30
|
+
zoomable?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Minimum zoom level.
|
|
33
|
+
*/
|
|
34
|
+
minZoom?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Maximum zoom level.
|
|
37
|
+
*/
|
|
38
|
+
maxZoom?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Current transformation state of the viewport.
|
|
41
|
+
*/
|
|
42
|
+
transformation?: ViewportTransformation | null;
|
|
43
|
+
/**
|
|
44
|
+
* Callback to update the transformation state.
|
|
45
|
+
*/
|
|
46
|
+
onTransformationChange?: (tranformation: ViewportTransformation) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Initial focus point of the viewport.
|
|
49
|
+
*/
|
|
50
|
+
initialFocusPoint?: FocusPoint;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* SVG Viewport component that supports panning and zooming.
|
|
54
|
+
*/
|
|
55
|
+
declare const SvgViewport: ({ width, height, pannable, zoomable, minZoom, maxZoom, transformation: externalTransformation, onTransformationChange, initialFocusPoint, style, children, ...otherProps }: SvgViewportProps) => react_jsx_runtime.JSX.Element;
|
|
56
|
+
|
|
57
|
+
export { SvgViewport as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,27 +1,57 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
2
3
|
|
|
3
4
|
type FocusPoint = "center" | "top-left";
|
|
4
5
|
|
|
5
|
-
type
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
type ViewportTransformation = {
|
|
7
|
+
zoom: number;
|
|
8
|
+
matrix: DOMMatrix;
|
|
8
9
|
};
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Props for the SvgViewport component.
|
|
13
|
+
*/
|
|
14
|
+
type SvgViewportProps = ComponentProps<"svg"> & {
|
|
15
|
+
/**
|
|
16
|
+
* Width of the SVG viewport.
|
|
17
|
+
*/
|
|
11
18
|
width: number;
|
|
19
|
+
/**
|
|
20
|
+
* Height of the SVG viewport.
|
|
21
|
+
*/
|
|
12
22
|
height: number;
|
|
23
|
+
/**
|
|
24
|
+
* Enable or disable panning functionality.
|
|
25
|
+
*/
|
|
13
26
|
pannable?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Enable or disable zooming functionality.
|
|
29
|
+
*/
|
|
14
30
|
zoomable?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Minimum zoom level.
|
|
33
|
+
*/
|
|
15
34
|
minZoom?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Maximum zoom level.
|
|
37
|
+
*/
|
|
16
38
|
maxZoom?: number;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Current transformation state of the viewport.
|
|
41
|
+
*/
|
|
42
|
+
transformation?: ViewportTransformation | null;
|
|
43
|
+
/**
|
|
44
|
+
* Callback to update the transformation state.
|
|
45
|
+
*/
|
|
46
|
+
onTransformationChange?: (tranformation: ViewportTransformation) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Initial focus point of the viewport.
|
|
49
|
+
*/
|
|
21
50
|
initialFocusPoint?: FocusPoint;
|
|
22
|
-
className?: string;
|
|
23
|
-
children: ReactNode;
|
|
24
51
|
};
|
|
25
|
-
|
|
52
|
+
/**
|
|
53
|
+
* SVG Viewport component that supports panning and zooming.
|
|
54
|
+
*/
|
|
55
|
+
declare const SvgViewport: ({ width, height, pannable, zoomable, minZoom, maxZoom, transformation: externalTransformation, onTransformationChange, initialFocusPoint, style, children, ...otherProps }: SvgViewportProps) => react_jsx_runtime.JSX.Element;
|
|
26
56
|
|
|
27
57
|
export { SvgViewport as default };
|
package/dist/index.js
CHANGED
|
@@ -1,110 +1,145 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
3
2
|
|
|
3
|
+
// src/components/SvgViewport.tsx
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5
|
+
|
|
6
|
+
// src/core/matrix.ts
|
|
4
7
|
function transform(matrix) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return `matrix(${a}, ${b}, ${c}, ${d}, ${e}, ${f})`;
|
|
8
|
+
if (!matrix) return void 0;
|
|
9
|
+
const { a, b, c, d, e, f } = matrix;
|
|
10
|
+
return `matrix(${a}, ${b}, ${c}, ${d}, ${e}, ${f})`;
|
|
9
11
|
}
|
|
10
12
|
function focusTo(matrix, { x, y }, width, height, zoom) {
|
|
11
|
-
|
|
13
|
+
return matrix.translate(
|
|
14
|
+
width / 2 / zoom - x - matrix.m41 / zoom,
|
|
15
|
+
height / 2 / zoom - y - matrix.m42 / zoom
|
|
16
|
+
);
|
|
12
17
|
}
|
|
13
18
|
function adjustWithZoom(matrix, scale, svgElement, eX, eY) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
.scale(scale)
|
|
20
|
-
.translate(-relativePoint.x, -relativePoint.y);
|
|
21
|
-
return matrix.multiply(modifier);
|
|
19
|
+
const rect = svgElement.getBoundingClientRect();
|
|
20
|
+
const focusPoint = new DOMPoint(eX - rect.left, eY - rect.top);
|
|
21
|
+
const relativePoint = focusPoint.matrixTransform(matrix.inverse());
|
|
22
|
+
const modifier = new DOMMatrix().translate(relativePoint.x, relativePoint.y).scale(scale).translate(-relativePoint.x, -relativePoint.y);
|
|
23
|
+
return matrix.multiply(modifier);
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
// src/core/initial-focus.ts
|
|
24
27
|
function getFocusedMatrix(focusPoint, width, height) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function usePolyfillState(state, dispatch) {
|
|
34
|
-
const [polyfill, setPolyfill] = useState(state);
|
|
35
|
-
if (dispatch) {
|
|
36
|
-
return [state, dispatch];
|
|
37
|
-
}
|
|
38
|
-
return [polyfill, setPolyfill];
|
|
28
|
+
switch (focusPoint) {
|
|
29
|
+
case "center":
|
|
30
|
+
return focusTo(new DOMMatrix(), { x: 0, y: 0 }, width, height, 1);
|
|
31
|
+
case "top-left":
|
|
32
|
+
return focusTo(new DOMMatrix(), { x: 0, y: 0 }, 0, 0, 1);
|
|
33
|
+
}
|
|
39
34
|
}
|
|
40
35
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
};
|
|
64
|
-
setActivePanning(true);
|
|
65
|
-
}
|
|
66
|
-
setGrabbing(true);
|
|
36
|
+
// src/components/SvgViewport.tsx
|
|
37
|
+
import { jsx } from "react/jsx-runtime";
|
|
38
|
+
var SvgViewport = ({
|
|
39
|
+
width,
|
|
40
|
+
height,
|
|
41
|
+
pannable = false,
|
|
42
|
+
zoomable = false,
|
|
43
|
+
minZoom = 0.5,
|
|
44
|
+
maxZoom = 2,
|
|
45
|
+
transformation: externalTransformation,
|
|
46
|
+
onTransformationChange,
|
|
47
|
+
initialFocusPoint = "center",
|
|
48
|
+
style,
|
|
49
|
+
children,
|
|
50
|
+
...otherProps
|
|
51
|
+
}) => {
|
|
52
|
+
const isControlled = externalTransformation !== void 0;
|
|
53
|
+
const [internalTransformation, setInternalTransformation] = useState(() => {
|
|
54
|
+
if (isControlled) return null;
|
|
55
|
+
return {
|
|
56
|
+
zoom: 1,
|
|
57
|
+
matrix: getFocusedMatrix(initialFocusPoint, width, height)
|
|
67
58
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
59
|
+
});
|
|
60
|
+
const transformation = isControlled ? externalTransformation : internalTransformation;
|
|
61
|
+
const transformationRef = useRef(transformation);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
transformationRef.current = transformation;
|
|
64
|
+
}, [transformation]);
|
|
65
|
+
const pointer = useRef({ x: 0, y: 0 });
|
|
66
|
+
const [grabbing, setGrabbing] = useState(false);
|
|
67
|
+
const [isPanning, setIsPanning] = useState(false);
|
|
68
|
+
const setTransformation = useCallback((value) => {
|
|
69
|
+
if (!isControlled) {
|
|
70
|
+
setInternalTransformation(value);
|
|
71
|
+
}
|
|
72
|
+
onTransformationChange?.(value);
|
|
73
|
+
}, [isControlled, onTransformationChange]);
|
|
74
|
+
const stopGrabbing = () => {
|
|
75
|
+
setGrabbing(false);
|
|
76
|
+
};
|
|
77
|
+
const down = (e) => {
|
|
78
|
+
if (e.button !== 0) return;
|
|
79
|
+
pointer.current = { x: e.clientX, y: e.clientY };
|
|
80
|
+
setIsPanning(true);
|
|
81
|
+
setGrabbing(true);
|
|
82
|
+
};
|
|
83
|
+
const move = useCallback((e) => {
|
|
84
|
+
const currentTrans = transformationRef.current;
|
|
85
|
+
if (currentTrans) {
|
|
86
|
+
const x = (e.clientX - pointer.current.x) / currentTrans.zoom;
|
|
87
|
+
const y = (e.clientY - pointer.current.y) / currentTrans.zoom;
|
|
88
|
+
pointer.current = { x: e.clientX, y: e.clientY };
|
|
89
|
+
setTransformation({
|
|
90
|
+
...currentTrans,
|
|
91
|
+
matrix: currentTrans.matrix.translate(x, y)
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}, [setTransformation]);
|
|
95
|
+
const up = useCallback(() => {
|
|
96
|
+
setIsPanning(false);
|
|
97
|
+
setGrabbing(false);
|
|
98
|
+
}, []);
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (isPanning) {
|
|
101
|
+
document.addEventListener("mousemove", move);
|
|
102
|
+
document.addEventListener("mouseup", up);
|
|
103
|
+
}
|
|
104
|
+
return () => {
|
|
105
|
+
document.removeEventListener("mousemove", move);
|
|
106
|
+
document.removeEventListener("mouseup", up);
|
|
104
107
|
};
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
}, [isPanning, move, up]);
|
|
109
|
+
const adjustZoom = (e) => {
|
|
110
|
+
if (!zoomable) return;
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
const scale = e.deltaY < 0 ? 1.25 : 0.8;
|
|
113
|
+
const eventTarget = e.currentTarget;
|
|
114
|
+
const { clientX, clientY } = e;
|
|
115
|
+
if (transformation && transformation.zoom * scale > minZoom && transformation.zoom * scale < maxZoom) {
|
|
116
|
+
setTransformation({
|
|
117
|
+
zoom: transformation.zoom * scale,
|
|
118
|
+
matrix: adjustWithZoom(transformation.matrix, scale, eventTarget, clientX, clientY)
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const cursor = pannable ? grabbing ? "grabbing" : "grab" : "auto";
|
|
123
|
+
return /* @__PURE__ */ jsx(
|
|
124
|
+
"svg",
|
|
125
|
+
{
|
|
126
|
+
width,
|
|
127
|
+
height,
|
|
128
|
+
onMouseDown: pannable ? down : void 0,
|
|
129
|
+
onMouseUp: pannable ? stopGrabbing : void 0,
|
|
130
|
+
onMouseLeave: pannable ? stopGrabbing : void 0,
|
|
131
|
+
onWheel: zoomable ? adjustZoom : void 0,
|
|
132
|
+
onContextMenu: (e) => e.preventDefault(),
|
|
133
|
+
style: { ...style, cursor },
|
|
134
|
+
...otherProps,
|
|
135
|
+
children: /* @__PURE__ */ jsx("g", { transform: transform(transformation?.matrix), children: transformation && children })
|
|
136
|
+
}
|
|
137
|
+
);
|
|
108
138
|
};
|
|
139
|
+
var SvgViewport_default = SvgViewport;
|
|
109
140
|
|
|
110
|
-
|
|
141
|
+
// src/index.ts
|
|
142
|
+
var index_default = SvgViewport_default;
|
|
143
|
+
export {
|
|
144
|
+
index_default as default
|
|
145
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omer-x/svg-viewport",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Provides a simple React component for displaying SVG content with zooming and panning capabilities",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -23,9 +23,6 @@
|
|
|
23
23
|
"publishConfig": {
|
|
24
24
|
"access": "public"
|
|
25
25
|
},
|
|
26
|
-
"files": [
|
|
27
|
-
"dist/"
|
|
28
|
-
],
|
|
29
26
|
"author": {
|
|
30
27
|
"name": "Omer Mecitoglu",
|
|
31
28
|
"email": "omer.mecitoglu@gmail.com",
|
|
@@ -33,28 +30,35 @@
|
|
|
33
30
|
},
|
|
34
31
|
"license": "MIT",
|
|
35
32
|
"type": "module",
|
|
36
|
-
"
|
|
37
|
-
|
|
33
|
+
"files": [
|
|
34
|
+
"dist/"
|
|
35
|
+
],
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"import": "./dist/index.js",
|
|
40
|
+
"require": "./dist/index.cjs"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
38
43
|
"scripts": {
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
44
|
+
"lint": "eslint --flag unstable_native_nodejs_ts_config",
|
|
45
|
+
"lint:fix": "eslint --fix --flag unstable_native_nodejs_ts_config",
|
|
46
|
+
"test": "vitest run --coverage",
|
|
47
|
+
"test:watch": "vitest --coverage",
|
|
48
|
+
"dev": "tsup --watch",
|
|
49
|
+
"build": "tsup"
|
|
43
50
|
},
|
|
44
|
-
"
|
|
45
|
-
"react": "^
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"react": "^19"
|
|
46
53
|
},
|
|
47
54
|
"devDependencies": {
|
|
48
|
-
"@omer-x/eslint-config": "^
|
|
49
|
-
"@
|
|
50
|
-
"@
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"ts-unused-exports": "^10.0.1",
|
|
57
|
-
"tslib": "^2.6.2",
|
|
58
|
-
"typescript": "^5.3.3"
|
|
55
|
+
"@omer-x/eslint-config": "^2.2.6",
|
|
56
|
+
"@types/react": "^19.2.10",
|
|
57
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
58
|
+
"eslint": "^9.39.2",
|
|
59
|
+
"semantic-release": "^25.0.3",
|
|
60
|
+
"tsup": "^8.5.1",
|
|
61
|
+
"typescript": "^5.9.3",
|
|
62
|
+
"vitest": "^4.0.18"
|
|
59
63
|
}
|
|
60
64
|
}
|