文字数によって区切りのあるテキストフォームを作る
GitHub - nosir/cleave.js: Format input text content when you are typing...
Format input text content when you are typing... Contribute to nosir/cleave.js development by creating an account on GitHub.
https://github.com/nosir/cleave.js/
空白のある箇所ごとに <input type="text">
をわけるような実装でもよさそうではありそうですが、デザイン上1つのフォームで実装する必要がでてくることもあります。
UXが向上しそうなのですが日本語圏ではかな入力時になにも入力されない状態が発生しまうので、入力可能な文字列次第では微妙になる場合もありそうな気はしています。
Cleave.js を使う
上記のライブラリを使えば特に問題なく実装できそうです。
yarn add -E cleave.js
yarn add -DE @types/cleave.js
<Cleave options={{ blocks: [4, 4, 4] }} />
CleaveOptions
での設定が豊富なのでほとんどのケースはこれで対応できるはずです。
自分で頑張る
自分で頑張る場合は少し面倒です。
import React, { FC, useState, useCallback, useRef } from "react";
const InputText: FC = () => {
const inputRef = useRef<HTMLInputElement>(null);
const [value, setValue] = useState("");
const handleChange = useCallback(() => {
const target = inputRef.current;
if (target && target.value.length >= 0) {
const chunkedValue =
target.value.match(/[\da-zA-Z]{1,4}/g)?.join(" ") ?? "";
setValue(chunkedValue);
}
}, []);
return (
<input
ref={inputRef}
value={value}
onChange={handleChange}
maxLength={4 * 3 + 3}
/>
);
};
export default InputText;
上記のコードは一見うまくいきそうに思えますが、これはカーソル位置を移動してbackspaceをしたときに正しく機能しません。
- 4つ区切りになっていない
- カーソルが空白の箇所で最後尾にとんでしまう
この2点が問題になります。
import React, { FC, useState, useCallback, useRef, useEffect } from "react";
const InputText: FC = () => {
const inputRef = useRef<HTMLInputElement>(null);
const [value, setValue] = useState("");
const [prevPosition, setPrevPosition] =
useState<HTMLInputElement["selectionEnd"]>(null);
const handleChange = useCallback(() => {
const target = inputRef.current;
if (target === null) return;
const chunkedValue = target.value
.replace(/[^\da-zA-Z]/g, "")
.replace(/(.{4})/g, "$1 ")
.trim();
setPrevPosition(target.selectionEnd);
setValue(chunkedValue);
}, []);
useEffect(() => {
const target = inputRef.current;
if (target === null || prevPosition === null) return;
const currentPosition = target.selectionEnd;
if (currentPosition === null || prevPosition === null) return;
// 半角空白が追加されたときのカーソルを補正する
const nextPositionDiff =
prevPosition + 1 === currentPosition &&
value.charAt(prevPosition - 1) === " "
? 1
: 0;
target.selectionEnd = prevPosition + nextPositionDiff;
}, [prevPosition, value]);
return (
<input
ref={inputRef}
value={value}
onChange={handleChange}
maxLength={4 * 3 + 3}
/>
);
};
export default InputText;
この方法では chunkedValue
の文字列が変化するため、削除時にカーソルが最後尾に飛ばされます。そのため useEffect
で selectionEnd
の補正をしています。
とりあえず作ってはみたのですが、ちょっと自信がありません。素直にプラグインを使ったほうが良さそうです。