Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions src/lib/Fallback/Fallback.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script lang="ts">
import { resolveHashValue } from '$lib/core/RouterEngine.svelte.js';
import { getRouterContext } from '$lib/Router/Router.svelte';
import { type Snippet } from 'svelte';
import type { RouteStatus } from '$lib/types.js';
import type { Snippet } from 'svelte';

type Props = {
/**
Expand Down Expand Up @@ -33,8 +34,11 @@
*
* This rendering is conditioned to the parent router engine's `noMatches` property being `true`. This means
* that the children will only be rendered when no route matches the current location.
* @param state The state object stored in in the window's History API for the universe the fallback component
* is associated to.
* @param routeStatus The router's route status data.
*/
children?: Snippet;
children?: Snippet<[any, Record<string, RouteStatus>]>;
};

let { hash, children }: Props = $props();
Expand All @@ -43,5 +47,5 @@
</script>

{#if router?.noMatches}
{@render children?.()}
{@render children?.(router.state, router.routeStatus)}
{/if}
2 changes: 1 addition & 1 deletion src/lib/Fallback/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ route status data is calculated.
| Property | Type | Default Value | Bindable | Description |
|-|-|-|-|-|
| `hash` | `boolean \| string` | `undefined` | | Sets the hash mode of the component. |
| `children` | `Snippet` | `undefined` | | Renders the children of the component. |
| `children` | `Snippet<[any, Record<string, RouteStatus>]>` | `undefined` | | Renders the children of the component. |

[Online Documentation](https://wjfe-n-savant.hashnode.space/wjfe-n-savant/components/fallback)

Expand Down
13 changes: 9 additions & 4 deletions src/lib/Link/Link.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { joinPaths, resolveHashValue } from '$lib/core/RouterEngine.svelte.js';
import { getLinkContext, type ILinkContext } from '$lib/LinkContext/LinkContext.svelte';
import { getRouterContext } from '$lib/Router/Router.svelte';
import type { ActiveState } from '$lib/types.js';
import type { ActiveState, RouteStatus } from '$lib/types.js';
import { type Snippet } from 'svelte';
import type { HTMLAnchorAttributes } from 'svelte/elements';

Expand Down Expand Up @@ -59,8 +59,12 @@
activeState?: ActiveState;
/**
* Renders the children of the component.
* @param state The state object stored in in the window's History API for the universe the link is
* associated to.
* @param routeStatus The router's route status data, if the `Link` component is within the context of a
* router.
*/
children?: Snippet;
children?: Snippet<[any, Record<string, RouteStatus> | undefined]>;
};

let {
Expand All @@ -77,7 +81,8 @@
...restProps
}: Props = $props();

const router = getRouterContext(resolveHashValue(hash));
const resolvedHash = resolveHashValue(hash);
const router = getRouterContext(resolvedHash);
const linkContext = getLinkContext();

const calcReplace = $derived(replace ?? linkContext?.replace ?? false);
Expand Down Expand Up @@ -152,5 +157,5 @@
onclick={handleClick}
{...restProps}
>
{@render children?.()}
{@render children?.(location.getState(resolvedHash), router?.routeStatus)}
</a>
2 changes: 1 addition & 1 deletion src/lib/Link/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ SPA-friendly navigation (navigation without reloading).
| `activeState` | `ActiveState` | `undefined` | | Sets the various options that are used to automatically style the anchor tag whenever a particular route becomes active. |
| `prependBasePath` | `boolean` | `false` | | Configures the component to prepend the parent router's base path to the `href` property. |
| `preserveQuery` | `boolean \| string \| string[]` | `false` | | Configures the component to preserve the query string whenever it triggers navigation. |
| `children` | `Snippet` | `undefined` | | Renders the children of the component. |
| `children` | `Snippet<[any, Record<string, RouteStatus> \| undefined]>` | `undefined` | | Renders the children of the component. |

[Online Documentation](https://wjfe-n-savant.hashnode.space/wjfe-n-savant/components/link)

Expand Down
2 changes: 1 addition & 1 deletion src/lib/Route/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ they can be embedded anywhere down the hierarchy, including being children of ot
| `caseSensitive` | `boolean` | `false` | | Sets whether the route's path pattern should be matched case-sensitively. |
| `hash` | `boolean \| string` | `undefined` | | Sets the hash mode of the route. |
| `params` | `Record<RouteParameters<T>, ParameterValue>` | `undefined` | Yes | Provides a way to obtain a route's parameters through property binding. |
| `children` | `Snippet<[Record<RouteParameters<T>, ParameterValue> \| undefined]>` | `undefined` | | Renders the children of the route. |
| `children` | `Snippet<[Record<RouteParameters<T>, ParameterValue> \| undefined, any, Record<string, RouteStatus>]>` | `undefined` | | Renders the children of the route. |

[Online Documentation](https://wjfe-n-savant.hashnode.space/wjfe-n-savant/components/route)

Expand Down
7 changes: 5 additions & 2 deletions src/lib/Route/Route.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,11 @@
/**
* Renders the children of the route.
* @param params The route's parameters.
* @param state The state object stored in in the window's History API for the universe the route is associated
* to.
* @param routeStatus The router's route status object.
*/
children?: Snippet<[Record<RouteParameters<T>, ParameterValue> | undefined]>;
children?: Snippet<[Record<RouteParameters<T>, ParameterValue> | undefined, any, Record<string, RouteStatus>]>;
};

let {
Expand Down Expand Up @@ -184,5 +187,5 @@
</script>

{#if (router.routeStatus[key]?.match ?? true) && (untrack(() => router.routes)[key]?.when?.(router.routeStatus) ?? true)}
{@render children?.(params)}
{@render children?.(params, router.state, router.routeStatus)}
{/if}
2 changes: 1 addition & 1 deletion src/lib/Router/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ children via context.
| `basePath` | `string` | `'/'` | | Sets the router's base path, which is a segment of the URL that is implicitly added to all routes. |
| `id` | `string` | `undefined` | | Gives the router an identifier that shows up in `RouterTrace` components. |
| `hash` | `boolean \| string` | `undefined` | | Sets the hash mode of the router. |
| `children` | `Snippet` | `undefined` | | Renders the children of the router. |
| `children` | `Snippet<[any, Record<string, RouteStatus>]>` | `undefined` | | Renders the children of the router. |

[Online Documentation](https://wjfe-n-savant.hashnode.space/wjfe-n-savant/components/router)

Expand Down
8 changes: 6 additions & 2 deletions src/lib/Router/Router.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts" module>
import { resolveHashValue, RouterEngine } from '$lib/core/RouterEngine.svelte.js';
import type { RouteStatus } from '$lib/types.js';

const parentCtxKey = Symbol();
const hashParentCtxKey = Symbol();
Expand Down Expand Up @@ -98,8 +99,11 @@
*
* Children content can be anything, but note that that the useful children are the `Route` components. Any
* other content will be rendered regardless of the current route.
* @param state The state object stored in in the window's History API for the universe the route is associated
* to.
* @param routeStatus The router's route status data.
*/
children?: Snippet;
children?: Snippet<[any, Record<string, RouteStatus>]>;
};

let { router = $bindable(), basePath, id, hash, children }: Props = $props();
Expand Down Expand Up @@ -133,4 +137,4 @@
});
</script>

{@render children?.()}
{@render children?.(router.state, router.routeStatus)}
21 changes: 13 additions & 8 deletions src/lib/core/LocationFull.svelte.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import { LocationFull } from "./LocationFull.js";
import type { Events, Location } from "$lib/types.js";
import type { State, Location } from "$lib/types.js";
import { flushSync } from "svelte";
import { joinPaths } from "./RouterEngine.svelte.js";

describe("LocationFull", () => {
const initialUrl = "http://example.com/";
let interceptedState: any = null;
let interceptedState: State;
const pushStateMock = vi.fn((state, _, url) => {
url = !url.startsWith('http://') ? joinPaths(initialUrl, url) : url;
globalThis.window.location.href = new URL(url).href;
interceptedState = state;
});
const replaceStateMock = vi.fn((state, _, url) => {
url = !url.startsWith('http://') ? joinPaths(initialUrl, url) : url;
globalThis.window.location.href = new URL(url).href;
interceptedState = state;
});
Expand All @@ -37,6 +40,7 @@ describe("LocationFull", () => {
});
beforeEach(() => {
globalThis.window.location.href = initialUrl;
interceptedState = { path: undefined, hash: {} };
pushStateMock.mockReset();
replaceStateMock.mockReset();
location = new LocationFull();
Expand All @@ -48,7 +52,6 @@ describe("LocationFull", () => {
test("Should create a new instance with the expected default values.", () => {
// Assert.
expect(location.url.href).toBe(initialUrl);
expect(location.state).toBe(null);
});
});
describe('on', () => {
Expand Down Expand Up @@ -171,7 +174,7 @@ describe("LocationFull", () => {
] satisfies (keyof History)[])("Should update whenever an external call to %s is made.", (fn) => {
// Arrange.
const newUrl = "http://example.com/new";

// Act.
globalThis.window.history[fn](null, '', newUrl);
flushSync();
Expand All @@ -180,20 +183,22 @@ describe("LocationFull", () => {
expect(location.url.href).toBe(newUrl);
});
});
describe('state', () => {
describe('getState', () => {
test.each([
'pushState',
'replaceState',
] satisfies (keyof History)[])("Should update whenever an external call to %s is made.", (fn) => {
// Arrange.
const state = { test: 'value' };
const state: State = { path: { test: 'value' }, hash: { single: '/abc', p1: '/def' } };

// Act.
globalThis.window.history[fn](state, '', 'http://example.com/new');
flushSync();

// Assert.
expect(location.state).toBe(state);
expect(location.getState(false)).toEqual(state.path);
expect(location.getState(true)).toEqual(state.hash.single);
expect(location.getState('p1')).toEqual(state.hash.p1);
});
});
});
2 changes: 1 addition & 1 deletion src/lib/core/LocationFull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class LocationFull extends LocationLite {
}
} else {
const navFn = method === 'push' ? this.#originalPushState : this.#originalReplaceState;
navFn(state, '', url);
navFn(event.state, '', url);
this.url.href = globalThis.window?.location.href;
this.#innerState.state = state;
}
Expand Down
Loading