fix: debouncing sync and other improvements

This commit is contained in:
2026-08-22 22:58:20 +02:00
parent e27e7a679e
commit c6e45cd067
17 changed files with 350 additions and 207 deletions
+37
View File
@@ -0,0 +1,37 @@
import { onMounted, onUnmounted, type Ref } from 'vue'
type OutsideHandler = (event: MouseEvent) => void
const handlers = new Set<OutsideHandler>()
function dispatch(event: MouseEvent) {
for (const handler of handlers) {
handler(event)
}
}
// Backs every call with a single shared `document` click listener instead of
// one per component instance, so a page rendering many dismissable menus
// (e.g. one per row in a list) doesn't fan out into one global listener per
// row.
export function useClickOutside(target: Ref<HTMLElement | null>, onOutside: () => void): void {
function handler(event: MouseEvent) {
if (target.value && !target.value.contains(event.target as Node)) {
onOutside()
}
}
onMounted(() => {
handlers.add(handler)
if (handlers.size === 1) {
document.addEventListener('click', dispatch)
}
})
onUnmounted(() => {
handlers.delete(handler)
if (handlers.size === 0) {
document.removeEventListener('click', dispatch)
}
})
}
+30
View File
@@ -0,0 +1,30 @@
import { onMounted, onUnmounted } from 'vue'
type EscapeHandler = () => void
const handlers = new Set<EscapeHandler>()
function dispatch(event: KeyboardEvent) {
if (event.key !== 'Escape') return
for (const handler of handlers) {
handler()
}
}
// Backs every call with a single shared `document` keydown listener instead
// of one per component instance.
export function useEscapeKey(onEscape: EscapeHandler): void {
onMounted(() => {
handlers.add(onEscape)
if (handlers.size === 1) {
document.addEventListener('keydown', dispatch)
}
})
onUnmounted(() => {
handlers.delete(onEscape)
if (handlers.size === 0) {
document.removeEventListener('keydown', dispatch)
}
})
}