<input type="number">は、数値以外の入力を空文字に変換する。受け入れられる数値の実体を、WATHWGの仕様と実験によって理解してみたい。

入力が許される値

WHATWGの仕様では、<input type="number">に入力できる数値は以下の条件を上から順に満たすべしと定められている。

  • 任意:マイナス記号(-
  • 以下のいずれか、あるいは両方
    • 1つ以上の0-9
    • 1つの.と、それに続く1つ以上の0-9
  • 任意:eまたはE
  • 任意:-あるいは+
  • 任意:1つ以上の0-9

これ以外の値は、そもそも入力できないか、入力確定と同時にボックスから削除される。

実験で確かめる

先の条件を踏まえて、いくつかの適当な値を動的に<input type="number">valueプロパティに代入し、その結果を確かめる。

html
<html>
  <head><style>table{border-collapse:collapse;}table,th,td{border:solid 1px #ddd;padding:8px;}</style></head>
  <body>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.0.nomodule.min.js"></script>
    <script>
    const { input, table, tr, th, td } = van.tags;
    const values = [
      "0",
      "-1",
      "-0",
      "0.01",
      "01",
      "0.10",
      "00.0",
      "1e1",
      "1.23e-4",
      ".1",
      "1.",
      " 1",
      " 1 ",
      "e-4",
      "1+1",
      "--1",
      "1 1 1",
      "ー1",
      "2a",
      "a+1",
    ];

    const TableRows = values.map((v) =>
      tr(
        td(v),
        td(
          input({
            style: "text-align:center",
            type: "number",
            value: v,
          })
        ),
        td(parseFloat(v)),
        td(isNaN(v)),
      )
    );

    const Table = table(
      { style: "text-align:center" },
      tr(th("検証する値"), th("input.value"), th("parseFloat()"), th("isNaN()")),
      TableRows
    );
    van.add(
            document.body,
            Table
    );
    </script>
  </body>
</html>

当然だが、仕様が定める数値以外は空っぽになっている。00.0とか-0とか01とか.1とかは通る。へぇ〜って感じ。

ちなみに、浮動小数点数を判断しているならparseFloat()と似通ったところがあるのでは、と脊髄反射で試してみたらあんまり関係なさそうだった。浮動小数点数をパースするアルゴリズムは心が折れて読んでいないが、isNaN()の結果とほぼ一致するようだ。前後に半角の空白が入る場合と、1.の場合の解釈だけ違った。

バリデーションのタイミング

<input type="number">の入力値のバリデーションは、DOMのイベントハンドラでは捕まえられないタイミングで行われる。

そもそも、半角の文字は0-9の数値と.eE-+以外はボックス内にすら入力できず、inputイベントもchangeイベントも発火しない。

全角の文字はボックス内に表示され、入力するたびにinputイベントが発火するが、valueプロパティには空文字が入る。つまりボックス内の入力値(目で見ている値)とvalueプロパティの値が一致しない。

また、有効な文字であっても、仕様にそぐわない位置に打ち込むと、その時点でvalueプロパティには空文字が入る。この場合も、ボックス内の入力値はそのままで、valueプロパティの値と食い違いが生まれる。

html
<html>
  <body>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.0.nomodule.min.js"></script>
    <script>
      const { input, p } = van.tags;
      const value = van.state("");
      const handler = (e) => value.val += `->"${e.target.value}"@${e.type}`;
      const displayValue = () => `value:${value.val}`;
      van.add(
        document.body,
        input({
          type: "number",
          onfocus: handler,
          onchange: handler,
          oninput: handler,
        }),
        p(displayValue)
      );
    </script>
  </body>
</html>

ごちゃごちゃしてきた。要するに仕様にそぐわない記号や文字の並びを入力した瞬間に、inputオブジェクトのvalueプロパティは空文字になる。イベントリスナーでは、その弾かれた値を取得できない。

使用上の注意

<input type="number">は、無効な半角文字を入力した場合と、無効な全角文字を入力した場合とで、UIの挙動が微妙に異なる。前者はそもそも入力できず、後者は確定時点でボックスから削除される。

デフォルトでは、この挙動に対して何のアナウンスも表示されない。例えば全角で数字を入力することに慣れているユーザーにとっては、バグと受け取られかねない。受け入れる入力値を事前に示す、何らかの工夫をしたいところである。

参考資料