入力マスク
stimeo--input-mask
固定パターンで入力欄を整形し、キャレット位置を保つ。
stimeo--input-mask コントローラは固定パターン(9=数字、a=英字、*=英数、その他はリテラル)に沿って入力欄をその場で整形します。入力に合わせて区切り文字を挿入し、トークンが許可しない文字は拒否し、そして要点として、挿入・Backspace・範囲置換のいずれでもキャレット位置を保ちます。表示用(マスク済み)と生値を分離し、同じフォーム内の hidden な [data-stimeo--input-mask-unmask] へ生の数字を同期するため、サーバには生値が届きます。トークンは設定可能で(指定キーが既定にマージ)、 connect はサーバ描画値を再整形して冪等、モジュールスコープ状態を持たないため Turbo の復元・morph でも安定します。data-mask-complete / data-mask-empty を反映し、stimeo--input-mask:change を発火します。金額整形は Currency Input の担当で、本パーツは汎用マスクです。ライブラリは挙動のみを提供します。
サーバへ送られる生値:
(空)
<%# Markup for the input mask demo.
The controller formats the field in place against the pattern (9=digit), inserts
the separators, rejects non-digits, preserves the caret, and syncs the raw value
to the hidden field. demo.js mirrors that hidden raw value (and the complete flag)
into the visible readout below, since the unmask field is hidden. %>
<div class="mask-demo">
<form class="mask-demo__form">
<label class="mask-demo__field">
<span><%= t("components.input_mask.demo.label") %></span>
<input type="text" class="demo-input" inputmode="numeric"
data-controller="stimeo--input-mask"
data-stimeo--input-mask-pattern-value="(999) 999-9999"
data-action="input->stimeo--input-mask#format"
aria-describedby="mask-hint"
placeholder="<%= t("components.input_mask.demo.placeholder") %>">
<input type="hidden" name="phone" data-stimeo--input-mask-unmask>
</label>
<p id="mask-hint" class="mask-demo__hint"><%= t("components.input_mask.demo.hint") %></p>
</form>
<p class="mask-demo__raw"
data-empty="<%= t("components.input_mask.demo.empty") %>"
data-complete="<%= t("components.input_mask.demo.complete") %>">
<%= t("components.input_mask.demo.raw_label") %>
<code data-mask-demo="raw"><%= t("components.input_mask.demo.empty") %></code>
<span data-mask-demo="status"></span>
</p>
</div>
/*
* Presentation-only styles for the input mask demo.
* The library formats the value, preserves the caret, and reflects
* data-mask-complete / data-mask-empty; this CSS owns layout and the readout, and
* can react to the completion flag for a finished-state affordance.
*/
.mask-demo {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-width: 28rem;
}
.mask-demo__field {
display: flex;
flex-direction: column;
gap: 0.25rem;
font-size: 0.9rem;
font-weight: 600;
}
/* The library marks a fully-filled mask with data-mask-complete. */
.mask-demo .demo-input[data-mask-complete] {
border-color: var(--leaf-500);
}
.mask-demo__hint {
margin: 0.25rem 0 0;
font-size: 0.8rem;
font-weight: 400;
color: var(--color-text-muted);
}
.mask-demo__raw {
margin: 0;
font-size: 0.9rem;
}
.mask-demo__raw code {
padding: 0.1rem 0.4rem;
border-radius: 0.25rem;
background: var(--surface-subtle);
font-family: ui-monospace, monospace;
}
// Consumer-side JS for the input mask demo (demo-only).
// The raw (unmasked) value the controller syncs lives in a hidden field, so this
// mirrors it into a visible readout from the change event, and shows a "complete"
// note when every slot is filled. Localized strings come from data attributes.
const readout = document.querySelector(".mask-demo__raw");
const raw = document.querySelector("[data-mask-demo='raw']");
const status = document.querySelector("[data-mask-demo='status']");
if (readout && raw) {
document.addEventListener("stimeo--input-mask:change", (event) => {
const { unmasked, complete } = event.detail;
raw.textContent = unmasked || readout.dataset.empty;
if (status) status.textContent = complete ? ` ${readout.dataset.complete}` : "";
});
}
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--input-mask"
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
pattern
|
マスクパターン(9 / a / * トークンとリテラル。既定は空)。 |
data-stimeo--input-mask-pattern-value |
tokens
|
プレースホルダ→正規表現ソースの対応。指定キーが既定にマージされる。 | data-stimeo--input-mask-tokens-value |
unmaskToHidden
|
生値を hidden フィールドへ同期するか(既定 true)。 |
data-stimeo--input-mask-unmask-to-hidden-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
format
|
キャレットを保ったまま整形する。input イベントに結線。 |
stimeo--input-mask#format |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
change
|
値が変化したとき発火。detail に masked・unmasked・complete を伴う。 |
stimeo--input-mask:change |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
data-mask-complete |
マスク対象の入力欄 | パターンの全トークンが埋まると "true"。 |
data-mask-empty |
マスク対象の入力欄 | 値が空のあいだ "true"(プレースホルダ表示制御など)。 |