@bwp-web/canvas 0.4.1 → 0.4.3

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.
Files changed (37) hide show
  1. package/dist/Canvas/Canvas.d.ts +11 -1
  2. package/dist/Canvas/Canvas.d.ts.map +1 -1
  3. package/dist/background.d.ts +15 -3
  4. package/dist/background.d.ts.map +1 -1
  5. package/dist/hooks/index.d.ts +8 -0
  6. package/dist/hooks/index.d.ts.map +1 -1
  7. package/dist/hooks/shared.d.ts +3 -2
  8. package/dist/hooks/shared.d.ts.map +1 -1
  9. package/dist/hooks/useCanvasClick.d.ts +27 -0
  10. package/dist/hooks/useCanvasClick.d.ts.map +1 -0
  11. package/dist/hooks/useCanvasEvents.d.ts +35 -0
  12. package/dist/hooks/useCanvasEvents.d.ts.map +1 -0
  13. package/dist/hooks/useCanvasTooltip.d.ts +45 -0
  14. package/dist/hooks/useCanvasTooltip.d.ts.map +1 -0
  15. package/dist/hooks/useEditCanvas.d.ts +18 -1
  16. package/dist/hooks/useEditCanvas.d.ts.map +1 -1
  17. package/dist/hooks/useObjectOverlay.d.ts +49 -0
  18. package/dist/hooks/useObjectOverlay.d.ts.map +1 -0
  19. package/dist/hooks/useViewCanvas.d.ts +3 -1
  20. package/dist/hooks/useViewCanvas.d.ts.map +1 -1
  21. package/dist/index.cjs +471 -102
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.ts +13 -4
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +399 -32
  26. package/dist/index.js.map +1 -1
  27. package/dist/interactions/dragToCreate.d.ts +2 -0
  28. package/dist/interactions/dragToCreate.d.ts.map +1 -1
  29. package/dist/interactions/drawToCreate.d.ts +7 -1
  30. package/dist/interactions/drawToCreate.d.ts.map +1 -1
  31. package/dist/serialization.d.ts +12 -1
  32. package/dist/serialization.d.ts.map +1 -1
  33. package/dist/types.d.ts +5 -0
  34. package/dist/types.d.ts.map +1 -1
  35. package/dist/viewport.d.ts +12 -1
  36. package/dist/viewport.d.ts.map +1 -1
  37. package/package.json +6 -5
package/dist/index.d.ts CHANGED
@@ -5,6 +5,14 @@ export { useEditCanvas } from './hooks';
5
5
  export type { UseEditCanvasOptions } from './hooks';
6
6
  export { useViewCanvas } from './hooks';
7
7
  export type { UseViewCanvasOptions, ViewObjectStyle } from './hooks';
8
+ export { useCanvasEvents } from './hooks';
9
+ export type { CanvasEventHandlers } from './hooks';
10
+ export { useCanvasTooltip } from './hooks';
11
+ export type { UseCanvasTooltipOptions, CanvasTooltipState } from './hooks';
12
+ export { useCanvasClick } from './hooks';
13
+ export type { UseCanvasClickOptions } from './hooks';
14
+ export { useObjectOverlay } from './hooks';
15
+ export type { UseObjectOverlayOptions } from './hooks';
8
16
  export type { Point2D, ShapeStyleOptions, SnappingOptions, InteractionModeOptions, SnappableInteractionOptions, DragBounds, ModeSetup, } from './types';
9
17
  export { createRectangle, createRectangleAtPoint, editRectangle, } from './shapes';
10
18
  export type { RectangleOptions, RectangleAtPointOptions } from './shapes';
@@ -20,7 +28,7 @@ export type { DrawToCreateOptions } from './interactions';
20
28
  export { enableVertexEdit } from './interactions';
21
29
  export type { VertexEditOptions } from './interactions';
22
30
  export { enablePanAndZoom, resetViewport } from './viewport';
23
- export type { ViewportController, ViewportMode, PanAndZoomOptions, } from './viewport';
31
+ export type { ViewportController, ViewportMode, PanAndZoomOptions, PanToObjectOptions, } from './viewport';
24
32
  export { getSnapPoints, registerSnapPointExtractor } from './alignment';
25
33
  export type { SnapPointExtractor } from './alignment';
26
34
  export { enableObjectAlignment } from './alignment';
