Skip to content

Commit 1cdfdc4

Browse files
author
Michael Jordan
authored
ColorWheel: sync input ChangeEvent and value with state (#1532)
So that a screen reader user can change the value with focus on the ColorWheel input we need to keep the input value in sync with the ColorWheel value and handle change events from the input.
1 parent b24faf6 commit 1cdfdc4

File tree

3 files changed

+29
-5
lines changed

3 files changed

+29
-5
lines changed

packages/@react-aria/color/src/useColorWheel.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import {ColorWheelProps} from '@react-types/color';
1414
import {ColorWheelState} from '@react-stately/color';
1515
import {focusWithoutScrolling, mergeProps, useGlobalListeners} from '@react-aria/utils';
16-
import React, {HTMLAttributes, InputHTMLAttributes, RefObject, useCallback, useRef} from 'react';
16+
import React, {ChangeEvent, HTMLAttributes, InputHTMLAttributes, RefObject, useCallback, useRef} from 'react';
1717
import {useKeyboard, useMove} from '@react-aria/interactions';
1818

1919
interface ColorWheelAriaProps extends ColorWheelProps {
@@ -209,7 +209,11 @@ export function useColorWheel(props: ColorWheelAriaProps, state: ColorWheelState
209209
min: '0',
210210
max: '360',
211211
step: String(step),
212-
disabled: isDisabled
212+
disabled: isDisabled,
213+
value: `${state.value.getChannelValue('hue')}`,
214+
onChange: (e: ChangeEvent<HTMLInputElement>) => {
215+
state.setHue(parseFloat(e.target.value));
216+
}
213217
},
214218
thumbPosition: angleToCartesian(state.value.getChannelValue('hue'), thumbRadius)
215219
};

packages/@react-aria/color/test/useColorWheel.test.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ describe('useColorWheel', () => {
8383
expect(slider).toHaveAttribute('min', '0');
8484
expect(slider).toHaveAttribute('max', '360');
8585
expect(slider).toHaveAttribute('step', '1');
86+
expect(slider).toHaveAttribute('value', '0');
8687
});
8788

8889
it('the slider is focusable', () => {
@@ -132,9 +133,11 @@ describe('useColorWheel', () => {
132133
fireEvent.keyDown(slider, {key: 'Right'});
133134
expect(onChangeSpy).toHaveBeenCalledTimes(1);
134135
expect(onChangeSpy.mock.calls[0][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 1).toString('hsla'));
136+
expect(slider).toHaveAttribute('value', '1');
135137
fireEvent.keyDown(slider, {key: 'Left'});
136138
expect(onChangeSpy).toHaveBeenCalledTimes(2);
137139
expect(onChangeSpy.mock.calls[1][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 0).toString('hsla'));
140+
expect(slider).toHaveAttribute('value', '0');
138141
});
139142

140143
it('up/down works', () => {
@@ -146,9 +149,11 @@ describe('useColorWheel', () => {
146149
fireEvent.keyDown(slider, {key: 'Up'});
147150
expect(onChangeSpy).toHaveBeenCalledTimes(1);
148151
expect(onChangeSpy.mock.calls[0][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 1).toString('hsla'));
152+
expect(slider).toHaveAttribute('value', '1');
149153
fireEvent.keyDown(slider, {key: 'Down'});
150154
expect(onChangeSpy).toHaveBeenCalledTimes(2);
151155
expect(onChangeSpy.mock.calls[1][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 0).toString('hsla'));
156+
expect(slider).toHaveAttribute('value', '0');
152157
});
153158

154159
it('doesn\'t work when disabled', () => {
@@ -172,6 +177,7 @@ describe('useColorWheel', () => {
172177
fireEvent.keyDown(slider, {key: 'Left'});
173178
expect(onChangeSpy).toHaveBeenCalledTimes(1);
174179
expect(onChangeSpy.mock.calls[0][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 359).toString('hsla'));
180+
expect(slider).toHaveAttribute('value', '359');
175181
});
176182

177183
it('respects step', () => {
@@ -183,9 +189,11 @@ describe('useColorWheel', () => {
183189
fireEvent.keyDown(slider, {key: 'Right'});
184190
expect(onChangeSpy).toHaveBeenCalledTimes(1);
185191
expect(onChangeSpy.mock.calls[0][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 45).toString('hsla'));
192+
expect(slider).toHaveAttribute('value', '45');
186193
fireEvent.keyDown(slider, {key: 'Left'});
187194
expect(onChangeSpy).toHaveBeenCalledTimes(2);
188195
expect(onChangeSpy.mock.calls[1][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 0).toString('hsla'));
196+
expect(slider).toHaveAttribute('value', '0');
189197
});
190198

191199
it('can always get back to 0 even with step', () => {
@@ -197,9 +205,11 @@ describe('useColorWheel', () => {
197205
fireEvent.keyDown(slider, {key: 'Right'});
198206
expect(onChangeSpy).toHaveBeenCalledTimes(1);
199207
expect(onChangeSpy.mock.calls[0][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 0).toString('hsla'));
208+
expect(slider).toHaveAttribute('value', '0');
200209
fireEvent.keyDown(slider, {key: 'Left'});
201210
expect(onChangeSpy).toHaveBeenCalledTimes(2);
202211
expect(onChangeSpy.mock.calls[1][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 330).toString('hsla'));
212+
expect(slider).toHaveAttribute('value', '330');
203213
});
204214

205215
it('steps with page up/down', () => {
@@ -211,9 +221,11 @@ describe('useColorWheel', () => {
211221
fireEvent.keyDown(slider, {key: 'PageUp'});
212222
expect(onChangeSpy).toHaveBeenCalledTimes(1);
213223
expect(onChangeSpy.mock.calls[0][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 6).toString('hsla'));
224+
expect(slider).toHaveAttribute('value', '6');
214225
fireEvent.keyDown(slider, {key: 'PageDown'});
215226
expect(onChangeSpy).toHaveBeenCalledTimes(2);
216227
expect(onChangeSpy.mock.calls[1][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 0).toString('hsla'));
228+
expect(slider).toHaveAttribute('value', '0');
217229
});
218230
});
219231

@@ -254,6 +266,7 @@ describe('useColorWheel', () => {
254266
expect(onChangeSpy).toHaveBeenCalledTimes(1);
255267
expect(onChangeSpy.mock.calls[0][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 90).toString('hsla'));
256268
expect(document.activeElement).toBe(slider);
269+
expect(slider).toHaveAttribute('value', '90');
257270

258271
end(thumb, {pageX: CENTER, pageY: CENTER + THUMB_RADIUS});
259272
expect(onChangeSpy).toHaveBeenCalledTimes(1);
@@ -284,8 +297,9 @@ describe('useColorWheel', () => {
284297

285298
it('dragging the thumb respects the step', () => {
286299
let defaultColor = parseColor('hsl(0, 100%, 50%)');
287-
let {getByTestId} = render(<ColorWheel defaultValue={defaultColor} onChange={onChangeSpy} step={120} />);
300+
let {getByRole, getByTestId} = render(<ColorWheel defaultValue={defaultColor} onChange={onChangeSpy} step={120} />);
288301
let thumb = getByTestId('thumb');
302+
let slider = getByRole('slider');
289303
let container = getByTestId('container');
290304
container.getBoundingClientRect = getBoundingClientRect;
291305

@@ -294,6 +308,7 @@ describe('useColorWheel', () => {
294308
move(thumb, {pageX: CENTER, pageY: CENTER + THUMB_RADIUS});
295309
expect(onChangeSpy).toHaveBeenCalledTimes(1);
296310
expect(onChangeSpy.mock.calls[0][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 120).toString('hsla'));
311+
expect(slider).toHaveAttribute('value', '120');
297312
end(thumb, {pageX: CENTER, pageY: CENTER + THUMB_RADIUS});
298313
expect(onChangeSpy).toHaveBeenCalledTimes(1);
299314
});
@@ -310,11 +325,13 @@ describe('useColorWheel', () => {
310325
start(container, {pageX: CENTER, pageY: CENTER + THUMB_RADIUS});
311326
expect(onChangeSpy).toHaveBeenCalledTimes(1);
312327
expect(onChangeSpy.mock.calls[0][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 90).toString('hsla'));
328+
expect(slider).toHaveAttribute('value', '90');
313329
expect(document.activeElement).toBe(slider);
314330

315331
move(thumb, {pageX: CENTER - THUMB_RADIUS, pageY: CENTER});
316332
expect(onChangeSpy).toHaveBeenCalledTimes(2);
317333
expect(onChangeSpy.mock.calls[1][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 180).toString('hsla'));
334+
expect(slider).toHaveAttribute('value', '180');
318335
expect(document.activeElement).toBe(slider);
319336

320337
end(thumb, {pageX: CENTER - THUMB_RADIUS, pageY: CENTER});
@@ -345,17 +362,20 @@ describe('useColorWheel', () => {
345362

346363
it('clicking and dragging on the track respects the step', () => {
347364
let defaultColor = parseColor('hsl(0, 100%, 50%)');
348-
let {getByTestId} = render(<ColorWheel defaultValue={defaultColor} onChange={onChangeSpy} step={120} />);
365+
let {getByRole, getByTestId} = render(<ColorWheel defaultValue={defaultColor} onChange={onChangeSpy} step={120} />);
349366
let thumb = getByTestId('thumb');
367+
let slider = getByRole('slider');
350368
let container = getByTestId('container');
351369
container.getBoundingClientRect = getBoundingClientRect;
352370

353371
start(container, {pageX: CENTER, pageY: CENTER + THUMB_RADIUS});
354372
expect(onChangeSpy).toHaveBeenCalledTimes(1);
355373
expect(onChangeSpy.mock.calls[0][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 120).toString('hsla'));
374+
expect(slider).toHaveAttribute('value', '120');
356375
move(thumb, {pageX: CENTER, pageY: CENTER - THUMB_RADIUS});
357376
expect(onChangeSpy).toHaveBeenCalledTimes(2);
358377
expect(onChangeSpy.mock.calls[1][0].toString('hsla')).toBe(defaultColor.withChannelValue('hue', 240).toString('hsla'));
378+
expect(slider).toHaveAttribute('value', '240');
359379
end(thumb, {pageX: CENTER, pageY: CENTER - THUMB_RADIUS});
360380
expect(onChangeSpy).toHaveBeenCalledTimes(2);
361381
});

packages/@react-spectrum/slider/stories/Slider.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ storiesOf('Slider', module)
9494
)
9595
.add(
9696
'step',
97-
() => render({label: 'Label', minValue: 0, maxValue: 100, step: 10})
97+
() => render({label: 'Label', minValue: 0, maxValue: 100, step: 5})
9898
)
9999
.add(
100100
'isFilled: true',

0 commit comments

Comments
 (0)