<template>
- <div :class="{ 'active': showDropdown, 'share-select': true }" ref="quickShareDropdown">
- <span class="trigger-text" @click="toggleDropdown">
+ <div ref="quickShareDropdownContainer"
+ :class="{ 'active': showDropdown, 'share-select': true }">
+ <span :id="dropdownId"
+ class="trigger-text"
+ :aria-expanded="showDropdown"
+ :aria-haspopup="true"
+ aria-label="Quick share options dropdown"
+ @click="toggleDropdown">
{{ selectedOption }}
<DropdownIcon :size="15" />
</span>
- <div v-if="showDropdown" class="share-select-dropdown-container">
- <div v-for="option in options"
+ <div v-if="showDropdown"
+ ref="quickShareDropdown"
+ class="share-select-dropdown"
+ :aria-labelledby="dropdownId"
+ tabindex="0"
+ @keydown.down="handleArrowDown"
+ @keydown.up="handleArrowUp"
+ @keydown.esc="closeDropdown">
+ <button v-for="option in options"
:key="option"
:class="{ 'dropdown-item': true, 'selected': option === selectedOption }"
+ :aria-selected="option === selectedOption"
@click="selectOption(option)">
{{ option }}
- </div>
+ </button>
</div>
</div>
</template>
ATOMIC_PERMISSIONS,
} from '../lib/SharePermissionsToolBox.js'
+import { createFocusTrap } from 'focus-trap'
+
export default {
components: {
DropdownIcon,
return {
selectedOption: '',
showDropdown: this.toggle,
+ focusTrap: null,
}
},
computed: {
return BUNDLED_PERMISSIONS.READ_ONLY
}
},
+ dropdownId() {
+ // Generate a unique ID for ARIA attributes
+ return `dropdown-${Math.random().toString(36).substr(2, 9)}`
+ },
},
watch: {
toggle(toggleValue) {
},
mounted() {
this.initializeComponent()
- window.addEventListener('click', this.handleClickOutside);
+ window.addEventListener('click', this.handleClickOutside)
},
beforeDestroy() {
- // Remove the global click event listener to prevent memory leaks
- window.removeEventListener('click', this.handleClickOutside);
- },
+ // Remove the global click event listener to prevent memory leaks
+ window.removeEventListener('click', this.handleClickOutside)
+ },
methods: {
toggleDropdown() {
this.showDropdown = !this.showDropdown
+ if (this.showDropdown) {
+ this.$nextTick(() => {
+ this.useFocusTrap()
+ })
+ } else {
+ this.clearFocusTrap()
+ }
+ },
+ closeDropdown() {
+ this.clearFocusTrap()
+ this.showDropdown = false
},
selectOption(option) {
this.selectedOption = option
this.selectedOption = this.preSelectedOption
},
handleClickOutside(event) {
- const dropdownElement = this.$refs.quickShareDropdown;
+ const dropdownContainer = this.$refs.quickShareDropdownContainer
+
+ if (dropdownContainer && !dropdownContainer.contains(event.target)) {
+ this.showDropdown = false
+ }
+ },
+ useFocusTrap() {
+ // Create global stack if undefined
+ // Use in with trapStack to avoid conflicting traps
+ Object.assign(window, { _nc_focus_trap: window._nc_focus_trap || [] })
+ const dropdownElement = this.$refs.quickShareDropdown
+ this.focusTrap = createFocusTrap(dropdownElement, {
+ allowOutsideClick: true,
+ trapStack: window._nc_focus_trap,
+ })
- if (dropdownElement && !dropdownElement.contains(event.target)) {
- this.showDropdown = false;
+ this.focusTrap.activate()
+ },
+ clearFocusTrap() {
+ this.focusTrap?.deactivate()
+ this.focusTrap = null
+ },
+ shiftFocusForward() {
+ const currentElement = document.activeElement
+ let nextElement = currentElement.nextElementSibling
+ if (!nextElement) {
+ nextElement = this.$refs.quickShareDropdown.firstElementChild
+ }
+ nextElement.focus()
+ },
+ shiftFocusBackward() {
+ const currentElement = document.activeElement
+ let previousElement = currentElement.previousElementSibling
+ if (!previousElement) {
+ previousElement = this.$refs.quickShareDropdown.lastElementChild
}
- },
+ previousElement.focus()
+ },
+ handleArrowUp() {
+ this.shiftFocusBackward()
+ },
+ handleArrowDown() {
+ this.shiftFocusForward()
+ },
},
}
color: var(--color-primary-element);
}
- .share-select-dropdown-container {
+ .share-select-dropdown {
position: absolute;
+ display: flex;
+ flex-direction: column;
top: 100%;
left: 0;
background-color: var(--color-main-background);
.dropdown-item {
padding: 8px;
font-size: 12px;
+ background: none;
+ border: none;
+ border-radius: 0;
+ font: inherit;
+ cursor: pointer;
+ color: inherit;
+ outline: none;
+ width: 100%;
+ white-space: nowrap;
+ text-align: left;
&:hover {
background-color: #f2f2f2;
}
/* Optional: Add a transition effect for smoother dropdown animation */
- .share-select-dropdown-container {
+ .share-select-dropdown {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
- &.active .share-select-dropdown-container {
+ &.active .share-select-dropdown {
max-height: 200px;
/* Adjust the value to your desired height */
}