@deepnoid/canvas 0.1.28 → 0.1.30

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.
@@ -14,8 +14,9 @@ type Props = {
14
14
  }>;
15
15
  resetOnImageChange?: boolean;
16
16
  editable?: boolean;
17
- onImageLoadError?: (error: Error) => void;
17
+ timeout?: number;
18
18
  onImageLoadSuccess?: () => void;
19
+ onImageLoadError?: (error: Error) => void;
19
20
  };
20
- declare const AnnotatedCanvas: ({ image, coordinates, panZoomable, ZoomButton, resetOnImageChange, editable, onImageLoadError, onImageLoadSuccess, }: Props) => import("react/jsx-runtime").JSX.Element | null;
21
+ declare const AnnotatedCanvas: ({ image, coordinates, panZoomable, ZoomButton, resetOnImageChange, editable, timeout, onImageLoadSuccess, onImageLoadError, }: Props) => import("react/jsx-runtime").JSX.Element;
21
22
  export default AnnotatedCanvas;
@@ -2,31 +2,123 @@
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from 'react';
4
4
  import Canvas from './Canvas';
5
- import { useImageLoader } from '../hooks/useImageLoader';
6
5
  import { usePanZoom } from '../hooks/usePanZoom';
7
- const AnnotatedCanvas = ({ image, coordinates, panZoomable = false, ZoomButton, resetOnImageChange = true, editable = false, onImageLoadError, onImageLoadSuccess, }) => {
6
+ import useResizeObserver from '../hooks/useResizeObserver';
7
+ import { drawCanvas, resolutionCanvas } from '../utils/graphic';
8
+ const AnnotatedCanvas = ({ image, coordinates, panZoomable = false, ZoomButton, resetOnImageChange = true, editable = false, timeout = 10000, onImageLoadSuccess, onImageLoadError, }) => {
8
9
  const canvasRef = useRef(null);
9
10
  const imageRef = useRef(new Image());
10
- const [displayCoordinates, setDisplayCoordinates] = useState();
11
- const { image: loadedImage, isLoading, error, } = useImageLoader(image, imageRef, 10000, onImageLoadSuccess, onImageLoadError);
12
- const { moveX, moveY, zoom, initZoom, zoomX, zoomY, dx, dy, dw, dh, handleWheel, handleMouseDown, handleMouseMove, handleMouseUp, handleMouseLeave, initCanvas, } = usePanZoom({
11
+ const [isLoading, setIsLoading] = useState(false);
12
+ const [isError, setIsError] = useState(false);
13
+ const { width, height } = useResizeObserver({ ref: canvasRef });
14
+ const { moveX, moveY, zoom, initZoom, zoomX, zoomY, dx, dy, dw, dh, imageOnloadCount, setImageOnloadCount, init, initCanvas, clearCanvas, handleWheel, handleMouseDown, handleMouseMove, handleMouseUp, handleMouseLeave, } = usePanZoom({
13
15
  canvasRef,
14
16
  imageRef,
15
17
  panZoomable,
16
- resetOnImageChange,
17
18
  });
18
- const loaded = loadedImage && !isLoading;
19
19
  useEffect(() => {
20
- if (loaded) {
21
- setDisplayCoordinates(coordinates);
20
+ const redraw = () => {
21
+ if (canvasRef.current) {
22
+ drawCanvas(moveX, moveY, zoomX, zoomY, zoom, dx, dy, resolutionCanvas(canvasRef.current), imageRef.current, true);
23
+ }
24
+ };
25
+ redraw();
26
+ }, [moveX, moveY, zoomX, zoomY, zoom, dx, dy, dw, dh, imageOnloadCount]);
27
+ // --------
28
+ // useEffect(() => {
29
+ // if (!imageRef?.current || !canvasRef?.current || !width || !height) return;
30
+ // const canvasEl = resolutionCanvas(canvasRef.current);
31
+ // if (!canvasEl) return;
32
+ // const isFirstLoad = imageOnloadCount === 0;
33
+ // if (isFirstLoad) {
34
+ // init(canvasEl, imageRef.current);
35
+ // setImageOnloadCount((prev) => prev + 1);
36
+ // return;
37
+ // }
38
+ // if (resetOnImageChange) {
39
+ // init(canvasEl, imageRef.current);
40
+ // } else {
41
+ // drawCanvas(moveX, moveY, zoomX, zoomY, zoom, dx, dy, canvasEl, imageRef.current, true);
42
+ // }
43
+ // setImageOnloadCount((prev) => prev + 1);
44
+ // }, [imageRef?.current, width, height, resetOnImageChange]);
45
+ // ----------
46
+ useEffect(() => {
47
+ if (!image || image.trim() === '') {
48
+ setIsLoading(false);
49
+ setIsError(false);
50
+ clearCanvas();
51
+ return;
52
+ }
53
+ setIsLoading(true);
54
+ imageRef.current.src = image;
55
+ imageRef.current.onload = () => {
56
+ console.log('> ======================= useEffect onload ============================');
57
+ setIsLoading(false);
58
+ setIsError(false);
59
+ onImageLoadSuccess?.();
60
+ // const canvas = resolutionCanvas(canvasRef.current);
61
+ // if (canvas) init(canvas, imageRef.current);
62
+ // setImageOnloadCount((prev) => prev + 1);
63
+ console.log('> canvasRef.current : ', canvasRef.current);
64
+ const canvas = resolutionCanvas(canvasRef.current);
65
+ console.log(' > canvas :', canvas);
66
+ if (!canvas)
67
+ return;
68
+ if (resetOnImageChange)
69
+ init(canvas, imageRef.current);
70
+ setImageOnloadCount((prev) => prev + 1);
71
+ // if (prevImageRef.current !== image) {
72
+ // // prevImageRef.current = image;
73
+ // prevImageRef.current = imageRef.current.src;
74
+ // const canvas = resolutionCanvas(canvasRef.current);
75
+ // const isFirstLoad = imageOnloadCount === 0;
76
+ // console.log('> onload - isFirstLoad : ', isFirstLoad);
77
+ // // if (canvas && isFirstLoad) {
78
+ // // init(canvas, imageRef.current);
79
+ // // setImageOnloadCount((prev) => prev + 1);
80
+ // // }
81
+ // if (!canvas) return;
82
+ // if (isFirstLoad) {
83
+ // init(canvas, imageRef.current);
84
+ // setImageOnloadCount((prev) => prev + 1);
85
+ // return;
86
+ // }
87
+ // console.log('> !!!!!!!!!!!!!!!! resetOnImageChange : ', resetOnImageChange);
88
+ // if (resetOnImageChange) {
89
+ // init(canvas, imageRef.current);
90
+ // } else {
91
+ // // drawCanvas(moveX, moveY, zoomX, zoomY, zoom, dx, dy, canvas, imageRef.current, true);
92
+ // }
93
+ // setImageOnloadCount((prev) => prev + 1);
94
+ // // if (canvas) init(canvas, imageRef.current);
95
+ // }
96
+ // setImageOnloadCount((prev) => prev + 1);
97
+ };
98
+ imageRef.current.onerror = () => {
99
+ setIsLoading(false);
100
+ setIsError(true);
101
+ const err = new Error(`Failed to load image: ${image}`);
102
+ onImageLoadError?.(err);
103
+ clearCanvas();
104
+ };
105
+ }, [image, canvasRef.current]);
106
+ useEffect(() => {
107
+ console.log('> ======================= useEffect width, height !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
108
+ if (image || !isError) {
109
+ imageRef.current.src = image;
110
+ if (!resetOnImageChange) {
111
+ const canvas = resolutionCanvas(canvasRef.current);
112
+ if (canvas)
113
+ init(canvas, imageRef.current);
114
+ setImageOnloadCount((prev) => prev + 1);
115
+ }
22
116
  }
23
- }, [loaded, coordinates]);
24
- if (!image || error)
25
- return null;
117
+ }, [width, height]);
26
118
  return (_jsx("div", { style: { width: '100%', height: '100%', display: 'flex', flex: 1 }, children: _jsx("div", { onWheel: handleWheel, onMouseMove: handleMouseMove, onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, onMouseLeave: handleMouseLeave, style: {
27
119
  flex: 1,
28
120
  position: 'relative',
29
121
  cursor: panZoomable ? 'grab' : 'default',
30
- }, children: loaded && (_jsxs(_Fragment, { children: [_jsx("canvas", { ref: canvasRef, style: { position: 'absolute', width: '100%', height: '100%', left: 0, top: 0 } }), _jsx(Canvas, { moveX: moveX, moveY: moveY, zoomX: zoomX, zoomY: zoomY, zoom: zoom, dx: dx, dy: dy, dw: dw, dh: dh, coordinates: displayCoordinates, editable: editable }), ZoomButton && initZoom > 0 && zoom > 0 && (_jsx(ZoomButton, { onClick: initCanvas, children: `${Math.round((zoom / initZoom) * 100)}%` }))] })) }) }));
122
+ }, children: imageRef.current.src && !isLoading && !isError && (_jsxs(_Fragment, { children: [_jsx("canvas", { ref: canvasRef, style: { position: 'absolute', width: '100%', height: '100%', left: 0, top: 0 } }), _jsx(Canvas, { moveX: moveX, moveY: moveY, zoomX: zoomX, zoomY: zoomY, zoom: zoom, dx: dx, dy: dy, dw: dw, dh: dh, coordinates: coordinates, editable: editable }), ZoomButton && _jsx(ZoomButton, { onClick: initCanvas, children: `${Math.round((zoom / initZoom) * 100)}%` })] })) }) }));
31
123
  };
