From 7f0f08725a668c5ac88c510f43791d90ce2f795e Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 31 Jan 2026 12:16:36 -0600 Subject: [PATCH] fix(web): prevent interaction blocking during search transitions Remove document-level view transition fallback that applies visibility:hidden to the entire page. Use scoped table transitions to keep filters and controls interactive during search result updates. --- .../lib/components/AttributesPopover.svelte | 2 +- web/src/lib/components/MorePopover.svelte | 2 +- web/src/lib/components/PageTransition.svelte | 3 ++- web/src/lib/components/SchedulePopover.svelte | 2 +- web/src/lib/components/StatusPopover.svelte | 2 +- web/src/lib/components/SubjectCombobox.svelte | 2 +- web/src/lib/components/TermCombobox.svelte | 2 +- web/src/routes/+page.svelte | 18 +++++++----------- web/src/routes/layout.css | 11 +---------- 9 files changed, 16 insertions(+), 28 deletions(-) diff --git a/web/src/lib/components/AttributesPopover.svelte b/web/src/lib/components/AttributesPopover.svelte index a0fe603..ea2a27d 100644 --- a/web/src/lib/components/AttributesPopover.svelte +++ b/web/src/lib/components/AttributesPopover.svelte @@ -82,7 +82,7 @@ function toggle(key: "instructionalMethod" | "campus" | "partOfTerm" | "attribut > {#snippet child({ wrapperProps, props, open })} {#if open} -
+
{#each sections as { label, key, dataKey }, i (key)} diff --git a/web/src/lib/components/MorePopover.svelte b/web/src/lib/components/MorePopover.svelte index 102f14a..e54a4fb 100644 --- a/web/src/lib/components/MorePopover.svelte +++ b/web/src/lib/components/MorePopover.svelte @@ -52,7 +52,7 @@ function parseNumberInput(value: string): number | null { > {#snippet child({ wrapperProps, props, open })} {#if open} -
+
diff --git a/web/src/lib/components/PageTransition.svelte b/web/src/lib/components/PageTransition.svelte index 0792f50..a7c081a 100644 --- a/web/src/lib/components/PageTransition.svelte +++ b/web/src/lib/components/PageTransition.svelte @@ -48,7 +48,8 @@ function inTransition(_node: HTMLElement): TransitionConfig { function outTransition(_node: HTMLElement): TransitionConfig { const dir = navigationStore.direction; - const base = "position: absolute; top: 0; left: 0; width: 100%; height: 100%"; + const base = + "position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none"; if (dir === "fade") { return { duration: DURATION, diff --git a/web/src/lib/components/SchedulePopover.svelte b/web/src/lib/components/SchedulePopover.svelte index b565815..3464041 100644 --- a/web/src/lib/components/SchedulePopover.svelte +++ b/web/src/lib/components/SchedulePopover.svelte @@ -91,7 +91,7 @@ function formatTime(time: string | null): string { > {#snippet child({ wrapperProps, props, open })} {#if open} -
+
diff --git a/web/src/lib/components/StatusPopover.svelte b/web/src/lib/components/StatusPopover.svelte index f4dc774..fa1aac2 100644 --- a/web/src/lib/components/StatusPopover.svelte +++ b/web/src/lib/components/StatusPopover.svelte @@ -34,7 +34,7 @@ const hasActiveFilters = $derived(openOnly || waitCountMax !== null); > {#snippet child({ wrapperProps, props, open })} {#if open} -
+
diff --git a/web/src/lib/components/SubjectCombobox.svelte b/web/src/lib/components/SubjectCombobox.svelte index cfa9676..53eacc5 100644 --- a/web/src/lib/components/SubjectCombobox.svelte +++ b/web/src/lib/components/SubjectCombobox.svelte @@ -126,7 +126,7 @@ $effect(() => { > {#snippet child({ wrapperProps, props, open: isOpen })} {#if isOpen} -
+
{#each filteredSubjects as subject (subject.code)} diff --git a/web/src/lib/components/TermCombobox.svelte b/web/src/lib/components/TermCombobox.svelte index 8c6c398..4d5a8ee 100644 --- a/web/src/lib/components/TermCombobox.svelte +++ b/web/src/lib/components/TermCombobox.svelte @@ -96,7 +96,7 @@ $effect(() => { > {#snippet child({ wrapperProps, props, open: isOpen })} {#if isOpen} -
+
{#each filteredTerms as term, i (term.slug)} diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index dcbc096..502789d 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -439,23 +439,19 @@ async function performSearch() { }; }; + // Scoped view transitions only affect the table element, so filters and + // other controls remain fully interactive. Document-level transitions + // apply visibility:hidden to the entire page for the transition duration, + // blocking all pointer interactions — so we skip those entirely and let + // Svelte's animate:flip / in:fade handle the visual update instead. const tableEl = document.querySelector("[data-search-results]") as HTMLElement | null; - const scopedSupport = tableEl && "startViewTransition" in tableEl; - if (scopedSupport) { - // Scoped transition — no top-layer issue, no need for filter-overlay workaround + if (tableEl && "startViewTransition" in tableEl) { const transition = (tableEl as any).startViewTransition(async () => { applyUpdate(); await tick(); }); - await transition.finished; - } else if (document.startViewTransition) { - // Document-level fallback with z-index layering for filter overlays - const transition = document.startViewTransition(async () => { - applyUpdate(); - await tick(); - }); - await transition.finished; + await transition.updateCallbackDone; } else { applyUpdate(); } diff --git a/web/src/routes/layout.css b/web/src/routes/layout.css index 4a75764..4790fed 100644 --- a/web/src/routes/layout.css +++ b/web/src/routes/layout.css @@ -133,7 +133,7 @@ input[type="checkbox"]:checked::before { clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); } -/* View Transitions API - disable default cross-fade so JS can animate clip-path */ +/* View Transitions API - disable default cross-fade for scoped table transitions */ ::view-transition-old(root), ::view-transition-new(root) { animation: none; @@ -251,12 +251,3 @@ body::-webkit-scrollbar { ::view-transition-new(search-results) { animation-duration: 200ms; } - -/* Keep filter overlays above view-transition snapshots */ -::view-transition-group(filter-overlay) { - z-index: 100; -} -::view-transition-old(filter-overlay), -::view-transition-new(filter-overlay) { - animation: none; -}