@@ -31,8 +39,9 @@ export { snapCursorPoint } from './alignment';
31
39
  export type { CursorSnapResult, CursorSnapOptions, GuidelineStyle, } from './alignment';
32
40
  export { deleteObjects, enableKeyboardShortcuts } from './keyboard';
33
41
  export { enableScaledStrokes, serializeCanvas, loadCanvas, getBaseStrokeWidth, } from './serialization';
34
- export type { SerializeOptions } from './serialization';
35
- export { fitViewportToBackground, setBackgroundOpacity, getBackgroundOpacity, setBackgroundInverted, getBackgroundInverted, resizeImageUrl, setBackgroundImage, } from './background';
36
- export type { FitViewportOptions, ResizeResult, ResizeImageOptions, } from './background';
42
+ export type { SerializeOptions, LoadCanvasOptions } from './serialization';
43
+ export { fitViewportToBackground, getBackgroundSrc, setBackgroundOpacity, getBackgroundOpacity, setBackgroundInverted, getBackgroundInverted, resizeImageUrl, setBackgroundImage, } from './background';
44
+ export type { FitViewportOptions, ResizeResult, ResizeImageOptions, SetBackgroundImageOptions, } from './background';
37
45
  export { DEFAULT_CONTROL_STYLE, DEFAULT_SHAPE_STYLE, DEFAULT_CIRCLE_STYLE, DEFAULT_DRAG_SHAPE_STYLE, DEFAULT_GUIDELINE_SHAPE_STYLE, DEFAULT_ALIGNMENT_STYLE, } from './styles';
46
+ export { Canvas as FabricCanvas, FabricObject, FabricImage, Rect, Polygon, Point, util, } from 'fabric';
38
47
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ // src/fabricAugmentation.ts
2
+ import "fabric";
3
+
1
4
  // src/Canvas/Canvas.tsx
2
5
  import { Canvas as FabricCanvas } from "fabric";
3
6
  import { useEffect, useRef } from "react";
@@ -26,27 +29,56 @@ function enableKeyboardShortcuts(canvas) {
26
29
  // src/Canvas/Canvas.tsx
27
30
  import { jsx } from "react/jsx-runtime";
28
31
  function Canvas({
29
- width = 800,
30
- height = 600,
32
+ width,
33
+ height,
31
34
  className,
32
35
  style,
33
36
  onReady
34
37
  }) {
35
38
  const canvasRef = useRef(null);
39
+ const wrapperRef = useRef(null);
40
+ const isFixedSize = width !== void 0 && height !== void 0;
36
41
  useEffect(() => {
37
42
  const el = canvasRef.current;
38
- if (!el) {
39
- return;
40
- }
41
- const fabricCanvas = new FabricCanvas(el, { width, height });
43
+ const wrapper = wrapperRef.current;
44
+ if (!el || !wrapper) return;
45
+ const initialWidth = isFixedSize ? width : wrapper.clientWidth || 800;
46
+ const initialHeight = isFixedSize ? height : wrapper.clientHeight || 600;
47
+ const fabricCanvas = new FabricCanvas(el, {
48
+ width: initialWidth,
49
+ height: initialHeight
50
+ });
42
51
  onReady?.(fabricCanvas);
43
52
  const cleanupShortcuts = enableKeyboardShortcuts(fabricCanvas);
53
+ let observer;
54
+ let rafId = 0;
55
+ if (!isFixedSize) {
56
+ let currentWidth = initialWidth;
57
+ let currentHeight = initialHeight;
58
+ observer = new ResizeObserver((entries) => {
59
+ cancelAnimationFrame(rafId);
60
+ rafId = requestAnimationFrame(() => {
61
+ const entry = entries[0];
62
+ if (!entry) return;
63
+ const { width: newWidth, height: newHeight } = entry.contentRect;
64
+ if (newWidth > 0 && newHeight > 0 && (newWidth !== currentWidth || newHeight !== currentHeight)) {
65
+ currentWidth = newWidth;
66
+ currentHeight = newHeight;
67
+ fabricCanvas.setDimensions({ width: newWidth, height: newHeight });
68
+ }
69
+ });
70
+ });
71
+ observer.observe(wrapper);
72
+ }
44
73
  return () => {
74
+ cancelAnimationFrame(rafId);
75
+ observer?.disconnect();
45
76
  cleanupShortcuts();
46
77
  fabricCanvas.dispose();
47
78
  };
48
79
  }, []);
49
- return /* @__PURE__ */ jsx("div", { className, style, children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef }) });
80
+ const wrapperStyle = isFixedSize ? { ...style } : { width: "100%", height: "100%", ...style };
81
+ return /* @__PURE__ */ jsx("div", { ref: wrapperRef, className, style: wrapperStyle, children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef }) });
50
82
  }
