mirror of
https://github.com/Xevion/banner.git
synced 2026-01-31 22:23:34 -06:00
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.
This commit is contained in:
@@ -82,7 +82,7 @@ function toggle(key: "instructionalMethod" | "campus" | "partOfTerm" | "attribut
|
||||
>
|
||||
{#snippet child({ wrapperProps, props, open })}
|
||||
{#if open}
|
||||
<div {...wrapperProps} style:view-transition-name="filter-overlay">
|
||||
<div {...wrapperProps}>
|
||||
<div {...props} transition:fly={{ duration: 150, y: -4 }}>
|
||||
<div class="flex flex-col gap-3">
|
||||
{#each sections as { label, key, dataKey }, i (key)}
|
||||
|
||||
@@ -52,7 +52,7 @@ function parseNumberInput(value: string): number | null {
|
||||
>
|
||||
{#snippet child({ wrapperProps, props, open })}
|
||||
{#if open}
|
||||
<div {...wrapperProps} style:view-transition-name="filter-overlay">
|
||||
<div {...wrapperProps}>
|
||||
<div {...props} transition:fly={{ duration: 150, y: -4 }}>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex flex-col gap-1.5">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -91,7 +91,7 @@ function formatTime(time: string | null): string {
|
||||
>
|
||||
{#snippet child({ wrapperProps, props, open })}
|
||||
{#if open}
|
||||
<div {...wrapperProps} style:view-transition-name="filter-overlay">
|
||||
<div {...wrapperProps}>
|
||||
<div {...props} transition:fly={{ duration: 150, y: -4 }}>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex flex-col gap-1.5">
|
||||
|
||||
@@ -34,7 +34,7 @@ const hasActiveFilters = $derived(openOnly || waitCountMax !== null);
|
||||
>
|
||||
{#snippet child({ wrapperProps, props, open })}
|
||||
{#if open}
|
||||
<div {...wrapperProps} style:view-transition-name="filter-overlay">
|
||||
<div {...wrapperProps}>
|
||||
<div {...props} transition:fly={{ duration: 150, y: -4 }}>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex flex-col gap-1.5">
|
||||
|
||||
@@ -126,7 +126,7 @@ $effect(() => {
|
||||
>
|
||||
{#snippet child({ wrapperProps, props, open: isOpen })}
|
||||
{#if isOpen}
|
||||
<div {...wrapperProps} style:view-transition-name="filter-overlay">
|
||||
<div {...wrapperProps}>
|
||||
<div {...props} transition:fly={{ duration: 150, y: -4 }}>
|
||||
<Combobox.Viewport class="p-0.5">
|
||||
{#each filteredSubjects as subject (subject.code)}
|
||||
|
||||
@@ -96,7 +96,7 @@ $effect(() => {
|
||||
>
|
||||
{#snippet child({ wrapperProps, props, open: isOpen })}
|
||||
{#if isOpen}
|
||||
<div {...wrapperProps} style:view-transition-name="filter-overlay">
|
||||
<div {...wrapperProps}>
|
||||
<div {...props} transition:fly={{ duration: 150, y: -4 }}>
|
||||
<Combobox.Viewport class="p-0.5">
|
||||
{#each filteredTerms as term, i (term.slug)}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user