Files
dodo/frontend/src/lib/completion-motion.ts
T
bboysoul 8b475e4df5
ci / gitleaks (push) Successful in 7s
ci / docker (push) Successful in 3m25s
feat: animate completed items out
2026-09-11 08:56:51 +08:00

51 lines
1.5 KiB
TypeScript

type CompletionId = string
type CompletionTimer = number
type CompletionFrame = number
export const COMPLETION_EXIT_DURATION = 320
export function shouldAnimateCompletionExit({ completing, showCompleted }: { completing: boolean; showCompleted: boolean }) {
return completing && !showCompleted
}
export function completionExitDelay(reducedMotion: boolean) {
return reducedMotion ? 0 : COMPLETION_EXIT_DURATION
}
export function prefersReducedMotion() {
return window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false
}
export function waitForCompletionExit(reducedMotion = prefersReducedMotion()) {
const delay = completionExitDelay(reducedMotion)
return delay ? new Promise<void>((resolve) => window.setTimeout(resolve, delay)) : Promise.resolve()
}
export function createCompletionPulse(
activate: (id: CompletionId) => void,
deactivate: (id: CompletionId) => void,
duration = 420,
) {
const timers = new Map<CompletionId, CompletionTimer>()
const frames = new Map<CompletionId, CompletionFrame>()
return (id: CompletionId) => {
const previousTimer = timers.get(id)
if (previousTimer !== undefined) window.clearTimeout(previousTimer)
const previousFrame = frames.get(id)
if (previousFrame !== undefined) window.cancelAnimationFrame(previousFrame)
deactivate(id)
const frame = window.requestAnimationFrame(() => {
frames.delete(id)
activate(id)
timers.set(id, window.setTimeout(() => {
deactivate(id)
timers.delete(id)
}, duration))
})
frames.set(id, frame)
}
}