Update favicon and enhance UI components for improved user experience
- Replaced PNG favicon with SVG for better scalability and visual quality across devices. - Updated Vite configuration to include the new SVG favicon and adjusted asset paths. - Enhanced various UI components with improved focus management and accessibility features. - Introduced new styles to hide scrollbars while maintaining scroll functionality for a cleaner interface.
This commit is contained in:
@@ -218,6 +218,16 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// My Apps: Enter = launch (click Launch button when app is runnable)
|
||||
if (el.hasAttribute('data-controller-launch')) {
|
||||
const launchBtn = el.querySelector<HTMLButtonElement>('[data-controller-launch-btn]:not([disabled])')
|
||||
if (launchBtn) {
|
||||
playNavSound('action')
|
||||
launchBtn.click()
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
}
|
||||
// My Apps, etc: Enter = focus first inner control
|
||||
const inner = getInnerFocusables(el)
|
||||
const firstInner = inner[0]
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Modal keyboard navigation: Escape to close, Arrow keys to move between buttons.
|
||||
* Restores focus to the previously active element when closing via Escape.
|
||||
*/
|
||||
|
||||
import { onMounted, onBeforeUnmount, watch, type Ref } from 'vue'
|
||||
|
||||
const FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
||||
|
||||
export interface UseModalKeyboardOptions {
|
||||
restoreFocusRef?: Ref<HTMLElement | null>
|
||||
}
|
||||
|
||||
export function useModalKeyboard(
|
||||
containerRef: Ref<HTMLElement | null>,
|
||||
isOpen: Ref<boolean>,
|
||||
onClose: () => void,
|
||||
options?: UseModalKeyboardOptions
|
||||
) {
|
||||
const restoreFocusRef = options?.restoreFocusRef
|
||||
|
||||
// Save the element that had focus when modal opens (before focus moves to modal)
|
||||
watch(isOpen, (open) => {
|
||||
if (open && restoreFocusRef) {
|
||||
restoreFocusRef.value = document.activeElement as HTMLElement | null
|
||||
}
|
||||
})
|
||||
function getFocusables(): HTMLElement[] {
|
||||
const el = containerRef.value
|
||||
if (!el) return []
|
||||
return Array.from(el.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(
|
||||
(e) => e.offsetParent !== null
|
||||
)
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (!isOpen.value) return
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
restoreFocusRef?.value?.focus?.()
|
||||
onClose()
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
return
|
||||
}
|
||||
|
||||
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
|
||||
const focusables = getFocusables()
|
||||
if (focusables.length === 0) return
|
||||
|
||||
const current = document.activeElement as HTMLElement | null
|
||||
const idx = current ? focusables.indexOf(current) : -1
|
||||
|
||||
let nextIdx: number
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
|
||||
nextIdx = idx < focusables.length - 1 ? idx + 1 : 0
|
||||
} else {
|
||||
nextIdx = idx > 0 ? idx - 1 : focusables.length - 1
|
||||
}
|
||||
|
||||
focusables[nextIdx]?.focus()
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', handleKeydown, true)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', handleKeydown, true)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user