1
1
<template >
2
- <div class =" vue-focus-loop" >
2
+ <div
3
+ v-if =" isVisible"
4
+ ref =" VuefocusLoopContainer"
5
+ class =" vue-focus-loop"
6
+ >
3
7
<div
4
8
:tabindex =" getTabindex"
9
+ aria-hidden =" true"
5
10
@focus =" handleFocusStart"
6
11
/>
7
12
<div ref =" focusLoop" >
8
13
<slot />
9
14
</div >
10
15
<div
11
16
:tabindex =" getTabindex"
17
+ aria-hidden =" true"
12
18
@focus =" handleFocusEnd"
13
19
/>
14
20
</div >
@@ -24,6 +30,8 @@ const focusableElementsSelector = [
24
30
' [contenteditable]:not([contenteditable="false"])'
25
31
].join (' ,' )
26
32
33
+ let ariaHiddenElements = []
34
+
27
35
export default {
28
36
name: ' FocusLoop' ,
29
37
@@ -55,41 +63,70 @@ export default {
55
63
},
56
64
57
65
watch: {
58
- isVisible (val ) {
59
- this .managePrevFocusElement (val)
60
- this .focusFirst (val)
61
- }
66
+ isVisible: ' init' ,
67
+ disabled: ' init'
62
68
},
63
69
64
70
mounted () {
65
- this .managePrevFocusElement (this .isVisible )
66
- this .focusFirst (this .isVisible )
67
- },
68
-
69
- beforeDestroy () {
70
- this .managePrevFocusElement (false )
71
+ this .init ()
71
72
},
72
73
73
74
methods: {
74
- managePrevFocusElement (visible ) {
75
- if (! visible && window .vflPrevFocusedElement ) {
75
+ init () {
76
+ this .$nextTick (() => {
77
+ const active = this .isVisible && ! this .disabled
78
+ ! this .disabled && this .focusFirst (active && this .autoFocus )
79
+ this .managePrevFocusElement (active)
80
+ this .lockForSwipeScreenReader (active)
81
+ if (! active) {
82
+ ariaHiddenElements = []
83
+ }
84
+ })
85
+ },
86
+
87
+ managePrevFocusElement (active ) {
88
+ if (! active && window .vflPrevFocusedElement ) {
76
89
return window .vflPrevFocusedElement .focus ()
77
90
}
78
91
window .vflPrevFocusedElement = document .activeElement
79
92
},
80
93
94
+ getElementsToAriaHidden (focusLoopContainer ) {
95
+ function getElements (element ) {
96
+ const children = Array .from (element .children )
97
+ children .forEach (el => {
98
+ if (el === focusLoopContainer) return
99
+ if (! el .contains (focusLoopContainer)) {
100
+ ariaHiddenElements .push (el)
101
+ return
102
+ }
103
+ getElements (el)
104
+ })
105
+ }
106
+ getElements (document .body )
107
+ },
108
+
109
+ lockForSwipeScreenReader (active = true ) {
110
+ if (active) this .getElementsToAriaHidden (this .$refs .VuefocusLoopContainer )
111
+ ariaHiddenElements .forEach (el => {
112
+ if ([' SCRIPT' , ' STYLE' ].includes (el .nodeName ) || el .hasAttribute (' aria-live' )) return
113
+ el .setAttribute (' aria-hidden' , active .toString ())
114
+ })
115
+ },
116
+
117
+ focusFirst (isAutoFocus ) {
118
+ if (isAutoFocus) {
119
+ const elements = this .getFocusableElements ()
120
+ if (elements .length ) setTimeout (() => elements[0 ].focus (), 200 )
121
+ }
122
+ },
123
+
81
124
getFocusableElements () {
82
125
const focusableElements = this .$refs .focusLoop .querySelectorAll (focusableElementsSelector)
83
126
if (focusableElements && focusableElements .length ) return focusableElements
84
127
return []
85
128
},
86
129
87
- focusFirst (visible ) {
88
- if (! visible && ! this .autoFocus ) return
89
- const elements = this .getFocusableElements ()
90
- if (elements .length ) setTimeout (() => elements[0 ].focus (), 200 )
91
- },
92
-
93
130
handleFocusStart () {
94
131
const elements = this .getFocusableElements ()
95
132
if (elements .length ) {
0 commit comments