Skip to content

Commit e12edc2

Browse files
Focus text field when focusing spinner
1 parent 1947b46 commit e12edc2

File tree

6 files changed

+64
-11
lines changed

6 files changed

+64
-11
lines changed

.changeset/warm-points-punch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@shopify/polaris': patch
3+
---
4+
5+
Focus on TextField on Spinner click

polaris-react/src/components/TextField/TextField.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ $spinner-icon-size: 12px;
7070

7171
.focus > .Input,
7272
.focus > .VerticalContent,
73+
.TextField:focus-within > .Input,
7374
.Input:focus-visible {
7475
outline: none;
7576

polaris-react/src/components/TextField/TextField.stories.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Button,
55
LegacyCard,
66
ChoiceList,
7+
Form,
78
FormLayout,
89
InlineError,
910
Select,
@@ -829,3 +830,27 @@ export function All() {
829830
</FormLayout>
830831
);
831832
}
833+
834+
export function WithFormSubmit() {
835+
const [textFieldValue, setTextFieldValue] = useState('0');
836+
837+
return (
838+
<Form
839+
onSubmit={(event) => {
840+
event.preventDefault();
841+
setTextFieldValue('0');
842+
}}
843+
>
844+
<FormLayout>
845+
<TextField
846+
label="Store name"
847+
value={textFieldValue}
848+
onChange={(value) => setTextFieldValue(value)}
849+
autoComplete="off"
850+
type="number"
851+
/>
852+
<Button submit>Submit</Button>
853+
</FormLayout>
854+
</Form>
855+
);
856+
}

polaris-react/src/components/TextField/TextField.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -401,11 +401,11 @@ export function TextField({
401401
],
402402
);
403403

404-
const handleButtonRelease = useCallback(() => {
404+
const handleSpinnerButtonRelease = useCallback(() => {
405405
clearTimeout(buttonPressTimer.current);
406406
}, []);
407407

408-
const handleButtonPress: SpinnerProps['onMouseDown'] = useCallback(
408+
const handleSpinnerButtonPress: SpinnerProps['onMouseDown'] = useCallback(
409409
(onChange) => {
410410
const minInterval = 50;
411411
const decrementBy = 10;
@@ -422,20 +422,20 @@ export function TextField({
422422

423423
buttonPressTimer.current = window.setTimeout(onChangeInterval, interval);
424424

425-
document.addEventListener('mouseup', handleButtonRelease, {
425+
document.addEventListener('mouseup', handleSpinnerButtonRelease, {
426426
once: true,
427427
});
428428
},
429-
[handleButtonRelease],
429+
[handleSpinnerButtonRelease],
430430
);
431431

432432
const spinnerMarkup =
433433
isNumericType && step !== 0 && !disabled && !readOnly ? (
434434
<Spinner
435435
onClick={handleClickChild}
436436
onChange={handleNumberChange}
437-
onMouseDown={handleButtonPress}
438-
onMouseUp={handleButtonRelease}
437+
onMouseDown={handleSpinnerButtonPress}
438+
onMouseUp={handleSpinnerButtonRelease}
439439
ref={spinnerRef}
440440
onBlur={handleOnBlur}
441441
/>
@@ -654,6 +654,7 @@ export function TextField({
654654
}
655655

656656
setFocus(true);
657+
inputRef.current?.focus();
657658
}
658659

659660
function handleClearButtonPress() {

polaris-react/src/components/TextField/components/Spinner/Spinner.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const Spinner = React.forwardRef<HTMLDivElement, SpinnerProps>(
3131
function handleMouseDown(onChange: HandleStepFn) {
3232
return (event: React.MouseEvent) => {
3333
if (event.button !== 0) return;
34-
onMouseDown(onChange);
34+
onMouseDown?.(onChange);
3535
};
3636
}
3737

polaris-react/src/components/TextField/tests/TextField.test.tsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,24 @@ describe('<TextField />', () => {
120120
expect(onClick).toHaveBeenCalled();
121121
});
122122

123+
it('focuses the text field when the spinner is clicked', () => {
124+
const event = new MouseEvent('click', {
125+
view: window,
126+
bubbles: true,
127+
cancelable: true,
128+
});
129+
const textField = mountWithApp(
130+
<TextField type="number" label="TextField" autoComplete="off" />,
131+
);
132+
133+
textField
134+
.find(Spinner)!
135+
.findAll('div', {role: 'button'})[0]!
136+
.domNode?.dispatchEvent(event);
137+
138+
expect(document.activeElement).toBe(textField.find('input')!.domNode);
139+
});
140+
123141
it('does not bubble up to the parent element when it occurs in an element other than the input', () => {
124142
const onClick = jest.fn();
125143
const children = 'vertical-content-children';
@@ -221,7 +239,7 @@ describe('<TextField />', () => {
221239
);
222240

223241
expect(textField).toContainReactComponent('input', {
224-
id: ':ra:',
242+
id: expect.any(String),
225243
});
226244
});
227245

@@ -330,11 +348,12 @@ describe('<TextField />', () => {
330348
helpText="Some help"
331349
onChange={noop}
332350
autoComplete="off"
351+
id="textField"
333352
/>,
334353
);
335354

336355
expect(textField).toContainReactComponent('input', {
337-
'aria-describedby': ':ri:HelpText',
356+
'aria-describedby': 'textFieldHelpText',
338357
});
339358
expect(textField.find('div')).toContainReactText('Some help');
340359
});
@@ -369,11 +388,12 @@ describe('<TextField />', () => {
369388
error="Some error"
370389
onChange={noop}
371390
autoComplete="off"
391+
id="textField"
372392
/>,
373393
);
374394

375395
expect(textField).toContainReactComponent('input', {
376-
'aria-describedby': ':rk:Error',
396+
'aria-describedby': 'textFieldError',
377397
});
378398
});
379399

@@ -407,11 +427,12 @@ describe('<TextField />', () => {
407427
helpText="Some help"
408428
onChange={noop}
409429
autoComplete="off"
430+
id="textField"
410431
/>,
411432
);
412433

413434
expect(textField).toContainReactComponent('input', {
414-
'aria-describedby': ':rm:Error :rm:HelpText',
435+
'aria-describedby': 'textFieldError textFieldHelpText',
415436
});
416437

417438
expect(textField.find('div')).toContainReactText('Some error');

0 commit comments

Comments
 (0)