autocapitalize属性をcontenteditableへ転送しAndroidテキスト置換を回避
Lexxyエディタでハードコードされていた autocapitalize="none" を除去し、lexxy-editor[autocapitalize] の値を contenteditable 要素へ転送するようにしました。この変更により Android のテキスト置換バグを回避しつつ、属性の動的変更も正しく反映されます。
背景
従来、src/elements/editor.js はエディタ内部の contenteditable 要素に autocapitalize="none" を固定で付与していました。この固定値が Android のテキスト置換機構と衝突し、入力補正が期待通りに動作しないという報告がありました。さらに、開発者が <lexxy-editor autocapitalize="characters"> のように属性を設定しても、内部要素へ反映されないため属性の可視性が失われていました。これらの問題を解決するため、属性の転送と同期ロジックを導入する必要が生じました。
技術的な変更
observedAttributes 配列に "autocapitalize" が追加され、エレメントが属性変更を監視できるようになりました。
- static observedAttributes = [ "connected", "required" ]
+ static observedAttributes = [ "autocapitalize", "connected", "required" ]
attributeChangedCallback は、属性名に対応する *ChangedCallback メソッドが存在すれば呼び出す汎用ロジックへ置き換えられました。これにより connected や required だけでなく、将来的な属性追加にも同一パターンで対応できます。
- if (name === "connected") this.connectedChangedCallback(oldValue, newValue)
- if (name === "required") this.requiredChangedCallback(oldValue, newValue)
+ if (typeof this[`${name}ChangedCallback`] === "function") {
+ this[`${name}ChangedCallback`](oldValue, newValue)
+ }
新たに autocapitalizeChangedCallback が実装され、editorContentElement が存在すれば #transferAttributeToContentEditable を呼び出して属性を転送します。
autocapitalizeChangedCallback() {
if (this.editorContentElement) {
this.#transferAttributeToContentEditable(this.editorContentElement, "autocapitalize")
}
}
#createEditorContentElement からはハードコードされた autocapitalize="none" が削除され、代わりに属性転送ロジックが組み込まれました。tabindex についてもデフォルト 0 を設定しつつ、属性を同様に転送します。
- contenteditable: true,
- autocapitalize: "none",
+ contenteditable: true,
@@
- editorContentElement.id = `${this.id}-content`
+ this.#transferAttributeToContentEditable(editorContentElement, "autocapitalize")
+ this.#transferAttributeToContentEditable(editorContentElement, "tabindex", { defaultValue: 0, removeSource: true })
テスト用 fixture attribute-transfer.html と新規テスト attribute_transfer.test.js が追加され、属性の転送・同期・削除が期待通りに動作することを自動検証します。テストは Playwright を用いてエディタと内部コンテンツ要素の属性状態を確認し、動的な setAttribute / removeAttribute もカバーします。
設計判断
属性変更時の処理を 汎用コールバックディスパッチ に統一したことにより、個別の if 分岐を排除しコードの拡張性が向上しました。新しい属性を追加する際は observedAttributes に列挙し、*ChangedCallback を実装すれば自動的に呼び出されます。
autocapitalize と tabindex の転送は、#transferAttributeToContentEditable という単一メソッドに集約されました。これにより属性の同期ロジックが集中管理され、将来的な変更が局所的に抑えられます。また、tabindex のデフォルトを 0 に設定しつつ元属性を削除するオプションを提供することで、フォーカス制御の一貫性を保ちました。
この設計は既存の API(autocapitalize を明示的に設定しなくても動作する)を壊さず、Android のバグ回避という実装上の目的を達成すると同時に、属性拡張のためのスケーラブルな枠組みを提供しています。
まとめ
autocapitalize をハードコードから解放し、属性転送と同期を実装したことで Android 環境でのテキスト置換問題が解消されました。汎用的な属性コールバックと転送ロジックにより、今後の属性追加も最小変更で対応可能となり、エディタの拡張性が向上しています。