51
83
 
52
84
  // src/hooks/useEditCanvas.ts
@@ -54,7 +86,9 @@ import { useCallback, useEffect as useEffect2, useRef as useRef2, useState } fro
54
86
  import { Polygon as Polygon4 } from "fabric";
55
87
 
56
88
  // src/viewport.ts
57
- import { Point } from "fabric";
89
+ import {
90
+ Point
91
+ } from "fabric";
58
92
 
59
93
  // src/constants.ts
60
94
  var DEFAULT_MIN_ZOOM = 0.2;
@@ -256,6 +290,54 @@ function enablePanAndZoom(canvas, options) {
256
290
  z
257
291
  );
258
292
  },
293
+ panToObject(object, panOpts) {
294
+ const zoom = canvas.getZoom();
295
+ const objectCenter = object.getCenterPoint();
296
+ const canvasCenterX = canvas.getWidth() / 2;
297
+ const canvasCenterY = canvas.getHeight() / 2;
298
+ const targetX = canvasCenterX - objectCenter.x * zoom;
299
+ const targetY = canvasCenterY - objectCenter.y * zoom;
300
+ if (!panOpts?.animate) {
301
+ const vt2 = canvas.viewportTransform;
302
+ if (!vt2) return;
303
+ canvas.setViewportTransform([
304
+ vt2[0],
305
+ vt2[1],
306
+ vt2[2],
307
+ vt2[3],
308
+ targetX,
309
+ targetY
310
+ ]);
311
+ return;
312
+ }
313
+ const duration = panOpts.duration ?? 300;
314
+ const vt = canvas.viewportTransform;
315
+ if (!vt) return;
316
+ const startX = vt[4];
317
+ const startY = vt[5];
318
+ const startTime = performance.now();
319
+ function step(now) {
320
+ const elapsed = now - startTime;
321
+ const t = Math.min(elapsed / duration, 1);
322
+ const eased = 1 - Math.pow(1 - t, 3);
323
+ const currentX = startX + (targetX - startX) * eased;
324
+ const currentY = startY + (targetY - startY) * eased;
325
+ const currentVt = canvas.viewportTransform;
326
+ if (!currentVt) return;
327
+ canvas.setViewportTransform([
328
+ currentVt[0],
329
+ currentVt[1],
330
+ currentVt[2],
331
+ currentVt[3],
332
+ currentX,
333
+ currentY
334
+ ]);
335
+ if (t < 1) {
336
+ requestAnimationFrame(step);
337
+ }
338
+ }
339
+ requestAnimationFrame(step);
340
+ },
259
341
  cleanup() {
260
342
  canvas.off("mouse:wheel", handleWheel);
261
343
  canvas.off("mouse:down", panHandlers.handleMouseDown);
@@ -274,6 +356,11 @@ import { FabricImage, filters } from "fabric";
274
356
  function getBackgroundImage(canvas) {
275
357
  return canvas.backgroundImage;
276
358
  }
359
+ function getBackgroundSrc(canvas) {
360
+ const bg = getBackgroundImage(canvas);
361
+ if (!bg) return null;
362
+ return bg.getSrc() || null;
363
+ }
277
364
  function fitViewportToBackground(canvas, options) {
278
365
  const bg = getBackgroundImage(canvas);
279
366
  if (!bg) return;
@@ -369,14 +456,18 @@ function resizeImageUrl(url, options) {
369
456
  img.src = url;
370
457
  });
371
458
  }
