UNPKG

@yamada-ui/react

Version:

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

214 lines (210 loc) • 6.6 kB
"use client"; import { isComposing, runKeyAction } from "../../utils/dom.js"; import { useSafeLayoutEffect } from "../../utils/effect.js"; import { mergeRefs } from "../../utils/ref.js"; import { utils_exports } from "../../utils/index.js"; import { useFieldProps } from "../field/use-field-props.js"; import { useEventListener } from "../../hooks/use-event-listener/index.js"; import { useCounter } from "../../hooks/use-counter/index.js"; import { useNumberCounter } from "./use-number-counter.js"; import { useCallback, useMemo, useRef } from "react"; //#region src/components/number-input/use-number-input.ts const defaultFormat = (value) => value.toString(); const defaultParse = (value) => value; const isDefaultValidCharacter = (char) => /^[Ee0-9+\-.]$/.test(char); const isValidNumericKeyboardEvent = ({ key, altKey, ctrlKey, metaKey }, isValid) => { const modifierKey = ctrlKey || altKey || metaKey; if (!(key.length === 1) || modifierKey) return true; return isValid(key); }; const getStepRatio = ({ ctrlKey, metaKey, shiftKey }) => { let ratio = 1; if (metaKey || ctrlKey) ratio = .1; if (shiftKey) ratio = 10; return ratio; }; const useNumberInput = (props = {}) => { const { props: { allowMouseWheel, clampValueOnBlur = true, defaultValue, disabled, focusInputOnChange = true, format = defaultFormat, getAriaValueText, isValidCharacter = isDefaultValidCharacter, keepWithinRange = true, max: maxValue = Number.MAX_SAFE_INTEGER, min: minValue = Number.MIN_SAFE_INTEGER, parse = defaultParse, precision, readOnly, step = 1, value: valueProp, onChange: onChangeProp,...rest }, ariaProps, dataProps, eventProps } = useFieldProps(props); const interactive = !(readOnly || disabled); const inputRef = useRef(null); const { cast, max, min, out, setValue, update, value, valueAsNumber,...counter } = useCounter({ defaultValue, keepWithinRange, max: maxValue, min: minValue, precision, step, value: valueProp, onChange: onChangeProp }); const selectionRef = useRef(null); const valueText = useMemo(() => { let text = getAriaValueText?.(value); if (text != null) return text; text = value.toString(); return !text ? void 0 : text; }, [value, getAriaValueText]); const sanitize = useCallback((value$1) => value$1.split("").filter(isValidCharacter).join(""), [isValidCharacter]); const increment = useCallback((value$1 = step) => { if (!interactive) return; counter.increment(value$1); if (!focusInputOnChange) return; requestAnimationFrame(() => { inputRef.current?.focus(); }); }, [ interactive, counter, step, focusInputOnChange ]); const decrement = useCallback((value$1 = step) => { if (!interactive) return; counter.decrement(value$1); if (!focusInputOnChange) return; requestAnimationFrame(() => { inputRef.current?.focus(); }); }, [ interactive, counter, step, focusInputOnChange ]); const onChange = useCallback((ev) => { if (isComposing(ev)) return; const { selectionEnd, selectionStart, value: value$1 } = ev.currentTarget; update(sanitize(parse(value$1))); selectionRef.current = { end: selectionEnd, start: selectionStart }; }, [ parse, sanitize, update ]); const onFocus = useCallback((ev) => { if (!selectionRef.current) return; const { end, start } = selectionRef.current; const { selectionStart, value: value$1 } = ev.currentTarget; ev.currentTarget.selectionStart = start ?? value$1.length; ev.currentTarget.selectionEnd = end ?? selectionStart; }, []); const onBlur = useCallback(() => { if (!clampValueOnBlur) return; let nextValue = value; if (value === "") return; if (/^[eE]/.test(value.toString())) setValue(""); else { if (valueAsNumber < minValue) nextValue = minValue; if (valueAsNumber > maxValue) nextValue = maxValue; cast(nextValue); } }, [ cast, clampValueOnBlur, maxValue, minValue, setValue, value, valueAsNumber ]); const onKeyDown = useCallback((ev) => { if (isComposing(ev)) return; if (!isValidNumericKeyboardEvent(ev, isValidCharacter)) ev.preventDefault(); const stepValue = getStepRatio(ev) * step; runKeyAction(ev, { ArrowDown: () => decrement(stepValue), ArrowUp: () => increment(stepValue), End: () => update(maxValue), Home: () => update(minValue) }); }, [ decrement, increment, isValidCharacter, maxValue, minValue, step, update ]); const { getDecrementProps, getIncrementProps } = useNumberCounter({ "aria-disabled": (0, utils_exports.ariaAttr)(!interactive), decrement, disabled, increment, keepWithinRange, max, min, ...dataProps }); useSafeLayoutEffect(() => { if (!inputRef.current) return; if (!(inputRef.current.value != value)) return; setValue(sanitize(parse(inputRef.current.value))); }, [parse, sanitize]); useEventListener(inputRef.current, "wheel", (ev) => { const focused = (inputRef.current?.ownerDocument ?? document).activeElement === inputRef.current; if (!allowMouseWheel || !focused) return; ev.preventDefault(); const stepValue = getStepRatio(ev) * step; const direction = Math.sign(ev.deltaY); if (direction === -1) increment(stepValue); else if (direction === 1) decrement(stepValue); }, { passive: false }); return { getDecrementProps, getIncrementProps, getInputProps: useCallback(({ ref,...props$1 } = {}) => ({ ...ariaProps, ...dataProps, type: "text", "aria-invalid": (0, utils_exports.ariaAttr)(ariaProps["aria-invalid"] ?? out), "aria-valuemax": maxValue, "aria-valuemin": minValue, "aria-valuenow": Number.isNaN(valueAsNumber) ? void 0 : valueAsNumber, "aria-valuetext": valueText, autoComplete: "off", autoCorrect: "off", disabled, inputMode: "decimal", max: maxValue, min: minValue, pattern: "[0-9]*(.[0-9]+)?", readOnly, role: "spinbutton", step, value: format(value), ...rest, ...props$1, ref: mergeRefs(ref, rest.ref, inputRef), onBlur: (0, utils_exports.handlerAll)(eventProps.onBlur, props$1.onBlur, onBlur), onChange: (0, utils_exports.handlerAll)(props$1.onChange, onChange), onFocus: (0, utils_exports.handlerAll)(eventProps.onFocus, props$1.onFocus, onFocus), onKeyDown: (0, utils_exports.handlerAll)(rest.onKeyDown, props$1.onKeyDown, onKeyDown) }), [ format, out, value, valueText, ariaProps, dataProps, eventProps, maxValue, minValue, valueAsNumber, disabled, readOnly, step, rest, onKeyDown, onBlur, onFocus, onChange ]) }; }; //#endregion export { useNumberInput }; //# sourceMappingURL=use-number-input.js.map