summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaksim Sukharev <antreesy.web@gmail.com>2024-03-23 14:56:07 +0100
committerMaksim Sukharev <antreesy.web@gmail.com>2024-03-27 09:31:56 +0100
commit464426a24da2b5310f0dd190f8fafd79506cd629 (patch)
treefbe65ac798b5d27ce6a8c6ce062db1b2424824b1
parent6554cde654f14ded4855caaa68522824cc9f623c (diff)
fix(useArrowNavigation): detach navigation from using class selector
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
-rw-r--r--src/components/LeftSidebar/LeftSidebar.vue2
-rw-r--r--src/components/NewConversationDialog/NewConversationContactsPage.vue2
-rw-r--r--src/composables/useArrowNavigation.js47
3 files changed, 37 insertions, 14 deletions
diff --git a/src/components/LeftSidebar/LeftSidebar.vue b/src/components/LeftSidebar/LeftSidebar.vue
index 5783d53f1..abb1ba9d8 100644
--- a/src/components/LeftSidebar/LeftSidebar.vue
+++ b/src/components/LeftSidebar/LeftSidebar.vue
@@ -400,7 +400,7 @@ export default {
const federationStore = useFederationStore()
const talkHashStore = useTalkHashStore()
- const { initializeNavigation, resetNavigation } = useArrowNavigation(leftSidebar, searchBox, '.list-item')
+ const { initializeNavigation, resetNavigation } = useArrowNavigation(leftSidebar, searchBox)
const isMobile = useIsMobile()
return {
diff --git a/src/components/NewConversationDialog/NewConversationContactsPage.vue b/src/components/NewConversationDialog/NewConversationContactsPage.vue
index 1e357f38d..4c54dc8a2 100644
--- a/src/components/NewConversationDialog/NewConversationContactsPage.vue
+++ b/src/components/NewConversationDialog/NewConversationContactsPage.vue
@@ -131,7 +131,7 @@ export default {
const wrapper = ref(null)
const setContacts = ref(null)
- const { initializeNavigation, resetNavigation } = useArrowNavigation(wrapper, setContacts, '.participant-row')
+ const { initializeNavigation, resetNavigation } = useArrowNavigation(wrapper, setContacts)
return {
initializeNavigation,
diff --git a/src/composables/useArrowNavigation.js b/src/composables/useArrowNavigation.js
index b9e438cd9..7feed482d 100644
--- a/src/composables/useArrowNavigation.js
+++ b/src/composables/useArrowNavigation.js
@@ -21,11 +21,12 @@
import { computed, onMounted, ref, unref } from 'vue'
+/* selector to check, if item element or its children are focusable */
+const focusableCondition = 'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])'
+
/**
* Mount navigation according to https://www.w3.org/WAI/GL/wiki/Using_ARIA_menus
- * Item elements should have:
- * - specific valid CSS selector (tag, class or another attribute)
- * - unique "data-nav-id" attribute (on element or its parent, if it's not possible to pass it through the wrapper)
+ * Item elements should have unique "data-nav-id" attribute
*
* Controls:
* - ArrowDown or ArrowUp keys - to move through the itemElements list
@@ -36,11 +37,10 @@ import { computed, onMounted, ref, unref } from 'vue'
*
* @param {import('vue').Ref | HTMLElement} listElementRef component ref to mount navigation
* @param {import('vue').Ref} defaultElementRef component ref to return focus to // Vue component
- * @param {string} selector native selector of elements to look for
* @param {object} options navigation options
* @param {boolean} [options.confirmEnter=false] flag to confirm Enter click
*/
-export function useArrowNavigation(listElementRef, defaultElementRef, selector, options = { confirmEnter: false }) {
+export function useArrowNavigation(listElementRef, defaultElementRef, options = { confirmEnter: false }) {
const listRef = ref(null)
const defaultRef = ref(null)
@@ -50,17 +50,29 @@ export function useArrowNavigation(listElementRef, defaultElementRef, selector,
*/
const itemElements = ref([])
const itemElementsIdMap = computed(() => itemElements.value.map(item => {
- return item.getAttribute('data-nav-id') || item.parentElement.getAttribute('data-nav-id')
+ return item.getAttribute('data-nav-id')
}))
- const itemSelector = ref(selector)
const focusedIndex = ref(null)
const isConfirmationEnabled = ref(null)
+ const lookupNavId = (element) => {
+ if (element.hasAttribute('data-nav-id')) {
+ return element.getAttribute('data-nav-id')
+ }
+ // Find parent element with data-nav-id attribute
+ let parentElement = element.parentNode
+ while (parentElement && parentElement !== document.body) {
+ if (parentElement.hasAttribute('data-nav-id')) {
+ return parentElement.getAttribute('data-nav-id')
+ }
+ parentElement = parentElement.parentNode
+ }
+ }
+
// Set focused index according to selected element
const handleFocusEvent = (event) => {
- const newIndex = itemElementsIdMap.value.indexOf(event.target?.getAttribute('data-nav-id'))
-
+ const newIndex = itemElementsIdMap.value.indexOf(lookupNavId(event.target))
// Quit if triggered by arrow navigation as already handled
// or if using Tab key to navigate, and going through NcActions
if (focusedIndex.value !== newIndex && newIndex !== -1) {
@@ -104,7 +116,7 @@ export function useArrowNavigation(listElementRef, defaultElementRef, selector,
* Put a listener for focus/blur events on navigation area
*/
function initializeNavigation() {
- itemElements.value = Array.from(listRef.value?.querySelectorAll(itemSelector.value))
+ itemElements.value = Array.from(listRef.value?.querySelectorAll('[data-nav-id]'))
focusedIndex.value = null
listRef.value?.addEventListener('focus', handleFocusEvent, true)
@@ -125,11 +137,22 @@ export function useArrowNavigation(listElementRef, defaultElementRef, selector,
/**
* Focus natively the DOM element specified by index
*
- * @param {object} index the item index
+ * @param {number} index the item index
*/
function nativelyFocusElement(index) {
focusedIndex.value = index
- itemElements.value[index].focus()
+ const itemElement = itemElements.value[index]
+
+ if (itemElement.matches(focusableCondition)) {
+ itemElement.focus()
+ return
+ }
+
+ try {
+ itemElement.querySelector(focusableCondition).focus()
+ } catch (e) {
+ console.warn('Nav element does not have any focusable children')
+ }
}
/**