372
- async function setBackgroundImage(canvas, url, resize) {
459
+ async function setBackgroundImage(canvas, url, options) {
460
+ const prevOpacity = options?.preserveOpacity ? getBackgroundOpacity(canvas) : void 0;
373
461
  let imageUrl = url;
374
- if (resize !== void 0) {
375
- const result = await resizeImageUrl(url, resize);
462
+ if (options !== void 0) {
463
+ const result = await resizeImageUrl(url, options);
376
464
  imageUrl = result.url;
377
465
  }
378
466
  const img = await FabricImage.fromURL(imageUrl, { crossOrigin: "anonymous" });
379
467
  canvas.backgroundImage = img;
468
+ if (prevOpacity !== void 0 && prevOpacity !== 1) {
469
+ img.set("opacity", prevOpacity);
470
+ }
380
471
  canvas.requestRenderAll();
381
472
  return img;
382
473
  }
@@ -405,7 +496,10 @@ function createViewportActions(canvasRef, viewportRef, setZoom) {
405
496
  viewportRef.current?.zoomOut(step);
406
497
  syncZoom(canvasRef, setZoom);
407
498
  };
408
- return { resetViewport: resetViewport2, zoomIn, zoomOut };
499
+ const panToObject = (object, panOpts) => {
500
+ viewportRef.current?.panToObject(object, panOpts);
501
+ };
502
+ return { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject };
409
503
  }
410
504
  function resolveAlignmentEnabled(enableAlignment, alignmentProp) {
411
505
  if (enableAlignment !== void 0) return enableAlignment;
@@ -1301,21 +1395,39 @@ function enableDragToCreate(canvas, factory, options) {
1301
1395
  options?.onCreated?.(obj);
1302
1396
  previewRect = null;
1303
1397
  };
1398
+ const handleKeyDown = (e) => {
1399
+ if (e.key === "Escape") {
1400
+ e.stopImmediatePropagation();
1401
+ e.preventDefault();
1402
+ cleanup("cancel");
1403
+ }
1404
+ };
1304
1405
  canvas.on("mouse:down", handleMouseDown);
1305
1406
  canvas.on("mouse:move", handleMouseMove);
1306
1407
  canvas.on("mouse:up", handleMouseUp);
1307
- return () => {
1408
+ document.addEventListener("keydown", handleKeyDown, true);
1409
+ let exited = false;
1410
+ function cleanup(reason) {
1411
+ if (exited) return;
1412
+ exited = true;
1308
1413
  canvas.off("mouse:down", handleMouseDown);
1309
1414
  canvas.off("mouse:move", handleMouseMove);
1310
1415
  canvas.off("mouse:up", handleMouseUp);
1416
+ document.removeEventListener("keydown", handleKeyDown, true);
1311
1417
  shiftTracker.cleanup();
1312
1418
  snapping.cleanup();
1313
1419
  if (isDrawing && previewRect) {
1420
+ snapping.excludeSet.delete(previewRect);
1314
1421
  canvas.remove(previewRect);
1422
+ canvas.selection = previousSelection;
1315
1423
  canvas.requestRenderAll();
1316
1424
  }
1317
1425
  restoreViewport(options?.viewport);
1318
- };
1426
+ if (reason === "cancel") {
1427
+ options?.onCancel?.();
1428
+ }
1429
+ }
1430
+ return () => cleanup();
1319
1431
  }
1320
1432
 
1321
1433
  // src/interactions/drawToCreate.ts
@@ -1595,6 +1707,9 @@ function enableDrawToCreate(canvas, options) {
1595
1707
  removePreviewElements();
1596
1708
  snapping.clearSnapResult();
1597
1709
  const polygon = createPolygonFromVertices(canvas, points, options?.style);
1710
+ if (options?.data) {
1711
+ polygon.data = options.data;
1712
+ }
1598
1713
  canvas.selection = previousSelection;
1599
1714
  canvas.requestRenderAll();
1600
1715
  restoreViewport(options?.viewport);
@@ -1973,8 +2088,15 @@ function serializeCanvas(canvas, options) {
1973
2088
  });
1974
2089
  return json;
1975
2090
  }
