11<template >
2- <div class =" vue-focus-loop" >
2+ <div
3+ v-if =" isVisible"
4+ ref =" VuefocusLoopContainer"
5+ class =" vue-focus-loop"
6+ >
37 <div
48:tabindex =" getTabindex"
9+ aria-hidden =" true"
510@focus =" handleFocusStart"
611 />
712 <div ref =" focusLoop" >
813 <slot />
914 </div >
1015 <div
1116:tabindex =" getTabindex"
17+ aria-hidden =" true"
1218@focus =" handleFocusEnd"
1319 />
1420 </div >
@@ -24,6 +30,8 @@ const focusableElementsSelector = [
2430' [contenteditable]:not([contenteditable="false"])'
2531].join (' ,' )
2632
33+ let ariaHiddenElements = []
34+
2735export default {
2836 name: ' FocusLoop' ,
2937
@@ -55,41 +63,70 @@ export default{
5563 },
5664
5765 watch: {
58- isVisible (val ){
59- this .managePrevFocusElement (val)
60- this .focusFirst (val)
61- }
66+ isVisible: ' init' ,
67+ disabled: ' init'
6268 },
6369
6470mounted (){
65- this .managePrevFocusElement (this .isVisible )
66- this .focusFirst (this .isVisible )
67- },
68-
69- beforeDestroy (){
70- this .managePrevFocusElement (false )
71+ this .init ()
7172 },
7273
7374 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 ){
7689return window .vflPrevFocusedElement .focus ()
7790 }
7891window .vflPrevFocusedElement = document .activeElement
7992 },
8093
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+
81124getFocusableElements (){
82125const focusableElements = this .$refs .focusLoop .querySelectorAll (focusableElementsSelector)
83126if (focusableElements && focusableElements .length ) return focusableElements
84127return []
85128 },
86129
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-
93130handleFocusStart (){
94131const elements = this .getFocusableElements ()
95132if (elements .length ){
0 commit comments