TypeScript: 数字入力テキストインプット (Windows10のFirefox107で動作する)

2022年6月28日(火)

環境

数字入力テキストインプット

重要事項

この実装はWindows10のFirefox107で期待通りに動作する。Windows10のChrome107ではIME入力の確定と貼り付けおよびドロップの入力制限が動作しない。

GitHubのリポジトリ

https://github.com/yvafdevnsk/typescript/tree/main/number-input

ファイルの一覧

number-input
    |- number-input.html
    |- number-input.css
    |- number-input.ts
    |- number-input.js (tsファイルから生成される)
    |- tsconfig.json
          

目標

テキストインプットのイベント

テキストインプットの入力値の状態遷移

  1. 画面に反映される前
  2. 画面に反映された後 (未確定)
  3. 確定 (Enterキーが押されたまたはフォーカスを失った)

イベントの発生順序

  1. beforeinput (新しい入力値が画面に反映される前)
  2. input (新しい入力値が画面に反映された後)
  3. change (入力値に変更があり、未確定の状態で、Enterキーが押されたまたはフォーカスを失ったとき)

Firefoxの仕様

実装1: 入力可能とする文字の集合の定義

const allowMap: Map<string, string> = new Map([
    ["1","1"],["2","2"],["3","3"],["4","4"],["5","5"],
    ["6","6"],["7","7"],["8","8"],["9","9"],["0","0"]
]);
          

実装2: イベントリスナーの登録

const load: any = () => {
    init();
};
window.onload = load;

function init(): void {
    const numberInput: HTMLElement | null = document.getElementById("number-input");
    if (numberInput !== null) {
        numberInput.addEventListener("beforeinput", beforeinputEventListener);
        numberInput.addEventListener("input", inputEventListener);
    }
}
          

実装3: beforeinputイベントの処理

function beforeinputEventListener(e: InputEvent): void {
    // IME入力が未確定の場合は何もしない。
    if (e.isComposing) {
        return;
    }

    // 入力値がある場合は受け入れ可能かの判断を行う。
    if ((e.data !== null) && (e.data.length > 0)) {
        // Array.from()でサロゲートペア単位での比較もできる。
        const dataList: string[] = Array.from(e.data);
        if (dataList.every((s) => allowMap.has(s))) {
            // 入力値のすべてがOKの場合は、入力値を受け入れる。
        }
        else if (dataList.some((s) => allowMap.has(s))) {
            // 入力値の何れかがNGの場合は、NGの文字を取り除く。
            // 取り除く処理はinputイベントで行う。
        }
        else {
            // 入力値のすべてがNGの場合は、入力をキャンセルする。
            e.preventDefault();
        }
    }
}
          

実装4: inputイベントの処理

function inputEventListener(e: InputEvent | Event): void {
    if (e instanceof InputEvent) {
        // IME入力が未確定の場合は何もしない。
        if (e.isComposing) {
            return;
        }

        // 入力値がある場合は受け入れ可能かの判断を行う。
        if ((e.data !== null) && (e.data.length > 0)) {
            // Array.from()でサロゲートペア単位での比較もできる。
            const dataList: string[] = Array.from(e.data);
            if (dataList.every((s) => allowMap.has(s))) {
                // 入力値のすべてがOKの場合は、入力値を受け入れる。
            }
            else {
                // 入力値の何れかがNGの場合は、NGの文字を取り除く。

                // inputイベントはキャンセルできないので
                // テキストインプットの内容から入力不可の文字を取り除く。

                // IMEで入力された場合はcompositionendイベントの後にe.isComposing=falseとなってinputイベントが呼ばれる。
                // IMEで入力された場合はbeforeinputイベントの処理はe.isComposing=trueになるのでスキップされる。
                // IMEで入力された場合はinputイベントで入力不可の文字を取り除く。
                const inputElement: HTMLInputElement = e.target as HTMLInputElement;
                inputElement.value = Array.from(inputElement.value)
                    .filter((s) => allowMap.has(s))
                    .reduce((previousValue, currentValue) => previousValue + currentValue, "");
            }
        }
    }
}
          

ビルドする

$ npx tsc --target es2015 number-input.ts
        

確認する

サンプルページを表示する

参考情報