Skip to content

Commit 8616596

Browse files
committed
feat(transition): adds delay support to loading views
1 parent b16c6fc commit 8616596

File tree

4 files changed

+53
-16
lines changed

4 files changed

+53
-16
lines changed

src/lib/components/RouteComponent.svelte

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,28 @@
5656
// Generate a unique identifier for each loading state, to prevent cancelled navigations from updating the view
5757
const change: ViewChangeEvent = $derived(new ViewChangeEvent({ view: { id: view.id, name: view.name }, route }));
5858
59-
// Extract routing state
60-
let routing: boolean | undefined = $state();
59+
// Debounce routing state
60+
let isRouting: boolean | undefined = $state();
6161
const setRouting = debounce((val: boolean) => {
62-
routing = val;
63-
return routing;
64-
}, 0);
65-
$effect(() => {
62+
isRouting = val;
63+
return isRouting;
64+
}, typeof transition?.delay === 'number' ? transition?.delay : transition?.delay?.routing ?? 0);
65+
66+
$effect.pre(() => {
6667
setRouting(!!router?.routing?.active);
6768
});
6869
70+
// Debounce loading state
71+
let isLoading: boolean | undefined = $state();
72+
const setLoading = debounce((val: boolean) => {
73+
isLoading = val;
74+
return isLoading;
75+
}, typeof transition?.delay === 'number' ? transition?.delay : transition?.delay?.loading ?? 0);
76+
77+
$effect.pre(() => {
78+
setLoading(!!view.loading);
79+
});
80+
6981
// Delay properties update until component is resolved
7082
let _properties: ComponentProps | undefined = $state();
7183
@@ -74,25 +86,25 @@
7486
7587
// Routed component to be rendered (loading, error, or resolved component)
7688
const routedComponent = $derived.by<IComponentOrSnippet | undefined>(() => {
77-
if (routing && routingSnippet) return routingSnippet;
78-
else if (view.loading && loading) return loading;
79-
else if (view.loading && loadingSnippet) return loadingSnippet;
89+
if (isRouting && routingSnippet) return routingSnippet;
90+
else if (isLoading && loading) return loading;
91+
else if (isLoading && loadingSnippet) return loadingSnippet;
8092
else if (view.error && error) return error;
8193
else if (view.error && errorSnippet) return errorSnippet;
8294
else if (resolvedComponent) return resolvedComponent;
8395
});
8496
8597
// Routed snippet properties
8698
const routedProps = $derived.by(() => {
87-
if (routedComponent === errorSnippet) return view.error;
88-
if (routedComponent === routingSnippet) return router.routing;
89-
if (routedComponent === loadingSnippet) return route;
99+
if (routingSnippet === routedComponent) return router.routing;
100+
if ([loadingSnippet, loading].includes(routedComponent)) return route;
101+
if ([errorSnippet, error].includes(routedComponent)) return view.error;
90102
return _properties;
91103
});
92104
93105
// Trigger transition on route change or component update
94106
const transitionKey = $derived.by(() => {
95-
const _keys = [routedComponent];
107+
const _keys: any[] = [routedComponent];
96108
if (transition?.updateOnPropsChange) _keys.push(_properties);
97109
return _keys;
98110
});
@@ -108,6 +120,7 @@
108120
},
109121
onLoading: () => {
110122
if (change.uuid !== _uuid) return;
123+
if (loading || loadingSnippet) resolvedComponent = undefined;
111124
return untrack(() => view.load());
112125
},
113126
onLoaded: (_component?: AnyComponent | AnySnippet) => {
@@ -120,6 +133,7 @@
120133
onError: (err: unknown) => {
121134
if (change.uuid !== _uuid) return;
122135
if (force) resolvedID = _uuid;
136+
if (error || errorSnippet) resolvedComponent = undefined;
123137
return untrack(() => view.fail(err));
124138
},
125139
};

src/lib/models/component.model.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ export interface TransitionProps<
7575
* @see [view transition api](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)
7676
*/
7777
viewTransitionName?: boolean | string;
78+
/**
79+
* Optional delay before displaying routing and loading views.
80+
*
81+
* @default 0
82+
*/
83+
delay?: number | {
84+
routing?: number;
85+
loading?: number;
86+
};
7887
}
7988

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

test/components/components.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ describe('routerView', () => {
102102
render(component, { router });
103103
await router.push({ path: '/loading' });
104104

105+
// Flush the microtask queue to ensure the loading component is rendered
106+
await wait(0);
107+
105108
const loading = screen.getByTestId('loading-component');
106109
expect(loading).toBeDefined();
107110

@@ -121,6 +124,9 @@ describe('routerView', () => {
121124
render(component, { router });
122125
await router.push({ path: '/default-loading' });
123126

127+
// Flush the microtask queue to ensure the loading component is rendered
128+
await wait(0);
129+
124130
const loading = screen.getByTestId('default-loading');
125131
expect(loading).toBeDefined();
126132

@@ -578,6 +584,8 @@ describe('routerView', () => {
578584
const errorComponent = screen.getByTestId('default-error');
579585
expect(errorComponent).toBeDefined();
580586

587+
console.info(errorComponent.innerHTML);
588+
581589
const message = errorComponent.querySelector('p[data-testid="error-message"]');
582590
expect(message).toBeDefined();
583591
expect(message?.textContent).toBe('Loading error');
@@ -598,6 +606,9 @@ describe('routerView', () => {
598606
});
599607
await router.push({ path: partialRoute.path });
600608

609+
// Flush the microtask queue to ensure the loading component is rendered
610+
await wait(0);
611+
601612
const loading = screen.getByTestId('loading-component');
602613
expect(loading).toBeDefined();
603614

@@ -623,6 +634,9 @@ describe('routerView', () => {
623634
});
624635
await router.push({ path: partialRoute.path });
625636

637+
// Flush the microtask queue to ensure the loading component is rendered
638+
await wait(0);
639+
626640
const loading = screen.getByTestId('default-loading');
627641
expect(loading).toBeDefined();
628642

test/components/stub/mocks.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { vi } from 'vitest';
22

33
export const LifeCycle: Record<string, { onMounted: () => unknown; onDestroyed: () => unknown }> = {
4-
Hello: { onMounted: vi.fn(), onDestroyed: vi.fn() },
5-
Goodbye: { onMounted: vi.fn(), onDestroyed: vi.fn() },
6-
Error: { onMounted: vi.fn(), onDestroyed: vi.fn() },
4+
Hello: { onMounted: vi.fn().mockImplementation(() => console.info('Mounted Hello Component !')), onDestroyed: vi.fn().mockImplementation(() => console.info('Destroyed Hello Component !')) },
5+
Goodbye: { onMounted: vi.fn().mockImplementation(() => console.info('Mounted Goodbye Component !')), onDestroyed: vi.fn().mockImplementation(() => console.info('Destroyed Goodbye Component !')) },
6+
Error: { onMounted: vi.fn().mockImplementation(() => console.info('Mounted Error Component !')), onDestroyed: vi.fn().mockImplementation(() => console.info('Destroyed Error Component !')) },
77
};

0 commit comments

Comments
 (0)