fix: fix focus behaviour

This commit is contained in:
2025-09-13 08:03:41 +02:00
parent 181ae28548
commit bfe77ae86a
3 changed files with 45 additions and 15 deletions

View File

@@ -2,6 +2,7 @@ import Database from 'better-sqlite3';
import { DB_PATH } from './config'; import { DB_PATH } from './config';
import { logger } from './globals'; import { logger } from './globals';
import { readdir, readFile } from "fs/promises"; import { readdir, readFile } from "fs/promises";
import { existsSync, mkdirSync } from "fs";
import { join, dirname } from "path"; import { join, dirname } from "path";
export let FTS5Enabled = true; export let FTS5Enabled = true;
@@ -57,6 +58,18 @@ export const migrate = async () => {
logger.info(`Loading database at ${DB_PATH}`); logger.info(`Loading database at ${DB_PATH}`);
// Ensure parent directory exists (avoid better-sqlite3 directory error)
try {
const dir = dirname(DB_PATH);
// Skip if dir is current directory or drive root-like (e.g., "C:")
const isTrivialDir = dir === '.' || dir === '' || /^[A-Za-z]:\\?$/.test(dir);
if (!isTrivialDir && !existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
} catch (e) {
logger.warn(`Failed to ensure DB directory exists: ${e}`);
}
export const db = new Database(DB_PATH); export const db = new Database(DB_PATH);

View File

@@ -46,7 +46,9 @@ const emit = defineEmits<{
const handleKeydown = (event: KeyboardEvent) => { const handleKeydown = (event: KeyboardEvent) => {
if (event.key === 'Enter' || event.key === ' ') { if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault() event.preventDefault()
emit('click', event as any) const btn = event.currentTarget as HTMLButtonElement | null
// Trigger native click so type="submit" works and parent @click receives it
btn?.click()
} }
} }
</script> </script>

View File

@@ -16,6 +16,7 @@
'dialog', 'dialog',
`dialog--${size}` `dialog--${size}`
]" ]"
tabindex="-1"
@click.stop @click.stop
> >
<div class="dialog__header" v-if="$slots.header || title"> <div class="dialog__header" v-if="$slots.header || title">
@@ -87,6 +88,12 @@ const handleOverlayClick = () => {
let lastFocusedElement: HTMLElement | null = null let lastFocusedElement: HTMLElement | null = null
const trapFocus = (event: KeyboardEvent) => { const trapFocus = (event: KeyboardEvent) => {
// Close on Escape regardless of focused element when dialog is open
if (event.key === 'Escape') {
event.preventDefault()
handleClose()
return
}
if (event.key !== 'Tab') return if (event.key !== 'Tab') return
const focusableElements = dialogRef.value?.querySelectorAll( const focusableElements = dialogRef.value?.querySelectorAll(
@@ -119,16 +126,24 @@ watch(() => props.show, async (isVisible) => {
await nextTick() await nextTick()
// Focus first focusable element or the dialog itself // Focus [autofocus] first, then first focusable, else the dialog itself
const firstFocusable = dialogRef.value?.querySelector( const root = dialogRef.value as HTMLElement | undefined
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' const selector = '[autofocus], button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
) as HTMLElement const firstFocusable = root?.querySelector(selector) as HTMLElement | null
if (firstFocusable) { if (firstFocusable) {
firstFocusable.focus() firstFocusable.focus()
} else { } else {
dialogRef.value?.focus() root?.focus()
} }
// Retry shortly after in case slotted children mount slightly later
setTimeout(() => {
if (!root) return
if (!root.contains(document.activeElement)) {
const retryTarget = (root.querySelector(selector) as HTMLElement) || root
retryTarget?.focus()
}
}, 0)
} else { } else {
document.body.style.overflow = '' document.body.style.overflow = ''
document.removeEventListener('keydown', trapFocus) document.removeEventListener('keydown', trapFocus)