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 kB
{"version":3,"file":"use-pin-input.cjs","names":["createDescendants","useFieldProps","useControllableState","values","value","nextValue","index","ev","getRootProps: PropGetter","props"],"sources":["../../../../src/components/pin-input/use-pin-input.ts"],"sourcesContent":["\"use client\"\n\nimport type { ChangeEvent, KeyboardEvent } from \"react\"\nimport type { HTMLProps, PropGetter, RequiredPropGetter } from \"../../core\"\nimport type { FieldProps } from \"../field\"\nimport { useCallback, useEffect, useId, useState } from \"react\"\nimport { useControllableState } from \"../../hooks/use-controllable-state\"\nimport { createDescendants } from \"../../hooks/use-descendants\"\nimport { filterUndefined, handlerAll, runKeyAction } from \"../../utils\"\nimport { useFieldProps } from \"../field\"\n\nconst {\n DescendantsContext: PinInputDescendantsContext,\n useDescendant: usePinInputDescendant,\n useDescendants: usePinInputDescendants,\n} = createDescendants<HTMLInputElement>()\n\nexport {\n PinInputDescendantsContext,\n usePinInputDescendant,\n usePinInputDescendants,\n}\n\nconst toArray = (value?: string) => value?.split(\"\")\n\nconst validate = (value: string, type: UsePinInputProps[\"type\"]) => {\n const NUMERIC_REGEX = /^[0-9]+$/\n const ALPHA_NUMERIC_REGEX = /^[a-zA-Z0-9]+$/i\n\n const regex = type === \"alphanumeric\" ? ALPHA_NUMERIC_REGEX : NUMERIC_REGEX\n\n return regex.test(value)\n}\n\nexport interface UsePinInputProps\n extends Omit<HTMLProps, \"defaultValue\" | \"mask\" | \"onChange\" | \"value\">,\n FieldProps {\n /**\n * The top-level id string that will be applied to the input fields.\n * The index of the input will be appended to this top-level id.\n */\n id?: string\n /**\n * The type of values the pin-input should allow.\n *\n * @default 'number'\n */\n type?: \"alphanumeric\" | \"number\"\n /**\n * If `true`, the pin input receives focus on mount.\n *\n * @default false\n */\n autoFocus?: boolean\n /**\n * The initial value of the pin input.\n */\n defaultValue?: string\n /**\n * The number of inputs to display.\n *\n * @default 4\n */\n items?: number\n /**\n * If `true`, focus will move automatically to the next input once filled.\n *\n * @default true\n */\n manageFocus?: boolean\n /**\n * If `true`, the input's value will be masked just like `type=password`.\n */\n mask?: boolean\n /**\n * If `true`, the pin input component signals to its fields that they should.\n */\n otp?: boolean\n /**\n * The placeholder for the pin input.\n *\n * @default '◯'\n */\n placeholder?: string\n /**\n * The value of the pin input.\n */\n value?: string\n /**\n * Function called on input change.\n */\n onChange?: (value: string) => void\n /**\n * Function called when all inputs have valid values.\n */\n onComplete?: (value: string) => void\n}\n\nexport const usePinInput = (props: UsePinInputProps = {}) => {\n const uuid = useId()\n const {\n props: {\n id = uuid,\n type = \"number\",\n autoFocus,\n defaultValue,\n disabled,\n manageFocus = true,\n mask,\n otp = false,\n placeholder = \"\",\n readOnly,\n value,\n onChange: onChangeProp,\n onComplete,\n ...rest\n },\n ariaProps,\n dataProps,\n eventProps,\n } = useFieldProps(props)\n const descendants = usePinInputDescendants()\n const [moveFocus, setMoveFocus] = useState<boolean>(true)\n const [focusedIndex, setFocusedIndex] = useState<number>(-1)\n const [values, setValues] = useControllableState<string[]>({\n defaultValue: toArray(defaultValue) || [],\n value: toArray(value),\n onChange: (values) => onChangeProp?.(values.join(\"\")),\n })\n\n const focusNext = useCallback(\n (index: number) => {\n if (!moveFocus || !manageFocus) return\n\n const next = descendants.nextValue(index, false)\n\n if (!next) return\n\n requestAnimationFrame(() => next.node.focus())\n },\n [descendants, moveFocus, manageFocus],\n )\n\n const focusInputField = useCallback(\n (direction: \"next\" | \"prev\", index: number) => {\n const input =\n direction === \"next\"\n ? descendants.nextValue(index, false)\n : descendants.prevValue(index, false)\n\n if (!input) return\n\n const valueLength = input.node.value.length\n\n requestAnimationFrame(() => {\n input.node.focus()\n input.node.setSelectionRange(0, valueLength)\n })\n },\n [descendants],\n )\n\n const setValue = useCallback(\n (value: string, index: number, focus = true) => {\n let nextValues = [...values]\n\n nextValues[index] = value\n\n setValues(nextValues)\n\n nextValues = nextValues.filter(Boolean)\n\n const complete = value !== \"\" && nextValues.length === descendants.count()\n\n if (complete) {\n onComplete?.(nextValues.join(\"\"))\n descendants.value(index)?.node.blur()\n } else if (focus) {\n focusNext(index)\n }\n },\n [values, setValues, descendants, onComplete, focusNext],\n )\n\n const getNextValue = useCallback(\n (value: string | undefined, eventValue: string) => {\n let nextValue = eventValue\n\n if (!value?.length) return nextValue\n\n if (value.startsWith(eventValue.charAt(0))) {\n nextValue = eventValue.charAt(1)\n } else if (value.startsWith(eventValue.charAt(1))) {\n nextValue = eventValue.charAt(0)\n }\n\n return nextValue\n },\n [],\n )\n\n const onChange = useCallback(\n (index: number) =>\n ({ target }: ChangeEvent<HTMLInputElement>) => {\n const eventValue = target.value\n const currentValue = values[index]\n const nextValue = getNextValue(currentValue, eventValue)\n\n if (nextValue === \"\") {\n setValue(\"\", index)\n\n return\n }\n\n if (eventValue.length > 2) {\n if (!validate(eventValue, type)) return\n\n const nextValue = eventValue\n .split(\"\")\n .filter((_, index) => index < descendants.count())\n\n setValues(nextValue)\n\n if (nextValue.length === descendants.count()) {\n onComplete?.(nextValue.join(\"\"))\n descendants.value(index)?.node.blur()\n }\n } else {\n if (validate(nextValue, type)) setValue(nextValue, index)\n\n setMoveFocus(true)\n }\n },\n [descendants, getNextValue, onComplete, setValue, setValues, type, values],\n )\n\n const onKeyDown = useCallback(\n (index: number) => (ev: KeyboardEvent<HTMLInputElement>) => {\n if (!manageFocus) return\n\n runKeyAction(\n ev,\n {\n ArrowLeft: (ev) => {\n ev.preventDefault()\n focusInputField(\"prev\", index)\n },\n ArrowRight: (ev) => {\n ev.preventDefault()\n focusInputField(\"next\", index)\n },\n Backspace: (ev) => {\n if ((ev.target as HTMLInputElement).value === \"\") {\n const prevInput = descendants.prevValue(index, false)\n\n if (!prevInput) return\n\n setValue(\"\", index - 1, false)\n prevInput.node.focus()\n setMoveFocus(true)\n } else {\n setMoveFocus(false)\n }\n },\n },\n { preventDefault: false },\n )\n },\n [descendants, focusInputField, manageFocus, setValue],\n )\n\n const onFocus = useCallback(\n (index: number) => () => setFocusedIndex(index),\n [],\n )\n\n const onBlur = useCallback(() => setFocusedIndex(-1), [])\n\n useEffect(() => {\n if (!autoFocus) return\n\n const firstValue = descendants.firstValue()\n\n if (!firstValue) return\n\n requestAnimationFrame(() => firstValue.node.focus())\n }, [autoFocus, descendants])\n\n const getRootProps: PropGetter = useCallback(\n (props) => ({\n role: \"group\",\n ...rest,\n ...props,\n }),\n [rest],\n )\n\n const getInputProps: RequiredPropGetter<\"input\", { index: number }> =\n useCallback(\n ({ index, ...props }) => ({\n ...ariaProps,\n ...dataProps,\n type: mask ? \"password\" : type === \"number\" ? \"tel\" : \"text\",\n autoComplete: otp ? \"one-time-code\" : \"off\",\n disabled,\n inputMode: type === \"number\" ? \"numeric\" : \"text\",\n placeholder:\n focusedIndex === index && !readOnly && !props.readOnly\n ? \"\"\n : placeholder,\n readOnly,\n value: values[index] || \"\",\n ...filterUndefined(props),\n id: `${id}${index ? `-${index}` : \"\"}`,\n onBlur: handlerAll(eventProps.onBlur, props.onBlur, onBlur),\n onChange: handlerAll(props.onChange, onChange(index)),\n onFocus: handlerAll(eventProps.onFocus, props.onFocus, onFocus(index)),\n onKeyDown: handlerAll(props.onKeyDown, onKeyDown(index)),\n }),\n [\n ariaProps,\n dataProps,\n eventProps,\n mask,\n type,\n disabled,\n readOnly,\n id,\n otp,\n focusedIndex,\n placeholder,\n values,\n onBlur,\n onChange,\n onFocus,\n onKeyDown,\n ],\n )\n\n return {\n descendants,\n getInputProps,\n getRootProps,\n }\n}\n\nexport type UsePinInputReturn = ReturnType<typeof usePinInput>\n"],"mappings":"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"}