11<script setup lang="ts">
2+ import { nextTick , onBeforeUnmount , onMounted , ref } from ' vue' ;
23import { useI18n } from ' vue-i18n' ;
34import AppBadge from ' ../../components/AppBadge.vue' ;
45import type { ContainerChoice } from ' ./securityViewTypes' ;
@@ -14,14 +15,82 @@ const emit = defineEmits<{
1415}>();
1516
1617const { t } = useI18n ();
18+ const modalRoot = ref <HTMLElement | null >(null );
19+ let previouslyFocusedElement: HTMLElement | null = null ;
20+
21+ const focusableSelector = [
22+ ' a[href]' ,
23+ ' button:not([disabled])' ,
24+ ' input:not([disabled])' ,
25+ ' select:not([disabled])' ,
26+ ' textarea:not([disabled])' ,
27+ ' [tabindex]:not([tabindex="-1"])' ,
28+ ].join (' ,' );
29+
30+ function getFocusableElements() {
31+ return Array .from (modalRoot .value ?.querySelectorAll <HTMLElement >(focusableSelector ) ?? []).filter (
32+ (element ) => element .tabIndex >= 0 ,
33+ );
34+ }
35+
36+ function focusFirstElement() {
37+ getFocusableElements ()[0 ]?.focus ();
38+ }
39+
40+ function handleModalKeydown(event : KeyboardEvent ) {
41+ if (event .key !== ' Tab' ) {
42+ return ;
43+ }
44+
45+ const focusableElements = getFocusableElements ();
46+ if (focusableElements .length === 0 ) {
47+ event .preventDefault ();
48+ modalRoot .value ?.focus ();
49+ return ;
50+ }
51+
52+ const firstElement = focusableElements [0 ];
53+ const lastElement = focusableElements .at (- 1 );
54+ if (! lastElement ) {
55+ return ;
56+ }
57+
58+ if (event .shiftKey && document .activeElement === firstElement ) {
59+ event .preventDefault ();
60+ lastElement .focus ();
61+ return ;
62+ }
63+
64+ if (! event .shiftKey && document .activeElement === lastElement ) {
65+ event .preventDefault ();
66+ firstElement .focus ();
67+ }
68+ }
69+
70+ onMounted (() => {
71+ previouslyFocusedElement =
72+ document .activeElement instanceof HTMLElement ? document .activeElement : null ;
73+ void nextTick (() => focusFirstElement ());
74+ });
75+
76+ onBeforeUnmount (() => {
77+ if (previouslyFocusedElement && document .body .contains (previouslyFocusedElement )) {
78+ previouslyFocusedElement .focus ();
79+ }
80+ });
1781 </script >
1882
1983<template >
2084 <Teleport to =" body" >
2185 <div
86+ ref =" modalRoot"
2287 class =" fixed inset-0 z-overlay"
88+ role =" dialog"
89+ aria-modal =" true"
90+ tabindex =" -1"
2391 @pointerdown.self =" emit('close')"
24- @keydown.escape =" emit('close')" >
92+ @keydown.escape =" emit('close')"
93+ @keydown =" handleModalKeydown" >
2594 <div
2695 class =" fixed left-1/2 top-1/3 -translate-x-1/2 w-full max-w-xs mx-4 dd-rounded-lg overflow-hidden shadow-lg"
2796 :style =" {
0 commit comments