UNPKG

@yamada-ui/react

Version:

React UI components of the Yamada, by the Yamada, for the Yamada built with React and Emotion

1 lines • 13.4 kB
{"version":3,"file":"use-number-input.cjs","names":["useFieldProps","useCounter","value","isComposing","useNumberCounter","props","mergeRefs"],"sources":["../../../../src/components/number-input/use-number-input.ts"],"sourcesContent":["\"use client\"\n\nimport type { ChangeEvent, FocusEvent, KeyboardEvent } from \"react\"\nimport type { HTMLProps, PropGetter } from \"../../core\"\nimport type { UseCounterProps } from \"../../hooks/use-counter\"\nimport type { FieldProps } from \"../field\"\nimport { useCallback, useMemo, useRef } from \"react\"\nimport { useCounter } from \"../../hooks/use-counter\"\nimport { useEventListener } from \"../../hooks/use-event-listener\"\nimport {\n ariaAttr,\n handlerAll,\n isComposing,\n mergeRefs,\n runKeyAction,\n useSafeLayoutEffect,\n} from \"../../utils\"\nimport { useFieldProps } from \"../field\"\nimport { useNumberCounter } from \"./use-number-counter\"\n\nconst defaultFormat = (value: number | string) => value.toString()\n\nconst defaultParse = (value: string) => value\n\nconst isDefaultValidCharacter = (char: string) => /^[Ee0-9+\\-.]$/.test(char)\n\nconst isValidNumericKeyboardEvent = (\n { key, altKey, ctrlKey, metaKey }: KeyboardEvent,\n isValid: (key: string) => boolean,\n) => {\n const modifierKey = ctrlKey || altKey || metaKey\n const singleCharacterKey = key.length === 1\n\n if (!singleCharacterKey || modifierKey) return true\n\n return isValid(key)\n}\n\nconst getStepRatio = <Y extends KeyboardEvent | WheelEvent>({\n ctrlKey,\n metaKey,\n shiftKey,\n}: Y) => {\n let ratio = 1\n\n if (metaKey || ctrlKey) ratio = 0.1\n\n if (shiftKey) ratio = 10\n\n return ratio\n}\n\nexport interface UseNumberInputProps\n extends Omit<HTMLProps<\"input\">, keyof UseCounterProps>,\n UseCounterProps,\n FieldProps {\n /**\n * If `true`, the input's value will change based on mouse wheel.\n *\n * @default false\n */\n allowMouseWheel?: boolean\n /**\n * This controls the value update when you blur out of the input.\n * - If `true` and the value is greater than `max`, the value will be reset to `max`.\n * - Else, the value remains the same.\n *\n * @default true\n */\n clampValueOnBlur?: boolean\n /**\n * If `true`, the input will be focused as you increment or decrement the value with the button.\n *\n * @default true\n */\n focusInputOnChange?: boolean\n /**\n * If using a custom display format, this converts the default format to the custom format.\n */\n format?: (value: number | string) => string\n /**\n * This is used to format the value so that screen readers\n * can speak out a more human-friendly value.\n *\n * It is used to set the `aria-valuetext` property of the input.\n */\n getAriaValueText?: (value: number | string) => string | undefined\n /**\n * Whether the pressed key should be allowed in the input.\n * The default behavior is to allow DOM floating point characters defined by /^[Ee0-9+\\-.]$/.\n */\n isValidCharacter?: (value: string) => boolean\n /**\n * If using a custom display format, this converts the custom format to a format `parseFloat` understands.\n */\n parse?: (value: string) => string\n}\n\nexport const useNumberInput = (props: UseNumberInputProps = {}) => {\n const {\n props: {\n allowMouseWheel,\n clampValueOnBlur = true,\n defaultValue,\n disabled,\n focusInputOnChange = true,\n format = defaultFormat,\n getAriaValueText,\n isValidCharacter = isDefaultValidCharacter,\n keepWithinRange = true,\n max: maxValue = Number.MAX_SAFE_INTEGER,\n min: minValue = Number.MIN_SAFE_INTEGER,\n parse = defaultParse,\n precision,\n readOnly,\n step = 1,\n value: valueProp,\n onChange: onChangeProp,\n ...rest\n },\n ariaProps,\n dataProps,\n eventProps,\n } = useFieldProps(props)\n const interactive = !(readOnly || disabled)\n const inputRef = useRef<HTMLInputElement>(null)\n const {\n cast,\n max,\n min,\n out,\n setValue,\n update,\n value,\n valueAsNumber,\n ...counter\n } = useCounter({\n defaultValue,\n keepWithinRange,\n max: maxValue,\n min: minValue,\n precision,\n step,\n value: valueProp,\n onChange: onChangeProp,\n })\n const selectionRef = useRef<null | {\n end: null | number\n start: null | number\n }>(null)\n const valueText = useMemo(() => {\n let text = getAriaValueText?.(value)\n\n if (text != null) return text\n\n text = value.toString()\n\n return !text ? undefined : text\n }, [value, getAriaValueText])\n\n const sanitize = useCallback(\n (value: string) => value.split(\"\").filter(isValidCharacter).join(\"\"),\n [isValidCharacter],\n )\n\n const increment = useCallback(\n (value: number = step) => {\n if (!interactive) return\n\n counter.increment(value)\n\n if (!focusInputOnChange) return\n\n requestAnimationFrame(() => {\n inputRef.current?.focus()\n })\n },\n [interactive, counter, step, focusInputOnChange],\n )\n\n const decrement = useCallback(\n (value: number = step) => {\n if (!interactive) return\n\n counter.decrement(value)\n\n if (!focusInputOnChange) return\n\n requestAnimationFrame(() => {\n inputRef.current?.focus()\n })\n },\n [interactive, counter, step, focusInputOnChange],\n )\n\n const onChange = useCallback(\n (ev: ChangeEvent<HTMLInputElement>) => {\n if (isComposing(ev)) return\n\n const { selectionEnd, selectionStart, value } = ev.currentTarget\n\n update(sanitize(parse(value)))\n\n selectionRef.current = { end: selectionEnd, start: selectionStart }\n },\n [parse, sanitize, update],\n )\n\n const onFocus = useCallback((ev: FocusEvent<HTMLInputElement>) => {\n if (!selectionRef.current) return\n\n const { end, start } = selectionRef.current\n const { selectionStart, value } = ev.currentTarget\n\n ev.currentTarget.selectionStart = start ?? value.length\n ev.currentTarget.selectionEnd = end ?? selectionStart\n }, [])\n\n const onBlur = useCallback(() => {\n if (!clampValueOnBlur) return\n\n let nextValue = value as number | string\n\n if (value === \"\") return\n\n const valueStartsWithE = /^[eE]/.test(value.toString())\n\n if (valueStartsWithE) {\n setValue(\"\")\n } else {\n if (valueAsNumber < minValue) nextValue = minValue\n\n if (valueAsNumber > maxValue) nextValue = maxValue\n\n cast(nextValue)\n }\n }, [\n cast,\n clampValueOnBlur,\n maxValue,\n minValue,\n setValue,\n value,\n valueAsNumber,\n ])\n\n const onKeyDown = useCallback(\n (ev: KeyboardEvent<HTMLInputElement>) => {\n if (isComposing(ev)) return\n\n if (!isValidNumericKeyboardEvent(ev, isValidCharacter))\n ev.preventDefault()\n\n const stepValue = getStepRatio(ev) * step\n\n runKeyAction(ev, {\n ArrowDown: () => decrement(stepValue),\n ArrowUp: () => increment(stepValue),\n End: () => update(maxValue),\n Home: () => update(minValue),\n })\n },\n [decrement, increment, isValidCharacter, maxValue, minValue, step, update],\n )\n\n const { getDecrementProps, getIncrementProps } = useNumberCounter({\n \"aria-disabled\": ariaAttr(!interactive),\n decrement,\n disabled,\n increment,\n keepWithinRange,\n max,\n min,\n ...dataProps,\n })\n\n useSafeLayoutEffect(() => {\n if (!inputRef.current) return\n\n const notInSync = inputRef.current.value != value\n\n if (!notInSync) return\n\n setValue(sanitize(parse(inputRef.current.value)))\n }, [parse, sanitize])\n\n useEventListener(\n inputRef.current,\n \"wheel\",\n (ev) => {\n const ownerDocument = inputRef.current?.ownerDocument ?? document\n const focused = ownerDocument.activeElement === inputRef.current\n\n if (!allowMouseWheel || !focused) return\n\n ev.preventDefault()\n\n const stepValue = getStepRatio(ev) * step\n const direction = Math.sign(ev.deltaY)\n\n if (direction === -1) {\n increment(stepValue)\n } else if (direction === 1) {\n decrement(stepValue)\n }\n },\n { passive: false },\n )\n\n const getInputProps: PropGetter<\"input\"> = useCallback(\n ({ ref, ...props } = {}) => ({\n ...ariaProps,\n ...dataProps,\n type: \"text\",\n \"aria-invalid\": ariaAttr(ariaProps[\"aria-invalid\"] ?? out),\n \"aria-valuemax\": maxValue,\n \"aria-valuemin\": minValue,\n \"aria-valuenow\": Number.isNaN(valueAsNumber) ? undefined : valueAsNumber,\n \"aria-valuetext\": valueText,\n autoComplete: \"off\",\n autoCorrect: \"off\",\n disabled,\n inputMode: \"decimal\",\n max: maxValue,\n min: minValue,\n pattern: \"[0-9]*(.[0-9]+)?\",\n readOnly,\n role: \"spinbutton\",\n step,\n value: format(value),\n ...rest,\n ...props,\n ref: mergeRefs(ref, rest.ref, inputRef),\n onBlur: handlerAll(eventProps.onBlur, props.onBlur, onBlur),\n onChange: handlerAll(props.onChange, onChange),\n onFocus: handlerAll(eventProps.onFocus, props.onFocus, onFocus),\n onKeyDown: handlerAll(rest.onKeyDown, props.onKeyDown, onKeyDown),\n }),\n [\n format,\n out,\n value,\n valueText,\n ariaProps,\n dataProps,\n eventProps,\n maxValue,\n minValue,\n valueAsNumber,\n disabled,\n readOnly,\n step,\n rest,\n onKeyDown,\n onBlur,\n onFocus,\n onChange,\n ],\n )\n\n return { getDecrementProps, getIncrementProps, getInputProps }\n}\n\nexport type UseNumberInputReturn = ReturnType<typeof useNumberInput>\n"],"mappings":"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"}