ツールバーの非表示アイテムを考慮したフォーカス管理の修正
ツールバーの最初のアイテムが非表示の場合に Shift+Tab でフォーカスが当たらなくなっていたバグを修正しました。あわせて、可視性チェック関数 isActiveAndVisible をヘルパーモジュールに集約し、再利用可能な形に整理しています。
背景
ツールバーは tabindex="0" を持つアイテムをキーボード操作のエントリポイントとして使用しており、エディタにフォーカスが当たった際に最初のフォーカス可能アイテムへ tabIndex = 0 を設定する仕組みになっていました。しかし、この処理が先頭インデックスのアイテムを無条件に対象としていたため、そのアイテムが非表示の場合はタブキーによるツールバーへのアクセスができない状態になっていました。
Shift+Tab でのキーボードナビゲーションはアクセシビリティ上の重要な要件であり、非表示アイテムに tabindex="0" が設定されるとフォーカスリングが見えないままフォーカスが移動する、あるいはフォーカスが移動しないという問題が生じます。
技術的な変更
#handleEditorFocus メソッドの修正により、最初の「表示されており、かつ有効な」アイテムを探してからフォーカスを設定するようになりました。
変更前:
#handleEditorFocus = () => {
this.#focusableItems[0].tabIndex = 0
}
変更後:
#handleEditorFocus = () => {
const firstVisible = this.#focusableItems.find(isActiveAndVisible)
if (firstVisible) firstVisible.tabIndex = 0
}
Array.prototype.find と isActiveAndVisible を組み合わせることで、disabled でなく checkVisibility() が true を返す最初のアイテムを特定します。対象が見つからない場合のガード(if (firstVisible))も加わり、全アイテムが非表示のエッジケースにも対応しています。
あわせて、isActiveAndVisible 関数の配置が整理されました。もともと src/helpers/accessibility_helper.js 内にプライベート関数として定義されていましたが、src/helpers/html_helper.js にエクスポート関数として移動し、accessibility_helper.js と toolbar.js の両方から import する形に変更されています。
export function isActiveAndVisible(element) {
return element && !element.disabled && element.checkVisibility()
}
設計判断
isActiveAndVisible の共通化が、このPRのもう一つの要点です。
修正前、同じロジックが accessibility_helper.js のモジュールスコープにプライベート関数として閉じていました。toolbar.js から同じ判定を行うためには関数を複製するか、モジュールをまたいで参照できる形に変更する必要がありました。今回は後者を選択し、HTML要素の汎用的な状態判定として html_helper.js に配置することで、ツールバー固有の処理とアクセシビリティ処理の双方から参照できる構造になっています。checkVisibility() はCSSによる非表示(display: none、visibility: hidden)を考慮したAPIであり、単純な hidden 属性チェックより堅牢な判定が可能です。
関数の移動先として html_helper.js を選んだことは、「要素の可視性と活性状態の判定はHTML要素の汎用ユーティリティである」という意味づけを明示しており、将来同様の判定が必要な箇所でも同じ関数を再利用できます。
まとめ
本PRは、ツールバーのキーボードアクセシビリティにおける先頭アイテム依存を排除し、表示状態を考慮した動的なフォーカス管理へ移行した修正です。バグ修正と同時に isActiveAndVisible を共通ヘルパーへ昇格させることで、コードの重複を排除し、同じ判定ロジックを必要とする将来の実装への再利用性も確保しています。