前置き
みなさん、タブインデックス気にしてますか!?
要素の追加順に自動で付与されるので基本は気にしなくても
テストしたら意図通りだった、もしくはフォーカスしてほしくない
ものに-1をつけるだけがほとんどだと思います。
しかし気にしないといけない時はやってくるもので。。
実装したいもの
- 表示されている物 + 更新などブラウザのボタンの中で
フォーカスがループすること - 表示されていないものまたはモーダルの裏にフォーカスが吸われないこと
- 他ファイルの事情を把握しないこと
(ファイルBがファイルAにある要素をID等で取得するなど)
前提
- 画面構成、ファイル構成は以下



実装
結論から書くとHTML属性 inertを付けることです!
HTMLElement: inert プロパティ - Web API | MDN
公式ドキュメントにあるように、親要素につけると
その配下にあるユーザー操作対象の要素にdisabled, select-noneと同等の効果を付与し
触れなくしてくれます。
違いとしては、disabled等の場合、明らかに押せない見た目になるのですが、
innertの場合見た目が変わらない点です。
cssでなんとでもなる場所ですね
innert 付与前
モーダルを表示後、タブを押せばわかるのですが、メイン画面側にタブが吸われます。。
(やりずらい場合は、htmlファイルに直書きして動かしてみてください
特殊な、ライブラリは未使用なので、styleタグ,scriptタグ直書きで行けます。)
inert 付与後
なんと当初の要望通り、フォーカスがループしました!!
page.tsxのwindowにインターフェースとなるイベントを定義してそれを各種ファイルで参照していますが、
まあ許容範囲でしょう(これ以外思いつかない。。)
React(Next.js)では
以下のような、Contextを作成しモーダル画面側と呼び出し側で
useContextしてTabOfOutModalのinvalid,valid関数を呼び出してます。
useContextの使い方はチュートリアルや記事をご覧ください。
関数を格納してる見たことないですが
英語が苦手な私としてはinertは覚えられる気がしなかったので英語を変更してます。
window.dispatchEventがcontextの関数に置き換わっただけなので、仕組みは一緒です。
const [modal, setModal] : [modal:ReactElement, setModal:Dispatch<SetStateAction<ReactElement>>] = useState(<></>);
const [inert, setInert] : [inert:boolean, setInert:Dispatch<SetStateAction<boolean>>] = useState(false);
const closeModal :() =>void = async ()=>{
setModal(<></>);
}
const showModal = async (attrs:IModalBase) => {
setModal(<ModalBase attrs={attrs} closer={closeModal}></ModalBase>);
}
const contextValue : GlobalContextAttrs = {
modal : {
show : showModal
,close : closeModal
}
,loading:{
show : async ()=>{showModal(GetLoadingModalAttrs({
closer : closeModal
}))}
,close : closeModal
}
,tabOfOutModal:{
invalid: ()=>setInert(true)
,vaild: ()=>setInert(false)
}
}
return (
<>
<GlobalContext.Provider value={contextValue}>
<div style={{height: 0}} inert={inert}>
{children}
</div>
{modal}
</GlobalContext.Provider>
</>
注意点
この機能自体2023年にリリースされたもので
主要ブラウザ(chrome, firefox, edge)は対応していますが、
IEは対応していなかったりするので、
Can I Useで対象ブラウザをしっかり確認してください。
いまだ、IEを用いる案件が存在するめんどくささ。。
あとがき
フロントエンドの進化は目覚ましいもので、この属性に出会ったときは感動を覚えました。
これがなければ、各種コンポーネントでタブ順リストを用意して、他コンポーネントに送って、もしnullだったら。。。などまあ頭抱えます。。
また、draw.ioが知らないうちに画像としてコピーができるようになってたんですね!!
今回の構成図画像はそれで張り付けてます。便利、便利♪
では、またお会いしましょう。