Compare commits

..

2 Commits

Author SHA1 Message Date
Jürgen Mummert 5f652530ed Scope view transitions to event list and prevent page shift 2026-04-05 13:55:44 +02:00
Jürgen Mummert 11b927b91f Fix event filter transition jank and restore layout animation 2026-04-05 13:46:00 +02:00
@@ -40,14 +40,62 @@
<style>
.event-filter-target-list {
view-transition-name: event-filter-list;
contain: layout paint;
}
html {
scrollbar-gutter: stable both-edges;
}
body {
overflow-y: scroll;
}
@keyframes event-filter-vt-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes event-filter-vt-fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@media (prefers-reduced-motion: no-preference) {
::view-transition-group(event-filter-list),
::view-transition-group(root),
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
}
::view-transition-group(event-filter-list) {
animation-duration: 240ms;
animation-timing-function: ease;
}
::view-transition-old(event-filter-list),
::view-transition-new(event-filter-list) {
animation-duration: 240ms;
animation-timing-function: ease;
mix-blend-mode: normal;
}
::view-transition-old(event-filter-list) {
animation-name: event-filter-vt-fade-out;
}
::view-transition-new(event-filter-list) {
animation-name: event-filter-vt-fade-in;
}
}
</style>
@@ -132,13 +180,15 @@
const animationMs = 220;
let hideTimers = new WeakMap();
const supportsViewTransitions = typeof document.startViewTransition === 'function';
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
let activeViewTransition = null;
let isViewTransitionMutation = false;
let hasAppliedInitialFilter = false;
let currentFilter = { type: 'all', value: '' };
let suppressedChangeEvents = 0;
const runWithLayoutTransition = (mutation) => {
if (!supportsViewTransitions) {
if (!supportsViewTransitions || prefersReducedMotion) {
mutation();
return;
}
@@ -183,6 +233,8 @@
}
};
const shouldMutateVisibilityImmediately = () => supportsViewTransitions && !prefersReducedMotion;
const hasOptionValue = (selectElement, value) => {
if (!selectElement) {
return false;
@@ -315,9 +367,15 @@
}
};
const showEvent = (eventItem) => {
const showEvent = (eventItem, { immediateVisibility = false } = {}) => {
clearHideTimer(eventItem);
if (immediateVisibility) {
eventItem.hidden = false;
eventItem.classList.remove('is-filtered-out');
return;
}
if (eventItem.hidden) {
eventItem.hidden = false;
}
@@ -327,8 +385,15 @@
});
};
const hideEvent = (eventItem) => {
const hideEvent = (eventItem, { immediateVisibility = false } = {}) => {
clearHideTimer(eventItem);
if (immediateVisibility) {
eventItem.classList.add('is-filtered-out');
eventItem.hidden = true;
return;
}
eventItem.classList.add('is-filtered-out');
const timer = window.setTimeout(() => {
@@ -425,15 +490,26 @@
currentFilter = filterState;
setActiveControl(filterState);
runWithLayoutTransition(() => {
const immediateVisibility = shouldMutateVisibilityImmediately();
const shouldAnimateLayout = immediateVisibility && hasAppliedInitialFilter;
const mutateVisibility = () => {
events.forEach((eventItem) => {
if (matches(eventItem, filterState)) {
showEvent(eventItem);
showEvent(eventItem, { immediateVisibility });
} else {
hideEvent(eventItem);
hideEvent(eventItem, { immediateVisibility });
}
});
});
};
if (shouldAnimateLayout) {
runWithLayoutTransition(mutateVisibility);
} else {
mutateVisibility();
}
hasAppliedInitialFilter = true;
updateStatus(filterState);
syncState(filterState);