インライン編集
stimeo--editable
その場で書き換える編集。クリックか F2 で始め、Enter で保存、Esc で取り消します。
表示されている文字を、その場で書き換えられるようにします。クリックか F2 で編集に入り、入力欄にフォーカスが移って中身が選ばれます。Enter で保存し、Esc で取り消します。長い文章のときは Ctrl か Command と Enter で保存します。どちらの場合も、終わればフォーカスは元の表示へ戻ります。ほかの場所をクリックしたときも保存しますが、行き先が編集の中のボタンなら編集を続けます。保存と取り消しはボタンからも行えるので、キーボードを使わなくても同じことができます。値が実際に変わったときだけイベントが出ます。編集中かどうかは CSS から読めるので、見た目は自由に作れます。
キーボード操作
| キー | 動作 |
|---|---|
| Enter / Space / F2 | 表示要素から編集モードへ入る。 |
| Enter | 保存(単一行)。textarea では改行を挿入。 |
| Ctrl+Enter / ⌘+Enter | 複数行 textarea の編集を保存。 |
| Esc | 変更を破棄して表示モードへ戻す。 |
<%# Markup for the editable (inline editing) demo.
Click / Enter / Space / F2 on the display element (a button) enters edit mode, focusing
the input and selecting all. The library handles mode switching (data-mode), focus /
select-all, and the commit event; the look lives in demo.css.
Two variants are shown:
1. Single-line <input> with Save / Cancel buttons: Enter saves, Escape cancels, and
the buttons reach the same paths without a keyboard. Focus moving to a button
inside the editor does not commit, so Cancel really cancels.
2. Multiline <textarea>: Enter inserts a newline, Ctrl+Enter saves, Escape cancels.
submitOnBlur=true also saves on blur for both, when focus leaves the editor. %>
<div class="editable-demo">
<%# 1. Single-line inline edit. %>
<div class="editable" data-controller="stimeo--editable"
data-stimeo--editable-submit-on-blur-value="true">
<span class="editable__hint"><%= t("components.editable.demo.hint") %></span>
<button
type="button"
class="editable__display"
aria-label="<%= t("components.editable.demo.aria_label") %>"
data-placeholder="<%= t("components.editable.demo.placeholder") %>"
data-stimeo--editable-target="display"
data-action="click->stimeo--editable#edit keydown->stimeo--editable#onDisplayKeydown">
<%= t("components.editable.demo.value") %>
</button>
<input
type="text"
class="editable__input"
hidden
aria-label="<%= t("components.editable.demo.aria_label") %>"
data-stimeo--editable-target="input"
data-action="keydown->stimeo--editable#onKeydown" />
<%# Only meaningful while editing; demo.css hides the row in display mode. %>
<div class="editable__controls">
<button
type="button"
class="demo-trigger demo-trigger--primary"
data-action="click->stimeo--editable#save"><%= t(
"components.editable.demo.save"
) %></button>
<button
type="button"
class="demo-trigger"
data-action="click->stimeo--editable#cancel"><%= t(
"components.editable.demo.cancel"
) %></button>
</div>
</div>
<%# 2. Multiline inline edit. The controller detects the <textarea> and treats Enter as a
newline, so saving is Ctrl+Enter (or blur). The display uses white-space: pre-line so
saved line breaks render. %>
<div class="editable" data-controller="stimeo--editable"
data-stimeo--editable-submit-on-blur-value="true">
<span class="editable__hint"><%= t("components.editable.demo.multiline.hint") %></span>
<button
type="button"
class="editable__display editable__display--multiline"
aria-label="<%= t("components.editable.demo.multiline.aria_label") %>"
data-placeholder="<%= t("components.editable.demo.multiline.placeholder") %>"
data-stimeo--editable-target="display"
data-action="click->stimeo--editable#edit
keydown->stimeo--editable#onDisplayKeydown"><%= t(
"components.editable.demo.multiline.value"
) %></button>
<textarea
class="editable__input editable__input--multiline"
hidden
rows="3"
aria-label="<%= t("components.editable.demo.multiline.aria_label") %>"
data-stimeo--editable-target="input"
data-action="keydown->stimeo--editable#onKeydown"></textarea>
</div>
</div>
/*
* Presentation-only styles for the editable demo.
* The library switches modes via data-mode and each element's hidden attribute.
* Here we give the display element an "editable-looking" appearance and style the
* input while editing.
*/
.editable-demo {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
}
.editable {
display: inline-flex;
flex-direction: column;
gap: 0.35rem;
/* Fixed width so switching display <-> edit does not resize the column: the display
button is content-sized but the <input>/<textarea> is not, which otherwise made the
multiline variant's layout jump when it entered edit mode. */
width: 18rem;
}
.editable__hint {
font-size: 0.8125rem;
color: var(--color-text-muted);
}
.editable__display {
text-align: left;
/* Saving an empty value leaves the button with no text. Without a floor the
click target would collapse to its padding, so the only way back into edit
mode would be a sliver of a strip. The floor matches the control it stands
in for, keeping the column the same height in both modes. */
min-height: 2.4rem;
padding: 0.4rem 0.6rem;
border: 1px dashed transparent;
border-radius: 0.375rem;
background: transparent;
color: var(--fg, var(--color-text));
font: inherit;
font-weight: 600;
cursor: text;
}
.editable__display:hover {
border-color: var(--border-strong);
background: var(--surface-subtle);
}
.editable__display:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* Multiline display: render saved line breaks (textContent keeps the newlines). */
.editable__display--multiline {
white-space: pre-line;
/* Stands in for a three-row textarea, so entering edit mode does not jump. */
min-height: 5.4rem;
}
/* An emptied value still needs to say what it is and invite the next edit. The
wording is authored per locale and read from the attribute, so this rule stays
free of copy. */
.editable__display:empty::before {
content: attr(data-placeholder);
color: var(--color-text-muted);
font-weight: 400;
font-style: italic;
}
.editable__input {
width: 100%;
box-sizing: border-box;
padding: 0.4rem 0.6rem;
border: 1px solid var(--accent, var(--color-primary));
border-radius: 0.375rem;
font: inherit;
color: var(--fg, var(--color-text));
}
.editable__input--multiline {
resize: vertical;
}
/* Save / Cancel belong to edit mode only; data-mode is the library's hook for that. */
.editable__controls {
display: flex;
gap: 0.5rem;
}
.editable[data-mode="display"] .editable__controls {
display: none;
}
このデモに固有の消費側 JS はありません(挙動はコントローラが担います)。
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--editable"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
display
必須
|
表示モードで見せる要素(通常はボタン)。起動すると編集モードに入る。 | data-stimeo--editable-target="display" |
input
必須
|
編集モードで表示し、編集中の値を保持する input または textarea。 | data-stimeo--editable-target="input" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
submitOnBlur
|
true(既定)のとき編集中のフォーカス喪失で保存し、false のとき編集を維持する。 | data-stimeo--editable-submit-on-blur-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
cancel
|
編集を破棄して表示モードへ戻し、cancel を発火する。 | stimeo--editable#cancel |
edit
|
編集モードに入る。宣言された値を input に設定し、フォーカスして全選択する。 | stimeo--editable#edit |
onDisplayKeydown
|
表示要素での F2 を編集モードへの追加の起点として処理する。 | stimeo--editable#onDisplayKeydown |
onKeydown
|
編集中、Enter(複数行では Ctrl/Cmd+Enter)で確定し、Escape で取り消す。 | stimeo--editable#onKeydown |
revert
|
直前の保存が置き換えた値へ 1 度だけ戻す。イベントは発火しない。 | stimeo--editable#revert |
save
|
編集を確定し、表示要素へフォーカスを戻す。 | stimeo--editable#save |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
cancel
|
Escape で編集を取り消したときに発火し、変更を破棄する。 | stimeo--editable:cancel |
change
|
保存成功時、値が変化した場合のみ発火。detail に { value, previous } を載せる。 |
stimeo--editable:change |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
data-mode |
コントローラ要素 | "display" / "editing"。 |
hidden |
表示 / 入力 | 現在のモードで非アクティブな方に付与。実行時に target が差し替わっても導出し直す。 |
data-value |
表示 | authored なら値の保存先。保存で更新し、描画済みテキストには触れない。 |