100 lines
2.7 KiB
TypeScript
100 lines
2.7 KiB
TypeScript
|
|
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};
|
||
|
|
`,
|
||
|
|
};
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}
|