@bwp-web/canvas 0.8.2 → 0.8.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.
package/dist/index.js CHANGED
@@ -85,7 +85,7 @@ function Canvas({
85
85
  }
86
86
 
87
87
  // src/hooks/useEditCanvas.ts
88
- import { useCallback, useEffect as useEffect2, useRef as useRef2, useState } from "react";
88
+ import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState } from "react";
89
89
  import { Polygon as Polygon4 } from "fabric";
90
90
 
91
91
  // src/viewport.ts
@@ -2455,6 +2455,7 @@ function useEditCanvas(options) {
2455
2455
  const vertexEditCleanupRef = useRef2(null);
2456
2456
  const keyboardCleanupRef = useRef2(null);
2457
2457
  const historyRef = useRef2(null);
2458
+ const isInitialLoadRef = useRef2(false);
2458
2459
  const optionsRef = useRef2(options);
2459
2460
  optionsRef.current = options;
2460
2461
  const savedSelectabilityRef = useRef2(
@@ -2467,6 +2468,11 @@ function useEditCanvas(options) {
2467
2468
  const [isDirty, setIsDirty] = useState(false);
2468
2469
  const [canUndo, setCanUndo] = useState(false);
2469
2470
  const [canRedo, setCanRedo] = useState(false);
2471
+ const [objects, setObjects] = useState([]);
2472
+ const [isLoading, setIsLoading] = useState(false);
2473
+ const [lockLightMode, setLockLightModeState] = useState(
2474
+ void 0
2475
+ );
2470
2476
  const setMode = useCallback((setup) => {
2471
2477
  vertexEditCleanupRef.current?.();
2472
2478
  vertexEditCleanupRef.current = null;
@@ -2551,11 +2557,14 @@ function useEditCanvas(options) {
2551
2557
  canvas.on("selection:created", (e) => setSelected(e.selected ?? []));
2552
2558
  canvas.on("selection:updated", (e) => setSelected(e.selected ?? []));
2553
2559
  canvas.on("selection:cleared", () => setSelected([]));
2554
- if (opts?.trackChanges) {
2555
- canvas.on("object:added", () => setIsDirty(true));
2556
- canvas.on("object:removed", () => setIsDirty(true));
2557
- canvas.on("object:modified", () => setIsDirty(true));
2558
- canvas.on("background:modified", () => setIsDirty(true));
2560
+ if (opts?.trackChanges !== false) {
2561
+ const markDirtyIfNotLoading = () => {
2562
+ if (!isInitialLoadRef.current) setIsDirty(true);
2563
+ };
2564
+ canvas.on("object:added", markDirtyIfNotLoading);
2565
+ canvas.on("object:removed", markDirtyIfNotLoading);
2566
+ canvas.on("object:modified", markDirtyIfNotLoading);
2567
+ canvas.on("background:modified", markDirtyIfNotLoading);
2559
2568
  }
2560
2569
  if (opts?.history) {
2561
2570
  const syncHistoryState = () => {
@@ -2593,8 +2602,31 @@ function useEditCanvas(options) {
2593
2602
  }
2594
2603
  }
2595
2604
  function invokeOnReady() {
2596
- const onReadyResult = opts?.onReady?.(canvas);
2597
- Promise.resolve(onReadyResult).then(() => {
2605
+ const initPromise = (async () => {
2606
+ if (opts?.canvasData) {
2607
+ setIsLoading(true);
2608
+ isInitialLoadRef.current = true;
2609
+ try {
2610
+ const loaded = await loadCanvas(canvas, opts.canvasData, {
2611
+ filter: opts.filter,
2612
+ borderRadius: opts.borderRadius
2613
+ });
2614
+ setObjects(loaded);
2615
+ } finally {
2616
+ isInitialLoadRef.current = false;
2617
+ setIsLoading(false);
2618
+ }
2619
+ }
2620
+ })();
2621
+ initPromise.then(async () => {
2622
+ const onReadyResult = opts?.onReady?.(canvas);
2623
+ await Promise.resolve(onReadyResult);
2624
+ if (opts?.invertBackground !== void 0) {
2625
+ setBackgroundInverted(canvas, opts.invertBackground);
2626
+ }
2627
+ if (canvas.lockLightMode !== void 0) {
2628
+ setLockLightModeState(canvas.lockLightMode);
2629
+ }
2598
2630
  if (opts?.autoFitToBackground !== false && canvas.backgroundImage) {
2599
2631
  fitViewportToBackground(canvas);
2600
2632
  syncZoom(canvasRef, setZoom);
@@ -2624,37 +2656,25 @@ function useEditCanvas(options) {
2624
2656
  alignmentCleanupRef.current = null;
2625
2657
  }
2626
2658
  }, [options?.enableAlignment]);
2659
+ useEffect2(() => {
2660
+ const canvas = canvasRef.current;
2661
+ if (!canvas || options?.invertBackground === void 0) return;
2662
+ setBackgroundInverted(canvas, options.invertBackground);
2663
+ }, [options?.invertBackground]);
2664
+ const setLockLightMode = useCallback((value) => {
2665
+ const canvas = canvasRef.current;
2666
+ if (canvas) {
2667
+ canvas.lockLightMode = value;
2668
+ }
2669
+ setLockLightModeState(value);
2670
+ }, []);
2627
2671
  const setViewportMode = useCallback((mode) => {
2628
2672
  viewportRef.current?.setMode(mode);
2629
2673
  setViewportModeState(mode);
2630
2674
  }, []);
2631
2675
  const { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject, zoomToFit } = useViewportActions(canvasRef, viewportRef, setZoom);
2632
- const setBackground = useCallback(
2633
- async (url, bgOpts) => {
2634
- const canvas = canvasRef.current;
2635
- if (!canvas) throw new Error("Canvas not ready");
2636
- const opts = optionsRef.current;
2637
- const resizeOpts = opts?.backgroundResize !== false ? typeof opts?.backgroundResize === "object" ? { ...opts.backgroundResize, ...bgOpts } : { ...bgOpts } : bgOpts?.preserveContrast ? { preserveContrast: true } : void 0;
2638
- const img = await setBackgroundImage(canvas, url, resizeOpts);
2639
- if (opts?.autoFitToBackground !== false) {
2640
- fitViewportToBackground(canvas);
2641
- syncZoom(canvasRef, setZoom);
2642
- }
2643
- return img;
2644
- },
2645
- []
2646
- );
2647
- return {
2648
- /** Pass this to `<Canvas onReady={...} />` */
2649
- onReady,
2650
- /** Ref to the underlying Fabric canvas instance. */
2651
- canvasRef,
2652
- /** Current zoom level (reactive). */
2653
- zoom,
2654
- /** Currently selected objects (reactive). */
2655
- selected,
2656
- /** Viewport controls. */
2657
- viewport: {
2676
+ const viewport = useMemo2(
2677
+ () => ({
2658
2678
  /** Current viewport mode (reactive). */
2659
2679
  mode: viewportMode,
2660
2680
  /** Switch between 'select' and 'pan' viewport modes. */
@@ -2669,65 +2689,129 @@ function useEditCanvas(options) {
2669
2689
  panToObject,
2670
2690
  /** Zoom and pan to fit a specific object in the viewport. */
2671
2691
  zoomToFit
2692
+ }),
2693
+ [
2694
+ viewportMode,
2695
+ setViewportMode,
2696
+ resetViewport2,
2697
+ zoomIn,
2698
+ zoomOut,
2699
+ panToObject,
2700
+ zoomToFit
2701
+ ]
2702
+ );
2703
+ const resetDirty = useCallback(() => setIsDirty(false), []);
2704
+ const markDirty = useCallback(() => setIsDirty(true), []);
2705
+ const undo = useCallback(async () => {
2706
+ const h = historyRef.current;
2707
+ if (!h) return;
2708
+ await h.undo();
2709
+ setCanUndo(h.canUndo());
2710
+ setCanRedo(h.canRedo());
2711
+ }, []);
2712
+ const redo = useCallback(async () => {
2713
+ const h = historyRef.current;
2714
+ if (!h) return;
2715
+ await h.redo();
2716
+ setCanUndo(h.canUndo());
2717
+ setCanRedo(h.canRedo());
2718
+ }, []);
2719
+ const setBackground = useCallback(
2720
+ async (url, bgOpts) => {
2721
+ const canvas = canvasRef.current;
2722
+ if (!canvas) throw new Error("Canvas not ready");
2723
+ const opts = optionsRef.current;
2724
+ const resizeOpts = opts?.backgroundResize !== false ? typeof opts?.backgroundResize === "object" ? { ...opts.backgroundResize, ...bgOpts } : { ...bgOpts } : bgOpts?.preserveContrast ? { preserveContrast: true } : void 0;
2725
+ const img = await setBackgroundImage(canvas, url, resizeOpts);
2726
+ if (opts?.autoFitToBackground !== false) {
2727
+ fitViewportToBackground(canvas);
2728
+ syncZoom(canvasRef, setZoom);
2729
+ }
2730
+ return img;
2672
2731
  },
2673
- /** Whether vertex edit mode is currently active (reactive). */
2674
- isEditingVertices,
2675
- /**
2676
- * Activate an interaction mode or return to select mode.
2677
- *
2678
- * Pass a setup function to activate a creation mode:
2679
- * ```ts
2680
- * canvas.setMode((c, viewport) =>
2681
- * enableClickToCreate(c, factory, { viewport })
2682
- * );
2683
- * ```
2684
- *
2685
- * Pass `null` to deactivate and return to select mode:
2686
- * ```ts
2687
- * canvas.setMode(null);
2688
- * ```
2689
- */
2690
- setMode,
2691
- /**
2692
- * Set a background image from a URL. Automatically resizes if the image
2693
- * exceeds the configured limits (opt out via `backgroundResize: false`),
2694
- * and fits the viewport after setting if `autoFitToBackground` is enabled.
2695
- *
2696
- * Pass `{ preserveContrast: true }` to keep the current background contrast
2697
- * when replacing the image.
2698
- */
2699
- setBackground,
2700
- /** Whether the canvas has been modified since the last `resetDirty()` call. Requires `trackChanges: true`. */
2701
- isDirty,
2702
- /** Reset the dirty flag (e.g., after a successful save). */
2703
- resetDirty: useCallback(() => setIsDirty(false), []),
2704
- /** Manually mark the canvas as dirty (e.g., after a custom operation not tracked automatically). */
2705
- markDirty: useCallback(() => setIsDirty(true), []),
2706
- /** Undo the last change. Requires `history: true`. */
2707
- undo: useCallback(async () => {
2708
- const h = historyRef.current;
2709
- if (!h) return;
2710
- await h.undo();
2711
- setCanUndo(h.canUndo());
2712
- setCanRedo(h.canRedo());
2713
- }, []),
2714
- /** Redo a previously undone change. Requires `history: true`. */
2715
- redo: useCallback(async () => {
2716
- const h = historyRef.current;
2717
- if (!h) return;
2718
- await h.redo();
2719
- setCanUndo(h.canUndo());
2720
- setCanRedo(h.canRedo());
2721
- }, []),
2722
- /** Whether an undo operation is available (reactive). Requires `history: true`. */
2723
- canUndo,
2724
- /** Whether a redo operation is available (reactive). Requires `history: true`. */
2725
- canRedo
2726
- };
2732
+ []
2733
+ );
2734
+ return useMemo2(
2735
+ () => ({
2736
+ /** Pass this to `<Canvas onReady={...} />` */
2737
+ onReady,
2738
+ /** Ref to the underlying Fabric canvas instance. */
2739
+ canvasRef,
2740
+ /** Current zoom level (reactive). */
2741
+ zoom,
2742
+ /** Loaded objects (reactive). Populated when `canvasData` is provided. */
2743
+ objects,
2744
+ /** Whether canvas data is currently being loaded. */
2745
+ isLoading,
2746
+ /** Currently selected objects (reactive). */
2747
+ selected,
2748
+ /** Viewport controls. */
2749
+ viewport,
2750
+ /** Whether vertex edit mode is currently active (reactive). */
2751
+ isEditingVertices,
2752
+ /**
2753
+ * Activate an interaction mode or return to select mode.
2754
+ *
2755
+ * Pass a setup function to activate a creation mode:
2756
+ * ```ts
2757
+ * canvas.setMode((c, viewport) =>
2758
+ * enableClickToCreate(c, factory, { viewport })
2759
+ * );
2760
+ * ```
2761
+ *
2762
+ * Pass `null` to deactivate and return to select mode:
2763
+ * ```ts
2764
+ * canvas.setMode(null);
2765
+ * ```
2766
+ */
2767
+ setMode,
2768
+ /**
2769
+ * Set a background image from a URL. Automatically resizes if the image
2770
+ * exceeds the configured limits (opt out via `backgroundResize: false`),
2771
+ * and fits the viewport after setting if `autoFitToBackground` is enabled.
2772
+ *
2773
+ * Pass `{ preserveContrast: true }` to keep the current background contrast
2774
+ * when replacing the image.
2775
+ */
2776
+ setBackground,
2777
+ /** Whether the canvas has been modified since the last `resetDirty()` call. Enabled by default (disable via `trackChanges: false`). */
2778
+ isDirty,
2779
+ /** Reset the dirty flag (e.g., after a successful save). */
2780
+ resetDirty,
2781
+ /** Manually mark the canvas as dirty (e.g., after a custom operation not tracked automatically). */
2782
+ markDirty,
2783
+ /** Undo the last change. Requires `history: true`. */
2784
+ undo,
2785
+ /** Redo a previously undone change. Requires `history: true`. */
2786
+ redo,
2787
+ /** Whether an undo operation is available (reactive). Requires `history: true`. */
2788
+ canUndo,
2789
+ /** Whether a redo operation is available (reactive). Requires `history: true`. */
2790
+ canRedo,
2791
+ /** Whether the canvas is locked to light mode. Read from loaded canvas data. */
2792
+ lockLightMode,
2793
+ /** Update lockLightMode on both the canvas instance and React state. */
2794
+ setLockLightMode
2795
+ }),
2796
+ // Only reactive state in deps — refs and stable callbacks are omitted
2797
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2798
+ [
2799
+ zoom,
2800
+ objects,
2801
+ isLoading,
2802
+ selected,
2803
+ viewport,
2804
+ isEditingVertices,
2805
+ isDirty,
2806
+ canUndo,
2807
+ canRedo,
2808
+ lockLightMode
2809
+ ]
2810
+ );
2727
2811
  }
2728
2812
 
2729
2813
  // src/hooks/useViewCanvas.ts
2730
- import { useCallback as useCallback2, useRef as useRef3, useState as useState2 } from "react";
2814
+ import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo3, useRef as useRef3, useState as useState2 } from "react";
2731
2815
  function lockCanvas(canvas) {
2732
2816
  canvas.selection = false;
2733
2817
  canvas.forEachObject((obj) => {
@@ -2740,6 +2824,8 @@ function useViewCanvas(options) {
2740
2824
  const optionsRef = useRef3(options);
2741
2825
  optionsRef.current = options;
2742
2826
  const [zoom, setZoom] = useState2(1);
2827
+ const [objects, setObjects] = useState2([]);
2828
+ const [isLoading, setIsLoading] = useState2(false);
2743
2829
  const onReady = useCallback2(
2744
2830
  (canvas) => {
2745
2831
  canvasRef.current = canvas;
@@ -2766,20 +2852,57 @@ function useViewCanvas(options) {
2766
2852
  canvas.on("mouse:wheel", () => {
2767
2853
  setZoom(canvas.getZoom());
2768
2854
  });
2769
- const onReadyResult = opts?.onReady?.(canvas);
2770
- if (opts?.autoFitToBackground !== false) {
2771
- Promise.resolve(onReadyResult).then(() => {
2772
- if (canvas.backgroundImage) {
2773
- fitViewportToBackground(canvas);
2774
- syncZoom(canvasRef, setZoom);
2855
+ const initPromise = (async () => {
2856
+ if (opts?.canvasData) {
2857
+ setIsLoading(true);
2858
+ try {
2859
+ const loaded = await loadCanvas(canvas, opts.canvasData, {
2860
+ filter: opts.filter,
2861
+ borderRadius: opts.borderRadius
2862
+ });
2863
+ lockCanvas(canvas);
2864
+ setObjects(loaded);
2865
+ } finally {
2866
+ setIsLoading(false);
2775
2867
  }
2776
- });
2777
- }
2868
+ }
2869
+ })();
2870
+ initPromise.then(async () => {
2871
+ const onReadyResult = opts?.onReady?.(canvas);
2872
+ await Promise.resolve(onReadyResult);
2873
+ if (opts?.invertBackground !== void 0) {
2874
+ setBackgroundInverted(canvas, opts.invertBackground);
2875
+ }
2876
+ if (opts?.autoFitToBackground !== false && canvas.backgroundImage) {
2877
+ fitViewportToBackground(canvas);
2878
+ syncZoom(canvasRef, setZoom);
2879
+ }
2880
+ });
2778
2881
  },
2779
2882
  // onReady and panAndZoom are intentionally excluded — we only initialize once
2780
2883
  []
2781
2884
  );
2885
+ useEffect3(() => {
2886
+ const canvas = canvasRef.current;
2887
+ if (!canvas || options?.invertBackground === void 0) return;
2888
+ setBackgroundInverted(canvas, options.invertBackground);
2889
+ }, [options?.invertBackground]);
2782
2890
  const { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject, zoomToFit } = useViewportActions(canvasRef, viewportRef, setZoom);
2891
+ const viewport = useMemo3(
2892
+ () => ({
2893
+ /** Reset viewport to default (no pan, zoom = 1), or fit to background if one is set. */
2894
+ reset: resetViewport2,
2895
+ /** Zoom in toward the canvas center. Default step: 0.2. */
2896
+ zoomIn,
2897
+ /** Zoom out from the canvas center. Default step: 0.2. */
2898
+ zoomOut,
2899
+ /** Pan the viewport to center on a specific object. */
2900
+ panToObject,
2901
+ /** Zoom and pan to fit a specific object in the viewport. */
2902
+ zoomToFit
2903
+ }),
2904
+ [resetViewport2, zoomIn, zoomOut, panToObject, zoomToFit]
2905
+ );
2783
2906
  const findObject = (id) => {
2784
2907
  const c = canvasRef.current;
2785
2908
  if (!c) return void 0;
@@ -2795,9 +2918,9 @@ function useViewCanvas(options) {
2795
2918
  (styles) => {
2796
2919
  const c = canvasRef.current;
2797
2920
  if (!c) return;
2798
- const objects = c.getObjects();
2921
+ const objects2 = c.getObjects();
2799
2922
  const idMap = /* @__PURE__ */ new Map();
2800
- for (const obj of objects) {
2923
+ for (const obj of objects2) {
2801
2924
  if (obj.data?.id) idMap.set(obj.data.id, obj);
2802
2925
  }
2803
2926
  let updated = false;
@@ -2827,42 +2950,101 @@ function useViewCanvas(options) {
2827
2950
  },
2828
2951
  []
2829
2952
  );
2830
- return {
2831
- /** Pass this to `<Canvas onReady={...} />` */
2832
- onReady,
2833
- /** Ref to the underlying Fabric canvas instance. */
2834
- canvasRef,
2835
- /** Current zoom level (reactive). */
2836
- zoom,
2837
- /** Viewport controls. */
2838
- viewport: {
2839
- /** Reset viewport to default (no pan, zoom = 1), or fit to background if one is set. */
2840
- reset: resetViewport2,
2841
- /** Zoom in toward the canvas center. Default step: 0.2. */
2842
- zoomIn,
2843
- /** Zoom out from the canvas center. Default step: 0.2. */
2844
- zoomOut,
2845
- /** Pan the viewport to center on a specific object. */
2846
- panToObject,
2847
- /** Zoom and pan to fit a specific object in the viewport. */
2848
- zoomToFit
2849
- },
2850
- /** Update a single object's visual style by its `data.id`. */
2851
- setObjectStyle,
2852
- /** Batch-update multiple objects' visual styles in one render. Keyed by `data.id`. */
2853
- setObjectStyles,
2854
- /** Apply a visual style to all objects whose `data.type` matches. */
2855
- setObjectStyleByType
2856
- };
2953
+ return useMemo3(
2954
+ () => ({
2955
+ /** Pass this to `<Canvas onReady={...} />` */
2956
+ onReady,
2957
+ /** Ref to the underlying Fabric canvas instance. */
2958
+ canvasRef,
2959
+ /** Current zoom level (reactive). */
2960
+ zoom,
2961
+ /** Loaded objects (reactive). Populated when `canvasData` is provided. */
2962
+ objects,
2963
+ /** Whether canvas data is currently being loaded. */
2964
+ isLoading,
2965
+ /** Viewport controls. */
2966
+ viewport,
2967
+ /** Update a single object's visual style by its `data.id`. */
2968
+ setObjectStyle,
2969
+ /** Batch-update multiple objects' visual styles in one render. Keyed by `data.id`. */
2970
+ setObjectStyles,
2971
+ /** Apply a visual style to all objects whose `data.type` matches. */
2972
+ setObjectStyleByType
2973
+ }),
2974
+ // Only reactive state in deps — refs and stable callbacks are omitted
2975
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2976
+ [zoom, objects, isLoading, viewport]
2977
+ );
2857
2978
  }
2858
2979
 
2859
2980
  // src/hooks/useCanvasEvents.ts
2860
- import { useEffect as useEffect3, useRef as useRef4 } from "react";
2861
- function useCanvasEvents(canvasRef, events) {
2981
+ import { useEffect as useEffect4, useRef as useRef4 } from "react";
2982
+
2983
+ // src/context/ViewCanvasContext.tsx
2984
+ import { createContext, useContext } from "react";
2985
+ import { jsx as jsx2 } from "react/jsx-runtime";
2986
+ var ViewCanvasContext = createContext(null);
2987
+ function ViewCanvasProvider({
2988
+ options,
2989
+ children
2990
+ }) {
2991
+ const canvas = useViewCanvas(options);
2992
+ return /* @__PURE__ */ jsx2(ViewCanvasContext.Provider, { value: canvas, children });
2993
+ }
2994
+ function useViewCanvasContext() {
2995
+ const ctx = useContext(ViewCanvasContext);
2996
+ if (ctx === null) {
2997
+ throw new Error(
2998
+ "useViewCanvasContext must be used within a <ViewCanvasProvider>"
2999
+ );
3000
+ }
3001
+ return ctx;
3002
+ }
3003
+ function useViewCanvasContextSafe() {
3004
+ return useContext(ViewCanvasContext);
3005
+ }
3006
+
3007
+ // src/context/EditCanvasContext.tsx
3008
+ import { createContext as createContext2, useContext as useContext2 } from "react";
3009
+ import { jsx as jsx3 } from "react/jsx-runtime";
3010
+ var EditCanvasContext = createContext2(null);
3011
+ function EditCanvasProvider({
3012
+ options,
3013
+ children
3014
+ }) {
3015
+ const canvas = useEditCanvas(options);
3016
+ return /* @__PURE__ */ jsx3(EditCanvasContext.Provider, { value: canvas, children });
3017
+ }
3018
+ function useEditCanvasContext() {
3019
+ const ctx = useContext2(EditCanvasContext);
3020
+ if (ctx === null) {
3021
+ throw new Error(
3022
+ "useEditCanvasContext must be used within an <EditCanvasProvider>"
3023
+ );
3024
+ }
3025
+ return ctx;
3026
+ }
3027
+ function useEditCanvasContextSafe() {
3028
+ return useContext2(EditCanvasContext);
3029
+ }
3030
+
3031
+ // src/context/useCanvasRef.ts
3032
+ function useCanvasRef() {
3033
+ const viewCtx = useViewCanvasContextSafe();
3034
+ const editCtx = useEditCanvasContextSafe();
3035
+ return viewCtx?.canvasRef ?? editCtx?.canvasRef ?? null;
3036
+ }
3037
+
3038
+ // src/hooks/useCanvasEvents.ts
3039
+ function useCanvasEvents(canvasRefOrEvents, maybeEvents) {
3040
+ const isContextOverload = maybeEvents === void 0;
3041
+ const contextCanvasRef = useCanvasRef();
3042
+ const resolvedCanvasRef = isContextOverload ? contextCanvasRef : canvasRefOrEvents;
3043
+ const events = isContextOverload ? canvasRefOrEvents : maybeEvents;
2862
3044
  const eventsRef = useRef4(events);
2863
3045
  eventsRef.current = events;
2864
- useEffect3(() => {
2865
- const canvas = canvasRef.current;
3046
+ useEffect4(() => {
3047
+ const canvas = resolvedCanvasRef?.current;
2866
3048
  if (!canvas) return;
2867
3049
  const wrappers = /* @__PURE__ */ new Map();
2868
3050
  for (const key of Object.keys(eventsRef.current)) {
@@ -2878,12 +3060,16 @@ function useCanvasEvents(canvasRef, events) {
2878
3060
  canvas.off(name, handler);
2879
3061
  });
2880
3062
  };
2881
- }, [canvasRef]);
3063
+ }, [resolvedCanvasRef]);
2882
3064
  }
2883
3065
 
2884
3066
  // src/hooks/useCanvasTooltip.ts
2885
- import { useEffect as useEffect4, useRef as useRef5, useState as useState3 } from "react";
2886
- function useCanvasTooltip(canvasRef, options) {
3067
+ import { useEffect as useEffect5, useRef as useRef5, useState as useState3 } from "react";
3068
+ function useCanvasTooltip(canvasRefOrOptions, maybeOptions) {
3069
+ const isContextOverload = maybeOptions === void 0;
3070
+ const contextCanvasRef = useCanvasRef();
3071
+ const resolvedCanvasRef = isContextOverload ? contextCanvasRef : canvasRefOrOptions;
3072
+ const options = isContextOverload ? canvasRefOrOptions : maybeOptions;
2887
3073
  const [state, setState] = useState3({
2888
3074
  visible: false,
2889
3075
  content: null,
@@ -2892,8 +3078,8 @@ function useCanvasTooltip(canvasRef, options) {
2892
3078
  const hoveredObjectRef = useRef5(null);
2893
3079
  const optionsRef = useRef5(options);
2894
3080
  optionsRef.current = options;
2895
- useEffect4(() => {
2896
- const canvas = canvasRef.current;
3081
+ useEffect5(() => {
3082
+ const canvas = resolvedCanvasRef?.current;
2897
3083
  if (!canvas) return;
2898
3084
  function calculatePosition(target) {
2899
3085
  const bounds = target.getBoundingRect();
@@ -2941,19 +3127,24 @@ function useCanvasTooltip(canvasRef, options) {
2941
3127
  canvas.off("after:render", updatePosition);
2942
3128
  canvas.off("mouse:wheel", updatePosition);
2943
3129
  };
2944
- }, [canvasRef]);
3130
+ }, [resolvedCanvasRef]);
2945
3131
  return state;
2946
3132
  }
2947
3133
 
2948
3134
  // src/hooks/useCanvasClick.ts
2949
- import { useEffect as useEffect5, useRef as useRef6 } from "react";
2950
- function useCanvasClick(canvasRef, onClick, options) {
3135
+ import { useEffect as useEffect6, useRef as useRef6 } from "react";
3136
+ function useCanvasClick(canvasRefOrOnClick, onClickOrOptions, maybeOptions) {
3137
+ const isContextOverload = typeof canvasRefOrOnClick === "function";
3138
+ const contextCanvasRef = useCanvasRef();
3139
+ const resolvedCanvasRef = isContextOverload ? contextCanvasRef : canvasRefOrOnClick;
3140
+ const onClick = isContextOverload ? canvasRefOrOnClick : onClickOrOptions;
3141
+ const options = isContextOverload ? onClickOrOptions : maybeOptions;
2951
3142
  const onClickRef = useRef6(onClick);
2952
3143
  onClickRef.current = onClick;
2953
3144
  const optionsRef = useRef6(options);
2954
3145
  optionsRef.current = options;
2955
- useEffect5(() => {
2956
- const canvas = canvasRef.current;
3146
+ useEffect6(() => {
3147
+ const canvas = resolvedCanvasRef?.current;
2957
3148
  if (!canvas) return;
2958
3149
  let mouseDown = null;
2959
3150
  let isPanning = false;
@@ -2993,66 +3184,26 @@ function useCanvasClick(canvasRef, onClick, options) {
2993
3184
  canvas.off("mouse:move", handleMouseMove);
2994
3185
  canvas.off("mouse:up", handleMouseUp);
2995
3186
  };
2996
- }, [canvasRef]);
2997
- }
2998
-
2999
- // src/context/EditCanvasContext.tsx
3000
- import { createContext, useContext } from "react";
3001
- import { jsx as jsx2 } from "react/jsx-runtime";
3002
- var EditCanvasContext = createContext(null);
3003
- function EditCanvasProvider({
3004
- options,
3005
- children
3006
- }) {
3007
- const canvas = useEditCanvas(options);
3008
- return /* @__PURE__ */ jsx2(EditCanvasContext.Provider, { value: canvas, children });
3009
- }
3010
- function useEditCanvasContext() {
3011
- const ctx = useContext(EditCanvasContext);
3012
- if (ctx === null) {
3013
- throw new Error(
3014
- "useEditCanvasContext must be used within an <EditCanvasProvider>"
3015
- );
3016
- }
3017
- return ctx;
3018
- }
3019
-
3020
- // src/context/ViewCanvasContext.tsx
3021
- import { createContext as createContext2, useContext as useContext2 } from "react";
3022
- import { jsx as jsx3 } from "react/jsx-runtime";
3023
- var ViewCanvasContext = createContext2(null);
3024
- function ViewCanvasProvider({
3025
- options,
3026
- children
3027
- }) {
3028
- const canvas = useViewCanvas(options);
3029
- return /* @__PURE__ */ jsx3(ViewCanvasContext.Provider, { value: canvas, children });
3030
- }
3031
- function useViewCanvasContext() {
3032
- const ctx = useContext2(ViewCanvasContext);
3033
- if (ctx === null) {
3034
- throw new Error(
3035
- "useViewCanvasContext must be used within a <ViewCanvasProvider>"
3036
- );
3037
- }
3038
- return ctx;
3187
+ }, [resolvedCanvasRef]);
3039
3188
  }
3040
3189
 
3041
3190
  // src/overlay/ObjectOverlay.tsx
3042
- import { useEffect as useEffect6, useRef as useRef7 } from "react";
3191
+ import { useEffect as useEffect7, useRef as useRef7 } from "react";
3043
3192
  import { Stack } from "@mui/material";
3044
3193
  import { util as util4 } from "fabric";
3045
3194
  import { jsx as jsx4 } from "react/jsx-runtime";
3046
3195
  function ObjectOverlay({
3047
- canvasRef,
3196
+ canvasRef: canvasRefProp,
3048
3197
  object,
3049
3198
  sx,
3050
3199
  children,
3051
3200
  ...rest
3052
3201
  }) {
3202
+ const contextCanvasRef = useCanvasRef();
3203
+ const canvasRef = canvasRefProp ?? contextCanvasRef;
3053
3204
  const stackRef = useRef7(null);
3054
- useEffect6(() => {
3055
- const canvas = canvasRef.current;
3205
+ useEffect7(() => {
3206
+ const canvas = canvasRef?.current;
3056
3207
  if (!canvas || !object) return;
3057
3208
  function update() {
3058
3209
  const el = stackRef.current;
@@ -3106,7 +3257,7 @@ function ObjectOverlay({
3106
3257
 
3107
3258
  // src/overlay/OverlayContent.tsx
3108
3259
  import { Stack as Stack2 } from "@mui/material";
3109
- import { useEffect as useEffect7, useRef as useRef8 } from "react";
3260
+ import { useEffect as useEffect8, useRef as useRef8 } from "react";
3110
3261
  import { jsx as jsx5 } from "react/jsx-runtime";
3111
3262
  function OverlayContent({
3112
3263
  children,
@@ -3117,7 +3268,7 @@ function OverlayContent({
3117
3268
  }) {
3118
3269
  const outerRef = useRef8(null);
3119
3270
  const innerRef = useRef8(null);
3120
- useEffect7(() => {
3271
+ useEffect8(() => {
3121
3272
  const outer = outerRef.current;
3122
3273
  const inner = innerRef.current;
3123
3274
  if (!outer || !inner) return;
@@ -3178,7 +3329,7 @@ function OverlayContent({
3178
3329
 
3179
3330
  // src/overlay/FixedSizeContent.tsx
3180
3331
  import { Stack as Stack3 } from "@mui/material";
3181
- import { useEffect as useEffect8, useRef as useRef9 } from "react";
3332
+ import { useEffect as useEffect9, useRef as useRef9 } from "react";
3182
3333
  import { jsx as jsx6 } from "react/jsx-runtime";
3183
3334
  function FixedSizeContent({
3184
3335
  children,
@@ -3189,7 +3340,7 @@ function FixedSizeContent({
3189
3340
  }) {
3190
3341
  const ref = useRef9(null);
3191
3342
  const totalContentHeightRef = useRef9(0);
3192
- useEffect8(() => {
3343
+ useEffect9(() => {
3193
3344
  const el = ref.current;
3194
3345
  if (!el) return;
3195
3346
  let clipAncestor = el.parentElement;
@@ -3252,7 +3403,7 @@ function FixedSizeContent({
3252
3403
 
3253
3404
  // src/overlay/OverlayBadge.tsx
3254
3405
  import { Stack as Stack4 } from "@mui/material";
3255
- import { useEffect as useEffect9, useRef as useRef10 } from "react";
3406
+ import { useEffect as useEffect10, useRef as useRef10 } from "react";
3256
3407
  import { jsx as jsx7 } from "react/jsx-runtime";
3257
3408
  function toPx(v) {
3258
3409
  if (v === void 0) return void 0;
@@ -3297,7 +3448,7 @@ function OverlayBadge({
3297
3448
  }) {
3298
3449
  const ref = useRef10(null);
3299
3450
  const baseSize = useRef10(null);
3300
- useEffect9(() => {
3451
+ useEffect10(() => {
3301
3452
  const el = ref.current;
3302
3453
  if (!el) return;
3303
3454
  const ancestor = el.parentElement;
@@ -3439,6 +3590,7 @@ export {
3439
3590
  snapCursorPoint,
3440
3591
  useCanvasClick,
3441
3592
  useCanvasEvents,
3593
+ useCanvasRef,
3442
3594
  useCanvasTooltip,
3443
3595
  useEditCanvas,
3444
3596
  useEditCanvasContext,