@bwp-web/canvas 0.8.2 → 0.9.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 CHANGED
@@ -79,11 +79,16 @@ __export(index_exports, {
79
79
  snapCursorPoint: () => snapCursorPoint,
80
80
  useCanvasClick: () => useCanvasClick,
81
81
  useCanvasEvents: () => useCanvasEvents,
82
+ useCanvasRef: () => useCanvasRef,
82
83
  useCanvasTooltip: () => useCanvasTooltip,
83
84
  useEditCanvas: () => useEditCanvas,
84
85
  useEditCanvasContext: () => useEditCanvasContext,
86
+ useEditCanvasState: () => useEditCanvasState,
87
+ useEditCanvasViewport: () => useEditCanvasViewport,
85
88
  useViewCanvas: () => useViewCanvas,
86
89
  useViewCanvasContext: () => useViewCanvasContext,
90
+ useViewCanvasState: () => useViewCanvasState,
91
+ useViewCanvasViewport: () => useViewCanvasViewport,
87
92
  util: () => import_fabric19.util
88
93
  });
89
94
  module.exports = __toCommonJS(index_exports);
@@ -2528,6 +2533,7 @@ function useEditCanvas(options) {
2528
2533
  const vertexEditCleanupRef = (0, import_react3.useRef)(null);
2529
2534
  const keyboardCleanupRef = (0, import_react3.useRef)(null);
2530
2535
  const historyRef = (0, import_react3.useRef)(null);
2536
+ const isInitialLoadRef = (0, import_react3.useRef)(false);
2531
2537
  const optionsRef = (0, import_react3.useRef)(options);
2532
2538
  optionsRef.current = options;
2533
2539
  const savedSelectabilityRef = (0, import_react3.useRef)(
@@ -2540,6 +2546,11 @@ function useEditCanvas(options) {
2540
2546
  const [isDirty, setIsDirty] = (0, import_react3.useState)(false);
2541
2547
  const [canUndo, setCanUndo] = (0, import_react3.useState)(false);
2542
2548
  const [canRedo, setCanRedo] = (0, import_react3.useState)(false);
2549
+ const [objects, setObjects] = (0, import_react3.useState)([]);
2550
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
2551
+ const [lockLightMode, setLockLightModeState] = (0, import_react3.useState)(
2552
+ void 0
2553
+ );
2543
2554
  const setMode = (0, import_react3.useCallback)((setup) => {
2544
2555
  vertexEditCleanupRef.current?.();
2545
2556
  vertexEditCleanupRef.current = null;
@@ -2624,11 +2635,14 @@ function useEditCanvas(options) {
2624
2635
  canvas.on("selection:created", (e) => setSelected(e.selected ?? []));
2625
2636
  canvas.on("selection:updated", (e) => setSelected(e.selected ?? []));
2626
2637
  canvas.on("selection:cleared", () => setSelected([]));
2627
- if (opts?.trackChanges) {
2628
- canvas.on("object:added", () => setIsDirty(true));
2629
- canvas.on("object:removed", () => setIsDirty(true));
2630
- canvas.on("object:modified", () => setIsDirty(true));
2631
- canvas.on("background:modified", () => setIsDirty(true));
2638
+ if (opts?.trackChanges !== false) {
2639
+ const markDirtyIfNotLoading = () => {
2640
+ if (!isInitialLoadRef.current) setIsDirty(true);
2641
+ };
2642
+ canvas.on("object:added", markDirtyIfNotLoading);
2643
+ canvas.on("object:removed", markDirtyIfNotLoading);
2644
+ canvas.on("object:modified", markDirtyIfNotLoading);
2645
+ canvas.on("background:modified", markDirtyIfNotLoading);
2632
2646
  }
2633
2647
  if (opts?.history) {
2634
2648
  const syncHistoryState = () => {
@@ -2666,8 +2680,31 @@ function useEditCanvas(options) {
2666
2680
  }
2667
2681
  }
2668
2682
  function invokeOnReady() {
2669
- const onReadyResult = opts?.onReady?.(canvas);
2670
- Promise.resolve(onReadyResult).then(() => {
2683
+ const initPromise = (async () => {
2684
+ if (opts?.canvasData) {
2685
+ setIsLoading(true);
2686
+ isInitialLoadRef.current = true;
2687
+ try {
2688
+ const loaded = await loadCanvas(canvas, opts.canvasData, {
2689
+ filter: opts.filter,
2690
+ borderRadius: opts.borderRadius
2691
+ });
2692
+ setObjects(loaded);
2693
+ } finally {
2694
+ isInitialLoadRef.current = false;
2695
+ setIsLoading(false);
2696
+ }
2697
+ }
2698
+ })();
2699
+ initPromise.then(async () => {
2700
+ const onReadyResult = opts?.onReady?.(canvas);
2701
+ await Promise.resolve(onReadyResult);
2702
+ if (opts?.invertBackground !== void 0) {
2703
+ setBackgroundInverted(canvas, opts.invertBackground);
2704
+ }
2705
+ if (canvas.lockLightMode !== void 0) {
2706
+ setLockLightModeState(canvas.lockLightMode);
2707
+ }
2671
2708
  if (opts?.autoFitToBackground !== false && canvas.backgroundImage) {
2672
2709
  fitViewportToBackground(canvas);
2673
2710
  syncZoom(canvasRef, setZoom);
@@ -2697,37 +2734,25 @@ function useEditCanvas(options) {
2697
2734
  alignmentCleanupRef.current = null;
2698
2735
  }
2699
2736
  }, [options?.enableAlignment]);
2737
+ (0, import_react3.useEffect)(() => {
2738
+ const canvas = canvasRef.current;
2739
+ if (!canvas || options?.invertBackground === void 0) return;
2740
+ setBackgroundInverted(canvas, options.invertBackground);
2741
+ }, [options?.invertBackground]);
2742
+ const setLockLightMode = (0, import_react3.useCallback)((value) => {
2743
+ const canvas = canvasRef.current;
2744
+ if (canvas) {
2745
+ canvas.lockLightMode = value;
2746
+ }
2747
+ setLockLightModeState(value);
2748
+ }, []);
2700
2749
  const setViewportMode = (0, import_react3.useCallback)((mode) => {
2701
2750
  viewportRef.current?.setMode(mode);
2702
2751
  setViewportModeState(mode);
2703
2752
  }, []);
2704
2753
  const { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject, zoomToFit } = useViewportActions(canvasRef, viewportRef, setZoom);
2705
- const setBackground = (0, import_react3.useCallback)(
2706
- async (url, bgOpts) => {
2707
- const canvas = canvasRef.current;
2708
- if (!canvas) throw new Error("Canvas not ready");
2709
- const opts = optionsRef.current;
2710
- const resizeOpts = opts?.backgroundResize !== false ? typeof opts?.backgroundResize === "object" ? { ...opts.backgroundResize, ...bgOpts } : { ...bgOpts } : bgOpts?.preserveContrast ? { preserveContrast: true } : void 0;
2711
- const img = await setBackgroundImage(canvas, url, resizeOpts);
2712
- if (opts?.autoFitToBackground !== false) {
2713
- fitViewportToBackground(canvas);
2714
- syncZoom(canvasRef, setZoom);
2715
- }
2716
- return img;
2717
- },
2718
- []
2719
- );
2720
- return {
2721
- /** Pass this to `<Canvas onReady={...} />` */
2722
- onReady,
2723
- /** Ref to the underlying Fabric canvas instance. */
2724
- canvasRef,
2725
- /** Current zoom level (reactive). */
2726
- zoom,
2727
- /** Currently selected objects (reactive). */
2728
- selected,
2729
- /** Viewport controls. */
2730
- viewport: {
2754
+ const viewport = (0, import_react3.useMemo)(
2755
+ () => ({
2731
2756
  /** Current viewport mode (reactive). */
2732
2757
  mode: viewportMode,
2733
2758
  /** Switch between 'select' and 'pan' viewport modes. */
@@ -2742,61 +2767,125 @@ function useEditCanvas(options) {
2742
2767
  panToObject,
2743
2768
  /** Zoom and pan to fit a specific object in the viewport. */
2744
2769
  zoomToFit
2770
+ }),
2771
+ [
2772
+ viewportMode,
2773
+ setViewportMode,
2774
+ resetViewport2,
2775
+ zoomIn,
2776
+ zoomOut,
2777
+ panToObject,
2778
+ zoomToFit
2779
+ ]
2780
+ );
2781
+ const resetDirty = (0, import_react3.useCallback)(() => setIsDirty(false), []);
2782
+ const markDirty = (0, import_react3.useCallback)(() => setIsDirty(true), []);
2783
+ const undo = (0, import_react3.useCallback)(async () => {
2784
+ const h = historyRef.current;
2785
+ if (!h) return;
2786
+ await h.undo();
2787
+ setCanUndo(h.canUndo());
2788
+ setCanRedo(h.canRedo());
2789
+ }, []);
2790
+ const redo = (0, import_react3.useCallback)(async () => {
2791
+ const h = historyRef.current;
2792
+ if (!h) return;
2793
+ await h.redo();
2794
+ setCanUndo(h.canUndo());
2795
+ setCanRedo(h.canRedo());
2796
+ }, []);
2797
+ const setBackground = (0, import_react3.useCallback)(
2798
+ async (url, bgOpts) => {
2799
+ const canvas = canvasRef.current;
2800
+ if (!canvas) throw new Error("Canvas not ready");
2801
+ const opts = optionsRef.current;
2802
+ const resizeOpts = opts?.backgroundResize !== false ? typeof opts?.backgroundResize === "object" ? { ...opts.backgroundResize, ...bgOpts } : { ...bgOpts } : bgOpts?.preserveContrast ? { preserveContrast: true } : void 0;
2803
+ const img = await setBackgroundImage(canvas, url, resizeOpts);
2804
+ if (opts?.autoFitToBackground !== false) {
2805
+ fitViewportToBackground(canvas);
2806
+ syncZoom(canvasRef, setZoom);
2807
+ }
2808
+ return img;
2745
2809
  },
2746
- /** Whether vertex edit mode is currently active (reactive). */
2747
- isEditingVertices,
2748
- /**
2749
- * Activate an interaction mode or return to select mode.
2750
- *
2751
- * Pass a setup function to activate a creation mode:
2752
- * ```ts
2753
- * canvas.setMode((c, viewport) =>
2754
- * enableClickToCreate(c, factory, { viewport })
2755
- * );
2756
- * ```
2757
- *
2758
- * Pass `null` to deactivate and return to select mode:
2759
- * ```ts
2760
- * canvas.setMode(null);
2761
- * ```
2762
- */
2763
- setMode,
2764
- /**
2765
- * Set a background image from a URL. Automatically resizes if the image
2766
- * exceeds the configured limits (opt out via `backgroundResize: false`),
2767
- * and fits the viewport after setting if `autoFitToBackground` is enabled.
2768
- *
2769
- * Pass `{ preserveContrast: true }` to keep the current background contrast
2770
- * when replacing the image.
2771
- */
2772
- setBackground,
2773
- /** Whether the canvas has been modified since the last `resetDirty()` call. Requires `trackChanges: true`. */
2774
- isDirty,
2775
- /** Reset the dirty flag (e.g., after a successful save). */
2776
- resetDirty: (0, import_react3.useCallback)(() => setIsDirty(false), []),
2777
- /** Manually mark the canvas as dirty (e.g., after a custom operation not tracked automatically). */
2778
- markDirty: (0, import_react3.useCallback)(() => setIsDirty(true), []),
2779
- /** Undo the last change. Requires `history: true`. */
2780
- undo: (0, import_react3.useCallback)(async () => {
2781
- const h = historyRef.current;
2782
- if (!h) return;
2783
- await h.undo();
2784
- setCanUndo(h.canUndo());
2785
- setCanRedo(h.canRedo());
2786
- }, []),
2787
- /** Redo a previously undone change. Requires `history: true`. */
2788
- redo: (0, import_react3.useCallback)(async () => {
2789
- const h = historyRef.current;
2790
- if (!h) return;
2791
- await h.redo();
2792
- setCanUndo(h.canUndo());
2793
- setCanRedo(h.canRedo());
2794
- }, []),
2795
- /** Whether an undo operation is available (reactive). Requires `history: true`. */
2796
- canUndo,
2797
- /** Whether a redo operation is available (reactive). Requires `history: true`. */
2798
- canRedo
2799
- };
2810
+ []
2811
+ );
2812
+ return (0, import_react3.useMemo)(
2813
+ () => ({
2814
+ /** Pass this to `<Canvas onReady={...} />` */
2815
+ onReady,
2816
+ /** Ref to the underlying Fabric canvas instance. */
2817
+ canvasRef,
2818
+ /** Current zoom level (reactive). */
2819
+ zoom,
2820
+ /** Loaded objects (reactive). Populated when `canvasData` is provided. */
2821
+ objects,
2822
+ /** Whether canvas data is currently being loaded. */
2823
+ isLoading,
2824
+ /** Currently selected objects (reactive). */
2825
+ selected,
2826
+ /** Viewport controls. */
2827
+ viewport,
2828
+ /** Whether vertex edit mode is currently active (reactive). */
2829
+ isEditingVertices,
2830
+ /**
2831
+ * Activate an interaction mode or return to select mode.
2832
+ *
2833
+ * Pass a setup function to activate a creation mode:
2834
+ * ```ts
2835
+ * canvas.setMode((c, viewport) =>
2836
+ * enableClickToCreate(c, factory, { viewport })
2837
+ * );
2838
+ * ```
2839
+ *
2840
+ * Pass `null` to deactivate and return to select mode:
2841
+ * ```ts
2842
+ * canvas.setMode(null);
2843
+ * ```
2844
+ */
2845
+ setMode,
2846
+ /**
2847
+ * Set a background image from a URL. Automatically resizes if the image
2848
+ * exceeds the configured limits (opt out via `backgroundResize: false`),
2849
+ * and fits the viewport after setting if `autoFitToBackground` is enabled.
2850
+ *
2851
+ * Pass `{ preserveContrast: true }` to keep the current background contrast
2852
+ * when replacing the image.
2853
+ */
2854
+ setBackground,
2855
+ /** Whether the canvas has been modified since the last `resetDirty()` call. Enabled by default (disable via `trackChanges: false`). */
2856
+ isDirty,
2857
+ /** Reset the dirty flag (e.g., after a successful save). */
2858
+ resetDirty,
2859
+ /** Manually mark the canvas as dirty (e.g., after a custom operation not tracked automatically). */
2860
+ markDirty,
2861
+ /** Undo the last change. Requires `history: true`. */
2862
+ undo,
2863
+ /** Redo a previously undone change. Requires `history: true`. */
2864
+ redo,
2865
+ /** Whether an undo operation is available (reactive). Requires `history: true`. */
2866
+ canUndo,
2867
+ /** Whether a redo operation is available (reactive). Requires `history: true`. */
2868
+ canRedo,
2869
+ /** Whether the canvas is locked to light mode. Read from loaded canvas data. */
2870
+ lockLightMode,
2871
+ /** Update lockLightMode on both the canvas instance and React state. */
2872
+ setLockLightMode
2873
+ }),
2874
+ // Only reactive state in deps — refs and stable callbacks are omitted
2875
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2876
+ [
2877
+ zoom,
2878
+ objects,
2879
+ isLoading,
2880
+ selected,
2881
+ viewport,
2882
+ isEditingVertices,
2883
+ isDirty,
2884
+ canUndo,
2885
+ canRedo,
2886
+ lockLightMode
2887
+ ]
2888
+ );
2800
2889
  }
2801
2890
 
2802
2891
  // src/hooks/useViewCanvas.ts
@@ -2813,6 +2902,8 @@ function useViewCanvas(options) {
2813
2902
  const optionsRef = (0, import_react4.useRef)(options);
2814
2903
  optionsRef.current = options;
2815
2904
  const [zoom, setZoom] = (0, import_react4.useState)(1);
2905
+ const [objects, setObjects] = (0, import_react4.useState)([]);
2906
+ const [isLoading, setIsLoading] = (0, import_react4.useState)(false);
2816
2907
  const onReady = (0, import_react4.useCallback)(
2817
2908
  (canvas) => {
2818
2909
  canvasRef.current = canvas;
@@ -2839,20 +2930,57 @@ function useViewCanvas(options) {
2839
2930
  canvas.on("mouse:wheel", () => {
2840
2931
  setZoom(canvas.getZoom());
2841
2932
  });
2842
- const onReadyResult = opts?.onReady?.(canvas);
2843
- if (opts?.autoFitToBackground !== false) {
2844
- Promise.resolve(onReadyResult).then(() => {
2845
- if (canvas.backgroundImage) {
2846
- fitViewportToBackground(canvas);
2847
- syncZoom(canvasRef, setZoom);
2933
+ const initPromise = (async () => {
2934
+ if (opts?.canvasData) {
2935
+ setIsLoading(true);
2936
+ try {
2937
+ const loaded = await loadCanvas(canvas, opts.canvasData, {
2938
+ filter: opts.filter,
2939
+ borderRadius: opts.borderRadius
2940
+ });
2941
+ lockCanvas(canvas);
2942
+ setObjects(loaded);
2943
+ } finally {
2944
+ setIsLoading(false);
2848
2945
  }
2849
- });
2850
- }
2946
+ }
2947
+ })();
2948
+ initPromise.then(async () => {
2949
+ const onReadyResult = opts?.onReady?.(canvas);
2950
+ await Promise.resolve(onReadyResult);
2951
+ if (opts?.invertBackground !== void 0) {
2952
+ setBackgroundInverted(canvas, opts.invertBackground);
2953
+ }
2954
+ if (opts?.autoFitToBackground !== false && canvas.backgroundImage) {
2955
+ fitViewportToBackground(canvas);
2956
+ syncZoom(canvasRef, setZoom);
2957
+ }
2958
+ });
2851
2959
  },
2852
2960
  // onReady and panAndZoom are intentionally excluded — we only initialize once
2853
2961
  []
2854
2962
  );
2963
+ (0, import_react4.useEffect)(() => {
2964
+ const canvas = canvasRef.current;
2965
+ if (!canvas || options?.invertBackground === void 0) return;
2966
+ setBackgroundInverted(canvas, options.invertBackground);
2967
+ }, [options?.invertBackground]);
2855
2968
  const { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject, zoomToFit } = useViewportActions(canvasRef, viewportRef, setZoom);
2969
+ const viewport = (0, import_react4.useMemo)(
2970
+ () => ({
2971
+ /** Reset viewport to default (no pan, zoom = 1), or fit to background if one is set. */
2972
+ reset: resetViewport2,
2973
+ /** Zoom in toward the canvas center. Default step: 0.2. */
2974
+ zoomIn,
2975
+ /** Zoom out from the canvas center. Default step: 0.2. */
2976
+ zoomOut,
2977
+ /** Pan the viewport to center on a specific object. */
2978
+ panToObject,
2979
+ /** Zoom and pan to fit a specific object in the viewport. */
2980
+ zoomToFit
2981
+ }),
2982
+ [resetViewport2, zoomIn, zoomOut, panToObject, zoomToFit]
2983
+ );
2856
2984
  const findObject = (id) => {
2857
2985
  const c = canvasRef.current;
2858
2986
  if (!c) return void 0;
@@ -2868,9 +2996,9 @@ function useViewCanvas(options) {
2868
2996
  (styles) => {
2869
2997
  const c = canvasRef.current;
2870
2998
  if (!c) return;
2871
- const objects = c.getObjects();
2999
+ const objects2 = c.getObjects();
2872
3000
  const idMap = /* @__PURE__ */ new Map();
2873
- for (const obj of objects) {
3001
+ for (const obj of objects2) {
2874
3002
  if (obj.data?.id) idMap.set(obj.data.id, obj);
2875
3003
  }
2876
3004
  let updated = false;
@@ -2900,42 +3028,58 @@ function useViewCanvas(options) {
2900
3028
  },
2901
3029
  []
2902
3030
  );
2903
- return {
2904
- /** Pass this to `<Canvas onReady={...} />` */
2905
- onReady,
2906
- /** Ref to the underlying Fabric canvas instance. */
2907
- canvasRef,
2908
- /** Current zoom level (reactive). */
2909
- zoom,
2910
- /** Viewport controls. */
2911
- viewport: {
2912
- /** Reset viewport to default (no pan, zoom = 1), or fit to background if one is set. */
2913
- reset: resetViewport2,
2914
- /** Zoom in toward the canvas center. Default step: 0.2. */
2915
- zoomIn,
2916
- /** Zoom out from the canvas center. Default step: 0.2. */
2917
- zoomOut,
2918
- /** Pan the viewport to center on a specific object. */
2919
- panToObject,
2920
- /** Zoom and pan to fit a specific object in the viewport. */
2921
- zoomToFit
2922
- },
2923
- /** Update a single object's visual style by its `data.id`. */
2924
- setObjectStyle,
2925
- /** Batch-update multiple objects' visual styles in one render. Keyed by `data.id`. */
2926
- setObjectStyles,
2927
- /** Apply a visual style to all objects whose `data.type` matches. */
2928
- setObjectStyleByType
2929
- };
3031
+ return (0, import_react4.useMemo)(
3032
+ () => ({
3033
+ /** Pass this to `<Canvas onReady={...} />` */
3034
+ onReady,
3035
+ /** Ref to the underlying Fabric canvas instance. */
3036
+ canvasRef,
3037
+ /** Current zoom level (reactive). */
3038
+ zoom,
3039
+ /** Loaded objects (reactive). Populated when `canvasData` is provided. */
3040
+ objects,
3041
+ /** Whether canvas data is currently being loaded. */
3042
+ isLoading,
3043
+ /** Viewport controls. */
3044
+ viewport,
3045
+ /** Update a single object's visual style by its `data.id`. */
3046
+ setObjectStyle,
3047
+ /** Batch-update multiple objects' visual styles in one render. Keyed by `data.id`. */
3048
+ setObjectStyles,
3049
+ /** Apply a visual style to all objects whose `data.type` matches. */
3050
+ setObjectStyleByType
3051
+ }),
3052
+ // Only reactive state in deps — refs and stable callbacks are omitted
3053
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3054
+ [zoom, objects, isLoading, viewport]
3055
+ );
2930
3056
  }
2931
3057
 
2932
3058
  // src/hooks/useCanvasEvents.ts
3059
+ var import_react6 = require("react");
3060
+
3061
+ // src/context/CanvasRefContext.ts
2933
3062
  var import_react5 = require("react");
2934
- function useCanvasEvents(canvasRef, events) {
2935
- const eventsRef = (0, import_react5.useRef)(events);
3063
+ var CanvasRefContext = (0, import_react5.createContext)(null);
3064
+ function useCanvasRefContext() {
3065
+ return (0, import_react5.useContext)(CanvasRefContext);
3066
+ }
3067
+
3068
+ // src/context/useCanvasRef.ts
3069
+ function useCanvasRef() {
3070
+ return useCanvasRefContext();
3071
+ }
3072
+
3073
+ // src/hooks/useCanvasEvents.ts
3074
+ function useCanvasEvents(canvasRefOrEvents, maybeEvents) {
3075
+ const isContextOverload = maybeEvents === void 0;
3076
+ const contextCanvasRef = useCanvasRef();
3077
+ const resolvedCanvasRef = isContextOverload ? contextCanvasRef : canvasRefOrEvents;
3078
+ const events = isContextOverload ? canvasRefOrEvents : maybeEvents;
3079
+ const eventsRef = (0, import_react6.useRef)(events);
2936
3080
  eventsRef.current = events;
2937
- (0, import_react5.useEffect)(() => {
2938
- const canvas = canvasRef.current;
3081
+ (0, import_react6.useEffect)(() => {
3082
+ const canvas = resolvedCanvasRef?.current;
2939
3083
  if (!canvas) return;
2940
3084
  const wrappers = /* @__PURE__ */ new Map();
2941
3085
  for (const key of Object.keys(eventsRef.current)) {
@@ -2951,22 +3095,26 @@ function useCanvasEvents(canvasRef, events) {
2951
3095
  canvas.off(name, handler);
2952
3096
  });
2953
3097
  };
2954
- }, [canvasRef]);
3098
+ }, [resolvedCanvasRef]);
2955
3099
  }
2956
3100
 
2957
3101
  // src/hooks/useCanvasTooltip.ts
2958
- var import_react6 = require("react");
2959
- function useCanvasTooltip(canvasRef, options) {
2960
- const [state, setState] = (0, import_react6.useState)({
3102
+ var import_react7 = require("react");
3103
+ function useCanvasTooltip(canvasRefOrOptions, maybeOptions) {
3104
+ const isContextOverload = maybeOptions === void 0;
3105
+ const contextCanvasRef = useCanvasRef();
3106
+ const resolvedCanvasRef = isContextOverload ? contextCanvasRef : canvasRefOrOptions;
3107
+ const options = isContextOverload ? canvasRefOrOptions : maybeOptions;
3108
+ const [state, setState] = (0, import_react7.useState)({
2961
3109
  visible: false,
2962
3110
  content: null,
2963
3111
  position: { x: 0, y: 0 }
2964
3112
  });
2965
- const hoveredObjectRef = (0, import_react6.useRef)(null);
2966
- const optionsRef = (0, import_react6.useRef)(options);
3113
+ const hoveredObjectRef = (0, import_react7.useRef)(null);
3114
+ const optionsRef = (0, import_react7.useRef)(options);
2967
3115
  optionsRef.current = options;
2968
- (0, import_react6.useEffect)(() => {
2969
- const canvas = canvasRef.current;
3116
+ (0, import_react7.useEffect)(() => {
3117
+ const canvas = resolvedCanvasRef?.current;
2970
3118
  if (!canvas) return;
2971
3119
  function calculatePosition(target) {
2972
3120
  const bounds = target.getBoundingRect();
@@ -3014,19 +3162,24 @@ function useCanvasTooltip(canvasRef, options) {
3014
3162
  canvas.off("after:render", updatePosition);
3015
3163
  canvas.off("mouse:wheel", updatePosition);
3016
3164
  };
3017
- }, [canvasRef]);
3165
+ }, [resolvedCanvasRef]);
3018
3166
  return state;
3019
3167
  }
3020
3168
 
3021
3169
  // src/hooks/useCanvasClick.ts
3022
- var import_react7 = require("react");
3023
- function useCanvasClick(canvasRef, onClick, options) {
3024
- const onClickRef = (0, import_react7.useRef)(onClick);
3170
+ var import_react8 = require("react");
3171
+ function useCanvasClick(canvasRefOrOnClick, onClickOrOptions, maybeOptions) {
3172
+ const isContextOverload = typeof canvasRefOrOnClick === "function";
3173
+ const contextCanvasRef = useCanvasRef();
3174
+ const resolvedCanvasRef = isContextOverload ? contextCanvasRef : canvasRefOrOnClick;
3175
+ const onClick = isContextOverload ? canvasRefOrOnClick : onClickOrOptions;
3176
+ const options = isContextOverload ? onClickOrOptions : maybeOptions;
3177
+ const onClickRef = (0, import_react8.useRef)(onClick);
3025
3178
  onClickRef.current = onClick;
3026
- const optionsRef = (0, import_react7.useRef)(options);
3179
+ const optionsRef = (0, import_react8.useRef)(options);
3027
3180
  optionsRef.current = options;
3028
- (0, import_react7.useEffect)(() => {
3029
- const canvas = canvasRef.current;
3181
+ (0, import_react8.useEffect)(() => {
3182
+ const canvas = resolvedCanvasRef?.current;
3030
3183
  if (!canvas) return;
3031
3184
  let mouseDown = null;
3032
3185
  let isPanning = false;
@@ -3066,66 +3219,169 @@ function useCanvasClick(canvasRef, onClick, options) {
3066
3219
  canvas.off("mouse:move", handleMouseMove);
3067
3220
  canvas.off("mouse:up", handleMouseUp);
3068
3221
  };
3069
- }, [canvasRef]);
3222
+ }, [resolvedCanvasRef]);
3070
3223
  }
3071
3224
 
3072
3225
  // src/context/EditCanvasContext.tsx
3073
- var import_react8 = require("react");
3226
+ var import_react9 = require("react");
3074
3227
  var import_jsx_runtime2 = require("react/jsx-runtime");
3075
- var EditCanvasContext = (0, import_react8.createContext)(null);
3228
+ var EditViewportContext = (0, import_react9.createContext)(null);
3229
+ var EditStateContext = (0, import_react9.createContext)(null);
3076
3230
  function EditCanvasProvider({
3077
3231
  options,
3078
3232
  children
3079
3233
  }) {
3080
3234
  const canvas = useEditCanvas(options);
3081
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EditCanvasContext.Provider, { value: canvas, children });
3235
+ const viewportValue = (0, import_react9.useMemo)(
3236
+ () => ({ zoom: canvas.zoom, viewport: canvas.viewport }),
3237
+ [canvas.zoom, canvas.viewport]
3238
+ );
3239
+ const stateValue = (0, import_react9.useMemo)(
3240
+ () => ({
3241
+ onReady: canvas.onReady,
3242
+ objects: canvas.objects,
3243
+ isLoading: canvas.isLoading,
3244
+ selected: canvas.selected,
3245
+ isEditingVertices: canvas.isEditingVertices,
3246
+ setMode: canvas.setMode,
3247
+ setBackground: canvas.setBackground,
3248
+ isDirty: canvas.isDirty,
3249
+ resetDirty: canvas.resetDirty,
3250
+ markDirty: canvas.markDirty,
3251
+ undo: canvas.undo,
3252
+ redo: canvas.redo,
3253
+ canUndo: canvas.canUndo,
3254
+ canRedo: canvas.canRedo,
3255
+ lockLightMode: canvas.lockLightMode,
3256
+ setLockLightMode: canvas.setLockLightMode
3257
+ }),
3258
+ // Only reactive state — stable callbacks omitted
3259
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3260
+ [
3261
+ canvas.objects,
3262
+ canvas.isLoading,
3263
+ canvas.selected,
3264
+ canvas.isEditingVertices,
3265
+ canvas.isDirty,
3266
+ canvas.canUndo,
3267
+ canvas.canRedo,
3268
+ canvas.lockLightMode
3269
+ ]
3270
+ );
3271
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CanvasRefContext.Provider, { value: canvas.canvasRef, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EditViewportContext.Provider, { value: viewportValue, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EditStateContext.Provider, { value: stateValue, children }) }) });
3082
3272
  }
3083
3273
  function useEditCanvasContext() {
3084
- const ctx = (0, import_react8.useContext)(EditCanvasContext);
3085
- if (ctx === null) {
3274
+ const canvasRef = (0, import_react9.useContext)(CanvasRefContext);
3275
+ const viewport = (0, import_react9.useContext)(EditViewportContext);
3276
+ const state = (0, import_react9.useContext)(EditStateContext);
3277
+ if (canvasRef === null || viewport === null || state === null) {
3086
3278
  throw new Error(
3087
3279
  "useEditCanvasContext must be used within an <EditCanvasProvider>"
3088
3280
  );
3089
3281
  }
3282
+ return (0, import_react9.useMemo)(
3283
+ () => ({ canvasRef, ...viewport, ...state }),
3284
+ [canvasRef, viewport, state]
3285
+ );
3286
+ }
3287
+ function useEditCanvasViewport() {
3288
+ const ctx = (0, import_react9.useContext)(EditViewportContext);
3289
+ if (ctx === null) {
3290
+ throw new Error(
3291
+ "useEditCanvasViewport must be used within an <EditCanvasProvider>"
3292
+ );
3293
+ }
3294
+ return ctx;
3295
+ }
3296
+ function useEditCanvasState() {
3297
+ const ctx = (0, import_react9.useContext)(EditStateContext);
3298
+ if (ctx === null) {
3299
+ throw new Error(
3300
+ "useEditCanvasState must be used within an <EditCanvasProvider>"
3301
+ );
3302
+ }
3090
3303
  return ctx;
3091
3304
  }
3092
3305
 
3093
3306
  // src/context/ViewCanvasContext.tsx
3094
- var import_react9 = require("react");
3307
+ var import_react10 = require("react");
3095
3308
  var import_jsx_runtime3 = require("react/jsx-runtime");
3096
- var ViewCanvasContext = (0, import_react9.createContext)(null);
3309
+ var ViewViewportContext = (0, import_react10.createContext)(null);
3310
+ var ViewStateContext = (0, import_react10.createContext)(null);
3097
3311
  function ViewCanvasProvider({
3098
3312
  options,
3099
3313
  children
3100
3314
  }) {
3101
3315
  const canvas = useViewCanvas(options);
3102
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ViewCanvasContext.Provider, { value: canvas, children });
3316
+ const viewportValue = (0, import_react10.useMemo)(
3317
+ () => ({ zoom: canvas.zoom, viewport: canvas.viewport }),
3318
+ [canvas.zoom, canvas.viewport]
3319
+ );
3320
+ const stateValue = (0, import_react10.useMemo)(
3321
+ () => ({
3322
+ onReady: canvas.onReady,
3323
+ objects: canvas.objects,
3324
+ isLoading: canvas.isLoading,
3325
+ setObjectStyle: canvas.setObjectStyle,
3326
+ setObjectStyles: canvas.setObjectStyles,
3327
+ setObjectStyleByType: canvas.setObjectStyleByType
3328
+ }),
3329
+ // Only reactive state — stable callbacks omitted
3330
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3331
+ [canvas.objects, canvas.isLoading]
3332
+ );
3333
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CanvasRefContext.Provider, { value: canvas.canvasRef, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ViewViewportContext.Provider, { value: viewportValue, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ViewStateContext.Provider, { value: stateValue, children }) }) });
3103
3334
  }
3104
3335
  function useViewCanvasContext() {
3105
- const ctx = (0, import_react9.useContext)(ViewCanvasContext);
3106
- if (ctx === null) {
3336
+ const canvasRef = (0, import_react10.useContext)(CanvasRefContext);
3337
+ const viewport = (0, import_react10.useContext)(ViewViewportContext);
3338
+ const state = (0, import_react10.useContext)(ViewStateContext);
3339
+ if (canvasRef === null || viewport === null || state === null) {
3107
3340
  throw new Error(
3108
3341
  "useViewCanvasContext must be used within a <ViewCanvasProvider>"
3109
3342
  );
3110
3343
  }
3344
+ return (0, import_react10.useMemo)(
3345
+ () => ({ canvasRef, ...viewport, ...state }),
3346
+ [canvasRef, viewport, state]
3347
+ );
3348
+ }
3349
+ function useViewCanvasViewport() {
3350
+ const ctx = (0, import_react10.useContext)(ViewViewportContext);
3351
+ if (ctx === null) {
3352
+ throw new Error(
3353
+ "useViewCanvasViewport must be used within a <ViewCanvasProvider>"
3354
+ );
3355
+ }
3356
+ return ctx;
3357
+ }
3358
+ function useViewCanvasState() {
3359
+ const ctx = (0, import_react10.useContext)(ViewStateContext);
3360
+ if (ctx === null) {
3361
+ throw new Error(
3362
+ "useViewCanvasState must be used within a <ViewCanvasProvider>"
3363
+ );
3364
+ }
3111
3365
  return ctx;
3112
3366
  }
3113
3367
 
3114
3368
  // src/overlay/ObjectOverlay.tsx
3115
- var import_react10 = require("react");
3369
+ var import_react11 = require("react");
3116
3370
  var import_material = require("@mui/material");
3117
3371
  var import_fabric18 = require("fabric");
3118
3372
  var import_jsx_runtime4 = require("react/jsx-runtime");
3119
3373
  function ObjectOverlay({
3120
- canvasRef,
3374
+ canvasRef: canvasRefProp,
3121
3375
  object,
3122
3376
  sx,
3123
3377
  children,
3124
3378
  ...rest
3125
3379
  }) {
3126
- const stackRef = (0, import_react10.useRef)(null);
3127
- (0, import_react10.useEffect)(() => {
3128
- const canvas = canvasRef.current;
3380
+ const contextCanvasRef = useCanvasRef();
3381
+ const canvasRef = canvasRefProp ?? contextCanvasRef;
3382
+ const stackRef = (0, import_react11.useRef)(null);
3383
+ (0, import_react11.useEffect)(() => {
3384
+ const canvas = canvasRef?.current;
3129
3385
  if (!canvas || !object) return;
3130
3386
  function update() {
3131
3387
  const el = stackRef.current;
@@ -3179,7 +3435,7 @@ function ObjectOverlay({
3179
3435
 
3180
3436
  // src/overlay/OverlayContent.tsx
3181
3437
  var import_material2 = require("@mui/material");
3182
- var import_react11 = require("react");
3438
+ var import_react12 = require("react");
3183
3439
  var import_jsx_runtime5 = require("react/jsx-runtime");
3184
3440
  function OverlayContent({
3185
3441
  children,
@@ -3188,9 +3444,9 @@ function OverlayContent({
3188
3444
  sx,
3189
3445
  ...rest
3190
3446
  }) {
3191
- const outerRef = (0, import_react11.useRef)(null);
3192
- const innerRef = (0, import_react11.useRef)(null);
3193
- (0, import_react11.useEffect)(() => {
3447
+ const outerRef = (0, import_react12.useRef)(null);
3448
+ const innerRef = (0, import_react12.useRef)(null);
3449
+ (0, import_react12.useEffect)(() => {
3194
3450
  const outer = outerRef.current;
3195
3451
  const inner = innerRef.current;
3196
3452
  if (!outer || !inner) return;
@@ -3251,7 +3507,7 @@ function OverlayContent({
3251
3507
 
3252
3508
  // src/overlay/FixedSizeContent.tsx
3253
3509
  var import_material3 = require("@mui/material");
3254
- var import_react12 = require("react");
3510
+ var import_react13 = require("react");
3255
3511
  var import_jsx_runtime6 = require("react/jsx-runtime");
3256
3512
  function FixedSizeContent({
3257
3513
  children,
@@ -3260,9 +3516,9 @@ function FixedSizeContent({
3260
3516
  sx,
3261
3517
  ...rest
3262
3518
  }) {
3263
- const ref = (0, import_react12.useRef)(null);
3264
- const totalContentHeightRef = (0, import_react12.useRef)(0);
3265
- (0, import_react12.useEffect)(() => {
3519
+ const ref = (0, import_react13.useRef)(null);
3520
+ const totalContentHeightRef = (0, import_react13.useRef)(0);
3521
+ (0, import_react13.useEffect)(() => {
3266
3522
  const el = ref.current;
3267
3523
  if (!el) return;
3268
3524
  let clipAncestor = el.parentElement;
@@ -3325,7 +3581,7 @@ function FixedSizeContent({
3325
3581
 
3326
3582
  // src/overlay/OverlayBadge.tsx
3327
3583
  var import_material4 = require("@mui/material");
3328
- var import_react13 = require("react");
3584
+ var import_react14 = require("react");
3329
3585
  var import_jsx_runtime7 = require("react/jsx-runtime");
3330
3586
  function toPx(v) {
3331
3587
  if (v === void 0) return void 0;
@@ -3368,9 +3624,9 @@ function OverlayBadge({
3368
3624
  sx,
3369
3625
  ...rest
3370
3626
  }) {
3371
- const ref = (0, import_react13.useRef)(null);
3372
- const baseSize = (0, import_react13.useRef)(null);
3373
- (0, import_react13.useEffect)(() => {
3627
+ const ref = (0, import_react14.useRef)(null);
3628
+ const baseSize = (0, import_react14.useRef)(null);
3629
+ (0, import_react14.useEffect)(() => {
3374
3630
  const el = ref.current;
3375
3631
  if (!el) return;
3376
3632
  const ancestor = el.parentElement;
@@ -3505,11 +3761,16 @@ var import_fabric19 = require("fabric");
3505
3761
  snapCursorPoint,
3506
3762
  useCanvasClick,
3507
3763
  useCanvasEvents,
3764
+ useCanvasRef,
3508
3765
  useCanvasTooltip,
3509
3766
  useEditCanvas,
3510
3767
  useEditCanvasContext,
3768
+ useEditCanvasState,
3769
+ useEditCanvasViewport,
3511
3770
  useViewCanvas,
3512
3771
  useViewCanvasContext,
3772
+ useViewCanvasState,
3773
+ useViewCanvasViewport,
3513
3774
  util
3514
3775
  });
3515
3776
  //# sourceMappingURL=index.cjs.map