Skip to content

Commit e87079c

Browse files
committed
feat(transition): discard stale transitions when routing
1 parent 6510d5e commit e87079c

File tree

3 files changed

+30
-3
lines changed

3 files changed

+30
-3
lines changed

src/lib/components/RouteComponent.svelte

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,16 @@
102102
return _properties;
103103
});
104104
105+
const updateOnPropsChange = $derived(!!transition?.updateOnPropsChange);
106+
const transitionProps = $derived([transition?.in, transition?.out, transition?.params]);
107+
105108
// Trigger transition on route change or component update
106109
const transitionKey = $derived.by(() => {
107-
const _keys: any[] = [routedComponent];
108-
if (transition?.updateOnPropsChange) _keys.push(_properties);
110+
const _keys: any[] = [routedComponent, ...transitionProps];
111+
if (updateOnPropsChange) _keys.push(_properties);
109112
return _keys;
110113
});
114+
111115
// Final unique identifier for the current route change
112116
let resolvedID = $state();
113117

src/lib/components/RouteTransition.svelte

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import type { TransitionProps } from '~/models/component.model.js';
66
77
import { toStyle } from '@dvcol/common-utils/common/class';
8+
import { mutation } from '@dvcol/svelte-utils/mutation';
89
910
const { children, key, id, transition }: { children: Snippet; id: string; key: any | any[]; transition: TransitionProps } = $props();
1011
@@ -37,9 +38,20 @@
3738
if (typeof _name === 'boolean') _name = `sr-container-${id}`;
3839
return toStyle(`--container-transition-name: ${_name}`, _containerProps?.style);
3940
});
41+
42+
const ifElement = (node?: Node | null): HTMLElement | undefined => {
43+
if (node instanceof HTMLElement) return node;
44+
};
45+
46+
const _mutation = $derived((record: MutationRecord, index: number, entries: MutationRecord[]) => {
47+
if (transition?.discard === false) return;
48+
if (typeof transition?.discard === 'function' && !transition.discard(record, index, entries)) return;
49+
if (!ifElement(record.previousSibling?.previousSibling)?.inert) return;
50+
ifElement(record.previousSibling)?.remove();
51+
});
4052
</script>
4153

42-
<div data-transition-id="container" {..._containerProps} style={_style}>
54+
<div data-transition-id="container" {..._containerProps} style={_style} use:mutation={{ callback: _mutation, options: { childList: true } }}>
4355
{#if transition?.in || transition?.out}
4456
{#key key}
4557
<div data-transition-id="wrapper" in:_in={_inParams} out:_out={_outParams} {..._wrapperProps} style={toStyle(_wrapperProps?.style)}>

src/lib/models/component.model.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export interface RouterContextProps<Name extends RouteName = any> {
2727
children?: Snippet<[IRouter<Name>]>;
2828
}
2929

30+
export type TransitionDiscardFunction = (entry: MutationRecord, index: number, entries: MutationRecord[]) => boolean;
31+
3032
export interface TransitionProps<
3133
T extends { in?: Record<string, any>; out?: Record<string, any> } = { in?: Record<string, any>; out?: Record<string, any> },
3234
> {
@@ -84,6 +86,15 @@ export interface TransitionProps<
8486
routing?: number;
8587
loading?: number;
8688
};
89+
/**
90+
* Whether to discard stale transitions when multiple transitions are triggered.
91+
* This is useful when the transition is triggered multiple times in quick succession and intro/outro transitions have not yet completed.
92+
*
93+
* By default, only the first inert elements and the latest node are kept.
94+
*
95+
* @default true
96+
*/
97+
discard?: boolean | TransitionDiscardFunction;
8798
}
8899

89100
export interface RouteContainerProps<Name extends RouteName = any> {

0 commit comments

Comments
 (0)