Skip to content

Commit

Permalink
fix: use focus composable to conditionally apply keyboard navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
Rohan Bansal committed May 6, 2024
1 parent 09cce10 commit 909982c
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 21 deletions.
3 changes: 2 additions & 1 deletion aform/src/components/form/ADatePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const selectedDate = ref(new Date(date.value))
const currentMonth = ref<number>(selectedDate.value.getMonth())
const currentYear = ref<number>(selectedDate.value.getFullYear())
const currentDates = ref<number[]>([])
const adatepicker = ref<HTMLElement | null>(null)
onMounted(async () => {
populateMonth()
Expand Down Expand Up @@ -133,7 +134,7 @@ const monthAndYear = computed(() => {
// setup keyboard navigation
useKeyboardNav([
{
parent: 'table.adate',
parent: adatepicker,
selectors: 'td',
handlers: {
...defaultKeypressHandlers,
Expand Down
57 changes: 39 additions & 18 deletions utilities/src/composables/keyboard.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { onMounted, onBeforeUnmount } from 'vue'
import { useElementVisibility } from '@/composables/visibility'
import { type WatchStopHandle, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useFocusWithin } from '@vueuse/core'

import type { KeyboardNavigationOptions, KeypressHandlers } from 'types'
import { useElementVisibility } from '@/composables/visibility'

// helper functions
const isVisible = (element: HTMLElement) => {
Expand Down Expand Up @@ -330,43 +331,43 @@ export const defaultKeypressHandlers: KeypressHandlers = {
export function useKeyboardNav(options: KeyboardNavigationOptions[]) {
const getSelectors = (option: KeyboardNavigationOptions) => {
// get parent element
let $parent: Element | null = null
let $parent: HTMLElement | null = null
if (option.parent) {
if (typeof option.parent === 'string') {
$parent = document.querySelector(option.parent)
} else if (option.parent instanceof Element) {
} else if (option.parent instanceof HTMLElement) {
$parent = option.parent
} else {
$parent = option.parent.value
}
}

// generate a list of selector(s)
let selectors: Element[] = []
let selectors: HTMLElement[] = []

if (option.selectors) {
if (typeof option.selectors === 'string') {
selectors = $parent
? Array.from($parent.querySelectorAll(option.selectors))
: Array.from(document.querySelectorAll(option.selectors))
} else if (option.selectors instanceof Element) {
} else if (option.selectors instanceof HTMLElement) {
selectors.push(option.selectors)
} else {
if (Array.isArray(option.selectors.value)) {
for (const element of option.selectors.value) {
if (element instanceof Element) {
if (element instanceof HTMLElement) {
selectors.push(element)
} else {
selectors.push(element.$el as Element)
selectors.push(element.$el as HTMLElement)
}
}
} else {
selectors.push(option.selectors.value)
}
}
} else {
const $children = Array.from($parent.children)
selectors = $children.filter((selector: HTMLElement) => {
const $children = Array.from($parent.children) as HTMLElement[]
selectors = $children.filter(selector => {
// ignore elements not in the tab order or are not visible
return isFocusable(selector) && isVisible(selector)
})
Expand Down Expand Up @@ -420,21 +421,41 @@ export function useKeyboardNav(options: KeyboardNavigationOptions[]) {
}
}

const watchStopHandlers: WatchStopHandle[] = []
onMounted(() => {
for (const option of options) {
const selectors = getSelectors(option)
for (const selector of selectors) {
selector.addEventListener('keydown', getEventListener(option))
// get parent element
let $parent: HTMLElement | null = null
if (option.parent) {
if (typeof option.parent === 'string') {
$parent = document.querySelector(option.parent)
} else if (option.parent instanceof HTMLElement) {
$parent = option.parent
} else {
$parent = option.parent.value
}
}

if ($parent) {
const { focused } = useFocusWithin(ref($parent))
const stopHandler = watch(focused, value => {
const selectors = getSelectors(option)
for (const selector of selectors) {
if (value) {
selector.addEventListener('keydown', getEventListener(option))
} else {
selector.removeEventListener('keydown', getEventListener(option))
}
}
})
watchStopHandlers.push(stopHandler)
}
}
})

onBeforeUnmount(() => {
for (const option of options) {
const selectors = getSelectors(option)
for (const selector of selectors) {
selector.removeEventListener('keydown', getEventListener(option))
}
for (const stopHandler of watchStopHandlers) {
stopHandler()
}
})
}
4 changes: 2 additions & 2 deletions utilities/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type KeypressHandlers = {
}

export type KeyboardNavigationOptions = {
parent?: string | Element | Ref<Element>
selectors?: string | Element | Ref<Element> | Ref<Element[]> | Ref<ComponentPublicInstance[]>
parent?: string | HTMLElement | Ref<HTMLElement>
selectors?: string | HTMLElement | Ref<HTMLElement> | Ref<HTMLElement[]> | Ref<ComponentPublicInstance[]>
handlers?: KeypressHandlers
}

0 comments on commit 909982c

Please sign in to comment.