input と textarea のフォント
input や textarea のフォントってどうするのがいいんでしょうか

デフォルトを見てみると input と textarea では異なるようです
またブラウザでも異なります

Windows 環境で Chrome/Edge (117) を見ると

input:
  ユーザーエージェントの font-family: 空
  表示されるフォント: Arial, Meiryo

textarea:
  ユーザーエージェントの font-family: monospace
  表示されるフォント: MS Gothic

Windows 環境で Firefox (118) を見ると

input:
  ユーザーエージェントの font-family: MS Shell Dlg 2
  表示されるフォント: tahoma, Meiryo

textarea:
  ユーザーエージェントの font-family: monospace
  表示されるフォント: consolas, MS Gothic



どちらも等幅かと思ったら input はそうではないようでした
また等幅フォントのデフォルトはギザギザして汚い MS Gothic でした

monospace で表示されるフォントはブラウザの設定で変えることができ 私は別フォントにしてたので全然気づいてませんでしたが 設定してないブラウザでみるとすごく残念な画面になってました
(Chrome のフォント設定画面は chrome://settings/fonts)
基本的にはデフォルトのものにしておきたいですが さすがにこれは汚すぎで見るに堪えないです
変えておこうかなと思っても標準フォントで等幅ってこれといったのが思いつかないです

UI ライブラリの入力コンポーネントや世の中のページを見てると そもそも等幅にしてるところがそんなに多くないようでした
等幅にしているところはだいたいウェブフォントで独自のフォントを使っているようです
可変幅のところでは inherit にして親と一緒 つまり div や p などの通常のテキストと同じフォントにしてるのを見かけました
また input と textarea でフォントを分けず まとめて同じ可変幅のフォントを設定していたりです
たしかに分ける必要って特に無いと思いますし 等幅じゃなくて困ったということもありません

とりあえず本文と同じ Meiryo でいいかなと思いました



この考えでいくと テキストエディタも可変幅でも良さそうに思えてきました
プログラムで 「=」 や 「:」 や 「//」 の位置など 行頭以外の場所を縦に揃えたがる人は抵抗がありそうですが 行頭以外は基本揃える必要ないって私からすると別に問題無い気もします
考えてみると 可変幅になってる textarea でコードを書くことって普通にあります
「見直してみたら可変幅だね ここ」 くらいに気にしてなかったです
また 日本語がない半角フォントが設定されていて 半角のみ等幅になっていて 日本語が入ると可変幅になるところもありました
フォントの違いが気になりはしますが 一時的なところなら実害はないので放置でした

VSCode のフォントを Meiryo にしてもいいかななんて思いましたが 考えてみたらプログラミング用フォントは等幅なだけではなく 紛らわしい文字の差別化もしてくれてるのですよね
やっぱりプログラミング用フォントを使うのが無難そうです

他には 等幅のほうがパフォーマンス的にも優れてそうな気はしました
文字関係なく幅が固定のほうが事前にサイズ計算ができて重たいファイルを開いて画面に表示するときは差が出そうです
と思いましたが等幅でも半角全角が存在しますし 絵文字みたいなところでは完全に等幅になっていないです
それらも考慮しないといけないので あまり変わらないかもです

あとは 矩形選択したいようなものを扱うときでしょうか
ただこれはマルチカーソル機能があればそっちのほうが高度なので 重要でないかもしれません

どちらかというと SQL の結果など 表形式のテキストを貼り付けてきれいに見えないほうが困りそうです
state と input のフォーマットをどこで揃えるか
DOM 外に state としてフォームの入力を保持する系ライブラリを使うとき今でも迷うところ

input は基本 値を文字列で扱います
type が number とか date でも value は文字列です
一応 valueAsNumber とか valueAsDate とかありはしますが 扱いづらいところがあるのでこれらは使いません
state 側では数値や Date 型など文字列以外で持っていることも少なくないです

そういうときにどうするかです

ひとつは input の入出力の際に毎回変換することです
value に渡すときに文字列に変換して 変更のイベント時に文字列から state の型に変換して state を更新します
それぞれの input の受け渡しのコードが長くなるのと 都度変換が必要なところが微妙です
日付型を文字列にフォーマットするくらいなら パフォーマンスに影響することはほとんどないと思いますが ユーザーの入力のたびにやるのってとてもムダに思えます
また 一度の再レンダリングで全部の input 分の変換を行うので 数が多い場合はパフォーマンスに影響無いとも言い切れないです
それに input は input そのものじゃなくてコンポーネントになってることもあります
その場合は変換がもっと重い処理のこともあります
別のオブジェクトを参照して内部の値を照合して とか

ベストな方法とは思えないなと思いつつ仕方ないか でやってる方法です

別の方法は フォーム用に state を用意するというものです
編集中として 編集開始時に state をコピーします
そのときそのままのコピーではなく input 用の表現に変換しておきます
そうすれば編集中 state は input と同じ型なので 編集中に変換不要です
state の値を そのまま input にセットして 変更後の値をそのまま state にセットできます
保存ボタンなど編集を完了するイベントで 編集中 state を本来の state に変換して更新します

変換は編集開始と終了の最小限なので こっちのほうが良さそうに思ってます
ただ state が増えるのはデメリットもあります
編集中に元 state が変わったときの扱いが難しくなります
全部新しい state で置き換えるならいいですが state 内の変更があったプロパティだけとなると自分で差分を見つけてそこだけを編集中 state に反映しないといけないので面倒です
React だと state 全体を更新だとしても変更を検知するための useEffect が必要になるのでローカル state を増やすこと自体が気が進まないです

また 編集中 state は基本的にそのフォーム内だけのものです
入力途中の state を参照したいとしてもほとんどの場合はそのフォーム内部の話です
なので問題なさそうに見えるのですが 稀にフォーム外の場所にも反映したいことがあります
例えば画面テーマの色を選べるとします
選んだ色をフォーム内でプレビューとして表示することができますが 実際にヘッダーとかサイドバーとかをその色にして確認したいなんてこともあります
そういうフォーム外にも編集中 state を反映したい場合は面倒が増えます

常にどっちの方がいいとは言えないので 未だにどっちにするか迷うところです
一応今のところは React は 1 つめの方法で lit (WebComponents) は 2 つめの方法にしてることが多めです

他にはフォームの機能によって変えてたりです
保存ボタンが存在しないフォームだと state を分けても都度 編集中 state を本来の state に反映することになって分ける意味がないです
なので 1 つめの方法です
キャンセルやリセットボタンがあるフォームなら 初期 state と編集中 state の 2 つを保持する必要があります
なので 2 つめの方法です
リセット機能はなく 保存ボタンだけがあるフォームだとどっちでもいいのでやっぱり迷います
ライブドアブログのカレンダーが
カレンダーがタグの input より z-index 小さくて裏側にいる
そのせいで一部隠れて日付が選べない場所がある
最近変更多いけど 何もしないでくれるのが一番なんだけど……

これに限らず カレンダーとか input 系って極力ブラウザデフォルトを使ってほしい
こういう問題が起きないのもそうだし 独自のってだいたい使いづらい
特に初めて使うとき

ブラウザデフォルトは基本のものだし多くのところで使われてる
多少使いづらくたって そのブラウザや OS を使ってる人からすれば一番慣れてるもの
細かな使い勝手よりも慣れが一番強いと思う

あとタブレットを使うとき
スマホ表示じゃなくて PC 表示にすることが多い
それでも入力はタッチなんだから入力操作は PC じゃなくてスマホと一緒にしてほしいと思うはず
こういうときライブラリじゃなくてブラウザデフォルト使ってるのが有利だと思う
form 内の要素が変更されたか調べる
input など入力系要素が変更されたか調べる時 changed や input イベントのリスナでフラグ更新するような方法を結構見ます

でも大抵の場合って 初期値と異なるかを見ればいいだけのはず
変更を繰り返した結果最初の値に戻ってるかもしれないし 変更イベントがあれば変更済み扱いにするよりは良い方法だと思います

ということで changed プロパティを作ってみました

Object.defineProperty(HTMLInputElement.prototype, "changed", {
get(){
if(this.type === "checkbox" || this.type === "radio"){
return this.checked !== this.hasAttribute("checked")
}else{
return this.value !== (this.getAttribute("value") || "")
}
},
})
Object.defineProperty(HTMLTextAreaElement.prototype, "changed", {
get(){
return this.value !== (this.getAttribute("value") || "")
},
})
Object.defineProperty(HTMLSelectElement.prototype, "changed", {
get(){
const initial = new Set(this.querySelectorAll(":scope>option[selected]"))
const actual = new Set(this.selectedOptions)

if(initial.size !== actual.size) return false
for(const item of initial){
if(!actual.has(item)) return false
}
return true
},
})

getter を使ってアクセスしたタイミングでの変更があるかを調べてます
属性が初期値でプロパティが現在値なのでそれを比較してるだけ

チェック系は checked の true/false の確認
select は選択中のセットが等しいかの確認
他は value が等しいかの確認