@hunterchen/canvas 0.11.0 → 0.11.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"draggable.d.ts","sourceRoot":"","sources":["../../../src/components/canvas/draggable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAMN,MAAM,OAAO,CAAC;AACf,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,eAAe,CAAC;AAGvB,UAAU,KAAK;IACb,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,cAAe,SAAQ,eAAe,CAAC,KAAK,CAAC;IAC5D,UAAU,CAAC,EAAE,KAAK,CAAC;IACnB,qBAAqB,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,YAAY,KAAK,OAAO,CAAC;CAC5D;AAID,eAAO,MAAM,SAAS,oGAoGrB,CAAC;AAIF,MAAM,WAAW,mBAAoB,SAAQ,cAAc;IACzD,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAuDD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,2CAwGxD"}
1
+ {"version":3,"file":"draggable.d.ts","sourceRoot":"","sources":["../../../src/components/canvas/draggable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAMN,MAAM,OAAO,CAAC;AACf,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,eAAe,CAAC;AAGvB,UAAU,KAAK;IACb,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,cAAe,SAAQ,eAAe,CAAC,KAAK,CAAC;IAC5D,UAAU,CAAC,EAAE,KAAK,CAAC;IACnB,qBAAqB,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,YAAY,KAAK,OAAO,CAAC;CAC5D;AAID,eAAO,MAAM,SAAS,oGAoGrB,CAAC;AAIF,MAAM,WAAW,mBAAoB,SAAQ,cAAc;IACzD,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAyDD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,2CAwGxD"}
@@ -85,6 +85,8 @@ const Draggable = forwardRef((props, ref) => {
85
85
  });
86
86
  Draggable.displayName = "Draggable";
87
87
  function drawImageToCanvas(img, canvas) {
88
+ canvas.width = img.naturalWidth;
89
+ canvas.height = img.naturalHeight;
88
90
  const ctx = canvas.getContext("2d", { willReadFrequently: true });
89
91
  if (!ctx) return;
90
92
  ctx.clearRect(0, 0, canvas.width, canvas.height);
@@ -95,8 +97,8 @@ function getAlphaAtCoords(clientX, clientY, canvas, img) {
95
97
  const ctx = canvas.getContext("2d");
96
98
  if (!ctx) return 0;
97
99
  const rect = img.getBoundingClientRect();
98
- const x = (clientX - rect.left) / rect.width * img.naturalWidth;
99
- const y = (clientY - rect.top) / rect.height * img.naturalHeight;
100
+ const x = (clientX - rect.left) / rect.width * canvas.width;
101
+ const y = (clientY - rect.top) / rect.height * canvas.height;
100
102
  return ctx.getImageData(x, y, 1, 1).data[3] ?? 0;
101
103
  }
102
104
  function isMouseOverImage(clientX, clientY, img) {
@@ -1 +1 @@
1
- {"version":3,"file":"draggable.js","names":[],"sources":["../../../src/components/canvas/draggable.tsx"],"sourcesContent":["import React, {\n useRef,\n useEffect,\n forwardRef,\n useState,\n useCallback,\n} from \"react\";\nimport {\n animate,\n motion,\n useAnimationControls,\n useMotionValue,\n type HTMLMotionProps,\n type PanInfo,\n} from \"framer-motion\";\nimport { useCanvasContext } from \"../../contexts/CanvasContext\";\n\ninterface Point {\n x: number;\n y: number;\n}\n\nexport interface DraggableProps extends HTMLMotionProps<\"div\"> {\n initialPos?: Point;\n shouldStopPropagation?: (e: React.PointerEvent) => boolean;\n}\n\nconst defaultPos = { x: 0, y: 0 };\n\nexport const Draggable = forwardRef<HTMLDivElement, DraggableProps>(\n (props, ref) => {\n const {\n initialPos: passedPos,\n children,\n style,\n shouldStopPropagation = () => true,\n ...restProps\n } = props;\n\n const {\n scale: parentZoom,\n isResetting,\n maxZIndex,\n setMaxZIndex,\n } = useCanvasContext();\n\n const initialPos = passedPos ?? defaultPos;\n\n const x = useMotionValue(initialPos.x);\n const y = useMotionValue(initialPos.y);\n\n const logicalPositionRef = useRef<Point>({ ...initialPos });\n const controls = useAnimationControls();\n\n const [zIndex, setZIndex] = useState(1);\n\n useEffect(() => {\n if (isResetting) {\n logicalPositionRef.current = { ...initialPos };\n void animate(x, initialPos.x, {\n duration: 0.3,\n type: \"spring\",\n damping: 14,\n stiffness: 120,\n mass: 1,\n });\n void animate(y, initialPos.y, {\n duration: 0.3,\n type: \"spring\",\n damping: 14,\n stiffness: 120,\n mass: 1,\n });\n }\n }, [initialPos, controls, isResetting, x, y]);\n\n const handleDrag = (\n _event: MouseEvent | TouchEvent | PointerEvent,\n info: PanInfo,\n ) => {\n controls.stop();\n const deltaParentX = info.delta.x / parentZoom.get();\n const deltaParentY = info.delta.y / parentZoom.get();\n\n logicalPositionRef.current.x += deltaParentX;\n logicalPositionRef.current.y += deltaParentY;\n\n x.set(logicalPositionRef.current.x);\n y.set(logicalPositionRef.current.y);\n\n if (zIndex < maxZIndex) {\n setZIndex(maxZIndex + 1);\n setMaxZIndex(maxZIndex + 1);\n }\n };\n\n return (\n <motion.div\n ref={ref}\n dragMomentum={false}\n drag\n animate={controls}\n onDrag={handleDrag}\n style={{\n ...style,\n x,\n y,\n zIndex,\n }}\n initial={{\n scale: 1,\n filter: \"drop-shadow(0 0px 0px rgba(0, 0, 0, 0)) brightness(1)\",\n position: \"relative\",\n }}\n onPointerDown={(e: React.PointerEvent) => {\n if (shouldStopPropagation?.(e)) {\n e.stopPropagation();\n }\n }}\n transition={{\n duration: 0.1,\n ease: \"easeOut\",\n }}\n {...restProps}\n >\n {children}\n </motion.div>\n );\n },\n);\n\nDraggable.displayName = \"Draggable\";\n\nexport interface DraggableImageProps extends DraggableProps {\n src: string;\n alt?: string;\n width?: string | number;\n height?: string | number;\n scale?: number;\n hoverScale?: number;\n}\n\nfunction drawImageToCanvas(img: HTMLImageElement, canvas: HTMLCanvasElement) {\n const ctx = canvas.getContext(\"2d\", { willReadFrequently: true });\n if (!ctx) return;\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.drawImage(img, 0, 0);\n}\n\nfunction getAlphaAtCoords(\n clientX: number,\n clientY: number,\n canvas: HTMLCanvasElement | null,\n img: HTMLImageElement | null,\n): number {\n if (!canvas || !img) return 0;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return 0;\n\n const rect = img.getBoundingClientRect();\n\n const x = ((clientX - rect.left) / rect.width) * img.naturalWidth;\n const y = ((clientY - rect.top) / rect.height) * img.naturalHeight;\n\n const alpha = ctx.getImageData(x, y, 1, 1).data[3] ?? 0;\n return alpha;\n}\n\nfunction isMouseOverImage(\n clientX: number,\n clientY: number,\n img: HTMLImageElement | null,\n) {\n if (!img) return false;\n const rect = img.getBoundingClientRect();\n return (\n clientX >= rect.left &&\n clientX <= rect.right &&\n clientY >= rect.top &&\n clientY <= rect.bottom\n );\n}\n\nfunction updateCursor(\n opaque: boolean,\n isMouseDown: boolean,\n img: HTMLImageElement | null,\n) {\n let cursor = \"url('customcursor.svg'), auto\"; // default\n if (opaque) cursor = \"grab\";\n if (isMouseDown) cursor = \"grabbing\";\n if (img) img.style.cursor = cursor;\n}\n\nexport function DraggableImage(props: DraggableImageProps) {\n const {\n src,\n alt,\n width,\n height,\n initialPos,\n animate,\n className,\n scale,\n hoverScale,\n ...restProps\n } = props;\n const imgRef = useRef<HTMLImageElement>(null);\n const [isOpaque, setIsOpaque] = useState(true); // default to true for better UX\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const isMouseDown = useRef(false);\n\n // create a invisible canvas element to check the alpha value of the image\n useEffect(() => {\n if (typeof window !== \"undefined\" && !canvasRef.current) {\n canvasRef.current = document.createElement(\"canvas\");\n }\n const img = imgRef.current;\n const canvas = canvasRef.current;\n if (!img || !canvas) return;\n if (!img.complete) {\n img.onload = () => drawImageToCanvas(img, canvas);\n } else {\n drawImageToCanvas(img, canvas);\n }\n return () => {\n if (img) img.onload = null;\n };\n }, []);\n\n // handle global mouse move to update cursor and opacity\n useEffect(() => {\n const handleGlobalMouseMove = (e: MouseEvent) => {\n if (\n !isMouseDown.current &&\n isMouseOverImage(e.clientX, e.clientY, imgRef.current)\n ) {\n const alpha = getAlphaAtCoords(\n e.clientX,\n e.clientY,\n canvasRef.current,\n imgRef.current,\n );\n\n // checking alpha > n rather than 0 to not trigger on shadows and such\n const opaque = alpha > 128;\n\n setIsOpaque(opaque);\n updateCursor(opaque, false, imgRef.current);\n }\n };\n window.addEventListener(\"mousemove\", handleGlobalMouseMove);\n return () => {\n window.removeEventListener(\"mousemove\", handleGlobalMouseMove);\n };\n }, []);\n\n const handlePointerDown = useCallback((e: React.PointerEvent) => {\n isMouseDown.current = true;\n e.stopPropagation(); // Prevents the event from bubbling up\n updateCursor(true, true, imgRef.current);\n }, []);\n\n const handlePointerUp = () => {\n isMouseDown.current = false;\n updateCursor(isOpaque, false, imgRef.current);\n };\n\n const hoverScaleValue = isOpaque ? (hoverScale ?? (scale ?? 1)) : (scale ?? 1);\n\n return (\n <Draggable\n initialPos={initialPos}\n className={className}\n drag={isOpaque}\n style={{\n height: 0,\n }}\n {...restProps}\n >\n <motion.img\n ref={imgRef}\n src={src}\n alt={alt}\n width={width}\n height={height}\n animate={animate}\n draggable=\"false\"\n whileHover={{ scale: hoverScaleValue }}\n style={{\n scale: scale ?? 1,\n pointerEvents: isOpaque ? \"auto\" : \"none\",\n }}\n onPointerDown={handlePointerDown}\n onPointerUp={handlePointerUp}\n />\n </Draggable>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"draggable.js","names":[],"sources":["../../../src/components/canvas/draggable.tsx"],"sourcesContent":["import React, {\n useRef,\n useEffect,\n forwardRef,\n useState,\n useCallback,\n} from \"react\";\nimport {\n animate,\n motion,\n useAnimationControls,\n useMotionValue,\n type HTMLMotionProps,\n type PanInfo,\n} from \"framer-motion\";\nimport { useCanvasContext } from \"../../contexts/CanvasContext\";\n\ninterface Point {\n x: number;\n y: number;\n}\n\nexport interface DraggableProps extends HTMLMotionProps<\"div\"> {\n initialPos?: Point;\n shouldStopPropagation?: (e: React.PointerEvent) => boolean;\n}\n\nconst defaultPos = { x: 0, y: 0 };\n\nexport const Draggable = forwardRef<HTMLDivElement, DraggableProps>(\n (props, ref) => {\n const {\n initialPos: passedPos,\n children,\n style,\n shouldStopPropagation = () => true,\n ...restProps\n } = props;\n\n const {\n scale: parentZoom,\n isResetting,\n maxZIndex,\n setMaxZIndex,\n } = useCanvasContext();\n\n const initialPos = passedPos ?? defaultPos;\n\n const x = useMotionValue(initialPos.x);\n const y = useMotionValue(initialPos.y);\n\n const logicalPositionRef = useRef<Point>({ ...initialPos });\n const controls = useAnimationControls();\n\n const [zIndex, setZIndex] = useState(1);\n\n useEffect(() => {\n if (isResetting) {\n logicalPositionRef.current = { ...initialPos };\n void animate(x, initialPos.x, {\n duration: 0.3,\n type: \"spring\",\n damping: 14,\n stiffness: 120,\n mass: 1,\n });\n void animate(y, initialPos.y, {\n duration: 0.3,\n type: \"spring\",\n damping: 14,\n stiffness: 120,\n mass: 1,\n });\n }\n }, [initialPos, controls, isResetting, x, y]);\n\n const handleDrag = (\n _event: MouseEvent | TouchEvent | PointerEvent,\n info: PanInfo,\n ) => {\n controls.stop();\n const deltaParentX = info.delta.x / parentZoom.get();\n const deltaParentY = info.delta.y / parentZoom.get();\n\n logicalPositionRef.current.x += deltaParentX;\n logicalPositionRef.current.y += deltaParentY;\n\n x.set(logicalPositionRef.current.x);\n y.set(logicalPositionRef.current.y);\n\n if (zIndex < maxZIndex) {\n setZIndex(maxZIndex + 1);\n setMaxZIndex(maxZIndex + 1);\n }\n };\n\n return (\n <motion.div\n ref={ref}\n dragMomentum={false}\n drag\n animate={controls}\n onDrag={handleDrag}\n style={{\n ...style,\n x,\n y,\n zIndex,\n }}\n initial={{\n scale: 1,\n filter: \"drop-shadow(0 0px 0px rgba(0, 0, 0, 0)) brightness(1)\",\n position: \"relative\",\n }}\n onPointerDown={(e: React.PointerEvent) => {\n if (shouldStopPropagation?.(e)) {\n e.stopPropagation();\n }\n }}\n transition={{\n duration: 0.1,\n ease: \"easeOut\",\n }}\n {...restProps}\n >\n {children}\n </motion.div>\n );\n },\n);\n\nDraggable.displayName = \"Draggable\";\n\nexport interface DraggableImageProps extends DraggableProps {\n src: string;\n alt?: string;\n width?: string | number;\n height?: string | number;\n scale?: number;\n hoverScale?: number;\n}\n\nfunction drawImageToCanvas(img: HTMLImageElement, canvas: HTMLCanvasElement) {\n canvas.width = img.naturalWidth;\n canvas.height = img.naturalHeight;\n const ctx = canvas.getContext(\"2d\", { willReadFrequently: true });\n if (!ctx) return;\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.drawImage(img, 0, 0);\n}\n\nfunction getAlphaAtCoords(\n clientX: number,\n clientY: number,\n canvas: HTMLCanvasElement | null,\n img: HTMLImageElement | null,\n): number {\n if (!canvas || !img) return 0;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return 0;\n\n const rect = img.getBoundingClientRect();\n\n const x = ((clientX - rect.left) / rect.width) * canvas.width;\n const y = ((clientY - rect.top) / rect.height) * canvas.height;\n\n const alpha = ctx.getImageData(x, y, 1, 1).data[3] ?? 0;\n return alpha;\n}\n\nfunction isMouseOverImage(\n clientX: number,\n clientY: number,\n img: HTMLImageElement | null,\n) {\n if (!img) return false;\n const rect = img.getBoundingClientRect();\n return (\n clientX >= rect.left &&\n clientX <= rect.right &&\n clientY >= rect.top &&\n clientY <= rect.bottom\n );\n}\n\nfunction updateCursor(\n opaque: boolean,\n isMouseDown: boolean,\n img: HTMLImageElement | null,\n) {\n let cursor = \"url('customcursor.svg'), auto\"; // default\n if (opaque) cursor = \"grab\";\n if (isMouseDown) cursor = \"grabbing\";\n if (img) img.style.cursor = cursor;\n}\n\nexport function DraggableImage(props: DraggableImageProps) {\n const {\n src,\n alt,\n width,\n height,\n initialPos,\n animate,\n className,\n scale,\n hoverScale,\n ...restProps\n } = props;\n const imgRef = useRef<HTMLImageElement>(null);\n const [isOpaque, setIsOpaque] = useState(true); // default to true for better UX\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const isMouseDown = useRef(false);\n\n // create a invisible canvas element to check the alpha value of the image\n useEffect(() => {\n if (typeof window !== \"undefined\" && !canvasRef.current) {\n canvasRef.current = document.createElement(\"canvas\");\n }\n const img = imgRef.current;\n const canvas = canvasRef.current;\n if (!img || !canvas) return;\n if (!img.complete) {\n img.onload = () => drawImageToCanvas(img, canvas);\n } else {\n drawImageToCanvas(img, canvas);\n }\n return () => {\n if (img) img.onload = null;\n };\n }, []);\n\n // handle global mouse move to update cursor and opacity\n useEffect(() => {\n const handleGlobalMouseMove = (e: MouseEvent) => {\n if (\n !isMouseDown.current &&\n isMouseOverImage(e.clientX, e.clientY, imgRef.current)\n ) {\n const alpha = getAlphaAtCoords(\n e.clientX,\n e.clientY,\n canvasRef.current,\n imgRef.current,\n );\n\n // checking alpha > n rather than 0 to not trigger on shadows and such\n const opaque = alpha > 128;\n\n setIsOpaque(opaque);\n updateCursor(opaque, false, imgRef.current);\n }\n };\n window.addEventListener(\"mousemove\", handleGlobalMouseMove);\n return () => {\n window.removeEventListener(\"mousemove\", handleGlobalMouseMove);\n };\n }, []);\n\n const handlePointerDown = useCallback((e: React.PointerEvent) => {\n isMouseDown.current = true;\n e.stopPropagation(); // Prevents the event from bubbling up\n updateCursor(true, true, imgRef.current);\n }, []);\n\n const handlePointerUp = () => {\n isMouseDown.current = false;\n updateCursor(isOpaque, false, imgRef.current);\n };\n\n const hoverScaleValue = isOpaque ? (hoverScale ?? (scale ?? 1)) : (scale ?? 1);\n\n return (\n <Draggable\n initialPos={initialPos}\n className={className}\n drag={isOpaque}\n style={{\n height: 0,\n }}\n {...restProps}\n >\n <motion.img\n ref={imgRef}\n src={src}\n alt={alt}\n width={width}\n height={height}\n animate={animate}\n draggable=\"false\"\n whileHover={{ scale: hoverScaleValue }}\n style={{\n scale: scale ?? 1,\n pointerEvents: isOpaque ? \"auto\" : \"none\",\n }}\n onPointerDown={handlePointerDown}\n onPointerUp={handlePointerUp}\n />\n </Draggable>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hunterchen/canvas",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "A React-based canvas library for creating pannable, zoomable, and interactive canvas experiences.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -141,6 +141,8 @@ export interface DraggableImageProps extends DraggableProps {
141
141
  }
142
142
 
143
143
  function drawImageToCanvas(img: HTMLImageElement, canvas: HTMLCanvasElement) {
144
+ canvas.width = img.naturalWidth;
145
+ canvas.height = img.naturalHeight;
144
146
  const ctx = canvas.getContext("2d", { willReadFrequently: true });
145
147
  if (!ctx) return;
146
148
  ctx.clearRect(0, 0, canvas.width, canvas.height);
@@ -160,8 +162,8 @@ function getAlphaAtCoords(
160
162
 
161
163
  const rect = img.getBoundingClientRect();
162
164
 
163
- const x = ((clientX - rect.left) / rect.width) * img.naturalWidth;
164
- const y = ((clientY - rect.top) / rect.height) * img.naturalHeight;
165
+ const x = ((clientX - rect.left) / rect.width) * canvas.width;
166
+ const y = ((clientY - rect.top) / rect.height) * canvas.height;
165
167
 
166
168
  const alpha = ctx.getImageData(x, y, 1, 1).data[3] ?? 0;
167
169
  return alpha;