1 | 'use client';
|
2 |
|
3 | import _extends from "@babel/runtime/helpers/esm/extends";
|
4 | import _formatMuiErrorMessage from "@mui/utils/formatMuiErrorMessage";
|
5 | import * as React from 'react';
|
6 | import { unstable_useForkRef as useForkRef, unstable_useId as useId } from '@mui/utils';
|
7 | import { extractEventHandlers } from '../utils/extractEventHandlers';
|
8 | import { useControllableReducer } from '../utils/useControllableReducer';
|
9 | import { useFormControlContext } from '../FormControl';
|
10 | import { NumberInputActionTypes } from './numberInputAction.types';
|
11 | import { numberInputReducer } from './numberInputReducer';
|
12 | import { isNumber } from './utils';
|
13 | const STEP_KEYS = ['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown'];
|
14 | const SUPPORTED_KEYS = [...STEP_KEYS, 'Home', 'End'];
|
15 | export function getInputValueAsString(v) {
|
16 | return v ? String(v.trim()) : String(v);
|
17 | }
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 | export function useNumberInput(parameters) {
|
30 | var _ref;
|
31 | const {
|
32 | min,
|
33 | max,
|
34 | step,
|
35 | shiftMultiplier = 10,
|
36 | defaultValue: defaultValueProp,
|
37 | disabled: disabledProp = false,
|
38 | error: errorProp = false,
|
39 | onBlur,
|
40 | onInputChange,
|
41 | onFocus,
|
42 | onChange,
|
43 | required: requiredProp = false,
|
44 | readOnly: readOnlyProp = false,
|
45 | value: valueProp,
|
46 | inputRef: inputRefProp,
|
47 | inputId: inputIdProp,
|
48 | componentName = 'useNumberInput'
|
49 | } = parameters;
|
50 |
|
51 |
|
52 | const formControlContext = useFormControlContext();
|
53 | const {
|
54 | current: isControlled
|
55 | } = React.useRef(valueProp != null);
|
56 | const handleInputRefWarning = React.useCallback(instance => {
|
57 | if (process.env.NODE_ENV !== 'production') {
|
58 | if (instance && instance.nodeName !== 'INPUT' && !instance.focus) {
|
59 | console.error(['MUI: You have provided a `slots.input` to the input component', 'that does not correctly handle the `ref` prop.', 'Make sure the `ref` prop is called with a HTMLInputElement.'].join('\n'));
|
60 | }
|
61 | }
|
62 | }, []);
|
63 | const inputRef = React.useRef(null);
|
64 | const handleInputRef = useForkRef(inputRef, inputRefProp, handleInputRefWarning);
|
65 | const inputId = useId(inputIdProp);
|
66 | const [focused, setFocused] = React.useState(false);
|
67 | const handleStateChange = React.useCallback((event, field, fieldValue, reason) => {
|
68 | if (field === 'value' && typeof fieldValue !== 'string') {
|
69 | switch (reason) {
|
70 |
|
71 | case 'numberInput:clamp':
|
72 | onChange == null || onChange(event, fieldValue);
|
73 | break;
|
74 | case 'numberInput:increment':
|
75 | case 'numberInput:decrement':
|
76 | case 'numberInput:incrementToMax':
|
77 | case 'numberInput:decrementToMin':
|
78 | onChange == null || onChange(event, fieldValue);
|
79 | break;
|
80 | default:
|
81 | break;
|
82 | }
|
83 | }
|
84 | }, [onChange]);
|
85 | const numberInputActionContext = React.useMemo(() => {
|
86 | return {
|
87 | min,
|
88 | max,
|
89 | step,
|
90 | shiftMultiplier,
|
91 | getInputValueAsString
|
92 | };
|
93 | }, [min, max, step, shiftMultiplier]);
|
94 | const initialValue = (_ref = valueProp != null ? valueProp : defaultValueProp) != null ? _ref : null;
|
95 | const initialState = {
|
96 | value: initialValue,
|
97 | inputValue: initialValue ? String(initialValue) : ''
|
98 | };
|
99 | const controlledState = React.useMemo(() => ({
|
100 | value: valueProp
|
101 | }), [valueProp]);
|
102 | const [state, dispatch] = useControllableReducer({
|
103 | reducer: numberInputReducer,
|
104 | controlledProps: controlledState,
|
105 | initialState,
|
106 | onStateChange: handleStateChange,
|
107 | actionContext: React.useMemo(() => numberInputActionContext, [numberInputActionContext]),
|
108 | componentName
|
109 | });
|
110 | const {
|
111 | value,
|
112 | inputValue
|
113 | } = state;
|
114 | React.useEffect(() => {
|
115 | if (!formControlContext && disabledProp && focused) {
|
116 | setFocused(false);
|
117 | onBlur == null || onBlur();
|
118 | }
|
119 | }, [formControlContext, disabledProp, focused, onBlur]);
|
120 | React.useEffect(() => {
|
121 | if (isControlled && isNumber(value)) {
|
122 | dispatch({
|
123 | type: NumberInputActionTypes.resetInputValue
|
124 | });
|
125 | }
|
126 | }, [value, dispatch, isControlled]);
|
127 | const createHandleFocus = otherHandlers => event => {
|
128 | var _otherHandlers$onFocu;
|
129 | (_otherHandlers$onFocu = otherHandlers.onFocus) == null || _otherHandlers$onFocu.call(otherHandlers, event);
|
130 | if (event.defaultMuiPrevented || event.defaultPrevented) {
|
131 | return;
|
132 | }
|
133 | if (formControlContext && formControlContext.onFocus) {
|
134 | var _formControlContext$o;
|
135 | formControlContext == null || (_formControlContext$o = formControlContext.onFocus) == null || _formControlContext$o.call(formControlContext);
|
136 | }
|
137 | setFocused(true);
|
138 | };
|
139 | const createHandleInputChange = otherHandlers => event => {
|
140 | var _formControlContext$o2, _otherHandlers$onInpu;
|
141 | if (!isControlled && event.target === null) {
|
142 | throw new Error(process.env.NODE_ENV !== "production" ? `MUI: Expected valid input target. Did you use a custom \`slots.input\` and forget to forward refs? See https://mui.com/r/input-component-ref-interface for more info.` : _formatMuiErrorMessage(17));
|
143 | }
|
144 | formControlContext == null || (_formControlContext$o2 = formControlContext.onChange) == null || _formControlContext$o2.call(formControlContext, event);
|
145 | (_otherHandlers$onInpu = otherHandlers.onInputChange) == null || _otherHandlers$onInpu.call(otherHandlers, event);
|
146 | if (event.defaultMuiPrevented || event.defaultPrevented) {
|
147 | return;
|
148 | }
|
149 | dispatch({
|
150 | type: NumberInputActionTypes.inputChange,
|
151 | event,
|
152 | inputValue: event.currentTarget.value
|
153 | });
|
154 | };
|
155 | const createHandleBlur = otherHandlers => event => {
|
156 | var _otherHandlers$onBlur;
|
157 | formControlContext == null || formControlContext.onBlur();
|
158 | (_otherHandlers$onBlur = otherHandlers.onBlur) == null || _otherHandlers$onBlur.call(otherHandlers, event);
|
159 | if (event.defaultMuiPrevented || event.defaultPrevented) {
|
160 | return;
|
161 | }
|
162 | dispatch({
|
163 | type: NumberInputActionTypes.clamp,
|
164 | event,
|
165 | inputValue: event.currentTarget.value
|
166 | });
|
167 | setFocused(false);
|
168 | };
|
169 | const createHandleClick = otherHandlers => event => {
|
170 | var _otherHandlers$onClic;
|
171 | (_otherHandlers$onClic = otherHandlers.onClick) == null || _otherHandlers$onClic.call(otherHandlers, event);
|
172 | if (event.defaultMuiPrevented || event.defaultPrevented) {
|
173 | return;
|
174 | }
|
175 | if (inputRef.current && event.currentTarget === event.target) {
|
176 | inputRef.current.focus();
|
177 | }
|
178 | };
|
179 | const handleStep = direction => event => {
|
180 | const applyMultiplier = Boolean(event.shiftKey);
|
181 | const actionType = {
|
182 | up: NumberInputActionTypes.increment,
|
183 | down: NumberInputActionTypes.decrement
|
184 | }[direction];
|
185 | dispatch({
|
186 | type: actionType,
|
187 | event,
|
188 | applyMultiplier
|
189 | });
|
190 | };
|
191 | const createHandleKeyDown = otherHandlers => event => {
|
192 | var _otherHandlers$onKeyD;
|
193 | (_otherHandlers$onKeyD = otherHandlers.onKeyDown) == null || _otherHandlers$onKeyD.call(otherHandlers, event);
|
194 | if (event.defaultMuiPrevented || event.defaultPrevented) {
|
195 | return;
|
196 | }
|
197 |
|
198 |
|
199 | if (SUPPORTED_KEYS.includes(event.key)) {
|
200 | event.preventDefault();
|
201 | }
|
202 | switch (event.key) {
|
203 | case 'ArrowUp':
|
204 | dispatch({
|
205 | type: NumberInputActionTypes.increment,
|
206 | event,
|
207 | applyMultiplier: !!event.shiftKey
|
208 | });
|
209 | break;
|
210 | case 'ArrowDown':
|
211 | dispatch({
|
212 | type: NumberInputActionTypes.decrement,
|
213 | event,
|
214 | applyMultiplier: !!event.shiftKey
|
215 | });
|
216 | break;
|
217 | case 'PageUp':
|
218 | dispatch({
|
219 | type: NumberInputActionTypes.increment,
|
220 | event,
|
221 | applyMultiplier: true
|
222 | });
|
223 | break;
|
224 | case 'PageDown':
|
225 | dispatch({
|
226 | type: NumberInputActionTypes.decrement,
|
227 | event,
|
228 | applyMultiplier: true
|
229 | });
|
230 | break;
|
231 | case 'Home':
|
232 | dispatch({
|
233 | type: NumberInputActionTypes.incrementToMax,
|
234 | event
|
235 | });
|
236 | break;
|
237 | case 'End':
|
238 | dispatch({
|
239 | type: NumberInputActionTypes.decrementToMin,
|
240 | event
|
241 | });
|
242 | break;
|
243 | default:
|
244 | break;
|
245 | }
|
246 | };
|
247 | const getRootProps = (externalProps = {}) => {
|
248 | const propsEventHandlers = extractEventHandlers(parameters, [
|
249 |
|
250 | 'onBlur', 'onInputChange', 'onFocus', 'onChange']);
|
251 | const externalEventHandlers = _extends({}, propsEventHandlers, extractEventHandlers(externalProps));
|
252 | return _extends({}, externalProps, externalEventHandlers, {
|
253 | onClick: createHandleClick(externalEventHandlers)
|
254 | });
|
255 | };
|
256 | const getInputProps = (externalProps = {}) => {
|
257 | var _ref2;
|
258 | const propsEventHandlers = {
|
259 | onBlur,
|
260 | onFocus,
|
261 |
|
262 | onChange: onInputChange
|
263 | };
|
264 | const externalEventHandlers = _extends({}, propsEventHandlers, extractEventHandlers(externalProps, [
|
265 |
|
266 | 'onClick'
|
267 |
|
268 | ]));
|
269 | const mergedEventHandlers = _extends({}, externalEventHandlers, {
|
270 | onFocus: createHandleFocus(externalEventHandlers),
|
271 |
|
272 | onChange: createHandleInputChange(_extends({}, externalEventHandlers, {
|
273 | onInputChange: externalEventHandlers.onChange
|
274 | })),
|
275 | onBlur: createHandleBlur(externalEventHandlers),
|
276 | onKeyDown: createHandleKeyDown(externalEventHandlers)
|
277 | });
|
278 | const displayValue = (_ref2 = focused ? inputValue : value) != null ? _ref2 : '';
|
279 |
|
280 |
|
281 |
|
282 | delete externalProps.onInputChange;
|
283 | return _extends({
|
284 | type: 'text',
|
285 | id: inputId,
|
286 | 'aria-invalid': errorProp || undefined,
|
287 | defaultValue: undefined,
|
288 | value: displayValue,
|
289 | 'aria-valuenow': displayValue,
|
290 | 'aria-valuetext': String(displayValue),
|
291 | 'aria-valuemin': min,
|
292 | 'aria-valuemax': max,
|
293 | autoComplete: 'off',
|
294 | autoCorrect: 'off',
|
295 | spellCheck: 'false',
|
296 | required: requiredProp,
|
297 | readOnly: readOnlyProp,
|
298 | 'aria-disabled': disabledProp,
|
299 | disabled: disabledProp
|
300 | }, externalProps, {
|
301 | ref: handleInputRef
|
302 | }, mergedEventHandlers);
|
303 | };
|
304 | const handleStepperButtonMouseDown = event => {
|
305 | event.preventDefault();
|
306 | if (inputRef.current) {
|
307 | inputRef.current.focus();
|
308 | }
|
309 | };
|
310 | const stepperButtonCommonProps = {
|
311 | 'aria-controls': inputId,
|
312 | tabIndex: -1
|
313 | };
|
314 | const isIncrementDisabled = disabledProp || (isNumber(value) ? value >= (max != null ? max : Number.MAX_SAFE_INTEGER) : false);
|
315 | const getIncrementButtonProps = (externalProps = {}) => {
|
316 | return _extends({}, externalProps, stepperButtonCommonProps, {
|
317 | disabled: isIncrementDisabled,
|
318 | 'aria-disabled': isIncrementDisabled,
|
319 | onMouseDown: handleStepperButtonMouseDown,
|
320 | onClick: handleStep('up')
|
321 | });
|
322 | };
|
323 | const isDecrementDisabled = disabledProp || (isNumber(value) ? value <= (min != null ? min : Number.MIN_SAFE_INTEGER) : false);
|
324 | const getDecrementButtonProps = (externalProps = {}) => {
|
325 | return _extends({}, externalProps, stepperButtonCommonProps, {
|
326 | disabled: isDecrementDisabled,
|
327 | 'aria-disabled': isDecrementDisabled,
|
328 | onMouseDown: handleStepperButtonMouseDown,
|
329 | onClick: handleStep('down')
|
330 | });
|
331 | };
|
332 | return {
|
333 | disabled: disabledProp,
|
334 | error: errorProp,
|
335 | focused,
|
336 | formControlContext,
|
337 | getInputProps,
|
338 | getIncrementButtonProps,
|
339 | getDecrementButtonProps,
|
340 | getRootProps,
|
341 | required: requiredProp,
|
342 | value,
|
343 | inputValue,
|
344 | isIncrementDisabled,
|
345 | isDecrementDisabled
|
346 | };
|
347 | } |
\ | No newline at end of file |