cross-router-svelte 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2026 Bricklou
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,81 @@
1
+ <script lang='ts'>
2
+ import type { Snippet } from 'svelte'
3
+ import type { HTMLFormAttributes } from 'svelte/elements'
4
+ import { getRouter } from './context.svelte'
5
+
6
+ // ============================================================
7
+ // PROPS
8
+ // ============================================================
9
+
10
+ interface Props extends HTMLFormAttributes {
11
+ // Called before submission — return false to cancel
12
+ onBeforeSubmit?: (formData: FormData) => boolean | Promise<boolean>
13
+
14
+ // Called after submission completes (success or error)
15
+ onAfterSubmit?: () => void
16
+
17
+ children: Snippet
18
+ }
19
+
20
+ const {
21
+ action,
22
+ class: className,
23
+ id,
24
+ enctype,
25
+ onBeforeSubmit,
26
+ onAfterSubmit,
27
+ children,
28
+ }: Props = $props()
29
+
30
+ // ============================================================
31
+ // ROUTER ACCESS
32
+ // ============================================================
33
+
34
+ const router = getRouter()
35
+
36
+ // ============================================================
37
+ // SUBMIT HANDLER
38
+ // ============================================================
39
+
40
+ async function handleSubmit(event: SubmitEvent) {
41
+ event.preventDefault()
42
+
43
+ const form = event.currentTarget as HTMLFormElement
44
+ const formData = new FormData(form)
45
+
46
+ // Include the submitter's name/value if present
47
+ // (e.g. multiple submit buttons with different values)
48
+ const submitter = event.submitter as HTMLButtonElement | null
49
+ if (submitter?.name) {
50
+ formData.set(submitter.name, submitter.value)
51
+ }
52
+
53
+ // Pre-submit hook — cancel if returns false
54
+ if (onBeforeSubmit) {
55
+ const shouldContinue = await onBeforeSubmit(formData)
56
+ if (shouldContinue === false)
57
+ return
58
+ }
59
+
60
+ // Resolve action path — defaults to current pathname
61
+ const actionPath = action ?? window.location.pathname
62
+
63
+ await router.submit(formData, {
64
+ action: actionPath,
65
+ method: 'POST',
66
+ })
67
+
68
+ onAfterSubmit?.()
69
+ }
70
+ </script>
71
+
72
+ <form
73
+ method='POST'
74
+ action={action ?? '#'}
75
+ class={className}
76
+ {id}
77
+ {enctype}
78
+ onsubmit={handleSubmit}
79
+ >
80
+ {@render children()}
81
+ </form>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLFormAttributes } from 'svelte/elements';
3
+ interface Props extends HTMLFormAttributes {
4
+ onBeforeSubmit?: (formData: FormData) => boolean | Promise<boolean>;
5
+ onAfterSubmit?: () => void;
6
+ children: Snippet;
7
+ }
8
+ declare const Form: import("svelte").Component<Props, {}, "">;
9
+ type Form = ReturnType<typeof Form>;
10
+ export default Form;
@@ -0,0 +1,102 @@
1
+ <script lang='ts'>
2
+ import type { NavigateOptions } from '@cross-router/core'
3
+ import type { Snippet } from 'svelte'
4
+ import type { HTMLAnchorAttributes } from 'svelte/elements'
5
+ import { getRouter, routerState } from './context.svelte'
6
+
7
+ // ============================================================
8
+ // PROPS
9
+ // ============================================================
10
+
11
+ interface Props extends HTMLAnchorAttributes {
12
+ // Target path — required
13
+ href: string
14
+
15
+ // Navigate options
16
+ replace?: boolean
17
+ viewTransition?: boolean
18
+ state?: unknown
19
+
20
+ // Active link styling
21
+ // Applied when the current pathname starts with href
22
+ activeClass?: string
23
+ // Applied only when href matches exactly
24
+ exactActiveClass?: string
25
+
26
+ // Content
27
+ children: Snippet
28
+ }
29
+
30
+ const {
31
+ href,
32
+ replace = false,
33
+ viewTransition = false,
34
+ state,
35
+ activeClass = '',
36
+ exactActiveClass = '',
37
+ target,
38
+ children,
39
+ ...props
40
+ }: Props = $props()
41
+
42
+ // ============================================================
43
+ // ROUTER ACCESS
44
+ // ============================================================
45
+
46
+ const router = getRouter()
47
+
48
+ // ============================================================
49
+ // ACTIVE STATE
50
+ // ============================================================
51
+
52
+ const isActive = $derived(
53
+ routerState.current?.location.pathname.startsWith(href) ?? false,
54
+ )
55
+
56
+ const isExactActive = $derived(
57
+ routerState.current?.location.pathname === href,
58
+ )
59
+
60
+ // ============================================================
61
+ // CLICK HANDLER
62
+ // Intercepts clicks to use client-side navigation.
63
+ // Respects modifier keys (Ctrl, Meta, Shift) and target="_blank"
64
+ // so the browser handles those normally.
65
+ // ============================================================
66
+
67
+ function handleClick(event: MouseEvent) {
68
+ // Let the browser handle modified clicks and external targets
69
+ if (
70
+ event.defaultPrevented
71
+ || event.metaKey
72
+ || event.ctrlKey
73
+ || event.shiftKey
74
+ || event.altKey
75
+ || target === '_blank'
76
+ ) {
77
+ return
78
+ }
79
+
80
+ event.preventDefault()
81
+
82
+ const opts: NavigateOptions = { replace, viewTransition, state }
83
+
84
+ if (replace) {
85
+ router.replace(href, opts)
86
+ }
87
+ else {
88
+ router.navigate(href, opts)
89
+ }
90
+ }
91
+ </script>
92
+
93
+ <a
94
+ {href}
95
+ {target}
96
+ class={{ [activeClass]: isActive, [exactActiveClass]: isExactActive }}
97
+ onclick={handleClick}
98
+ aria-current={isExactActive ? 'page' : undefined}
99
+ {...props}
100
+ >
101
+ {@render children()}
102
+ </a>
@@ -0,0 +1,14 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAnchorAttributes } from 'svelte/elements';
3
+ interface Props extends HTMLAnchorAttributes {
4
+ href: string;
5
+ replace?: boolean;
6
+ viewTransition?: boolean;
7
+ state?: unknown;
8
+ activeClass?: string;
9
+ exactActiveClass?: string;
10
+ children: Snippet;
11
+ }
12
+ declare const Link: import("svelte").Component<Props, {}, "">;
13
+ type Link = ReturnType<typeof Link>;
14
+ export default Link;
@@ -0,0 +1,147 @@
1
+ <script lang='ts' module>
2
+ export type SvelteRouteComponent = typeof import('svelte').SvelteComponent
3
+ </script>
4
+
5
+ <script lang='ts'>
6
+ import type { RouterInstance } from 'cross-router-core'
7
+ import type { Component } from 'svelte'
8
+ import { onDestroy, onMount, untrack } from 'svelte'
9
+ import {
10
+ routerState,
11
+ setMatchDepthContext,
12
+ setRouter,
13
+ } from './context.svelte'
14
+ import RouterView from './RouterView.svelte'
15
+
16
+ interface Props {
17
+ router?: RouterInstance
18
+ _depth?: number
19
+ }
20
+
21
+ const props: Props = $props()
22
+
23
+ const isRoot = untrack(() => props._depth === undefined)
24
+ const depth = untrack(() => props._depth ?? 0)
25
+ const nextDepth = depth + 1
26
+
27
+ // ── Reactive state ───────────────────────────────────────────
28
+
29
+ if (isRoot) {
30
+ const router = untrack(() => props.router)
31
+
32
+ if (!router) {
33
+ throw new Error(
34
+ '[RouterView] Root <RouterView> requires a `router` prop.\nUsage: <RouterView router={myRouter} />',
35
+ )
36
+ }
37
+
38
+ setRouter(router)
39
+
40
+ // Subscribe BEFORE initialize() so we never miss state updates.
41
+ const unsubscribe = router.subscribe((newState) => {
42
+ routerState.current = newState
43
+ })
44
+
45
+ routerState.current = router.state
46
+
47
+ setMatchDepthContext({
48
+ depth,
49
+ get match() { return routerState.current?.matches[depth]! },
50
+ })
51
+
52
+ // Initialize LAST — it fires setState({ status: 'loading' }) synchronously
53
+ // which immediately re-renders children. All context must be set first.
54
+ onMount(() => {
55
+ router.initialize()
56
+ })
57
+
58
+ onDestroy(() => {
59
+ unsubscribe()
60
+ router.destroy()
61
+ })
62
+ }
63
+ else {
64
+ // Set depth context synchronously so child hooks work on first render.
65
+ setMatchDepthContext({
66
+ depth,
67
+ get match() { return routerState.current?.matches[depth]! },
68
+ })
69
+ }
70
+
71
+ // ── Debug ────────────────────────────────────────────────────
72
+ $effect(() => {
73
+ try {
74
+ const val = localStorage.getItem('cross-router:debug')
75
+ if (!val)
76
+ return
77
+ }
78
+ catch { return }
79
+
80
+ const s = routerState.current
81
+ if (!s)
82
+ return
83
+ // eslint-disable-next-line no-console
84
+ console.log(
85
+ `%c[RouterView depth=${depth}] status=${s.status} matches=${s.matches.length}`,
86
+ 'color:#a855f7;font-weight:bold',
87
+ s.matches.map(m => m.route.id),
88
+ )
89
+ })
90
+
91
+ // ── Derived values ───────────────────────────────────────────
92
+ const currentState = $derived(routerState.current)
93
+ const match = $derived(currentState?.matches[depth] ?? null)
94
+ const hasChild = $derived(currentState !== null && (currentState?.matches.length ?? 0) > nextDepth)
95
+
96
+ // --- Foreign renderer mount
97
+ let foreignTarget: HTMLElement | null = $state(null)
98
+
99
+ $effect(() => {
100
+ const renderer = match?.route.__renderer
101
+ const component = match?.route.component
102
+ if (!renderer || !component || !foreignTarget)
103
+ return
104
+
105
+ return renderer.mount(component, foreignTarget)
106
+ })
107
+
108
+ // ── View transition ──────────────────────────────────────────
109
+
110
+ $effect(() => {
111
+ if (currentState?.viewTransition && 'startViewTransition' in document) {
112
+ document.startViewTransition(() => {})
113
+ }
114
+ })
115
+ </script>
116
+
117
+ {#if currentState === null}
118
+ <!-- Router not yet initialized -->
119
+ {:else if currentState.status === 'error' && !currentState.matches.length}
120
+ <!-- Global unhandled error — no errorComponent in tree -->
121
+ {:else if match}
122
+ {@const renderer = match.route.__renderer}
123
+ {@const RouteComponent = match.route.component as Component | undefined}
124
+ {@const ErrorComponent = match.route.errorComponent as Component | undefined}
125
+
126
+ {#if match.status === 'error' && ErrorComponent}
127
+ <ErrorComponent error={match.error} />
128
+ {:else if renderer && RouteComponent}
129
+ <!-- Foreign framework component - mountded via its adapter renderer -->
130
+ <div bind:this={foreignTarget}></div>
131
+ {#if hasChild}
132
+ <RouterView _depth={nextDepth} />
133
+ {/if}
134
+ {:else if RouteComponent}
135
+ <RouteComponent>
136
+ {#snippet outlet()}
137
+ {#if hasChild}
138
+ <RouterView _depth={nextDepth} />
139
+ {/if}
140
+ {/snippet}
141
+ </RouteComponent>
142
+ {:else if hasChild}
143
+ <!-- Pathless/componentless layout route — no component to render,
144
+ just pass through to the next depth immediately -->
145
+ <RouterView _depth={nextDepth} />
146
+ {/if}
147
+ {/if}
@@ -0,0 +1,11 @@
1
+ export type SvelteRouteComponent = typeof import('svelte').SvelteComponent;
2
+ import type { RouterInstance } from 'cross-router-core';
3
+ import type { Component } from 'svelte';
4
+ import RouterView from './RouterView.svelte';
5
+ interface Props {
6
+ router?: RouterInstance;
7
+ _depth?: number;
8
+ }
9
+ declare const RouterView: Component<Props, {}, "">;
10
+ type RouterView = ReturnType<typeof RouterView>;
11
+ export default RouterView;
@@ -0,0 +1,21 @@
1
+ import type { AnyRouteMatch, NavigationState, RouterInstance } from 'cross-router-core';
2
+ declare const ROUTER_SYMBOL: unique symbol;
3
+ declare const ROUTER_STATE_SYMBOL: unique symbol;
4
+ declare global {
5
+ interface Window {
6
+ [ROUTER_SYMBOL]: RouterInstance | null;
7
+ [ROUTER_STATE_SYMBOL]: NavigationState | null;
8
+ }
9
+ }
10
+ export declare function setRouter(router: RouterInstance): void;
11
+ export declare function getRouter(): RouterInstance;
12
+ export declare const routerState: {
13
+ current: NavigationState | null;
14
+ };
15
+ export interface MatchDepthContext {
16
+ readonly depth: number;
17
+ readonly match: AnyRouteMatch;
18
+ }
19
+ export declare function setMatchDepthContext(ctx: MatchDepthContext): void;
20
+ export declare function getMatchDepthContext(): MatchDepthContext | null;
21
+ export {};
@@ -0,0 +1,46 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ // ============================================================
3
+ // ROUTER SINGLETON
4
+ // Module-level — works outside components (loaders, actions, ts files).
5
+ // Set once at app startup by the framework adapter init.
6
+ // ============================================================
7
+ var ROUTER_SYMBOL = Symbol.for('cross-router-instance');
8
+ var ROUTER_STATE_SYMBOL = Symbol.for('cross-router-state');
9
+ window[ROUTER_SYMBOL] = null;
10
+ export function setRouter(router) {
11
+ window[ROUTER_SYMBOL] = router;
12
+ }
13
+ export function getRouter() {
14
+ var _router = window[ROUTER_SYMBOL];
15
+ if (!_router) {
16
+ throw new Error('[router] No router instance found. Make sure you called setRouter() before using any router hooks.');
17
+ }
18
+ return _router;
19
+ }
20
+ // ── Router state ─────────────────────────────────────────────
21
+ // Exported as a reactive object rather than via a getter function.
22
+ //
23
+ // $derived(getRouterState()) does NOT work across module boundaries:
24
+ // Svelte's tracker only intercepts $state reads that happen during
25
+ // the synchronous evaluation of the current reactive computation.
26
+ // A call to getRouterState() dispatches into another module's scope;
27
+ // the read of _state happens inside that function body, outside the
28
+ // tracker's view — so the derived never re-runs when state changes.
29
+ //
30
+ // Exporting a plain object whose property IS the $state variable
31
+ // lets consumers write `routerState` directly inside their
32
+ // $derived / template, which Svelte does track correctly.
33
+ export var routerState = $state({ current: null });
34
+ // ============================================================
35
+ // MATCH DEPTH CONTEXT
36
+ // Set by each <RouterView> level so useLoaderData() knows
37
+ // which match in the array it belongs to.
38
+ // ============================================================
39
+ var MATCH_DEPTH_KEY = 'cross-router-depth';
40
+ export function setMatchDepthContext(ctx) {
41
+ setContext(MATCH_DEPTH_KEY, ctx);
42
+ }
43
+ export function getMatchDepthContext() {
44
+ var _a;
45
+ return (_a = getContext(MATCH_DEPTH_KEY)) !== null && _a !== void 0 ? _a : null;
46
+ }
@@ -0,0 +1,12 @@
1
+ import type { AnyRouteMatch, Location, NavigateOptions, NavigationState, SubmitOptions } from '@cross-router/core';
2
+ export declare function useRouterState(): () => NavigationState;
3
+ export declare function useNavigationStatus(): () => NavigationState['status'];
4
+ export declare function useLocation(): () => Location;
5
+ export declare function useMatch(): () => AnyRouteMatch;
6
+ export declare function useParams<TParams extends Record<string, string> = Record<string, string>>(): () => TParams;
7
+ export declare function useLoaderData<TData = unknown>(): () => TData;
8
+ export declare function useActionData<TData = unknown>(): () => TData | undefined;
9
+ export declare function useNavigate(): (to: string, opts?: NavigateOptions) => Promise<void>;
10
+ export declare function useSubmit(): (formData: FormData, opts: SubmitOptions) => Promise<void>;
11
+ export declare function useIsTransitioning(): () => boolean;
12
+ export declare function useMatches(): () => AnyRouteMatch[];
package/dist/hooks.js ADDED
@@ -0,0 +1,116 @@
1
+ import { getMatchDepthContext, getRouter, routerState, } from './context.svelte';
2
+ // ============================================================
3
+ // HOOKS
4
+ //
5
+ // All hooks must be called inside a Svelte component
6
+ // (during component initialisation) — same rule as Svelte's
7
+ // own getContext().
8
+ //
9
+ // Exception: useNavigate() and useSubmit() return stable
10
+ // function references that can be stored and called anywhere.
11
+ // ============================================================
12
+ // ============================================================
13
+ // useRouterState
14
+ // Returns the full reactive NavigationState.
15
+ // Use this as an escape hatch — prefer the scoped hooks below.
16
+ // ============================================================
17
+ export function useRouterState() {
18
+ return function () { return routerState.current; };
19
+ }
20
+ // ============================================================
21
+ // useNavigationStatus
22
+ // Reactive shortcut for the current navigation status.
23
+ // Useful for global loading indicators.
24
+ // ============================================================
25
+ export function useNavigationStatus() {
26
+ return function () { return routerState.current.status; };
27
+ }
28
+ // ============================================================
29
+ // useLocation
30
+ // Reactive current location.
31
+ // ============================================================
32
+ export function useLocation() {
33
+ return function () { return routerState.current.location; };
34
+ }
35
+ // ============================================================
36
+ // useMatch
37
+ // Returns the RouteMatch for the current component's depth.
38
+ // Throws if called outside a RouterView context.
39
+ // ============================================================
40
+ export function useMatch() {
41
+ var depthCtx = getMatchDepthContext();
42
+ if (!depthCtx) {
43
+ throw new Error('[router] useMatch() must be called inside a route component '
44
+ + 'rendered by <RouterView>.');
45
+ }
46
+ var depth = depthCtx.depth;
47
+ return function () {
48
+ var _a;
49
+ var match = (_a = routerState.current) === null || _a === void 0 ? void 0 : _a.matches[depth];
50
+ if (!match) {
51
+ throw new Error("[router] useMatch() could not find a match at depth ".concat(depth, ". ")
52
+ + "This is likely a bug in RouterView.");
53
+ }
54
+ return match;
55
+ };
56
+ }
57
+ // ============================================================
58
+ // useParams
59
+ // Reactive params for the current route.
60
+ // Typed via generic — useParams<{ id: string }>()
61
+ // ============================================================
62
+ export function useParams() {
63
+ var getMatch = useMatch();
64
+ return function () { return getMatch().params; };
65
+ }
66
+ // ============================================================
67
+ // useLoaderData
68
+ // Reactive loader data for the current route.
69
+ // Typed via generic — useLoaderData<MyLoaderReturnType>()
70
+ // ============================================================
71
+ export function useLoaderData() {
72
+ var getMatch = useMatch();
73
+ return function () { return getMatch().loaderData; };
74
+ }
75
+ // ============================================================
76
+ // useActionData
77
+ // Reactive action data from the last form submission.
78
+ // Not scoped to depth — actions return a single global result.
79
+ // ============================================================
80
+ export function useActionData() {
81
+ return function () { var _a; return (_a = routerState.current) === null || _a === void 0 ? void 0 : _a.actionData; };
82
+ }
83
+ // ============================================================
84
+ // useNavigate
85
+ // Returns a stable navigate() function reference.
86
+ // Safe to call outside component lifecycle (event handlers, etc.)
87
+ // ============================================================
88
+ export function useNavigate() {
89
+ var router = getRouter();
90
+ return function (to, opts) { return router.navigate(to, opts); };
91
+ }
92
+ // ============================================================
93
+ // useSubmit
94
+ // Returns a stable submit() function reference.
95
+ // Useful for programmatic form submission outside <Form>.
96
+ // ============================================================
97
+ export function useSubmit() {
98
+ var router = getRouter();
99
+ return function (formData, opts) { return router.submit(formData, opts); };
100
+ }
101
+ // ============================================================
102
+ // useIsTransitioning
103
+ // True while a navigation is in flight.
104
+ // Useful for showing route-level skeleton states.
105
+ // ============================================================
106
+ export function useIsTransitioning() {
107
+ return function () { var _a, _b; return (_b = (_a = routerState.current) === null || _a === void 0 ? void 0 : _a.isTransitioning) !== null && _b !== void 0 ? _b : false; };
108
+ }
109
+ // ============================================================
110
+ // useMatches
111
+ // Returns the full ordered match array (root → leaf).
112
+ // Useful for breadcrumbs, tab bars, etc.
113
+ // ============================================================
114
+ export function useMatches() {
115
+ return function () { var _a, _b; return (_b = (_a = routerState.current) === null || _a === void 0 ? void 0 : _a.matches) !== null && _b !== void 0 ? _b : []; };
116
+ }
@@ -0,0 +1,14 @@
1
+ export { getRouter, setRouter } from './context.svelte';
2
+ export { default as Form } from './Form.svelte';
3
+ export { useActionData, useIsTransitioning, useLoaderData, useLocation, useMatch, useMatches, useNavigate, useNavigationStatus, useParams, useRouterState, useSubmit, } from './hooks';
4
+ export { default as Link } from './Link.svelte';
5
+ export { default as RouterView } from './RouterView.svelte';
6
+ export { route, svelteRenderer } from './routes';
7
+ export type { ActionArgs, ActionFn, AnyMiddleware, AnyRouteDefinition, AnyRouteMatch, History, InferSearch, LoaderArgs, LoaderFn, Location, Middleware, MiddlewareArgs, NavigateOptions, NavigationState, NavigationStatus, NextFn, ParamsFromPath, RevalidateArgs, RouteDefinition, RouteMatch, RouterContextProvider, RouteRegistry, RouterInstance, RouterOptions, SearchSchema, SubmitOptions, } from 'cross-router-core';
8
+ export { createBrowserHistory, createBrowserRouter, createMemoryHistory, createRegistry, createRouter, defineMiddleware, isRedirect, MiddlewareError, redirect, Redirect, RegistryError, } from 'cross-router-core';
9
+ /**
10
+ * Enable router debug logging in the browser console.
11
+ * @param scopes - 'all' or comma-separated: 'router,registry,matcher,loader,middleware'
12
+ */
13
+ export declare function enableDebug(scopes?: string): void;
14
+ export declare function disableDebug(): void;
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ // ============================================================
2
+ // cross-router-svelte — public API
3
+ // ============================================================
4
+ // ============================================================
5
+ // COMPONENTS
6
+ // ============================================================
7
+ export { getRouter, setRouter } from './context.svelte';
8
+ export { default as Form } from './Form.svelte';
9
+ export { useActionData, useIsTransitioning, useLoaderData, useLocation, useMatch, useMatches, useNavigate, useNavigationStatus, useParams, useRouterState, useSubmit, } from './hooks';
10
+ // ============================================================
11
+ // HOOKS
12
+ // All must be called during component initialisation
13
+ // (same constraint as Svelte's getContext)
14
+ // ============================================================
15
+ export { default as Link } from './Link.svelte';
16
+ // ============================================================
17
+ // ROUTER SINGLETON — for use outside components
18
+ // ============================================================
19
+ export { default as RouterView } from './RouterView.svelte';
20
+ export { route, svelteRenderer } from './routes';
21
+ export { createBrowserHistory, createBrowserRouter, createMemoryHistory, createRegistry,
22
+ // Router creation
23
+ createRouter,
24
+ // Route authoring
25
+ defineMiddleware, isRedirect, MiddlewareError, redirect, Redirect,
26
+ // Errors
27
+ RegistryError, } from 'cross-router-core';
28
+ // ── Debug ────────────────────────────────────────────────────
29
+ /**
30
+ * Enable router debug logging in the browser console.
31
+ * @param scopes - 'all' or comma-separated: 'router,registry,matcher,loader,middleware'
32
+ */
33
+ export function enableDebug(scopes) {
34
+ if (scopes === void 0) { scopes = 'all'; }
35
+ localStorage.setItem('cross-router:debug', scopes === 'all' ? 'true' : scopes);
36
+ // eslint-disable-next-line no-console
37
+ console.log('[cross-router] debug enabled:', scopes, '— reload to see logs from init');
38
+ }
39
+ export function disableDebug() {
40
+ localStorage.removeItem('cross-router:debug');
41
+ // eslint-disable-next-line no-console
42
+ console.log('[cross-router] debug disabled');
43
+ }
@@ -0,0 +1,25 @@
1
+ import type { AnyRouteDefinition, RouteDefinition, RouteRenderer } from 'cross-router-core';
2
+ import type { Component } from 'svelte';
3
+ /**
4
+ * The Svelte route renderer.
5
+ * RouterView uses this as the default - it's exported so other adapters
6
+ * can detect "is this already a Svelte-tagged route?" if needed
7
+ */
8
+ export declare const svelteRenderer: RouteRenderer;
9
+ /**
10
+ * Tag route definitions as Svelte-rendered
11
+ *
12
+ * This is the identity function for the host adapter - Svelte routes don't
13
+ * strictly need tagging since RouterView handles them natively, but calling
14
+ * `routes()` makes the framework ownership explicit and consistent with
15
+ * `routes()` from other adapters
16
+ *
17
+ * @example
18
+ * import { routes } from 'cross-router-svelte
19
+ *
20
+ * const appRoutes = [
21
+ * ...routes([{ id: 'home', path: '/', component: HomePage }]),
22
+ * ...otherRouter([ id: 'widget', path: '/widget', component: OtherWidget ])
23
+ * ]
24
+ */
25
+ export declare function route(defs: RouteDefinition<any, any, any, any, Component>[]): AnyRouteDefinition[];
package/dist/routes.js ADDED
@@ -0,0 +1,32 @@
1
+ import { tag } from 'cross-router-core';
2
+ import { mount, unmount } from 'svelte';
3
+ /**
4
+ * The Svelte route renderer.
5
+ * RouterView uses this as the default - it's exported so other adapters
6
+ * can detect "is this already a Svelte-tagged route?" if needed
7
+ */
8
+ export var svelteRenderer = {
9
+ mount: function (component, target) {
10
+ var instance = mount(component, { target: target });
11
+ return function () { return unmount(instance); };
12
+ },
13
+ };
14
+ /**
15
+ * Tag route definitions as Svelte-rendered
16
+ *
17
+ * This is the identity function for the host adapter - Svelte routes don't
18
+ * strictly need tagging since RouterView handles them natively, but calling
19
+ * `routes()` makes the framework ownership explicit and consistent with
20
+ * `routes()` from other adapters
21
+ *
22
+ * @example
23
+ * import { routes } from 'cross-router-svelte
24
+ *
25
+ * const appRoutes = [
26
+ * ...routes([{ id: 'home', path: '/', component: HomePage }]),
27
+ * ...otherRouter([ id: 'widget', path: '/widget', component: OtherWidget ])
28
+ * ]
29
+ */
30
+ export function route(defs) {
31
+ return tag(svelteRenderer, defs);
32
+ }
@@ -0,0 +1,6 @@
1
+ declare module '*.svelte' {
2
+ import type { Component } from 'svelte'
3
+
4
+ const component: Component<any, any, any>
5
+ export default component
6
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "cross-router-svelte",
3
+ "type": "module",
4
+ "version": "1.0.1",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "import": "./dist/index.js",
9
+ "svelte": "./dist/index.js"
10
+ }
11
+ },
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "peerDependencies": {
18
+ "svelte": "^5.53.7",
19
+ "cross-router-core": "1.0.1"
20
+ },
21
+ "devDependencies": {
22
+ "@sveltejs/package": "^2.5.7",
23
+ "@types/node": "^24.12.0",
24
+ "eslint-plugin-svelte": "^3.15.0",
25
+ "svelte": "^5.53.7",
26
+ "typescript": "^5.9.3",
27
+ "vitest": "^4.0.18"
28
+ },
29
+ "publishConfig": {
30
+ "linkDirectory": true
31
+ },
32
+ "scripts": {
33
+ "build": "svelte-package",
34
+ "typecheck": "tsc --noEmit"
35
+ }
36
+ }