@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
JavaScript
"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