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
2 changes: 1 addition & 1 deletion src/lib/Fallback/Fallback.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { resolveHashValue } from '$lib/core/RouterEngine.svelte.js';
import { resolveHashValue } from '$lib/core/resolveHashValue.js';
import { getRouterContext } from '$lib/Router/Router.svelte';
import type { RouteStatus } from '$lib/types.js';
import type { Snippet } from 'svelte';
Expand Down
8 changes: 4 additions & 4 deletions src/lib/Link/Link.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<script lang="ts">
import { calculateHref } from '$lib/core/calculateHref.js';
import { calculateState } from '$lib/core/calculateState.svelte.js';
import { calculateState } from '$lib/core/calculateState.js';
import { location } from '$lib/core/Location.js';
import { resolveHashValue } from '$lib/core/RouterEngine.svelte.js';
import { resolveHashValue } from '$lib/core/resolveHashValue.js';
import { getLinkContext, type ILinkContext } from '$lib/LinkContext/LinkContext.svelte';
import { getRouterContext } from '$lib/Router/Router.svelte';
import type { ActiveState, RouteStatus } from '$lib/types.js';
import type { ActiveState, Hash, RouteStatus } from '$lib/types.js';
import { type Snippet } from 'svelte';
import type { HTMLAnchorAttributes } from 'svelte/elements';

Expand Down Expand Up @@ -41,7 +41,7 @@
* route becomes active.
* - The `prependBasePath` property: It depends on the parent router to set the base path for the link.
*/
hash?: boolean | string;
hash?: Hash;
/**
* Sets the URL to navigate to. Never use a full URL; always use relative or absolute paths.
*/
Expand Down
14 changes: 7 additions & 7 deletions src/lib/LinkContext/LinkContext.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,18 @@
};