1976
- async function loadCanvas(canvas, json) {
2091
+ async function loadCanvas(canvas, json, options) {
1977
2092
  await canvas.loadFromJSON(json);
2093
+ if (options?.filter) {
2094
+ const toRemove = [];
2095
+ canvas.forEachObject((obj) => {
2096
+ if (!options.filter(obj)) toRemove.push(obj);
2097
+ });
2098
+ for (const obj of toRemove) canvas.remove(obj);
2099
+ }
1978
2100
  canvas.forEachObject((obj) => {
1979
2101
  obj.set(DEFAULT_CONTROL_STYLE);
1980
2102
  if (obj.shapeType === "circle" && obj instanceof Rect5) {
@@ -1982,6 +2104,7 @@ async function loadCanvas(canvas, json) {
1982
2104
  }
1983
2105
  });
1984
2106
  canvas.requestRenderAll();
2107
+ return canvas.getObjects();
1985
2108
  }
1986
2109
 
1987
2110
  // src/hooks/useEditCanvas.ts
@@ -1997,6 +2120,7 @@ function useEditCanvas(options) {
1997
2120
  const [selected, setSelected] = useState([]);
1998
2121
  const [viewportMode, setViewportModeState] = useState("select");
1999
2122
  const [isEditingVertices, setIsEditingVertices] = useState(false);
2123
+ const [isDirty, setIsDirty] = useState(false);
2000
2124
  const setMode = useCallback((setup) => {
2001
2125
  vertexEditCleanupRef.current?.();
2002
2126
  vertexEditCleanupRef.current = null;
@@ -2067,6 +2191,11 @@ function useEditCanvas(options) {
2067
2191
  canvas.on("selection:cleared", () => {
2068
2192
  setSelected([]);
2069
2193
  });
2194
+ if (options?.trackChanges) {
2195
+ canvas.on("object:added", () => setIsDirty(true));
2196
+ canvas.on("object:removed", () => setIsDirty(true));
2197
+ canvas.on("object:modified", () => setIsDirty(true));
2198
+ }
2070
2199
  if (options?.vertexEdit !== false) {
2071
2200
  const vertexOpts = typeof options?.vertexEdit === "object" ? options.vertexEdit : void 0;
2072
2201
  canvas.on("mouse:dblclick", (e) => {
@@ -2121,22 +2250,26 @@ function useEditCanvas(options) {
2121
2250
  viewportRef.current?.setMode(mode);
2122
2251
  setViewportModeState(mode);
2123
2252
  }, []);
2124
- const { resetViewport: resetViewport2, zoomIn, zoomOut } = createViewportActions(
2253
+ const { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject } = createViewportActions(
2125
2254
  canvasRef,
2126
2255
  viewportRef,
2127
2256
  setZoom
2128
2257
  );
2129
- const setBackground = useCallback(async (url) => {
2130
- const canvas = canvasRef.current;
2131
- if (!canvas) throw new Error("Canvas not ready");
2132
- const resizeOpts = options?.backgroundResize !== false ? typeof options?.backgroundResize === "object" ? options.backgroundResize : {} : void 0;
2133
- const img = await setBackgroundImage(canvas, url, resizeOpts);
2134
- if (options?.autoFitToBackground !== false) {
2135
- fitViewportToBackground(canvas);
2136
- syncZoom(canvasRef, setZoom);
2137
- }
2138
- return img;
2139
- }, []);
2258
+ const setBackground = useCallback(
2259
+ async (url, bgOpts) => {
2260
+ const canvas = canvasRef.current;
2261
+ if (!canvas) throw new Error("Canvas not ready");
2262
+ const resizeOpts = options?.backgroundResize !== false ? typeof options?.backgroundResize === "object" ? { ...options.backgroundResize, ...bgOpts } : { ...bgOpts } : bgOpts?.preserveOpacity ? { preserveOpacity: true } : void 0;
2263
+ const img = await setBackgroundImage(canvas, url, resizeOpts);
2264
+ if (options?.autoFitToBackground !== false) {
2265
+ fitViewportToBackground(canvas);
2266
+ syncZoom(canvasRef, setZoom);
2267
+ }
2268
+ return img;
2269
+ },
2270
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2271
+ []
2272
+ );
2140
2273
  return {
2141
2274
  /** Pass this to `<Canvas onReady={...} />` */
2142
2275
  onReady,
@@ -2157,7 +2290,9 @@ function useEditCanvas(options) {
2157
2290
  /** Zoom in toward the canvas center. Default step: 0.2. */
2158
2291
  zoomIn,
2159
2292
  /** Zoom out from the canvas center. Default step: 0.2. */
2160
- zoomOut
2293
+ zoomOut,
2294
+ /** Pan the viewport to center on a specific object. */
2295
+ panToObject
2161
2296
  },
2162
2297
  /** Whether vertex edit mode is currently active (reactive). */
2163
2298
  isEditingVertices,
@@ -2181,8 +2316,15 @@ function useEditCanvas(options) {
2181
2316
  * Set a background image from a URL. Automatically resizes if the image
2182
2317
  * exceeds the configured limits (opt out via `backgroundResize: false`),
2183
2318
  * and fits the viewport after setting if `autoFitToBackground` is enabled.
2319
+ *
2320
+ * Pass `{ preserveOpacity: true }` to keep the current background opacity
2321
+ * when replacing the image.
2184
2322
  */
2185
- setBackground
2323
+ setBackground,
2324
+ /** Whether the canvas has been modified since the last `resetDirty()` call. Requires `trackChanges: true`. */
2325
+ isDirty,
2326
+ /** Reset the dirty flag (e.g., after a successful save). */
2327
+ resetDirty: useCallback(() => setIsDirty(false), [])
2186
2328
  };
2187
2329
  }
2188
2330
 
@@ -2240,7 +2382,7 @@ function useViewCanvas(options) {
2240
2382
  // eslint-disable-next-line react-hooks/exhaustive-deps
2241
2383
  []
2242
2384
  );
2243
- const { resetViewport: resetViewport2, zoomIn, zoomOut } = createViewportActions(
2385
+ const { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject } = createViewportActions(
2244
2386
  canvasRef,
2245
2387
  viewportRef,
2246
2388
  setZoom
@@ -2306,7 +2448,9 @@ function useViewCanvas(options) {
2306
2448
  /** Zoom in toward the canvas center. Default step: 0.2. */
2307
2449
  zoomIn,
2308
2450
  /** Zoom out from the canvas center. Default step: 0.2. */
2309
- zoomOut
2451
+ zoomOut,
2452
+ /** Pan the viewport to center on a specific object. */
2453
+ panToObject
2310
2454
  },
2311
2455
  /** Update a single object's visual style by its `data.id`. */
2312
2456
  setObjectStyle,
@@ -2316,6 +2460,217 @@ function useViewCanvas(options) {
2316
2460
  setObjectStyleByType
2317
2461
  };
2318
2462
  }
2463
+
2464
+ // src/hooks/useCanvasEvents.ts
2465
+ import { useEffect as useEffect3, useRef as useRef4 } from "react";
2466
+ function useCanvasEvents(canvasRef, events) {
2467
+ const eventsRef = useRef4(events);
2468
+ eventsRef.current = events;
2469
+ useEffect3(() => {
2470
+ const canvas = canvasRef.current;
2471
+ if (!canvas) return;
2472
+ const wrappers = /* @__PURE__ */ new Map();
2473
+ for (const key of Object.keys(eventsRef.current)) {
2474
+ if (!eventsRef.current[key]) continue;
2475
+ const wrapped = (options) => {
2476
+ eventsRef.current[key]?.(options);
2477
+ };
2478
+ canvas.on(key, wrapped);
2479
+ wrappers.set(key, wrapped);
2480
+ }
2481
+ return () => {
2482
+ wrappers.forEach((handler, name) => {
2483
+ canvas.off(name, handler);
2484
+ });
2485
+ };
2486
+ }, [canvasRef]);
2487
+ }
2488
+
2489
+ // src/hooks/useCanvasTooltip.ts
2490
+ import { useEffect as useEffect4, useRef as useRef5, useState as useState3 } from "react";
2491
+ function useCanvasTooltip(canvasRef, options) {
2492
+ const [state, setState] = useState3({
2493
+ visible: false,
2494
+ content: null,
2495
+ position: { x: 0, y: 0 }
2496
+ });
2497
+ const hoveredObjectRef = useRef5(null);
2498
+ const optionsRef = useRef5(options);
2499
+ optionsRef.current = options;
2500
+ useEffect4(() => {
2501
+ const canvas = canvasRef.current;
2502
+ if (!canvas) return;
2503
+ function calculatePosition(target) {
2504
+ const bounds = target.getBoundingRect();
2505
+ const zoom = canvas.getZoom();
2506
+ const vt = canvas.viewportTransform;
2507
+ if (!vt) return null;
2508
+ return {
2509
+ x: (bounds.left + bounds.width / 2) * zoom + vt[4],
2510
+ y: bounds.top * zoom + vt[5] - 10
2511
+ };
2512
+ }
2513
+ const handleMouseOver = (e) => {
2514
+ const target = e.target;
2515
+ if (!target) return;
2516
+ const content = optionsRef.current.getContent(target);
2517
+ if (content === null) return;
2518
+ if (hoveredObjectRef.current !== target) {
2519
+ hoveredObjectRef.current = target;
2520
+ const position = calculatePosition(target);
2521
+ if (position) {
2522
+ setState({ visible: true, content, position });
2523
+ }
2524
+ }
2525
+ };
2526
+ const handleMouseOut = (e) => {
2527
+ if (e.target && hoveredObjectRef.current === e.target) {
2528
+ setState((prev) => ({ ...prev, visible: false }));
2529
+ hoveredObjectRef.current = null;
2530
+ }
2531
+ };
2532
+ const updatePosition = () => {
2533
+ if (!hoveredObjectRef.current) return;
2534
+ const position = calculatePosition(hoveredObjectRef.current);
2535
+ if (position) {
2536
+ setState((prev) => prev.visible ? { ...prev, position } : prev);
2537
+ }
2538
+ };
2539
+ canvas.on("mouse:over", handleMouseOver);
2540
+ canvas.on("mouse:out", handleMouseOut);
2541
+ canvas.on("after:render", updatePosition);
2542
+ canvas.on("mouse:wheel", updatePosition);
2543
+ return () => {
2544
+ canvas.off("mouse:over", handleMouseOver);
2545
+ canvas.off("mouse:out", handleMouseOut);
2546
+ canvas.off("after:render", updatePosition);
2547
+ canvas.off("mouse:wheel", updatePosition);
2548
+ };
2549
+ }, [canvasRef]);
2550
+ return state;
2551
+ }
2552
+
2553
+ // src/hooks/useCanvasClick.ts
2554
+ import { useEffect as useEffect5, useRef as useRef6 } from "react";
2555
+ function useCanvasClick(canvasRef, onClick, options) {
2556
+ const onClickRef = useRef6(onClick);
2557
+ onClickRef.current = onClick;
2558
+ const optionsRef = useRef6(options);
2559
+ optionsRef.current = options;
2560
+ useEffect5(() => {
2561
+ const canvas = canvasRef.current;
2562
+ if (!canvas) return;
2563
+ let mouseDown = null;
2564
+ let isPanning = false;
2565
+ const handleMouseDown = (e) => {
2566
+ const native = e.e instanceof TouchEvent ? e.e.touches[0] : e.e;
2567
+ if (native) {
2568
+ mouseDown = { x: native.clientX, y: native.clientY, time: Date.now() };
2569
+ isPanning = false;
2570
+ }
2571
+ };
2572
+ const handleMouseMove = (e) => {
2573
+ if (!mouseDown) return;
2574
+ const native = e.e instanceof TouchEvent ? e.e.touches[0] : e.e;
2575
+ if (!native) return;
2576
+ const threshold = optionsRef.current?.threshold ?? 5;
2577
+ const deltaX = Math.abs(native.clientX - mouseDown.x);
2578
+ const deltaY = Math.abs(native.clientY - mouseDown.y);
2579
+ if (deltaX > threshold || deltaY > threshold) {
2580
+ isPanning = true;
2581
+ }
2582
+ };
2583
+ const handleMouseUp = (e) => {
2584
+ if (!mouseDown) return;
2585
+ const maxDuration = optionsRef.current?.maxDuration ?? 300;
2586
+ const elapsed = Date.now() - mouseDown.time;
2587
+ if (!isPanning && elapsed < maxDuration) {
2588
+ onClickRef.current(e.target);
2589
+ }
2590
+ mouseDown = null;
2591
+ isPanning = false;
2592
+ };
2593
+ canvas.on("mouse:down", handleMouseDown);
2594
+ canvas.on("mouse:move", handleMouseMove);
2595
+ canvas.on("mouse:up", handleMouseUp);
2596
+ return () => {
2597
+ canvas.off("mouse:down", handleMouseDown);
2598
+ canvas.off("mouse:move", handleMouseMove);
2599
+ canvas.off("mouse:up", handleMouseUp);
2600
+ };
2601
+ }, [canvasRef]);
2602
+ }
2603
+
2604
+ // src/hooks/useObjectOverlay.ts
2605
+ import { useEffect as useEffect6, useRef as useRef7 } from "react";
2606
+ import { util as util4 } from "fabric";
2607
+ function useObjectOverlay(canvasRef, object, options) {
2608
+ const containerRef = useRef7(null);
2609
+ const optionsRef = useRef7(options);
2610
+ optionsRef.current = options;
2611
+ useEffect6(() => {
2612
+ const canvas = canvasRef.current;
2613
+ if (!canvas || !object) return;
2614
+ function update() {
2615
+ const el = containerRef.current;
2616
+ if (!el || !canvas || !object) return;
2617
+ const zoom = canvas.getZoom();
2618
+ const vt = canvas.viewportTransform;
2619
+ if (!vt) return;
2620
+ const center = object.getCenterPoint();
2621
+ const actualWidth = (object.width ?? 0) * (object.scaleX ?? 1);
2622
+ const actualHeight = (object.height ?? 0) * (object.scaleY ?? 1);
2623
+ const screenCoords = util4.transformPoint(center, vt);
2624
+ const screenWidth = actualWidth * zoom;
2625
+ const screenHeight = actualHeight * zoom;
2626
+ el.style.left = `${screenCoords.x - screenWidth / 2}px`;
2627
+ el.style.top = `${screenCoords.y - screenHeight / 2}px`;
2628
+ el.style.width = `${screenWidth}px`;
2629
+ el.style.height = `${screenHeight}px`;
2630
+ const angle = object.angle ?? 0;
2631
+ el.style.rotate = angle !== 0 ? `${angle}deg` : "";
2632
+ const opts = optionsRef.current;
2633
+ if (opts?.autoScaleContent) {
2634
+ const contentScale = Math.min(screenWidth, screenHeight) / 100;
2635
+ const clampedScale = Math.max(0.1, Math.min(contentScale, 2));
2636
+ el.style.setProperty("--overlay-scale", String(clampedScale));
2637
+ if (opts.textSelector) {
2638
+ const textMinScale = opts.textMinScale ?? 0.5;
2639
+ const textEls = el.querySelectorAll(opts.textSelector);
2640
+ const display = clampedScale < textMinScale ? "none" : "";
2641
+ textEls.forEach((t) => {
2642
+ t.style.display = display;
2643
+ });
2644
+ }
2645
+ }
2646
+ }
2647
+ update();
2648
+ canvas.on("after:render", update);
2649
+ canvas.on("mouse:wheel", update);
2650
+ object.on("moving", update);
2651
+ object.on("scaling", update);
2652
+ object.on("rotating", update);
2653
+ return () => {
2654
+ canvas.off("after:render", update);
2655
+ canvas.off("mouse:wheel", update);
2656
+ object.off("moving", update);
2657
+ object.off("scaling", update);
2658
+ object.off("rotating", update);
2659
+ };
2660
+ }, [canvasRef, object]);
2661
+ return containerRef;
2662
+ }
2663
+
2664
+ // src/index.ts
2665
+ import {
2666
+ Canvas as Canvas2,
2667
+ FabricObject as FabricObject5,
2668
+ FabricImage as FabricImage2,
2669
+ Rect as Rect7,
2670
+ Polygon as Polygon5,
2671
+ Point as Point9,
2672
+ util as util5
2673
+ } from "fabric";
2319
2674
  export {
2320
2675
  Canvas,
2321
2676
  DEFAULT_ALIGNMENT_STYLE,
@@ -2324,6 +2679,12 @@ export {
2324
2679
  DEFAULT_DRAG_SHAPE_STYLE,
2325
2680
  DEFAULT_GUIDELINE_SHAPE_STYLE,
2326
2681
  DEFAULT_SHAPE_STYLE,
2682
+ Canvas2 as FabricCanvas,
2683
+ FabricImage2 as FabricImage,
2684
+ FabricObject5 as FabricObject,
2685
+ Point9 as Point,
2686
+ Polygon5 as Polygon,
2687
+ Rect7 as Rect,
2327
2688
  createCircle,
2328
2689
  createCircleAtPoint,
2329
2690
  createPolygon,
@@ -2348,6 +2709,7 @@ export {
2348
2709
  fitViewportToBackground,
2349
2710
  getBackgroundInverted,
2350
2711
  getBackgroundOpacity,
2712
+ getBackgroundSrc,
2351
2713
  getBaseStrokeWidth,
2352
2714
  getSnapPoints,
2353
2715
  loadCanvas,
@@ -2359,7 +2721,12 @@ export {
2359
2721
  setBackgroundInverted,
2360
2722
  setBackgroundOpacity,
2361
2723
  snapCursorPoint,
2724
+ useCanvasClick,
2725
+ useCanvasEvents,
2726
+ useCanvasTooltip,
2362
2727
  useEditCanvas,
2363
- useViewCanvas
2728
+ useObjectOverlay,
2729
+ useViewCanvas,
2730
+ util5 as util
2364
2731
  };
2365
2732
  //# sourceMappingURL=index.js.map