32
124
  export default AnnotatedCanvas;
@@ -1,11 +1,10 @@
1
1
  import { RefObject, WheelEvent, MouseEvent } from 'react';
2
2
  type UsePanZoomProps = {
3
3
  canvasRef: RefObject<HTMLCanvasElement | null>;
4
- imageRef: RefObject<HTMLImageElement | null>;
4
+ imageRef: RefObject<HTMLImageElement>;
5
5
  panZoomable?: boolean;
6
- resetOnImageChange?: boolean;
7
6
  };
8
- export declare const usePanZoom: ({ canvasRef, imageRef, panZoomable, resetOnImageChange, }: UsePanZoomProps) => {
7
+ export declare function usePanZoom({ canvasRef, imageRef, panZoomable }: UsePanZoomProps): {
9
8
  moveX: number;
10
9
  moveY: number;
11
10
  zoom: number;
@@ -16,11 +15,15 @@ export declare const usePanZoom: ({ canvasRef, imageRef, panZoomable, resetOnIma
16
15
  dy: number;
17
16
  dw: number;
18
17
  dh: number;
18
+ imageOnloadCount: number;
19
+ setImageOnloadCount: import("react").Dispatch<import("react").SetStateAction<number>>;
20
+ init: (canvas: HTMLCanvasElement, image: HTMLImageElement) => void;
21
+ initCanvas: () => void;
22
+ clearCanvas: () => void;
19
23
  handleWheel: (event: WheelEvent) => void;
20
24
  handleMouseDown: (event: MouseEvent) => void;
21
25
  handleMouseMove: (event: MouseEvent) => void;
22
26
  handleMouseUp: (event: MouseEvent) => void;
23
27
  handleMouseLeave: (event: MouseEvent) => void;
24
- initCanvas: () => void;
25
28
  };
26
29
  export {};
@@ -1,13 +1,11 @@
1
1
  'use client';
2
- import { useCallback, useEffect, useState } from 'react';
3
- import { resolutionCanvas, drawCanvas, getMousePointTransform, canvasCenterPoint } from '../utils/graphic';
2
+ import { useCallback, useState } from 'react';
3
+ import { resolutionCanvas, getMousePointTransform, canvasCenterPoint } from '../utils/graphic';
4
4
  import { MouseStatus } from '../enum/common';
5
- import useResizeObserver from './useResizeObserver';
6
- export const usePanZoom = ({ canvasRef, imageRef, panZoomable = false, resetOnImageChange = true, }) => {
5
+ export function usePanZoom({ canvasRef, imageRef, panZoomable = false }) {
7
6
  const ZOOM_UNIT = 0.9;
8
7
  const MAX_ZOOM = 4;
9
8
  const MIN_ZOOM = 0.5;
10
- const { width, height } = useResizeObserver({ ref: canvasRef });
11
9
  const [initZoom, setInitZoom] = useState(0);
12
10
  const [zoom, setZoom] = useState(0);
13
11
  const [zoomX, setZoomX] = useState(0);
@@ -21,49 +19,61 @@ export const usePanZoom = ({ canvasRef, imageRef, panZoomable = false, resetOnIm
21
19
  const [status, setStatus] = useState('');
22
20
  const [imageOnloadCount, setImageOnloadCount] = useState(0);
23
21
  const [startMousePoint, setStartMousePoint] = useState();
24
- const calculatorZoomPoint = useCallback((canvasEl, movementX, movementY, dx, dy) => {
22
+ const calculatorZoomPoint = useCallback((canvas, movementX, movementY, dx, dy) => {
25
23
  let x = 0;
26
24
  let y = 0;
27
- if (canvasRef.current) {
28
- x = -dx - movementX + canvasEl.width / 2;
29
- y = -dy - movementY + canvasEl.height / 2;
25
+ if (canvas) {
26
+ x = -dx - movementX + canvas.width / 2;
27
+ y = -dy - movementY + canvas.height / 2;
30
28
  }
31
29
  return { x, y };
32
- }, [canvasRef]);
33
- const calculateInitZoom = useCallback((canvasEl, image) => {
34
- if (!image)
30
+ }, []);
31
+ const calculateInitZoom = useCallback((canvas, image) => {
32
+ if (!canvas || !image)
35
33
  return 1;
36
- const canvasRatio = canvasEl.clientWidth / canvasEl.clientHeight;
34
+ const canvasRatio = canvas.clientWidth / canvas.clientHeight;
37
35
  const imageRatio = image.naturalWidth / image.naturalHeight;
38
36
  return canvasRatio < imageRatio
39
- ? canvasEl.clientWidth / image.naturalWidth
40
- : canvasEl.clientHeight / image.naturalHeight;
37
+ ? canvas.clientWidth / image.naturalWidth
38
+ : canvas.clientHeight / image.naturalHeight;
41
39
  }, []);
42
- const init = (canvasEl, image) => {
43
- const point = canvasCenterPoint(canvasEl, image);
40
+ const init = (canvas, image) => {
41
+ if (!canvas || !image)
42
+ return;
43
+ const point = canvasCenterPoint(canvas, image);
44
44
  setDx(point.x);
45
45
  setDy(point.y);
46
46
  setDw(image.width);
47
47
  setDh(image.height);
48
48
  setMoveX(0);
49
49
  setMoveY(0);
50
- const init_zoom = calculateInitZoom(canvasEl, image);
50
+ const init_zoom = calculateInitZoom(canvas, image);
51
51
  setZoom(init_zoom);
52
52
  setInitZoom(init_zoom);
53
- const zoomPoint = calculatorZoomPoint(canvasEl, 0, 0, point.x, point.y);
53
+ const zoomPoint = calculatorZoomPoint(canvas, 0, 0, point.x, point.y);
54
54
  setZoomX(zoomPoint.x);
55
55
  setZoomY(zoomPoint.y);
56
56
  };
57
57
  const initCanvas = () => {
58
- if (imageRef?.current) {
59
- const canvasEl = resolutionCanvas(canvasRef.current);
60
- if (canvasEl)
61
- init(canvasEl, imageRef.current);
58
+ if (!canvasRef.current || !imageRef.current)
59
+ return;
60
+ const canvas = resolutionCanvas(canvasRef.current);
61
+ if (canvas) {
62
+ init(canvas, imageRef.current);
62
63
  setImageOnloadCount((prev) => prev + 1);
63
64
  }
64
65
  };
66
+ const clearCanvas = () => {
67
+ if (!canvasRef.current)
68
+ return;
69
+ const canvas = resolutionCanvas(canvasRef.current);
70
+ if (canvas) {
71
+ const ctx = canvas.getContext('2d');
72
+ ctx?.clearRect(0, 0, canvas.width, canvas.height);
73
+ }
74
+ };
65
75
  const handleWheel = (event) => {
66
- if (!panZoomable || !canvasRef.current || !imageRef?.current)
76
+ if (!panZoomable || !canvasRef.current)
67
77
  return;
68
78
  let calc_zoom = event.deltaY < 0 ? (zoom || 1) * (1 / ZOOM_UNIT) : (zoom || 1) * ZOOM_UNIT;
69
79
  if (initZoom * MAX_ZOOM < zoom * ZOOM_UNIT)
@@ -76,13 +86,13 @@ export const usePanZoom = ({ canvasRef, imageRef, panZoomable = false, resetOnIm
76
86
  setZoom(calc_zoom);
77
87
  };
78
88
  const handleMouseMove = (event) => {
79
- if (!panZoomable || status !== MouseStatus.MOVE || !canvasRef.current || !imageRef?.current)
89
+ if (!panZoomable || !canvasRef.current || status !== MouseStatus.MOVE)
80
90
  return;
81
- const canvasEl = canvasRef.current;
82
- const rect = canvasEl.getBoundingClientRect();
91
+ const canvas = canvasRef.current;
92
+ const rect = canvas.getBoundingClientRect();
83
93
  const mouseX = event.clientX - rect.left;
84
94
  const mouseY = event.clientY - rect.top;
85
- const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom || 1, canvasEl);
95
+ const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom || 1, canvas);
86
96
  let x = mouse.x;
87
97
  let y = mouse.y;
88
98
  if (startMousePoint) {
@@ -91,63 +101,35 @@ export const usePanZoom = ({ canvasRef, imageRef, panZoomable = false, resetOnIm
91
101
  }
92
102
  setMoveX(moveX + x);
93
103
  setMoveY(moveY + y);
94
- const zoomPoint = calculatorZoomPoint(canvasEl, moveX + x, moveY + y, dx, dy);
104
+ const zoomPoint = calculatorZoomPoint(canvas, moveX + x, moveY + y, dx, dy);
95
105
  setZoomX(zoomPoint.x);
96
106
  setZoomY(zoomPoint.y);
97
107
  event.preventDefault();
98
108
  };
99
109
  const handleMouseDown = (event) => {
100
- if (!panZoomable || !canvasRef.current || !imageRef?.current)
110
+ if (!panZoomable || !canvasRef.current)
101
111
  return;
102
112
  setStatus(MouseStatus.MOVE);
103
- const canvasEl = canvasRef.current;
104
- const rect = canvasEl.getBoundingClientRect();
113
+ const canvas = canvasRef.current;
114
+ const rect = canvas.getBoundingClientRect();
105
115
  const mouseX = event.clientX - rect.left;
106
116
  const mouseY = event.clientY - rect.top;
107
- const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom || 1, canvasEl);
117
+ const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom || 1, canvas);
108
118
  setStartMousePoint({ x: mouse.x, y: mouse.y });
109
119
  event.preventDefault();
110
120
  };
111
121
  const handleMouseUp = (event) => {
112
- if (!panZoomable)
122
+ if (!panZoomable || !canvasRef.current)
113
123
  return;
114
124
  setStatus(MouseStatus.STOP);
115
125
  event.preventDefault();
116
126
  };
117
127
  const handleMouseLeave = (event) => {
118
- if (!panZoomable)
128
+ if (!panZoomable || !canvasRef.current)
119
129
  return;
120
130
  setStatus(MouseStatus.STOP);
121
131
  event.preventDefault();
122
132
  };
123
- useEffect(() => {
124
- const redraw = () => {
125
- if (canvasRef.current && imageRef?.current) {
126
- drawCanvas(moveX, moveY, zoomX, zoomY, zoom, dx, dy, resolutionCanvas(canvasRef.current), imageRef.current, true);
127
- }
128
- };
129
- redraw();
130
- }, [moveX, moveY, zoomX, zoomY, zoom, dx, dy, dw, dh, imageOnloadCount, canvasRef, imageRef]);
131
- useEffect(() => {
132
- if (!imageRef?.current || !canvasRef?.current || !width || !height)
133
- return;
134
- const canvasEl = resolutionCanvas(canvasRef.current);
135
- if (!canvasEl)
136
- return;
137
- const isFirstLoad = imageOnloadCount === 0;
138
- if (isFirstLoad) {
139
- init(canvasEl, imageRef.current);
140
- setImageOnloadCount((prev) => prev + 1);
141
- return;
142
- }
143
- if (resetOnImageChange) {
144
- init(canvasEl, imageRef.current);
145
- }
146
- else {
147
- drawCanvas(moveX, moveY, zoomX, zoomY, zoom, dx, dy, canvasEl, imageRef.current, true);
148
- }
149
- setImageOnloadCount((prev) => prev + 1);
150
- }, [imageRef?.current, width, height, resetOnImageChange]);
151
133
  return {
152
134
  moveX,
153
135
  moveY,
@@ -159,11 +141,15 @@ export const usePanZoom = ({ canvasRef, imageRef, panZoomable = false, resetOnIm
159
141
  dy,
160
142
  dw,
161
143
  dh,
144
+ imageOnloadCount,
145
+ setImageOnloadCount,
146
+ init,
147
+ initCanvas,
148
+ clearCanvas,
162
149
  handleWheel,
163
150
  handleMouseDown,
164
151
  handleMouseMove,
165
152
  handleMouseUp,
166
153
  handleMouseLeave,
167
- initCanvas,
168
154
  };
169
- };
155
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepnoid/canvas",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.cjs",
@@ -1,6 +0,0 @@
1
- import { RefObject } from 'react';
2
- export declare const useImageLoader: (src: string, imageRef: RefObject<HTMLImageElement>, timeout?: number, onLoadSuccess?: () => void, onLoadError?: (error: Error) => void) => {
3
- image: HTMLImageElement | null;
4
- isLoading: boolean;
5
- error: string | null;
6
- };
@@ -1,71 +0,0 @@
1
- 'use client';
2
- import { useEffect, useState } from 'react';
3
- export const useImageLoader = (src, imageRef, timeout = 10000, onLoadSuccess, onLoadError) => {
4
- const [image, setImage] = useState(null);
5
- const [isLoading, setIsLoading] = useState(false);
6
- const [error, setError] = useState(null);
7
- useEffect(() => {
8
- if (!src || src.trim() === '') {
9
- setImage(null);
10
- setIsLoading(false);
11
- setError(null);
12
- return;
13
- }
14
- const controller = new AbortController();
15
- const { signal } = controller;
16
- setIsLoading(true);
17
- setError(null);
18
- setImage(null);
19
- const img = new Image();
20
- img.crossOrigin = 'anonymous';
21
- img.src = src;
22
- const timeoutId = setTimeout(() => {
23
- if (!signal.aborted) {
24
- controller.abort();
25
- const e = new Error(`Image load timeout: ${src}`);
26
- setError(e.message);
27
- setIsLoading(false);
28
- onLoadError?.(e);
29
- }
30
- }, timeout);
31
- const load = async () => {
32
- try {
33
- await img.decode();
34
- if (signal.aborted)
35
- return;
36
- if (img.naturalWidth === 0 || img.naturalHeight === 0) {
37
- throw new Error(`Invalid image dimensions: ${src}`);
38
- }
39
- try {
40
- imageRef.current = img;
41
- }
42
- catch { }
43
- setImage(img);
44
- setIsLoading(false);
45
- setError(null);
46
- onLoadSuccess?.();
47
- }
48
- catch (err) {
49
- if (signal.aborted)
50
- return;
51
- const e = new Error(`Failed to load image: ${src}`);
52
- setError(e.message);
53
- setIsLoading(false);
54
- onLoadError?.(err instanceof Error ? err : e);
55
- }
56
- finally {
57
- clearTimeout(timeoutId);
58
- }
59
- };
60
- load();
61
- return () => {
62
- controller.abort();
63
- clearTimeout(timeoutId);
64
- try {
65
- img.src = '';
66
- }
67
- catch { }
68
- };
69
- }, [src, timeout, onLoadError, onLoadSuccess, imageRef]);
70
- return { image, isLoading, error };
71
- };