Files
frontend-svelte/src/widgets/ComparisonView/lib/utils/dotTransition.ts
T

100 lines
2.7 KiB
TypeScript
Raw Normal View History

import { VIRTUAL_INDEX_NOT_LOADED } from '$entities/Font';
import { cubicOut } from 'svelte/easing';
import {
type CrossfadeParams,
type TransitionConfig,
crossfade,
} from 'svelte/transition';
/**
* Custom parameters for dot transitions in virtualized lists.
*/
export interface DotTransitionParams extends CrossfadeParams {
/**
* Unique key for crossfade pairing
*/
key: any;
/**
* Current index of the item in the list
*/
index: number;
/**
* Target index to move towards (e.g. counterpart side index)
*/
otherIndex: number;
}
/**
* Type-safe helper to create dot transition parameters.
*/
export function getDotTransitionParams(
key: 'active-dot' | 'inactive-dot',
index: number,
otherIndex: number,
): DotTransitionParams {
return { key, index, otherIndex };
}
/**
* Type guard for DotTransitionParams.
*/
function isDotTransitionParams(p: CrossfadeParams): p is DotTransitionParams {
return (
p !== null
&& typeof p === 'object'
&& 'index' in p
&& 'otherIndex' in p
);
}
/**
* Creates a crossfade transition pair optimized for virtualized font lists.
*
* It uses the 'index' and 'otherIndex' params to calculate the direction
* of the slide animation when a matching pair cannot be found in the DOM
* (e.g. because it was virtualized out).
*/
export function createDotCrossfade() {
return crossfade({
duration: 300,
easing: cubicOut,
fallback(node: Element, params: CrossfadeParams, _intro: boolean): TransitionConfig {
if (!isDotTransitionParams(params)) {
return {
duration: 300,
easing: cubicOut,
css: t => `opacity: ${t};`,
};
}
const { index, otherIndex } = params;
// If the other target is unknown, just fade in place
if (otherIndex === undefined || otherIndex === -1) {
return {
duration: 300,
easing: cubicOut,
css: t => `opacity: ${t};`,
};
}
const diff = otherIndex - index;
const sign = diff > 0 ? 1 : (diff < 0 ? -1 : 0);
// Use container height for a full-height slide
const listEl = node.closest('[data-font-list]');
const h = listEl?.clientHeight ?? 300;
const fromY = sign * h;
return {
duration: 300,
easing: cubicOut,
css: (t, u) => `
transform: translateY(${fromY * u}px);
opacity: ${t};
`,
};
},
});
}