Skip to content

Commit 122d569

Browse files
committed
feat: Enhance Saturation component with focus behavior and ref management. #198
1 parent 24fefea commit 122d569

3 files changed

Lines changed: 78 additions & 14 deletions

File tree

packages/color-saturation/src/index.tsx

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useMemo } from 'react';
1+
import React, { useCallback, useMemo, useRef } from 'react';
22
import type * as CSS from 'csstype';
33
import { HsvaColor, hsvaToHslaString } from '@uiw/color-convert';
44
import Interactive, { type Interaction } from '@uiw/react-drag-event-interactive';
@@ -25,17 +25,40 @@ const Saturation = React.forwardRef<HTMLDivElement, SaturationProps>((props, ref
2525
position: 'relative',
2626
};
2727

28-
const handleChange = (interaction: Interaction, event: MouseEvent | TouchEvent) => {
29-
onChange &&
30-
hsva &&
31-
onChange({
32-
h: hsva.h,
33-
s: interaction.left * 100,
34-
v: (1 - interaction.top) * 100,
35-
a: hsva.a,
36-
// source: 'hsv',
37-
});
38-
};
28+
const containerRef = useRef<HTMLDivElement | null>(null);
29+
30+
// Combine external ref with internal ref
31+
const combinedRef = useCallback(
32+
(node: HTMLDivElement | null) => {
33+
containerRef.current = node;
34+
if (typeof ref === 'function') {
35+
ref(node);
36+
} else if (ref && 'current' in ref) {
37+
(ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
38+
}
39+
},
40+
[ref],
41+
);
42+
43+
const handleChange = useCallback(
44+
(interaction: Interaction, event: MouseEvent | TouchEvent) => {
45+
onChange &&
46+
hsva &&
47+
onChange({
48+
h: hsva.h,
49+
s: interaction.left * 100,
50+
v: (1 - interaction.top) * 100,
51+
a: hsva.a,
52+
// source: 'hsv',
53+
});
54+
// Ensure the component maintains focus after drag interaction
55+
const element = containerRef.current;
56+
if (element) {
57+
element.focus();
58+
}
59+
},
60+
[hsva, onChange],
61+
);
3962

4063
const handleKeyDown = useCallback(
4164
(event: React.KeyboardEvent<HTMLDivElement>) => {
@@ -112,7 +135,7 @@ const Saturation = React.forwardRef<HTMLDivElement, SaturationProps>((props, ref
112135
...containerStyle,
113136
outline: 'none',
114137
}}
115-
ref={ref}
138+
ref={combinedRef}
116139
onMove={handleChange}
117140
onDown={handleChange}
118141
onKeyDown={handleKeyDown}

test/convert.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ it('Converts color => HEX to ColorResult', () => {
5454
expect(hsla).toEqual({ h: 353.04347826086956, l: 41.37254901960784, s: 98.10426540284361, a: 1 });
5555
expect(hsv).toEqual({ h: 353.04347826086956, s: 99.04306220095694, v: 81.96078431372548 });
5656
expect(hsva).toEqual({ h: 353.04347826086956, s: 99.04306220095694, v: 81.96078431372548, a: 1 });
57-
expect(xy).toEqual({ x: 0.26502656639062083, y: 0.13673307363113865, bri: 0.022196477290623275 });
57+
expect(xy).toEqual({ x: 0.26502656639062083, y: 0.13673307363113865, bri: 0.022196477290623278 });
5858
});
5959

6060
it('Converts color => HEXA to ColorResult', () => {

test/saturation.test.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,44 @@ it('Saturation onChange', async () => {
4646
fireEvent.mouseDown(elm, { clientX: 1 });
4747
expect(handleChange).toHaveReturnedWith(['h', 's', 'v', 'a']);
4848
});
49+
50+
it('Saturation focus behavior after interaction', () => {
51+
const handleChange = jest.fn((color) => color);
52+
53+
const MyComponent = () => {
54+
const [hsva, setHsva] = useState({ h: 200, s: 50, v: 80, a: 1 });
55+
return (
56+
<Saturation
57+
title="focus-test-element"
58+
hsva={hsva}
59+
onChange={(newColor) => {
60+
setHsva({ ...hsva, ...newColor, a: hsva.a });
61+
handleChange(newColor);
62+
}}
63+
/>
64+
);
65+
};
66+
67+
render(<MyComponent />);
68+
const elm = screen.getByTitle('focus-test-element');
69+
70+
// Mock getBoundingClientRect for proper drag simulation
71+
elm.getBoundingClientRect = jest.fn(() => ({
72+
left: 0,
73+
top: 0,
74+
width: 200,
75+
height: 200,
76+
right: 200,
77+
bottom: 200,
78+
})) as any;
79+
80+
// Simulate mouse down which should trigger handleChange and focus
81+
fireEvent.mouseDown(elm, { clientX: 100, clientY: 100, buttons: 1 });
82+
83+
// Verify that change was triggered
84+
expect(handleChange).toHaveBeenCalled();
85+
86+
// Verify that the element maintains its ability to be focused
87+
elm.focus();
88+
expect(elm).toHaveAttribute('tabindex', '0');
89+
});

0 commit comments

Comments
 (0)