react-svg-canvas 0.0.2 → 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 +33 -2
- package/lib/svgcanvas.d.ts +1 -1
- package/lib/svgcanvas.js +141 -103
- package/package.json +38 -5
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ pnpm add react-svg-canvas
|
|
|
24
24
|
yarn add react-svg-canvas
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
**Peer Dependencies:** React
|
|
27
|
+
**Peer Dependencies:** React 18+
|
|
28
28
|
|
|
29
29
|
## Quick Start
|
|
30
30
|
|
|
@@ -154,14 +154,17 @@ function Canvas({ objects }: { objects: MyObject[] }) {
|
|
|
154
154
|
const {
|
|
155
155
|
selectedIds,
|
|
156
156
|
selectedObjects,
|
|
157
|
+
selectionCount,
|
|
157
158
|
selectionBounds,
|
|
158
159
|
hasSelection,
|
|
159
160
|
select,
|
|
160
161
|
selectMultiple,
|
|
162
|
+
deselect,
|
|
161
163
|
toggle,
|
|
162
164
|
clear,
|
|
163
165
|
selectAll,
|
|
164
166
|
selectInRect,
|
|
167
|
+
setSelection,
|
|
165
168
|
isSelected
|
|
166
169
|
} = useSelection({ objects, onChange: (ids) => console.log('Selection:', ids) })
|
|
167
170
|
|
|
@@ -306,6 +309,27 @@ function Canvas({ objects }) {
|
|
|
306
309
|
}
|
|
307
310
|
```
|
|
308
311
|
|
|
312
|
+
#### useGrabPoint
|
|
313
|
+
|
|
314
|
+
Helper hook for calculating the normalized grab point when dragging objects.
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
import { useGrabPoint } from 'react-svg-canvas'
|
|
318
|
+
|
|
319
|
+
function MyDraggable({ bounds }) {
|
|
320
|
+
const { setGrabPoint, getGrabPoint } = useGrabPoint()
|
|
321
|
+
|
|
322
|
+
function handleDragStart(mousePos) {
|
|
323
|
+
setGrabPoint(mousePos, bounds)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function handleDrag(delta) {
|
|
327
|
+
const grabPoint = getGrabPoint() // Returns { x: 0-1, y: 0-1 }
|
|
328
|
+
// Use with snapDrag...
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
309
333
|
#### Snap Configuration
|
|
310
334
|
|
|
311
335
|
```tsx
|
|
@@ -488,6 +512,13 @@ interface SpatialObject {
|
|
|
488
512
|
bounds: Bounds
|
|
489
513
|
}
|
|
490
514
|
|
|
515
|
+
interface ToolEvent {
|
|
516
|
+
startX: number
|
|
517
|
+
startY: number
|
|
518
|
+
x: number
|
|
519
|
+
y: number
|
|
520
|
+
}
|
|
521
|
+
|
|
491
522
|
type ResizeHandle = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w'
|
|
492
523
|
```
|
|
493
524
|
|
|
@@ -569,7 +600,7 @@ function Editor() {
|
|
|
569
600
|
|
|
570
601
|
## Browser Support
|
|
571
602
|
|
|
572
|
-
- Modern browsers with
|
|
603
|
+
- Modern browsers with ES2021 support
|
|
573
604
|
- Touch devices (iOS Safari, Android Chrome)
|
|
574
605
|
|
|
575
606
|
## License
|
package/lib/svgcanvas.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export declare function useSvgCanvas(): {
|
|
|
12
12
|
translateTo: (x: number, y: number) => [number, number];
|
|
13
13
|
translateFrom: (x: number, y: number) => [number, number];
|
|
14
14
|
setDragHandler: React.Dispatch<React.SetStateAction<DragHandler | undefined>>;
|
|
15
|
-
startDrag: (evt: React.
|
|
15
|
+
startDrag: (evt: React.PointerEvent) => void;
|
|
16
16
|
};
|
|
17
17
|
/**
|
|
18
18
|
* Context exposed to consumers via onContextReady callback
|
package/lib/svgcanvas.js
CHANGED
|
@@ -17,11 +17,14 @@ export const SvgCanvas = React.forwardRef(function SvgCanvas({ className, style,
|
|
|
17
17
|
const svgRef = React.useRef(null);
|
|
18
18
|
const [active, setActive] = React.useState();
|
|
19
19
|
const [pan, setPan] = React.useState();
|
|
20
|
-
const [pinch, setPinch] = React.useState();
|
|
21
20
|
const [drag, setDrag] = React.useState();
|
|
22
21
|
const [toolStart, setToolStart] = React.useState();
|
|
23
22
|
const [dragHandler, setDragHandler] = React.useState();
|
|
24
23
|
const [matrix, setMatrix] = React.useState([1, 0, 0, 1, 0, 0]);
|
|
24
|
+
// Track active pointers for multi-touch gestures
|
|
25
|
+
const activePointersRef = React.useRef(new Map());
|
|
26
|
+
const lastPinchDistanceRef = React.useRef(undefined);
|
|
27
|
+
const lastPinchCenterRef = React.useRef(undefined);
|
|
25
28
|
const translateTo = React.useCallback(function traslateTo(x, y) {
|
|
26
29
|
return [(x - matrix[4]) / matrix[0], (y - matrix[5]) / matrix[3]];
|
|
27
30
|
}, [matrix]);
|
|
@@ -29,11 +32,10 @@ export const SvgCanvas = React.forwardRef(function SvgCanvas({ className, style,
|
|
|
29
32
|
return [x * matrix[0] + matrix[4], y * matrix[3] + matrix[5]];
|
|
30
33
|
}, [matrix]);
|
|
31
34
|
function startDrag(evt) {
|
|
32
|
-
const e =
|
|
35
|
+
const e = transformPointerEvent(evt);
|
|
33
36
|
if (!e)
|
|
34
37
|
return;
|
|
35
38
|
setToolStart({ startX: e.x, startY: e.y });
|
|
36
|
-
//setDrag({ target, startX: e.x, startY: e.y, lastX: e.x, lastY: e.y})
|
|
37
39
|
}
|
|
38
40
|
const svgContext = React.useMemo(() => ({
|
|
39
41
|
svg: svgRef.current || undefined,
|
|
@@ -90,11 +92,11 @@ export const SvgCanvas = React.forwardRef(function SvgCanvas({ className, style,
|
|
|
90
92
|
getMatrix: () => matrix,
|
|
91
93
|
setMatrix: (newMatrix) => setMatrix(newMatrix)
|
|
92
94
|
}), [matrix]);
|
|
93
|
-
function
|
|
95
|
+
function transformPointerEvent(evt) {
|
|
94
96
|
if (!svgRef.current)
|
|
95
97
|
return { x: 0, y: 0 };
|
|
96
|
-
const br = svgRef.current.getBoundingClientRect(), [x, y] = translateTo(evt.
|
|
97
|
-
return { buttons:
|
|
98
|
+
const br = svgRef.current.getBoundingClientRect(), [x, y] = translateTo(evt.clientX - br.left, evt.clientY - br.top);
|
|
99
|
+
return { buttons: evt.buttons, x, y };
|
|
98
100
|
}
|
|
99
101
|
function transformMouseEvent(evt) {
|
|
100
102
|
if (!svgRef.current)
|
|
@@ -102,126 +104,161 @@ export const SvgCanvas = React.forwardRef(function SvgCanvas({ className, style,
|
|
|
102
104
|
const br = svgRef.current.getBoundingClientRect(), [x, y] = translateTo(evt.clientX - br.left, evt.clientY - br.top);
|
|
103
105
|
return { buttons: evt.buttons, x, y };
|
|
104
106
|
}
|
|
105
|
-
function
|
|
107
|
+
function onPointerDown(evt) {
|
|
108
|
+
// Track this pointer
|
|
109
|
+
activePointersRef.current.set(evt.pointerId, {
|
|
110
|
+
id: evt.pointerId,
|
|
111
|
+
x: evt.clientX,
|
|
112
|
+
y: evt.clientY
|
|
113
|
+
});
|
|
114
|
+
const pointerCount = activePointersRef.current.size;
|
|
115
|
+
// If we have 2+ pointers, enter pinch-zoom mode
|
|
116
|
+
if (pointerCount >= 2) {
|
|
117
|
+
const pointers = Array.from(activePointersRef.current.values());
|
|
118
|
+
const distance = Math.sqrt((pointers[0].x - pointers[1].x) ** 2 +
|
|
119
|
+
(pointers[0].y - pointers[1].y) ** 2);
|
|
120
|
+
const centerX = (pointers[0].x + pointers[1].x) / 2;
|
|
121
|
+
const centerY = (pointers[0].y + pointers[1].y) / 2;
|
|
122
|
+
lastPinchDistanceRef.current = distance;
|
|
123
|
+
lastPinchCenterRef.current = { x: centerX, y: centerY };
|
|
124
|
+
// Cancel any active tool operation
|
|
125
|
+
setToolStart(undefined);
|
|
126
|
+
setPan(undefined);
|
|
127
|
+
evt.preventDefault();
|
|
128
|
+
evt.stopPropagation();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// Single pointer handling
|
|
106
132
|
evt.preventDefault();
|
|
107
133
|
evt.stopPropagation();
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
134
|
+
// Check if this is a touch event (pointerType === 'touch') or mouse
|
|
135
|
+
if (evt.pointerType === 'mouse') {
|
|
136
|
+
switch (evt.buttons) {
|
|
137
|
+
case 1: /* left button */
|
|
138
|
+
if (onToolStart) {
|
|
139
|
+
const e = transformPointerEvent(evt);
|
|
140
|
+
if (e) {
|
|
141
|
+
setToolStart({ startX: e.x, startY: e.y });
|
|
142
|
+
onToolStart({ startX: e.x, startY: e.y, x: e.x, y: e.y });
|
|
143
|
+
}
|
|
115
144
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const lastX = evt.clientX;
|
|
122
|
-
const lastY = evt.clientY;
|
|
123
|
-
setPan({ lastX, lastY });
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
function onTouchStart(evt) {
|
|
128
|
-
const lastX = evt.touches[0].clientX;
|
|
129
|
-
const lastY = evt.touches[0].clientY;
|
|
130
|
-
evt.stopPropagation();
|
|
131
|
-
if (onToolStart) {
|
|
132
|
-
const e = transformTouchEvent(evt);
|
|
133
|
-
if (e) {
|
|
134
|
-
setToolStart({ startX: e.x, startY: e.y });
|
|
135
|
-
onToolStart({ startX: e.x, startY: e.y, x: e.x, y: e.y });
|
|
145
|
+
setActive(undefined);
|
|
146
|
+
break;
|
|
147
|
+
case 4: /* middle button */
|
|
148
|
+
setPan({ lastX: evt.clientX, lastY: evt.clientY });
|
|
149
|
+
break;
|
|
136
150
|
}
|
|
137
151
|
}
|
|
138
152
|
else {
|
|
139
|
-
|
|
153
|
+
// Touch or pen - single finger
|
|
154
|
+
if (onToolStart) {
|
|
155
|
+
const e = transformPointerEvent(evt);
|
|
156
|
+
if (e) {
|
|
157
|
+
setToolStart({ startX: e.x, startY: e.y });
|
|
158
|
+
onToolStart({ startX: e.x, startY: e.y, x: e.x, y: e.y });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
// No tool active - pan mode
|
|
163
|
+
setPan({ lastX: evt.clientX, lastY: evt.clientY });
|
|
164
|
+
}
|
|
140
165
|
}
|
|
141
166
|
}
|
|
142
|
-
function
|
|
143
|
-
|
|
167
|
+
function onPointerMove(evt) {
|
|
168
|
+
// Update tracked pointer position
|
|
169
|
+
if (activePointersRef.current.has(evt.pointerId)) {
|
|
170
|
+
activePointersRef.current.set(evt.pointerId, {
|
|
171
|
+
id: evt.pointerId,
|
|
172
|
+
x: evt.clientX,
|
|
173
|
+
y: evt.clientY
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const pointerCount = activePointersRef.current.size;
|
|
177
|
+
// Handle 2-finger pinch+pan
|
|
178
|
+
if (pointerCount >= 2 && lastPinchDistanceRef.current !== undefined && lastPinchCenterRef.current !== undefined) {
|
|
179
|
+
const pointers = Array.from(activePointersRef.current.values());
|
|
180
|
+
const newDistance = Math.sqrt((pointers[0].x - pointers[1].x) ** 2 +
|
|
181
|
+
(pointers[0].y - pointers[1].y) ** 2);
|
|
182
|
+
const newCenterX = (pointers[0].x + pointers[1].x) / 2;
|
|
183
|
+
const newCenterY = (pointers[0].y + pointers[1].y) / 2;
|
|
184
|
+
// Calculate zoom scale
|
|
185
|
+
const scale = newDistance / lastPinchDistanceRef.current;
|
|
186
|
+
// Calculate pan delta (center point movement)
|
|
187
|
+
const dx = newCenterX - lastPinchCenterRef.current.x;
|
|
188
|
+
const dy = newCenterY - lastPinchCenterRef.current.y;
|
|
189
|
+
// Apply combined pan+zoom transformation
|
|
190
|
+
setMatrix(m => [
|
|
191
|
+
m[0] * scale,
|
|
192
|
+
m[1] * scale,
|
|
193
|
+
m[2] * scale,
|
|
194
|
+
m[3] * scale,
|
|
195
|
+
(m[4] + dx) - (newCenterX - (m[4] + dx)) * (scale - 1),
|
|
196
|
+
(m[5] + dy) - (newCenterY - (m[5] + dy)) * (scale - 1)
|
|
197
|
+
]);
|
|
198
|
+
// Update tracking state
|
|
199
|
+
lastPinchDistanceRef.current = newDistance;
|
|
200
|
+
lastPinchCenterRef.current = { x: newCenterX, y: newCenterY };
|
|
201
|
+
evt.stopPropagation();
|
|
202
|
+
evt.preventDefault();
|
|
144
203
|
return;
|
|
145
|
-
|
|
146
|
-
//
|
|
204
|
+
}
|
|
205
|
+
// Single pointer handling
|
|
147
206
|
if (dragHandler && toolStart) {
|
|
148
|
-
const e =
|
|
207
|
+
const e = transformPointerEvent(evt);
|
|
149
208
|
dragHandler.onDragMove({ x: e.x, y: e.y, startX: toolStart.startX, startY: toolStart.startY });
|
|
150
209
|
}
|
|
151
210
|
else if (pan) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
evt.stopPropagation();
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
onDragEnd();
|
|
163
|
-
}
|
|
211
|
+
const x = evt.clientX;
|
|
212
|
+
const y = evt.clientY;
|
|
213
|
+
const dx = x - pan.lastX;
|
|
214
|
+
const dy = y - pan.lastY;
|
|
215
|
+
setMatrix(matrix => [matrix[0], matrix[1], matrix[2], matrix[3], matrix[4] + dx, matrix[5] + dy]);
|
|
216
|
+
setPan({ lastX: x, lastY: y });
|
|
217
|
+
evt.stopPropagation();
|
|
164
218
|
}
|
|
165
219
|
else if (onToolMove && toolStart) {
|
|
166
|
-
const e =
|
|
220
|
+
const e = transformPointerEvent(evt);
|
|
167
221
|
if (e) {
|
|
168
222
|
onToolMove({ x: e.x, y: e.y, startX: toolStart.startX, startY: toolStart.startY });
|
|
169
223
|
}
|
|
170
224
|
}
|
|
171
|
-
// Don't call onDragEnd when nothing was started - allows external handlers (like resize) to work
|
|
172
225
|
}
|
|
173
|
-
function
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Pan
|
|
188
|
-
let x = evt.touches[0].clientX, y = evt.touches[0].clientY;
|
|
189
|
-
const dx = x - pan.lastX;
|
|
190
|
-
const dy = y - pan.lastY;
|
|
191
|
-
setMatrix(matrix => [matrix[0], matrix[1], matrix[2], matrix[3], matrix[4] + dx, matrix[5] + dy]);
|
|
192
|
-
setPan({ lastX: x, lastY: y });
|
|
193
|
-
evt.stopPropagation();
|
|
194
|
-
}
|
|
195
|
-
else if (onToolMove && toolStart) {
|
|
196
|
-
const e = transformTouchEvent(evt);
|
|
197
|
-
if (e) {
|
|
198
|
-
onToolMove({ x: e.x, y: e.y, startX: toolStart.startX, startY: toolStart.startY });
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
else
|
|
202
|
-
return onDragEnd();
|
|
226
|
+
function onPointerUp(evt) {
|
|
227
|
+
// Remove this pointer from tracking
|
|
228
|
+
activePointersRef.current.delete(evt.pointerId);
|
|
229
|
+
const pointerCount = activePointersRef.current.size;
|
|
230
|
+
// If we still have pointers, reinitialize pinch state with remaining pointers
|
|
231
|
+
if (pointerCount >= 2) {
|
|
232
|
+
const pointers = Array.from(activePointersRef.current.values());
|
|
233
|
+
lastPinchDistanceRef.current = Math.sqrt((pointers[0].x - pointers[1].x) ** 2 +
|
|
234
|
+
(pointers[0].y - pointers[1].y) ** 2);
|
|
235
|
+
lastPinchCenterRef.current = {
|
|
236
|
+
x: (pointers[0].x + pointers[1].x) / 2,
|
|
237
|
+
y: (pointers[0].y + pointers[1].y) / 2
|
|
238
|
+
};
|
|
239
|
+
return;
|
|
203
240
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
241
|
+
// If we're down to 1 pointer and were pinching, reset pinch state
|
|
242
|
+
if (pointerCount === 1) {
|
|
243
|
+
lastPinchDistanceRef.current = undefined;
|
|
244
|
+
lastPinchCenterRef.current = undefined;
|
|
245
|
+
// Don't start panning - let the remaining pointer continue without action
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
// All pointers released - cleanup
|
|
249
|
+
onDragEnd();
|
|
250
|
+
}
|
|
251
|
+
function onPointerCancel(evt) {
|
|
252
|
+
activePointersRef.current.delete(evt.pointerId);
|
|
253
|
+
if (activePointersRef.current.size === 0) {
|
|
254
|
+
lastPinchDistanceRef.current = undefined;
|
|
255
|
+
lastPinchCenterRef.current = undefined;
|
|
256
|
+
onDragEnd();
|
|
220
257
|
}
|
|
221
258
|
}
|
|
222
259
|
function onDragEnd() {
|
|
223
|
-
|
|
224
|
-
|
|
260
|
+
lastPinchDistanceRef.current = undefined;
|
|
261
|
+
lastPinchCenterRef.current = undefined;
|
|
225
262
|
if (dragHandler) {
|
|
226
263
|
dragHandler.onDragEnd?.();
|
|
227
264
|
setDragHandler(undefined);
|
|
@@ -233,6 +270,7 @@ export const SvgCanvas = React.forwardRef(function SvgCanvas({ className, style,
|
|
|
233
270
|
onToolEnd();
|
|
234
271
|
setDrag(undefined);
|
|
235
272
|
setPan(undefined);
|
|
273
|
+
setToolStart(undefined);
|
|
236
274
|
}
|
|
237
275
|
function onWheel(evt) {
|
|
238
276
|
const page = svgRef.current?.getBoundingClientRect() || { left: 0, top: 0 };
|
|
@@ -256,7 +294,7 @@ export const SvgCanvas = React.forwardRef(function SvgCanvas({ className, style,
|
|
|
256
294
|
]);
|
|
257
295
|
}
|
|
258
296
|
return React.createElement(SvgCanvasContext.Provider, { value: svgContext },
|
|
259
|
-
React.createElement("svg", { ref: svgRef, className: className, style: { ...style, touchAction: 'none' },
|
|
297
|
+
React.createElement("svg", { ref: svgRef, className: className, style: { ...style, touchAction: 'none' }, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onPointerCancel: onPointerCancel, onWheel: onWheel },
|
|
260
298
|
React.createElement("g", { transform: `matrix(${matrix.map(x => Math.round(x * 1000) / 1000).join(' ')})` }, children),
|
|
261
299
|
React.createElement("g", null, fixed)));
|
|
262
300
|
});
|
package/package.json
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-svg-canvas",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "React SVG
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React library for building interactive SVG canvas applications with pan, zoom, selection, drag-and-drop, resize, and Figma-style snapping",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
|
+
"module": "lib/index.js",
|
|
8
|
+
"types": "lib/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./lib/index.d.ts",
|
|
12
|
+
"import": "./lib/index.js",
|
|
13
|
+
"default": "./lib/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"sideEffects": false,
|
|
7
17
|
"files": [
|
|
8
18
|
"lib"
|
|
9
19
|
],
|
|
@@ -12,15 +22,38 @@
|
|
|
12
22
|
"build": "tsc",
|
|
13
23
|
"watch": "tsc -w",
|
|
14
24
|
"clean": "rimraf .cache lib",
|
|
25
|
+
"prepublishOnly": "npm run build",
|
|
15
26
|
"pub": "npm publish --access public"
|
|
16
27
|
},
|
|
17
28
|
"keywords": [
|
|
18
29
|
"react",
|
|
19
30
|
"svg",
|
|
20
|
-
"canvas"
|
|
31
|
+
"canvas",
|
|
32
|
+
"pan",
|
|
33
|
+
"zoom",
|
|
34
|
+
"drag-and-drop",
|
|
35
|
+
"selection",
|
|
36
|
+
"resize",
|
|
37
|
+
"snapping",
|
|
38
|
+
"interactive",
|
|
39
|
+
"editor",
|
|
40
|
+
"diagram",
|
|
41
|
+
"whiteboard",
|
|
42
|
+
"figma"
|
|
21
43
|
],
|
|
22
|
-
"author": "Szilard Hajba <
|
|
44
|
+
"author": "Szilard Hajba <szilard@cloudillo.org>",
|
|
23
45
|
"license": "MIT",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/cloudillo/react-svg-canvas.git"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/cloudillo/react-svg-canvas#readme",
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/cloudillo/react-svg-canvas/issues"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=18.0.0"
|
|
56
|
+
},
|
|
24
57
|
"devDependencies": {
|
|
25
58
|
"@types/react": "^19.2.7",
|
|
26
59
|
"@types/react-dom": "^19.2.3",
|
|
@@ -29,6 +62,6 @@
|
|
|
29
62
|
"typescript": "^5.9.3"
|
|
30
63
|
},
|
|
31
64
|
"peerDependencies": {
|
|
32
|
-
"react": "
|
|
65
|
+
"react": ">=18.0.0"
|
|
33
66
|
}
|
|
34
67
|
}
|