/** Shared lifecycle for user-triggered UI feedback. */ export function createUiBindings(document, env) { const media = env.matchMedia("(prefers-reduced-motion: reduce)"); const bindings = []; const animations = new Set(); const cancel = () => { animations.forEach((animation) => animation.cancel()); animations.clear(); }; const listen = (target, type, callback) => { if (!target) return; target.addEventListener(type, callback); bindings.push(() => target.removeEventListener(type, callback)); }; const allowsMotion = (event) => event.detail > 0 && document.documentElement.dataset.capture !== "true" && !media.matches; listen(media, "change", () => { if (media.matches) cancel(); }); return { listen, allowsMotion, animate(element, event, frames, options) { if (!element) return; element.getAnimations().forEach((animation) => animation.cancel()); if (!allowsMotion(event)) return; const animation = element.animate(frames, options); animations.add(animation); // Finished animations need no retained references. Cancellation also settles this promise. animation.finished?.then( () => animations.delete(animation), () => animations.delete(animation), ); }, dispose() { bindings.forEach((remove) => remove()); cancel(); }, }; }