mirror of
https://github.com/ant-design/ant-design.git
synced 2024-12-23 15:08:34 +08:00
80 lines
2.2 KiB
TypeScript
80 lines
2.2 KiB
TypeScript
import * as React from 'react';
|
|
import raf from 'rc-util/lib/raf';
|
|
|
|
import Input from '../Input';
|
|
import type { InputProps, InputRef } from '../Input';
|
|
|
|
export interface OTPInputProps extends Omit<InputProps, 'onChange'> {
|
|
index: number;
|
|
onChange: (index: number, value: string) => void;
|
|
/** Tell parent to do active offset */
|
|
onActiveChange: (nextIndex: number) => void;
|
|
|
|
mask?: boolean | string;
|
|
}
|
|
|
|
const OTPInput = React.forwardRef<InputRef, OTPInputProps>((props, ref) => {
|
|
const { value, onChange, onActiveChange, index, mask, ...restProps } = props;
|
|
|
|
const internalValue = value && typeof mask === 'string' ? mask : value;
|
|
|
|
const onInternalChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
|
|
onChange(index, e.target.value);
|
|
};
|
|
|
|
// ========================== Ref ===========================
|
|
const inputRef = React.useRef<InputRef>(null);
|
|
React.useImperativeHandle(ref, () => inputRef.current!);
|
|
|
|
// ========================= Focus ==========================
|
|
const syncSelection = () => {
|
|
raf(() => {
|
|
const inputEle = inputRef.current?.input;
|
|
if (document.activeElement === inputEle && inputEle) {
|
|
inputEle.select();
|
|
}
|
|
});
|
|
};
|
|
|
|
// ======================== Keyboard ========================
|
|
const onInternalKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {
|
|
const { key, ctrlKey, metaKey } = event;
|
|
|
|
if (key === 'ArrowLeft') {
|
|
onActiveChange(index - 1);
|
|
} else if (key === 'ArrowRight') {
|
|
onActiveChange(index + 1);
|
|
} else if (key === 'z' && (ctrlKey || metaKey)) {
|
|
event.preventDefault();
|
|
}
|
|
|
|
syncSelection();
|
|
};
|
|
|
|
const onInternalKeyUp: React.KeyboardEventHandler<HTMLInputElement> = (e) => {
|
|
if (e.key === 'Backspace' && !value) {
|
|
onActiveChange(index - 1);
|
|
}
|
|
|
|
syncSelection();
|
|
};
|
|
|
|
// ========================= Render =========================
|
|
return (
|
|
<Input
|
|
type={mask === true ? 'password' : 'text'}
|
|
{...restProps}
|
|
ref={inputRef}
|
|
value={internalValue}
|
|
onInput={onInternalChange}
|
|
onFocus={syncSelection}
|
|
onKeyDown={onInternalKeyDown}
|
|
onKeyUp={onInternalKeyUp}
|
|
onMouseDown={syncSelection}
|
|
onMouseUp={syncSelection}
|
|
/>
|
|
);
|
|
});
|
|
|
|
export default OTPInput;
|