Skip to content

Commit ad0fb49

Browse files
MeowIng44snowystingerLFDanLu
authored
Fix useNumberField to propagate name/placeholder props (#2884)
* Fixed bug where useNumberField was swallowing the name and placeholder props, added support to pass/use select additional props from TextInputDOMProps as well * Added jest tests for added functionality in useNumberField * Added back newline/spacing in call to useFormattedTextField * Addressing PR comments - moved input DOM events into a separate interface, removed support for name prop to useNumberField, updated Storybook/tests * Addressing PR comments - updated useNumberField.test to include test and code change recommended/provided in PR Co-authored-by: Robert Snow <[email protected]> Co-authored-by: Daniel Lu <[email protected]>
1 parent e770834 commit ad0fb49

File tree

5 files changed

+161
-52
lines changed

5 files changed

+161
-52
lines changed

packages/@react-aria/numberfield/src/useNumberField.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ export function useNumberField(props: AriaNumberFieldProps, state: NumberFieldSt
7676
onKeyDown,
7777
onKeyUp,
7878
description,
79-
errorMessage
79+
errorMessage,
80+
...otherProps
8081
} = props;
8182

8283
let {
@@ -175,6 +176,7 @@ export function useNumberField(props: AriaNumberFieldProps, state: NumberFieldSt
175176
let domProps = filterDOMProps(props);
176177

177178
let {labelProps, inputProps: textFieldProps, descriptionProps, errorMessageProps} = useFormattedTextField({
179+
...otherProps,
178180
...domProps,
179181
label,
180182
autoFocus,
@@ -183,6 +185,7 @@ export function useNumberField(props: AriaNumberFieldProps, state: NumberFieldSt
183185
isRequired,
184186
validationState,
185187
value: state.inputValue,
188+
defaultValue: undefined, // defaultValue already used to populate state.inputValue, unneeded here
186189
autoComplete: 'off',
187190
'aria-label': props['aria-label'] || null,
188191
'aria-labelledby': props['aria-labelledby'] || null,
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import {AriaNumberFieldProps} from '@react-types/numberfield';
2+
import React from 'react';
3+
import {renderHook} from '@testing-library/react-hooks';
4+
import {useLocale} from '@react-aria/i18n';
5+
import {useNumberField} from '../';
6+
import {useNumberFieldState} from '@react-stately/numberfield';
7+
8+
describe('useNumberField hook', () => {
9+
let ref;
10+
11+
beforeEach(() => {
12+
ref = React.createRef();
13+
ref.current = document.createElement('input');
14+
});
15+
16+
let renderNumberFieldHook = (props: AriaNumberFieldProps) => {
17+
let {result: stateResult} = renderHook(() => useNumberFieldState({...props, locale: useLocale().locale}));
18+
let {result} = renderHook(() => useNumberField(props, stateResult.current, ref));
19+
return result.current;
20+
};
21+
22+
describe('should return numberFieldProps', () => {
23+
it('with default numberField props if no props are provided', () => {
24+
let consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
25+
let {inputProps} = renderNumberFieldHook({});
26+
expect(inputProps.type).toBe('text');
27+
expect(inputProps.disabled).toBeFalsy();
28+
expect(inputProps.readOnly).toBeFalsy();
29+
expect(inputProps['aria-invalid']).toBeUndefined();
30+
expect(inputProps['aria-required']).toBeNull();
31+
expect(inputProps['aria-valuenow']).toBeNull();
32+
expect(inputProps['aria-valuetext']).toBeNull();
33+
expect(inputProps['aria-valuemin']).toBeNull();
34+
expect(inputProps['aria-valuemax']).toBeNull();
35+
expect(typeof inputProps.onChange).toBe('function');
36+
expect(inputProps.autoFocus).toBeFalsy();
37+
expect(consoleWarnSpy).toHaveBeenLastCalledWith('If you do not provide a visible label, you must specify an aria-label or aria-labelledby attribute for accessibility');
38+
});
39+
40+
it('with appropriate props if placeholder is defined', () => {
41+
let {inputProps} = renderNumberFieldHook({placeholder: 'Enter value', 'aria-label': 'mandatory label'});
42+
expect(inputProps['placeholder']).toBe('Enter value');
43+
});
44+
45+
it('all events are merged into the input element', () => {
46+
let onCopy = jest.fn();
47+
let onCut = jest.fn();
48+
let onPaste = jest.fn();
49+
let onCompositionStart = jest.fn();
50+
let onCompositionEnd = jest.fn();
51+
let onCompositionUpdate = jest.fn();
52+
let onSelect = jest.fn();
53+
let onBeforeInput = jest.fn();
54+
let onInput = jest.fn();
55+
let {inputProps} = renderNumberFieldHook({
56+
'aria-label': 'mandatory label',
57+
onCopy,
58+
onCut,
59+
onPaste,
60+
onCompositionStart,
61+
onCompositionEnd,
62+
onCompositionUpdate,
63+
onSelect,
64+
onBeforeInput,
65+
onInput
66+
});
67+
inputProps.onCopy({} as any);
68+
expect(onCopy).toHaveBeenCalled();
69+
inputProps.onCut({} as any);
70+
expect(onCut).toHaveBeenCalled();
71+
inputProps.onPaste({} as any);
72+
expect(onPaste).toHaveBeenCalled();
73+
inputProps.onCompositionStart({} as any);
74+
expect(onCompositionStart).toHaveBeenCalled();
75+
inputProps.onCompositionEnd({} as any);
76+
expect(onCompositionEnd).toHaveBeenCalled();
77+
inputProps.onCompositionUpdate({} as any);
78+
expect(onCompositionUpdate).toHaveBeenCalled();
79+
inputProps.onSelect({} as any);
80+
expect(onSelect).toHaveBeenCalled();
81+
inputProps.onBeforeInput({
82+
preventDefault: jest.fn(),
83+
target: {
84+
value: '',
85+
selectionStart: 0,
86+
selectionEnd: 0,
87+
data: ''
88+
}
89+
} as any);
90+
expect(onBeforeInput).toHaveBeenCalled();
91+
inputProps.onInput({} as any);
92+
expect(onInput).toHaveBeenCalled();
93+
});
94+
});
95+
});

packages/@react-spectrum/numberfield/stories/NumberField.stories.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,13 @@ storiesOf('NumberField', module)
228228
.add(
229229
'focus events',
230230
() => render({onBlur: action('onBlur'), onFocus: action('onFocus'), onFocusChange: action('onFocusChange'), onKeyDown: action('onKeyDown'), onKeyUp: action('onKeyUp')})
231+
)
232+
.add(
233+
'input dom events',
234+
() => render({
235+
onCopy: action('onCopy'), onCut: action('onCut'), onPaste: action('onPaste'), onCompositionStart: action('onCompositionStart'), onCompositionEnd: action('onCompositionEnd'),
236+
onCompositionUpdate: action('onCompositionUpdate'), onSelect: action('onSelect'), onBeforeInput: action('onBeforeInput'), onInput: action('onInput')
237+
})
231238
);
232239

233240
function render(props: any = {}) {

packages/@react-types/numberfield/src/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
RangeInputBase, SpectrumLabelableProps,
2020
StyleProps,
2121
TextInputBase,
22+
TextInputDOMEvents,
2223
Validation,
2324
ValueBase
2425
} from '@react-types/shared';
@@ -31,7 +32,7 @@ export interface NumberFieldProps extends InputBase, Validation, FocusableProps,
3132
formatOptions?: Intl.NumberFormatOptions
3233
}
3334

34-
export interface AriaNumberFieldProps extends NumberFieldProps, DOMProps, AriaLabelingProps {
35+
export interface AriaNumberFieldProps extends NumberFieldProps, DOMProps, AriaLabelingProps, TextInputDOMEvents {
3536
/** A custom aria-label for the decrement button. If not provided, the localized string "Decrement" is used. */
3637
decrementAriaLabel?: string,
3738
/** A custom aria-label for the increment button. If not provided, the localized string "Increment" is used. */

packages/@react-types/shared/src/dom.d.ts

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,60 @@ export interface FocusableDOMProps extends DOMProps {
6666
excludeFromTabOrder?: boolean
6767
}
6868

69+
70+
export interface TextInputDOMEvents {
71+
// Clipboard events
72+
/**
73+
* Handler that is called when the user copies text. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/oncopy).
74+
*/
75+
onCopy?: ClipboardEventHandler<HTMLInputElement>,
76+
77+
/**
78+
* Handler that is called when the user cuts text. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/oncut).
79+
*/
80+
onCut?: ClipboardEventHandler<HTMLInputElement>,
81+
82+
/**
83+
* Handler that is called when the user pastes text. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/onpaste).
84+
*/
85+
onPaste?: ClipboardEventHandler<HTMLInputElement>,
86+
87+
// Composition events
88+
/**
89+
* Handler that is called when a text composition system starts a new text composition session. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionstart_event).
90+
*/
91+
onCompositionStart?: CompositionEventHandler<HTMLInputElement>,
92+
93+
/**
94+
* Handler that is called when a text composition system completes or cancels the current text composition session. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionend_event).
95+
*/
96+
onCompositionEnd?: CompositionEventHandler<HTMLInputElement>,
97+
98+
/**
99+
* Handler that is called when a new character is received in the current text composition session. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionupdate_event).
100+
*/
101+
onCompositionUpdate?: CompositionEventHandler<HTMLInputElement>,
102+
103+
// Selection events
104+
/**
105+
* Handler that is called when text in the input is selected. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/select_event).
106+
*/
107+
onSelect?: ReactEventHandler<HTMLInputElement>,
108+
109+
// Input events
110+
/**
111+
* Handler that is called when the input value is about to be modified. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/beforeinput_event).
112+
*/
113+
onBeforeInput?: FormEventHandler<HTMLInputElement>,
114+
/**
115+
* Handler that is called when the input value is modified. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event).
116+
*/
117+
onInput?: FormEventHandler<HTMLInputElement>
118+
}
119+
69120
// DOM props that apply to all text inputs
70121
// Ensure this is synced with useTextField
71-
export interface TextInputDOMProps extends DOMProps {
122+
export interface TextInputDOMProps extends DOMProps, TextInputDOMEvents {
72123
/**
73124
* Describes the type of autocomplete functionality the input should provide if any. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefautocomplete).
74125
*/
@@ -107,53 +158,5 @@ export interface TextInputDOMProps extends DOMProps {
107158
/**
108159
* Hints at the type of data that might be entered by the user while editing the element or its contents. See [MDN](https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-inputmode-attribute).
109160
*/
110-
inputMode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search',
111-
112-
// Clipboard events
113-
/**
114-
* Handler that is called when the user copies text. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/oncopy).
115-
*/
116-
onCopy?: ClipboardEventHandler<HTMLInputElement>,
117-
118-
/**
119-
* Handler that is called when the user cuts text. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/oncut).
120-
*/
121-
onCut?: ClipboardEventHandler<HTMLInputElement>,
122-
123-
/**
124-
* Handler that is called when the user pastes text. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/onpaste).
125-
*/
126-
onPaste?: ClipboardEventHandler<HTMLInputElement>,
127-
128-
// Composition events
129-
/**
130-
* Handler that is called when a text composition system starts a new text composition session. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionstart_event).
131-
*/
132-
onCompositionStart?: CompositionEventHandler<HTMLInputElement>,
133-
134-
/**
135-
* Handler that is called when a text composition system completes or cancels the current text composition session. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionend_event).
136-
*/
137-
onCompositionEnd?: CompositionEventHandler<HTMLInputElement>,
138-
139-
/**
140-
* Handler that is called when a new character is received in the current text composition session. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionupdate_event).
141-
*/
142-
onCompositionUpdate?: CompositionEventHandler<HTMLInputElement>,
143-
144-
// Selection events
145-
/**
146-
* Handler that is called when text in the input is selected. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/select_event).
147-
*/
148-
onSelect?: ReactEventHandler<HTMLInputElement>,
149-
150-
// Input events
151-
/**
152-
* Handler that is called when the input value is about to be modified. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/beforeinput_event).
153-
*/
154-
onBeforeInput?: FormEventHandler<HTMLInputElement>,
155-
/**
156-
* Handler that is called when the input value is modified. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event).
157-
*/
158-
onInput?: FormEventHandler<HTMLInputElement>
161+
inputMode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'
159162
}

0 commit comments

Comments
 (0)