全角数字を入力したら半角数字に変換したい。
onBlurはフォーカスを外す一手間が必要になるので使いたくない。
全角文字は入力確定してから半角に変換したい。
数字以外の文字は入力されても消さない。
そんな要件に応えるフォームを試行錯誤しながら作ってみました。
macのchrome, firefox, safariで検証済みです。
↓完成したコードがこちら
// IME使用中のinput type
const IME_TYPES: string[] = [
'insertCompositionText',
'deleteCompositionText',
'insertFromComposition',
'deleteByComposition',
] as const
const handleInput: ChangeEventHandler<HTMLInputElement> = (e) => {
// IME使用中は後続処理を行わない
if (
e.nativeEvent instanceof InputEvent &&
IME_TYPES.includes(e.nativeEvent.inputType)
)
return
// ここで半角変換(コピペされた時用)
const halfWidthNum = toHalfWidthNum(e.target.value)
e.target.value = halfWidthNum
props.onChange?.(halfWidthNum) // 親に入力値を渡してバリデーション等を行う
}
// IMEが確定するとこれが呼ばれる(Enterキーはもちろん、マウスで確定しても呼ばれる)
const handleCompositionEnd: CompositionEventHandler<HTMLInputElement> = (
e,
) => {
const halfWidthNum = toHalfWidthNum(e.currentTarget.value) // ここで半角変換
// ここでセットすることで入力した文字が半角になる
e.currentTarget.value = halfWidthNum
props.onChange?.(halfWidthNum) // 親に入力値を渡してバリデーション等を行う
}
<input
type="text" // numberは使わない
inputMode="numeric"
defaultValue={value}
onInput={handleInput}
onCompositionEnd={handleCompositionEnd}
// value, onChangeは使わない
/>
ポイントはvalue, onChangeは使わずにdefaultValue, onInput, onCompositionEndで処理するところです。
inputにpropsをそのまま渡す場合はvalue, onChangeを渡さないように注意が必要です。
valueを入力以外で変更することがある場合はこちら
// IME使用中のinput type
const IME_TYPES: string[] = [
'insertCompositionText',
'deleteCompositionText',
'insertFromComposition',
'deleteByComposition',
] as const
const [isComposing, setIsComposing] = useState(false)
const handleInput: ChangeEventHandler<HTMLInputElement> = (e) => {
// IME使用中は後続処理を行わない
if (
e.nativeEvent instanceof InputEvent &&
IME_TYPES.includes(e.nativeEvent.inputType)
)
return
// ここで半角変換(コピペされた時用)
const halfWidthNum = toHalfWidthNum(e.target.value)
e.target.value = halfWidthNum
props.onChange?.(halfWidthNum) // 親に入力値を渡してバリデーション等を行う
}
// IMEが確定するとこれが呼ばれる(Enterキーはもちろん、マウスで確定しても呼ばれる)
const handleCompositionEnd: CompositionEventHandler<HTMLInputElement> = (
e,
) => {
const halfWidthNum = toHalfWidthNum(e.currentTarget.value) // ここで半角変換
// ここでセットすることで入力した文字が半角になる
e.currentTarget.value = halfWidthNum
props.onChange?.(halfWidthNum) // 親に入力値を渡してバリデーション等を行う
setIsComposing(false)
}
<input
type="text" // numberは使わない
inputMode="numeric"
value={isComposing ? undefined : value}
onInput={handleInput}
onCompositionStart={() => setIsComposing(true)}
onCompositionEnd={handleCompositionEnd}
// onChangeは使わない
/>
IME使用中はinput内部のvalueを使うようにすることで、確定するまで待ってから変換処理を行うことができます。
コンポーネントが制御になったり非制御になるのであまりいい方法では無いかもですが…^^;
参考:
https://qiita.com/alt_yamamoto/items/8663d047a3794dd5605e