11<template >
2- <div :class =" { 'active': showDropdown, 'share-select': true }" ref =" quickShareDropdown" >
3- <span class =" trigger-text" @click =" toggleDropdown" >
2+ <div ref =" quickShareDropdownContainer"
3+ :class =" { 'active': showDropdown, 'share-select': true }"
4+ tabindex =" 0"
5+ @keydown.down =" handleArrowDown"
6+ @keydown.up =" handleArrowUp" >
7+ <span :id =" dropdownId"
8+ class =" trigger-text"
9+ :aria-expanded =" showDropdown"
10+ :aria-haspopup =" true"
11+ aria-label =" Quick share options dropdown"
12+ @click =" toggleDropdown" >
413 {{ selectedOption }}
514 <DropdownIcon :size =" 15" />
615 </span >
7- <div v-if =" showDropdown" class =" share-select-dropdown-container" >
8- <div v-for =" option in options"
16+ <div v-if =" showDropdown"
17+ ref =" quickShareDropdown"
18+ class =" share-select-dropdown-container"
19+ :aria-labelledby =" dropdownId"
20+ tabindex =" 0"
21+ @keydown.esc =" closeDropdown" >
22+ <button v-for =" option in options"
923 :key =" option"
1024 :class =" { 'dropdown-item': true, 'selected': option === selectedOption }"
25+ tabindex =" 0"
26+ :aria-selected =" option === selectedOption"
1127 @click =" selectOption(option)" >
1228 {{ option }}
13- </div >
29+ </button >
1430 </div >
1531 </div >
1632</template >
@@ -26,6 +42,8 @@ import {
2642 ATOMIC_PERMISSIONS ,
2743} from ' ../lib/SharePermissionsToolBox.js'
2844
45+ import { createFocusTrap } from ' focus-trap'
46+
2947export default {
3048 components: {
3149 DropdownIcon,
@@ -45,6 +63,7 @@ export default {
4563 return {
4664 selectedOption: ' ' ,
4765 showDropdown: this .toggle ,
66+ focusTrap: null ,
4867 }
4968 },
5069 computed: {
@@ -102,6 +121,10 @@ export default {
102121 return BUNDLED_PERMISSIONS .READ_ONLY
103122 }
104123 },
124+ dropdownId () {
125+ // Generate a unique ID for ARIA attributes
126+ return ` dropdown-${ Math .random ().toString (36 ).substr (2 , 9 )} `
127+ },
105128 },
106129 watch: {
107130 toggle (toggleValue ) {
@@ -110,15 +133,26 @@ export default {
110133 },
111134 mounted () {
112135 this .initializeComponent ()
113- window .addEventListener (' click' , this .handleClickOutside );
136+ window .addEventListener (' click' , this .handleClickOutside )
114137 },
115138 beforeDestroy () {
116- // Remove the global click event listener to prevent memory leaks
117- window .removeEventListener (' click' , this .handleClickOutside );
118- },
139+ // Remove the global click event listener to prevent memory leaks
140+ window .removeEventListener (' click' , this .handleClickOutside )
141+ },
119142 methods: {
120143 toggleDropdown () {
121144 this .showDropdown = ! this .showDropdown
145+ if (this .showDropdown ) {
146+ this .$nextTick (() => {
147+ this ._useFocusTrap ()
148+ })
149+ } else {
150+ this ._clearFocusTrap ()
151+ }
152+ },
153+ closeDropdown () {
154+ this .showDropdown = false
155+ this ._clearFocusTrap ()
122156 },
123157 selectOption (option ) {
124158 this .selectedOption = option
@@ -134,12 +168,46 @@ export default {
134168 this .selectedOption = this .preSelectedOption
135169 },
136170 handleClickOutside (event ) {
137- const dropdownElement = this .$refs .quickShareDropdown ;
171+ const dropdownContainer = this .$refs .quickShareDropdownContainer
172+
173+ if (dropdownContainer && ! dropdownContainer .contains (event .target )) {
174+ this .showDropdown = false
175+ }
176+ },
177+ _useFocusTrap () {
178+ const dropdownElement = this .$refs .quickShareDropdown
179+ this .focusTrap = createFocusTrap (dropdownElement, {
180+ allowOutsideClick: true ,
181+ })
138182
139- if (dropdownElement && ! dropdownElement .contains (event .target )) {
140- this .showDropdown = false ;
183+ this .focusTrap .activate ()
184+ },
185+ _clearFocusTrap () {
186+ this .focusTrap ? .deactivate ()
187+ this .focusTrap = null
188+ },
189+ shiftFocusForward () {
190+ const currentElement = document .activeElement
191+ let nextElement = currentElement .nextElementSibling
192+ if (nextElement) {
193+ nextElement = this .$refs .quickShareDropdown .firstElementChild
194+ }
195+ nextElement .focus ()
196+ },
197+ shiftFocusBackward () {
198+ const currentElement = document .activeElement
199+ let previousElement = currentElement .previousElementSibling
200+ if (! previousElement) {
201+ previousElement = this .$refs .quickShareDropdown .lastElementChild
141202 }
142- },
203+ previousElement .focus ()
204+ },
205+ handleArrowUp () {
206+ this .shiftFocusBackward ()
207+ },
208+ handleArrowDown () {
209+ this .shiftFocusForward ()
210+ },
143211 },
144212
145213}
@@ -161,6 +229,8 @@ export default {
161229
162230 .share - select- dropdown- container {
163231 position: absolute;
232+ display: flex;
233+ flex- direction: column;
164234 top: 100 % ;
165235 left: 0 ;
166236 background- color: var (-- color- main- background);
@@ -172,6 +242,15 @@ export default {
172242 .dropdown - item {
173243 padding: 8px ;
174244 font- size: 12px ;
245+ background: none;
246+ border: none;
247+ border- radius: 0 ;
248+ font: inherit;
249+ cursor: pointer;
250+ color: inherit;
251+ outline: none;
252+ width: 100 % ;
253+ white- space: nowrap;
175254
176255 & : hover {
177256 background- color: #f2f2f2;
0 commit comments