UNPKG

react-aria

Version:
1 lines • 24.9 kB
{"mappings":"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","sources":["packages/react-aria/src/numberfield/useNumberField.ts"],"sourcesContent":["/*\n * Copyright 2020 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {announce} from '../live-announcer/LiveAnnouncer';\n\nimport {AriaButtonProps} from '../button/useButton';\nimport {\n AriaLabelingProps,\n DOMAttributes,\n DOMProps,\n GroupDOMAttributes,\n TextInputDOMEvents,\n TextInputDOMProps,\n ValidationResult\n} from '@react-types/shared';\nimport {chain} from '../utils/chain';\nimport {\n type ClipboardEvent,\n type ClipboardEventHandler,\n InputHTMLAttributes,\n LabelHTMLAttributes,\n RefObject,\n useCallback,\n useMemo,\n useState\n} from 'react';\nimport {filterDOMProps} from '../utils/filterDOMProps';\nimport {flushSync} from 'react-dom';\nimport {getActiveElement, getEventTarget} from '../utils/shadowdom/DOMFunctions';\nimport intlMessages from '../../intl/numberfield/*.json';\nimport {isAndroid, isIOS, isIPhone} from '../utils/platform';\nimport {mergeProps} from '../utils/mergeProps';\nimport {NumberFieldProps, NumberFieldState} from 'react-stately/useNumberFieldState';\nimport {privateValidationStateProp} from 'react-stately/private/form/useFormValidationState';\n// @ts-ignore\nimport {useFocus} from '../interactions/useFocus';\nimport {useFocusWithin} from '../interactions/useFocusWithin';\nimport {useFormattedTextField} from '../textfield/useFormattedTextField';\nimport {useFormReset} from '../utils/useFormReset';\nimport {useId} from '../utils/useId';\nimport {useLayoutEffect} from '../utils/useLayoutEffect';\nimport {useLocalizedStringFormatter} from '../i18n/useLocalizedStringFormatter';\nimport {useNumberFormatter} from '../i18n/useNumberFormatter';\nimport {useScrollWheel} from '../interactions/useScrollWheel';\nimport {useSpinButton} from '../spinbutton/useSpinButton';\n\nexport interface AriaNumberFieldProps\n extends NumberFieldProps, DOMProps, AriaLabelingProps, TextInputDOMEvents {\n /**\n * A custom aria-label for the decrement button. If not provided, the localized string \"Decrement\"\n * is used.\n */\n decrementAriaLabel?: string;\n /**\n * A custom aria-label for the increment button. If not provided, the localized string \"Increment\"\n * is used.\n */\n incrementAriaLabel?: string;\n /**\n * Enables or disables changing the value with scroll.\n */\n isWheelDisabled?: boolean;\n}\n\nexport interface NumberFieldAria extends ValidationResult {\n /** Props for the label element. */\n labelProps: LabelHTMLAttributes<HTMLLabelElement>;\n /** Props for the group wrapper around the input and stepper buttons. */\n groupProps: GroupDOMAttributes;\n /** Props for the input element. */\n inputProps: InputHTMLAttributes<HTMLInputElement>;\n /** Props for the increment button, to be passed to `useButton`. */\n incrementButtonProps: AriaButtonProps;\n /** Props for the decrement button, to be passed to `useButton`. */\n decrementButtonProps: AriaButtonProps;\n /** Props for the number field's description element, if any. */\n descriptionProps: DOMAttributes;\n /** Props for the number field's error message element, if any. */\n errorMessageProps: DOMAttributes;\n}\n\n/**\n * Provides the behavior and accessibility implementation for a number field component. Number\n * fields allow users to enter a number, and increment or decrement the value using stepper\n * buttons.\n */\nexport function useNumberField(\n props: AriaNumberFieldProps,\n state: NumberFieldState,\n inputRef: RefObject<HTMLInputElement | null>\n): NumberFieldAria {\n let {\n id,\n decrementAriaLabel,\n incrementAriaLabel,\n isDisabled,\n isReadOnly,\n isRequired,\n minValue,\n maxValue,\n autoFocus,\n label,\n formatOptions,\n onBlur = () => {},\n onFocus,\n onFocusChange,\n onKeyDown,\n onKeyUp,\n description,\n errorMessage,\n isWheelDisabled,\n ...otherProps\n } = props;\n\n let {\n increment,\n incrementToMax,\n decrement,\n decrementToMin,\n numberValue,\n inputValue,\n commit,\n commitValidation\n } = state;\n\n const stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/numberfield');\n let commitAndAnnounce = useCallback(() => {\n let oldValue = inputRef.current?.value ?? '';\n // Set input value to normalized valid value\n flushSync(() => {\n commit();\n });\n\n if (inputRef.current?.value !== oldValue) {\n announce(inputRef.current?.value ?? '', 'assertive');\n }\n }, [commit, inputRef]);\n\n let inputId = useId(id);\n let {focusProps} = useFocus({\n onBlur() {\n commitAndAnnounce();\n }\n });\n\n let numberFormatter = useNumberFormatter(formatOptions);\n let intlOptions = useMemo(() => numberFormatter.resolvedOptions(), [numberFormatter]);\n\n // Replace negative textValue formatted using currencySign: 'accounting'\n // with a textValue that can be announced using a minus sign.\n let textValueFormatter = useNumberFormatter({...formatOptions, currencySign: undefined});\n let textValue = useMemo(\n () => (isNaN(numberValue) ? '' : textValueFormatter.format(numberValue)),\n [textValueFormatter, numberValue]\n );\n\n let {\n spinButtonProps,\n incrementButtonProps: incButtonProps,\n decrementButtonProps: decButtonProps\n } = useSpinButton({\n isDisabled,\n isReadOnly,\n isRequired,\n maxValue,\n minValue,\n onIncrement: increment,\n onIncrementToMax: incrementToMax,\n onDecrement: decrement,\n onDecrementToMin: decrementToMin,\n value: numberValue,\n textValue\n });\n\n let [focusWithin, setFocusWithin] = useState(false);\n let {focusWithinProps} = useFocusWithin({isDisabled, onFocusWithinChange: setFocusWithin});\n\n let onWheel = useCallback(\n e => {\n // if on a trackpad, users can scroll in both X and Y at once, check the magnitude of the change\n // if it's mostly in the X direction, then just return, the user probably doesn't mean to inc/dec\n // this isn't perfect, events come in fast with small deltas and a part of the scroll may give a false indication\n // especially if the user is scrolling near 45deg\n if (Math.abs(e.deltaY) <= Math.abs(e.deltaX)) {\n return;\n }\n if (e.deltaY > 0) {\n increment();\n } else if (e.deltaY < 0) {\n decrement();\n }\n },\n [decrement, increment]\n );\n // If the input isn't supposed to receive input, disable scrolling.\n let scrollingDisabled = isWheelDisabled || isDisabled || isReadOnly || !focusWithin;\n useScrollWheel({onScroll: onWheel, isDisabled: scrollingDisabled}, inputRef);\n\n // The inputMode attribute influences the software keyboard that is shown on touch devices.\n // Browsers and operating systems are quite inconsistent about what keys are available, however.\n // We choose between numeric and decimal based on whether we allow negative and fractional numbers,\n // and based on testing on various devices to determine what keys are available in each inputMode.\n let hasDecimals = (intlOptions.maximumFractionDigits ?? 0) > 0;\n let hasNegative = state.minValue === undefined || isNaN(state.minValue) || state.minValue < 0;\n let inputMode: TextInputDOMProps['inputMode'] = 'numeric';\n if (isIPhone()) {\n // iPhone doesn't have a minus sign in either numeric or decimal.\n // Note this is only for iPhone, not iPad, which always has both\n // minus and decimal in numeric.\n if (hasNegative) {\n inputMode = 'text';\n } else if (hasDecimals) {\n inputMode = 'decimal';\n }\n } else if (isAndroid()) {\n // Android numeric has both a decimal point and minus key.\n // decimal does not have a minus key.\n if (hasNegative) {\n inputMode = 'numeric';\n } else if (hasDecimals) {\n inputMode = 'decimal';\n }\n }\n\n let onChange = value => {\n if (state.validate(value)) {\n state.setInputValue(value);\n }\n };\n\n let onPaste: ClipboardEventHandler<HTMLInputElement> = (e: ClipboardEvent<HTMLInputElement>) => {\n props.onPaste?.(e);\n let inputElement = getEventTarget(e) as HTMLInputElement;\n // we can only handle the case where the paste takes over the entire input, otherwise things get very complicated\n // trying to calculate the new string based on what the paste is replacing and where in the source string it is\n if (\n inputElement &&\n (inputElement.selectionEnd ?? -1) - (inputElement.selectionStart ?? 0) ===\n inputElement.value.length\n ) {\n e.preventDefault();\n // commit so that the user gets to see what it formats to immediately\n // paste happens before inputRef's value is updated, so have to prevent the default and do it ourselves\n // spin button will then handle announcing the new value, this should work with controlled state as well\n // because the announcement is done as a result of the new rendered input value if there is one\n commit(e.clipboardData?.getData?.('text/plain')?.trim() ?? '');\n }\n };\n\n let domProps = filterDOMProps(props);\n let onKeyDownEnter = useCallback(\n e => {\n if (e.nativeEvent.isComposing) {\n return;\n }\n\n if (e.key === 'Enter') {\n flushSync(() => {\n commit();\n });\n commitValidation();\n } else {\n e.continuePropagation();\n }\n },\n [commit, commitValidation]\n );\n\n let {isInvalid, validationErrors, validationDetails} = state.displayValidation;\n let {\n labelProps,\n inputProps: textFieldProps,\n descriptionProps,\n errorMessageProps\n } = useFormattedTextField(\n {\n ...otherProps,\n ...domProps,\n // These props are added to a hidden input rather than the formatted textfield.\n name: undefined,\n form: undefined,\n label,\n autoFocus,\n isDisabled,\n isReadOnly,\n isRequired,\n validate: undefined,\n [privateValidationStateProp]: state,\n value: inputValue,\n defaultValue: '!', // an invalid value so that form reset is ignored in onChange above\n autoComplete: 'off',\n 'aria-label': props['aria-label'] || undefined,\n 'aria-labelledby': props['aria-labelledby'] || undefined,\n id: inputId,\n type: 'text', // Can't use type=\"number\" because then we can't have things like $ in the field.\n inputMode,\n onChange,\n onBlur,\n onFocus,\n onFocusChange,\n onKeyDown: useMemo(() => chain(onKeyDownEnter, onKeyDown), [onKeyDownEnter, onKeyDown]),\n onKeyUp,\n onPaste,\n description,\n errorMessage\n },\n state,\n inputRef\n );\n\n useFormReset(inputRef, state.defaultNumberValue, state.setNumberValue);\n useNativeValidation(\n state,\n props.validationBehavior,\n props.commitBehavior,\n inputRef,\n state.minValue,\n state.maxValue,\n props.step,\n state.numberValue\n );\n\n let inputProps: InputHTMLAttributes<HTMLInputElement> = mergeProps(\n spinButtonProps,\n focusProps,\n textFieldProps,\n {\n // override the spinbutton role, we can't focus a spin button with VO\n role: null,\n // ignore aria-roledescription on iOS so that required state will announce when it is present\n 'aria-roledescription': !isIOS() ? stringFormatter.format('numberField') : null,\n 'aria-valuemax': null,\n 'aria-valuemin': null,\n 'aria-valuenow': null,\n 'aria-valuetext': null,\n autoCorrect: 'off',\n spellCheck: 'false'\n }\n );\n\n if (props.validationBehavior === 'native') {\n inputProps['aria-required'] = undefined;\n }\n\n let onButtonPressStart = e => {\n // If focus is already on the input, keep it there so we don't hide the\n // software keyboard when tapping the increment/decrement buttons.\n if (getActiveElement() === inputRef.current) {\n return;\n }\n\n // Otherwise, when using a mouse, move focus to the input.\n // On touch, or with a screen reader, focus the button so that the software\n // keyboard does not appear and the screen reader cursor is not moved off the button.\n if (e.pointerType === 'mouse') {\n inputRef.current?.focus();\n } else {\n e.target.focus();\n }\n };\n\n // Determine the label for the increment and decrement buttons. There are 4 cases:\n //\n // 1. With a visible label that is a string: aria-label: `Increase ${props.label}`\n // 2. With a visible label that is JSX: aria-label: 'Increase', aria-labelledby: '${incrementId} ${labelId}'\n // 3. With an aria-label: aria-label: `Increase ${props['aria-label']}`\n // 4. With an aria-labelledby: aria-label: 'Increase', aria-labelledby: `${incrementId} ${props['aria-labelledby']}`\n //\n // (1) and (2) could possibly be combined and both use aria-labelledby. However, placing the label in\n // the aria-label string rather than using aria-labelledby gives more flexibility to translators to change\n // the order or add additional words around the label if needed.\n let fieldLabel = props['aria-label'] || (typeof props.label === 'string' ? props.label : '');\n let ariaLabelledby: string | undefined;\n if (!fieldLabel) {\n ariaLabelledby = props.label != null ? labelProps.id : props['aria-labelledby'];\n }\n\n let incrementId = useId();\n let decrementId = useId();\n\n let incrementButtonProps: AriaButtonProps = mergeProps(incButtonProps, {\n 'aria-label': incrementAriaLabel || stringFormatter.format('increase', {fieldLabel}).trim(),\n id: ariaLabelledby && !incrementAriaLabel ? incrementId : null,\n 'aria-labelledby':\n ariaLabelledby && !incrementAriaLabel ? `${incrementId} ${ariaLabelledby}` : null,\n 'aria-controls': inputId,\n excludeFromTabOrder: true,\n preventFocusOnPress: true,\n allowFocusWhenDisabled: true,\n isDisabled: !state.canIncrement,\n onPressStart: onButtonPressStart\n });\n\n let decrementButtonProps: AriaButtonProps = mergeProps(decButtonProps, {\n 'aria-label': decrementAriaLabel || stringFormatter.format('decrease', {fieldLabel}).trim(),\n id: ariaLabelledby && !decrementAriaLabel ? decrementId : null,\n 'aria-labelledby':\n ariaLabelledby && !decrementAriaLabel ? `${decrementId} ${ariaLabelledby}` : null,\n 'aria-controls': inputId,\n excludeFromTabOrder: true,\n preventFocusOnPress: true,\n allowFocusWhenDisabled: true,\n isDisabled: !state.canDecrement,\n onPressStart: onButtonPressStart\n });\n\n return {\n groupProps: {\n ...focusWithinProps,\n role: 'group',\n 'aria-disabled': isDisabled,\n 'aria-invalid': isInvalid ? 'true' : undefined\n },\n labelProps,\n inputProps,\n incrementButtonProps,\n decrementButtonProps,\n errorMessageProps,\n descriptionProps,\n isInvalid,\n validationErrors,\n validationDetails\n };\n}\n\nlet numberInput: HTMLInputElement | null = null;\n\nfunction useNativeValidation(\n state: NumberFieldState,\n validationBehavior: 'native' | 'aria' | undefined,\n commitBehavior: 'snap' | 'validate' | undefined,\n inputRef: RefObject<HTMLInputElement | null>,\n min: number | undefined,\n max: number | undefined,\n step: number | undefined,\n value: number | undefined\n) {\n useLayoutEffect(() => {\n let input = inputRef.current;\n if (\n commitBehavior !== 'validate' ||\n state.realtimeValidation.isInvalid ||\n !input ||\n input.disabled\n ) {\n return;\n }\n\n // Create a native number input and use it to implement validation of min/max/step.\n // This lets us get the native validation message provided by the browser instead of needing our own translations.\n if (!numberInput && typeof document !== 'undefined') {\n numberInput = document.createElement('input');\n numberInput.type = 'number';\n }\n\n if (!numberInput) {\n // For TypeScript.\n return;\n }\n\n numberInput.min = min != null && !isNaN(min) ? String(min) : '';\n numberInput.max = max != null && !isNaN(max) ? String(max) : '';\n numberInput.step = step != null && !isNaN(step) ? String(step) : '';\n numberInput.value = value != null && !isNaN(value) ? String(value) : '';\n\n // Merge validity with the visible text input (for other validations like required).\n let valid = input.validity.valid && numberInput.validity.valid;\n let validationMessage = input.validationMessage || numberInput.validationMessage;\n let validity = {\n isInvalid: !valid,\n validationErrors: validationMessage ? [validationMessage] : [],\n validationDetails: {\n badInput: input.validity.badInput,\n customError: input.validity.customError,\n patternMismatch: input.validity.patternMismatch,\n rangeOverflow: numberInput.validity.rangeOverflow,\n rangeUnderflow: numberInput.validity.rangeUnderflow,\n stepMismatch: numberInput.validity.stepMismatch,\n tooLong: input.validity.tooLong,\n tooShort: input.validity.tooShort,\n typeMismatch: input.validity.typeMismatch,\n valueMissing: input.validity.valueMissing,\n valid\n }\n };\n\n state.updateValidation(validity);\n\n // Block form submission if validation behavior is native.\n // This won't overwrite any user-defined validation message because we checked realtimeValidation above.\n if (validationBehavior === 'native' && !numberInput.validity.valid) {\n input.setCustomValidity(numberInput.validationMessage);\n }\n });\n}\n"],"names":[],"version":3,"file":"useNumberField.mjs.map"}