fix: fix focus behaviour
This commit is contained in:
@@ -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);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user