class _LinkContext implements ILinkContext {
replace = $state(false);
prependBasePath = $state(false);
preserveQuery = $state<ILinkContext['preserveQuery']>(false);
replace;
prependBasePath;
preserveQuery;

constructor(
replace: boolean,
prependBasePath: boolean,
preserveQuery: ILinkContext['preserveQuery']
preserveQuery: PreserveQuery
) {
this.replace = replace;
this.prependBasePath = prependBasePath;
this.preserveQuery = preserveQuery;
this.replace = $state(replace);
this.prependBasePath = $state(prependBasePath);
this.preserveQuery = $state(preserveQuery);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/Route/Route.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<script lang="ts" generics="T extends string | RegExp">
import { untrack, type Snippet } from 'svelte';
import { getRouterContext } from '../Router/Router.svelte';
import { resolveHashValue } from '$lib/core/RouterEngine.svelte.js';
import { resolveHashValue } from '$lib/core/resolveHashValue.js';
import type { ParameterValue, RouteStatus } from '$lib/types.js';

type Props = {
Expand Down
3 changes: 2 additions & 1 deletion 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 { RouterEngine } from '$lib/core/RouterEngine.svelte.js';
import { resolveHashValue } from '$lib/core/resolveHashValue.js';
import type { RouteStatus } from '$lib/types.js';

const parentCtxKey = Symbol();
Expand Down
2 changes: 1 addition & 1 deletion src/lib/RouterTrace/RouterTrace.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script lang="ts">
import { traceOptions, getAllChildRouters } from '$lib/core/trace.svelte.js';
import {
resolveHashValue,
routePatternsKey,
RouterEngine
} from '$lib/core/RouterEngine.svelte.js';
import { resolveHashValue } from '$lib/core/resolveHashValue.js';
import { getRouterContext } from '$lib/Router/Router.svelte';
import type { PatternRouteInfo } from '$lib/types.js';
import type { HTMLTableAttributes } from 'svelte/elements';
Expand Down
3 changes: 3 additions & 0 deletions src/lib/core/LocationFull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import type { BeforeNavigateEvent, Events, NavigationCancelledEvent, NavigationE
import { LocationLite } from "./LocationLite.svelte.js";
import { LocationState } from "./LocationState.svelte.js";

/**
* Location implementation of the library's full mode feature.
*/
export class LocationFull extends LocationLite {
#eventSubs: Record<Events, Record<number, Function>> = {
beforeNavigate: {},
Expand Down
4 changes: 2 additions & 2 deletions src/lib/core/LocationLite.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { getCompleteStateKey } from "./Location.js";
import { on } from "svelte/events";
import { LocationState } from "./LocationState.svelte.js";
import { routingOptions } from "./options.js";
import { resolveHashValue } from "./RouterEngine.svelte.js";
import { resolveHashValue } from "./resolveHashValue.js";
import { calculateHref } from "./calculateHref.js";
import { calculateState } from "./calculateState.svelte.js";
import { calculateState } from "./calculateState.js";
import { preserveQueryInUrl } from "./preserveQuery.js";

/**
Expand Down
4 changes: 3 additions & 1 deletion src/lib/core/LocationState.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { State } from "$lib/types.js";
import { SvelteURL } from "svelte/reactivity";
import { isConformantState } from "./isConformantState.js";

/**
* Helper class used to manage the reactive data of Location implementations.
*/
export class LocationState {
url = new SvelteURL(globalThis.window?.location?.href);
state;
Expand Down
13 changes: 1 addition & 12 deletions src/lib/core/RouterEngine.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { AndUntyped, Hash, PatternRouteInfo, RegexRouteInfo, RouteInfo, Rou
import { traceOptions, registerRouter, unregisterRouter } from "./trace.svelte.js";
import { location } from "./Location.js";
import { routingOptions } from "./options.js";
import { resolveHashValue } from "./resolveHashValue.js";

/**
* RouterEngine's options.
Expand All @@ -24,18 +25,6 @@ export type RouterEngineOptions = {
hash?: boolean | string;
}

/**
* Resolves the given hash value taking into account the library's routing options.
* @param hash Hash value to resolve.
* @returns The resolved hash value.
*/
export function resolveHashValue(hash: boolean | string | undefined) {
if (hash === undefined) {
return routingOptions.implicitMode === 'hash';
}
return hash;
}

function isRouterEngine(obj: unknown): obj is RouterEngine {
return obj instanceof RouterEngine;
}
Expand Down
73 changes: 43 additions & 30 deletions src/lib/core/calculateHref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import type { Hash, PreserveQuery } from "$lib/types.js";
import { dissectHrefs } from "./dissectHrefs.js";
import { location } from "./Location.js";
import { mergeQueryParams } from "./preserveQuery.js";
import { joinPaths, resolveHashValue } from "./RouterEngine.svelte.js";
import { joinPaths } from "./RouterEngine.svelte.js";
import { resolveHashValue } from "./resolveHashValue.js";

export type CalculateHrefOptions = {
/**
Expand All @@ -12,55 +13,68 @@ export type CalculateHrefOptions = {
* the current URL will be appended with the ones from the new URL.
*/
preserveQuery?: PreserveQuery;
} & ({
/**
* Determines the routing universe the new URL will be for.
*
* Read the [online documentation](https://wjfe-n-savant.hashnode.space/wjfe-n-savant/routing-modes) to understand
* the concept of routing modes (or universes).
*/
hash: false;
/**
* Whether to preserve the current hash in the new URL. This is only applicable when the `hash` property is set to
* `false` (path routing universe).
*/
preserveHash?: boolean;
} | {
/**
* Determines the routing universe the new URL will be for.
*
* Read the [online documentation](https://wjfe-n-savant.hashnode.space/wjfe-n-savant/routing-modes) to understand
* the concept of routing modes (or universes).
*/
hash: Exclude<Hash, false>;
});
hash?: Hash;
};

function calculateMultiHashHref(hashId: string, newPath: string) {
let idExists = false;
let finalUrl = '';
for (let [id, path] of Object.entries(location.hashPaths)) {
if (id === hashId) {
idExists = true;
finalUrl += `;${id}=${newPath}`;
continue;
path = newPath;
}
finalUrl += `;${id}=${path}`;
}
if (!idExists) {
finalUrl += `;${hashId}=${newPath}`;
}
return '#' + finalUrl.substring(1);
return finalUrl.substring(1);
}

export function calculateHref(...paths: (string | undefined)[]): string;
export function calculateHref(options: CalculateHrefOptions, ...paths: (string | undefined)[]): string;
/**
* Combines the given HREF's into a single HREF that also includes any query string parameters that are either carried
* by the given HREF's, or preserved from the current environment's URL.
*
* Calculation is done assuming the resultant HREF will be for the routing universe specified in the library's
* `implicitMode` option.
* @param hrefs The HREF's used to calculate the final HREF for the routing universe implied by the library's implicit
* mode.
*/
export function calculateHref(...hrefs: (string | undefined)[]): string;
/**
* Combines the given HREF's into a single HREF that also includes any query string parameters that are either carried
* by the given HREF's, or preserved from the current environment's URL.
*
* Calculation is done assuming the resultant HREF will be for the routing universe specified by the options' `hash`
* property. If the option is not specified, then a value will be resolved based on the library's `implicitMode`
* option.
* @param options Desired options that control how the resultant HREF is calculated.
* @param hrefs The HREF's used to calculate the final HREF for the desired routing universe.
*/
export function calculateHref(options: CalculateHrefOptions, ...hrefs: (string | undefined)[]): string;
export function calculateHref(...allArgs: (CalculateHrefOptions | string | undefined)[]): string {
const options = (typeof allArgs[0] === 'object' ? allArgs.shift() : { hash: resolveHashValue(undefined) }) as CalculateHrefOptions;
const paths = allArgs as (string | undefined)[];
options.preserveQuery ??= false;
const dissected = dissectHrefs(...paths);
if (options.hash !== false && dissected.hashes.some(h => !!h.length)) {
throw new Error("Specifying hashes in the 'href' property is only allowed for path routing.");
let options = (typeof allArgs[0] === 'object' ? allArgs.shift() : {}) as CalculateHrefOptions;
let {
hash = resolveHashValue(undefined),
preserveQuery = false,
preserveHash = false
} = options;
const allHrefs = allArgs as (string | undefined)[];
const dissected = dissectHrefs(...allHrefs);
if (hash !== false && dissected.hashes.some(h => !!h.length)) {
throw new Error("Specifying hashes in HREF's is only allowed for path routing.");
}
let searchParams: URLSearchParams | undefined;
let joinedSearchParams = '';
Expand All @@ -72,13 +86,12 @@ export function calculateHref(...allArgs: (CalculateHrefOptions | string | undef
if (joinedSearchParams.length) {
searchParams = new URLSearchParams(joinedSearchParams.substring(1));
}
searchParams = mergeQueryParams(searchParams, options.preserveQuery);
const path = typeof options.hash === 'string' ?
calculateMultiHashHref(options.hash, joinPaths(...dissected.paths)) :
searchParams = mergeQueryParams(searchParams, preserveQuery);
const path = typeof hash === 'string' ?
calculateMultiHashHref(hash, joinPaths(...dissected.paths)) :
joinPaths(...dissected.paths);
let hashTag = options.hash === false ?
dissected.hashes.find(h => h.length) || ((options.preserveHash ? location.url.hash : '') ?? '') :
let hashValue = hash === false ?
dissected.hashes.find(h => h.length) || (preserveHash ? location.url.hash.substring(1) : '') :
path;
hashTag = hashTag.length && hashTag[0] !== '#' ? `#${hashTag}` : hashTag;
return `${options.hash ? '' : path}${searchParams ? `?${searchParams}` : ''}${hashTag.length ? `${hashTag}` : ''}`;
return `${hash ? '' : path}${searchParams ? `?${searchParams}` : ''}${hashValue.length ? `#${hashValue}` : ''}`;
}
2 changes: 1 addition & 1 deletion src/lib/core/calculateState.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { init, location } from '$lib/index.js';
import { describe, test, expect, beforeAll, afterAll, beforeEach, vi } from 'vitest';
import { calculateState } from './calculateState.svelte.js';
import { calculateState } from './calculateState.js';

describe('calculateState', () => {
const initialUrl = "http://example.com/";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,41 @@
import type { Hash, Location, State } from "$lib/types.js";
import { location } from "./Location.js";
import { getCompleteStateKey } from "./Location.js";
import { resolveHashValue } from "./resolveHashValue.js";

type LocationInternal = Location & { [getCompleteStateKey]: () => State };

/**
* Calculates the complete state object that should be set in the History API, setting the given state as the state of
* the implicit routing universe, making sure that all states for all other routing universes are preserved.
* @param state The desired state for the given hash.
*/
export function calculateState(state: any): State;
/**
* Calculates the state object that should be set for a given hash value, making sure that all states for all other
* routing universes are preserved.
* @param hash The hash value associated with the state.
* @param state The desired state for the given hash.
* @returns The state object that should be set, accounting for all routing universes.
*/
export function calculateState(hash: Hash, state: any): State {
export function calculateState(hash: Hash, state: any): State;
export function calculateState(hashOrState: any, state?: any): State {
let hash: Hash;
if (arguments.length === 1) {
state = hashOrState;
hash = resolveHashValue(undefined);
}
else {
hash = hashOrState;
}
// Get a deep clone of the complete current state using the internal symbol method
const newState = (location as LocationInternal)[getCompleteStateKey]();

// Set the new state in the appropriate routing universe
if (typeof hash === 'string') {
// For named hash routing (multi-hash mode), set the state for this specific hash name
// Preserve all existing named hash universes by only setting the target one
newState.hash[hash] = state;
}
else if (hash) {
// For single hash routing (traditional mode), set the single hash state
// NOTE: We do NOT preserve other named hash states as we're in traditional mode
newState.hash = { single: state };
}
else {
Expand Down
1 change: 0 additions & 1 deletion src/lib/core/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ describe('index', () => {
'RouterEngine',
'joinPaths',
'isConformantState',
'dissectHrefs',
'calculateHref',
'calculateState',
];
Expand Down
3 changes: 1 addition & 2 deletions src/lib/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export { location } from "./Location.js";
export { RouterEngine, joinPaths } from "./RouterEngine.svelte.js";
export { isConformantState } from "./isConformantState.js";
export { dissectHrefs } from "./dissectHrefs.js";
export { calculateHref } from "./calculateHref.js";
export { calculateState } from "./calculateState.svelte.js";
export { calculateState } from "./calculateState.js";
13 changes: 13 additions & 0 deletions src/lib/core/resolveHashValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { routingOptions } from "./options.js";

/**
* Resolves the given hash value taking into account the library's routing options.
* @param hash Hash value to resolve.
* @returns The resolved hash value.
*/
export function resolveHashValue(hash: boolean | string | undefined) {
if (hash === undefined) {
return routingOptions.implicitMode === 'hash';
}
return hash;
}
3 changes: 0 additions & 3 deletions src/lib/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ describe('index', () => {
'init',
'getRouterContext',
'setRouterContext',
'isConformantState',
'calculateHref',
'calculateState',
];

// Act.
Expand Down
3 changes: 0 additions & 3 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,5 @@ export * from "$lib/Fallback/Fallback.svelte";
export { default as Fallback } from "$lib/Fallback/Fallback.svelte";
export type * from "./types.js";
export { location } from "./core/Location.js";
export { isConformantState } from "./core/isConformantState.js";
export { calculateHref } from "./core/calculateHref.js";
export { calculateState } from "./core/calculateState.svelte.js";
export * from './RouterTrace/RouterTrace.svelte';
export { default as RouterTrace } from './RouterTrace/RouterTrace.svelte';
Loading