1- import { useEffect , useRef , useState } from 'react' ;
1+ import { useCallback , useEffect , useRef , useState } from 'react' ;
22import { hsvToRgb , rgbToHsv } from '../utils/colorConversion' ;
33
44interface ColorWheelProps {
@@ -12,6 +12,56 @@ export function ColorWheel({ color, onChange, size = 200 }: ColorWheelProps) {
1212 const [ isDragging , setIsDragging ] = useState ( false ) ;
1313 const hsv = rgbToHsv ( color . r , color . g , color . b ) ;
1414
15+ const handleInteraction = useCallback ( ( clientX : number , clientY : number ) => {
16+ const canvas = canvasRef . current ;
17+ if ( ! canvas ) return ;
18+
19+ const rect = canvas . getBoundingClientRect ( ) ;
20+ const x = clientX - rect . left ;
21+ const y = clientY - rect . top ;
22+
23+ const centerX = size / 2 ;
24+ const centerY = size / 2 ;
25+ const radius = size / 2 - 10 ;
26+
27+ // Calculate distance from center
28+ const dx = x - centerX ;
29+ const dy = y - centerY ;
30+ const distance = Math . sqrt ( dx * dx + dy * dy ) ;
31+
32+ // Calculate hue from angle
33+ let angle = Math . atan2 ( dy , dx ) * ( 180 / Math . PI ) + 90 ;
34+ if ( angle < 0 ) angle += 360 ;
35+
36+ // Calculate saturation from distance, clamped to radius
37+ const saturation = Math . min ( distance / radius , 1 ) ;
38+
39+ // Convert to RGB and update
40+ const newColor = hsvToRgb ( angle , saturation , 1 ) ;
41+ onChange ( newColor ) ;
42+ } , [ onChange , size ] ) ;
43+
44+ // Add document-level mouse listeners when dragging
45+ useEffect ( ( ) => {
46+ if ( ! isDragging ) return ;
47+
48+ const handleDocumentMouseMove = ( e : MouseEvent ) => {
49+ handleInteraction ( e . clientX , e . clientY ) ;
50+ } ;
51+
52+ const handleDocumentMouseUp = ( ) => {
53+ setIsDragging ( false ) ;
54+ } ;
55+
56+ document . addEventListener ( 'mousemove' , handleDocumentMouseMove ) ;
57+ document . addEventListener ( 'mouseup' , handleDocumentMouseUp ) ;
58+
59+ return ( ) => {
60+ document . removeEventListener ( 'mousemove' , handleDocumentMouseMove ) ;
61+ document . removeEventListener ( 'mouseup' , handleDocumentMouseUp ) ;
62+ } ;
63+ } , [ isDragging , handleInteraction ] ) ;
64+
1565 // Draw the color wheel and indicator
1666 useEffect ( ( ) => {
1767 const canvas = canvasRef . current ;
@@ -66,14 +116,13 @@ export function ColorWheel({ color, onChange, size = 200 }: ColorWheelProps) {
66116 ctx . stroke ( ) ;
67117 } , [ hsv , size ] ) ;
68118
69- const handleInteraction = ( e : React . MouseEvent < HTMLCanvasElement > ) => {
119+ const handleMouseDown = ( e : React . MouseEvent < HTMLCanvasElement > ) => {
70120 const canvas = canvasRef . current ;
71121 if ( ! canvas ) return ;
72122
73123 const rect = canvas . getBoundingClientRect ( ) ;
74124 const x = e . clientX - rect . left ;
75125 const y = e . clientY - rect . top ;
76-
77126 const centerX = size / 2 ;
78127 const centerY = size / 2 ;
79128 const radius = size / 2 - 10 ;
@@ -83,50 +132,20 @@ export function ColorWheel({ color, onChange, size = 200 }: ColorWheelProps) {
83132 const dy = y - centerY ;
84133 const distance = Math . sqrt ( dx * dx + dy * dy ) ;
85134
86- // Clamp to wheel radius
87- if ( distance > radius ) return ;
88-
89- // Calculate hue from angle
90- let angle = Math . atan2 ( dy , dx ) * ( 180 / Math . PI ) + 90 ;
91- if ( angle < 0 ) angle += 360 ;
92-
93- // Calculate saturation from distance
94- const saturation = Math . min ( distance / radius , 1 ) ;
95-
96- // Convert to RGB and update
97- const newColor = hsvToRgb ( angle , saturation , 1 ) ;
98- onChange ( newColor ) ;
99- } ;
100-
101- const handleMouseDown = ( e : React . MouseEvent < HTMLCanvasElement > ) => {
102- setIsDragging ( true ) ;
103- handleInteraction ( e ) ;
104- } ;
105-
106- const handleMouseMove = ( e : React . MouseEvent < HTMLCanvasElement > ) => {
107- if ( isDragging ) {
108- handleInteraction ( e ) ;
135+ // Only start dragging if click is inside the circle
136+ if ( distance <= radius ) {
137+ setIsDragging ( true ) ;
138+ handleInteraction ( e . clientX , e . clientY ) ;
109139 }
110140 } ;
111141
112- const handleMouseUp = ( ) => {
113- setIsDragging ( false ) ;
114- } ;
115-
116- const handleMouseLeave = ( ) => {
117- setIsDragging ( false ) ;
118- } ;
119-
120142 return (
121143 < canvas
122144 ref = { canvasRef }
123145 width = { size }
124146 height = { size }
125147 className = "cursor-crosshair"
126148 onMouseDown = { handleMouseDown }
127- onMouseMove = { handleMouseMove }
128- onMouseUp = { handleMouseUp }
129- onMouseLeave = { handleMouseLeave }
130149 />
131150 ) ;
132151}
0 commit comments