@streetui/router 1.0.0
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 +21 -0
- package/README.md +187 -0
- package/dist/index.cjs +373 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +246 -0
- package/dist/index.d.ts +246 -0
- package/dist/index.js +337 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { PageDSL, ContainerDSL } from '@streetui/dsl';
|
|
2
|
+
import { ReadonlySignal } from '@streetui/state';
|
|
3
|
+
import { StreetRenderer } from '@streetui/runtime';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* StreetUI Router — public type surface.
|
|
7
|
+
*
|
|
8
|
+
* The router sits ABOVE the DSL/compiler/runtime/renderer and composes them.
|
|
9
|
+
* It introduces no new rendering path and no second reactive system: route
|
|
10
|
+
* state is a StreetUI `signal`, route cleanup reuses the core `CleanupRegistry`,
|
|
11
|
+
* and each route renders as an ordinary compiled StreetUI application tree.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Context handed to a route builder when its route becomes active.
|
|
16
|
+
*
|
|
17
|
+
* `params` are the values captured from dynamic segments (`/users/:id`),
|
|
18
|
+
* `query` is the parsed query string, and `onCleanup` registers work to run
|
|
19
|
+
* when the router navigates away from this route (subscriptions, effects,
|
|
20
|
+
* timers). It is backed by a route-scoped `CleanupRegistry` — there is no
|
|
21
|
+
* separate cleanup mechanism.
|
|
22
|
+
*/
|
|
23
|
+
interface RouteContext {
|
|
24
|
+
/** Full matched pathname, e.g. `/users/123`. */
|
|
25
|
+
readonly path: string;
|
|
26
|
+
/** The route pattern that matched, e.g. `/users/:id` or `*`. */
|
|
27
|
+
readonly pattern: string;
|
|
28
|
+
/** Dynamic segment values captured from the path. */
|
|
29
|
+
readonly params: Readonly<Record<string, string>>;
|
|
30
|
+
/** Parsed query string (everything after `?`). */
|
|
31
|
+
readonly query: URLSearchParams;
|
|
32
|
+
/** Register a callback to run when navigating away from this route. */
|
|
33
|
+
onCleanup(fn: () => void): void;
|
|
34
|
+
}
|
|
35
|
+
/** Builds a route's page tree. Receives the page scope and the route context. */
|
|
36
|
+
type RouteBuilder = (page: PageDSL, ctx: RouteContext) => void;
|
|
37
|
+
/** A single route: a path pattern and the builder that renders it. */
|
|
38
|
+
interface RouteDefinition {
|
|
39
|
+
/**
|
|
40
|
+
* Path pattern. Supports:
|
|
41
|
+
* - static segments: `/`, `/docs`, `/docs/getting-started`
|
|
42
|
+
* - dynamic `:param`: `/users/:id`
|
|
43
|
+
* - catch-all wildcard: `*` (matches anything — use for a 404 route)
|
|
44
|
+
*/
|
|
45
|
+
readonly path: string;
|
|
46
|
+
readonly builder: RouteBuilder;
|
|
47
|
+
}
|
|
48
|
+
/** The result of resolving a location against the route table. */
|
|
49
|
+
interface RouteMatch {
|
|
50
|
+
/** The active pathname (no query string). */
|
|
51
|
+
readonly path: string;
|
|
52
|
+
/** The pattern of the matched route. */
|
|
53
|
+
readonly pattern: string;
|
|
54
|
+
/** Captured dynamic params. */
|
|
55
|
+
readonly params: Readonly<Record<string, string>>;
|
|
56
|
+
/** Parsed query string. */
|
|
57
|
+
readonly query: URLSearchParams;
|
|
58
|
+
/** The route definition that produced this match. */
|
|
59
|
+
readonly route: RouteDefinition;
|
|
60
|
+
/** True when this match came from the wildcard catch-all (`*`) — i.e. a 404. */
|
|
61
|
+
readonly isFallback: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Route matching — pure functions, no DOM, no reactivity.
|
|
66
|
+
*
|
|
67
|
+
* A pattern is matched segment-by-segment against a pathname:
|
|
68
|
+
* - a literal segment must equal the path segment,
|
|
69
|
+
* - a `:name` segment captures the path segment into `params.name`,
|
|
70
|
+
* - a `*` segment (or a whole-pattern `*`) is a catch-all that matches the
|
|
71
|
+
* remainder of the path and captures it into `params['*']`.
|
|
72
|
+
*
|
|
73
|
+
* Matching is intentionally small: no optional segments, no regex constraints,
|
|
74
|
+
* no nested route trees. Composition of layouts is done in the DSL, not here.
|
|
75
|
+
*/
|
|
76
|
+
/** Normalize a pathname: ensure a single leading slash, drop a trailing slash. */
|
|
77
|
+
declare function normalizePath(path: string): string;
|
|
78
|
+
/**
|
|
79
|
+
* Try to match a single pattern against a pathname.
|
|
80
|
+
* Returns the captured params on success, or `null` on no match.
|
|
81
|
+
*/
|
|
82
|
+
declare function matchPattern(pattern: string, pathname: string): Record<string, string> | null;
|
|
83
|
+
interface MatchResult<R> {
|
|
84
|
+
readonly route: R;
|
|
85
|
+
readonly params: Record<string, string>;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Match a pathname against an ordered list of routes. The first route whose
|
|
89
|
+
* pattern matches wins (definition order), so more specific routes should be
|
|
90
|
+
* listed before a `*` fallback.
|
|
91
|
+
*/
|
|
92
|
+
declare function matchRoutes<R extends {
|
|
93
|
+
path: string;
|
|
94
|
+
}>(routes: readonly R[], pathname: string): MatchResult<R> | null;
|
|
95
|
+
/** Split a `to` target into its pathname and (already-stripped) search string. */
|
|
96
|
+
declare function splitTarget(to: string): {
|
|
97
|
+
pathname: string;
|
|
98
|
+
search: string;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Router history — a small abstraction over the navigation source so the router
|
|
103
|
+
* can run both in the browser (real `window.history` + `popstate`) and in tests
|
|
104
|
+
* (an in-memory stack, fully deterministic, no globals).
|
|
105
|
+
*
|
|
106
|
+
* Internal navigation never triggers a full-page reload: the browser history
|
|
107
|
+
* uses `pushState`/`replaceState` and notifies listeners synchronously.
|
|
108
|
+
*/
|
|
109
|
+
interface RouterLocation {
|
|
110
|
+
/** Pathname, always normalized with a single leading slash. */
|
|
111
|
+
readonly pathname: string;
|
|
112
|
+
/** Query string without the leading `?`. */
|
|
113
|
+
readonly search: string;
|
|
114
|
+
}
|
|
115
|
+
interface RouterHistory {
|
|
116
|
+
/** The current location. */
|
|
117
|
+
location(): RouterLocation;
|
|
118
|
+
/** Push a new entry and notify listeners. */
|
|
119
|
+
push(pathname: string, search: string): void;
|
|
120
|
+
/** Replace the current entry and notify listeners. */
|
|
121
|
+
replace(pathname: string, search: string): void;
|
|
122
|
+
/** Go back one entry. */
|
|
123
|
+
back(): void;
|
|
124
|
+
/** Go forward one entry. */
|
|
125
|
+
forward(): void;
|
|
126
|
+
/** Subscribe to location changes. Returns an unsubscribe function. */
|
|
127
|
+
listen(cb: () => void): () => void;
|
|
128
|
+
/** Detach any global listeners (browser only). */
|
|
129
|
+
dispose(): void;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Browser history backed by `window.history`. `pushState`/`replaceState` do not
|
|
133
|
+
* emit `popstate`, so we notify listeners ourselves after those calls; genuine
|
|
134
|
+
* back/forward navigation arrives via the `popstate` event.
|
|
135
|
+
*/
|
|
136
|
+
declare function createBrowserHistory(): RouterHistory;
|
|
137
|
+
/**
|
|
138
|
+
* In-memory history for tests and non-DOM environments. Maintains an explicit
|
|
139
|
+
* stack and cursor so `back()`/`forward()` are deterministic.
|
|
140
|
+
*/
|
|
141
|
+
declare function createMemoryHistory(initial?: string): RouterHistory;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* StreetUI Router core — renderer-agnostic.
|
|
145
|
+
*
|
|
146
|
+
* Holds the route table, resolves the current location into a `RouteMatch`,
|
|
147
|
+
* and exposes that match as a StreetUI `signal`. Navigation is delegated to a
|
|
148
|
+
* `RouterHistory`; when the location changes (via `navigate`, `back`, `forward`,
|
|
149
|
+
* or a browser `popstate`) the router recomputes the match and updates the
|
|
150
|
+
* signal, which is how every consumer (`isActive`, the mount integration, any
|
|
151
|
+
* `derived` the app builds) stays in sync. No second reactive system.
|
|
152
|
+
*/
|
|
153
|
+
|
|
154
|
+
interface RouterOptions {
|
|
155
|
+
/** The route table. Order matters — the first matching pattern wins. */
|
|
156
|
+
readonly routes: readonly RouteDefinition[];
|
|
157
|
+
/**
|
|
158
|
+
* Navigation source. Defaults to a browser history. Pass a memory history
|
|
159
|
+
* for tests or non-DOM environments.
|
|
160
|
+
*/
|
|
161
|
+
readonly history?: RouterHistory;
|
|
162
|
+
/**
|
|
163
|
+
* Fallback route used when nothing else matches and no `*` route is present.
|
|
164
|
+
* Defaults to a built-in 404 page (a normal StreetUI tree — no special path).
|
|
165
|
+
*/
|
|
166
|
+
readonly notFound?: RouteDefinition;
|
|
167
|
+
}
|
|
168
|
+
interface NavigateOptions {
|
|
169
|
+
/** Replace the current history entry instead of pushing a new one. */
|
|
170
|
+
readonly replace?: boolean;
|
|
171
|
+
}
|
|
172
|
+
interface IsActiveOptions {
|
|
173
|
+
/** Require an exact pathname match rather than a prefix match. */
|
|
174
|
+
readonly exact?: boolean;
|
|
175
|
+
}
|
|
176
|
+
interface Router {
|
|
177
|
+
/** Reactive current match. Consumers subscribe via StreetUI signals. */
|
|
178
|
+
readonly currentRoute: ReadonlySignal<RouteMatch>;
|
|
179
|
+
/** Navigate to a target path (may include a query string). */
|
|
180
|
+
navigate(to: string, options?: NavigateOptions): void;
|
|
181
|
+
/** Go back one history entry. */
|
|
182
|
+
back(): void;
|
|
183
|
+
/** Go forward one history entry. */
|
|
184
|
+
forward(): void;
|
|
185
|
+
/** Reactive predicate: is `path` the active route (or a prefix of it)? */
|
|
186
|
+
isActive(path: string, options?: IsActiveOptions): ReadonlySignal<boolean>;
|
|
187
|
+
/** Tear down history listeners. */
|
|
188
|
+
destroy(): void;
|
|
189
|
+
}
|
|
190
|
+
declare function createRouter(options: RouterOptions): Router;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* StreetUI Router — DOM integration.
|
|
194
|
+
*
|
|
195
|
+
* `mountRouter` wires a `Router` to real DOM:
|
|
196
|
+
*
|
|
197
|
+
* 1. Mounts an optional persistent shell (layout + navigation) ONCE. The shell
|
|
198
|
+
* declares an outlet element (see `routerOutlet`) into which route content
|
|
199
|
+
* is rendered.
|
|
200
|
+
* 2. Subscribes to `router.currentRoute`. On every change it disposes the
|
|
201
|
+
* previous route (route-scoped `CleanupRegistry.run()` + `runtime.unmount()`)
|
|
202
|
+
* and mounts the new route's compiled application into the outlet.
|
|
203
|
+
* Only the outlet subtree is re-created — the shell persists.
|
|
204
|
+
* 3. Intercepts clicks on internal `<a>` elements for client-side navigation.
|
|
205
|
+
* External links (absolute URLs, `target="_blank"`, `mailto:`/`tel:`) keep
|
|
206
|
+
* their normal browser behaviour, and `link()` is used unchanged.
|
|
207
|
+
*
|
|
208
|
+
* Each route is an ordinary compiled StreetUI application — no special renderer
|
|
209
|
+
* path, no virtual DOM, no full-application rerender on navigation.
|
|
210
|
+
*/
|
|
211
|
+
|
|
212
|
+
/** Default id used for the route outlet element inside a shell. */
|
|
213
|
+
declare const ROUTER_OUTLET_ID = "streetui-router-outlet";
|
|
214
|
+
/**
|
|
215
|
+
* Declare the route outlet inside a shell builder. The router replaces this
|
|
216
|
+
* element's contents on every navigation.
|
|
217
|
+
*/
|
|
218
|
+
declare function routerOutlet(scope: ContainerDSL, id?: string): void;
|
|
219
|
+
type ShellBuilder = (shell: PageDSL, router: Router) => void;
|
|
220
|
+
interface MountRouterOptions {
|
|
221
|
+
/** Element to mount into. With a shell, the shell fills this; otherwise routes do. */
|
|
222
|
+
readonly container: Element;
|
|
223
|
+
/** Optional persistent layout. Must include a `routerOutlet(...)`. */
|
|
224
|
+
readonly shell?: ShellBuilder;
|
|
225
|
+
/** Id of the outlet element within the shell. Defaults to `ROUTER_OUTLET_ID`. */
|
|
226
|
+
readonly outletId?: string;
|
|
227
|
+
/** Override the renderer (e.g. a custom DOM adapter for tests). */
|
|
228
|
+
readonly renderer?: StreetRenderer;
|
|
229
|
+
/** Intercept internal `<a>` clicks for client-side navigation. Defaults to true. */
|
|
230
|
+
readonly interceptLinks?: boolean;
|
|
231
|
+
/**
|
|
232
|
+
* Hydrate server-rendered HTML already present in the container instead of
|
|
233
|
+
* mounting fresh. The shell and the *initial* route adopt the existing DOM;
|
|
234
|
+
* subsequent client-side navigations mount normally. Defaults to false.
|
|
235
|
+
*/
|
|
236
|
+
readonly hydrate?: boolean;
|
|
237
|
+
}
|
|
238
|
+
interface MountedRouter {
|
|
239
|
+
/** The element route content is rendered into. */
|
|
240
|
+
readonly outlet: Element;
|
|
241
|
+
/** Tear down the current route, the shell, link interception and the router. */
|
|
242
|
+
unmount(): void;
|
|
243
|
+
}
|
|
244
|
+
declare function mountRouter(router: Router, options: MountRouterOptions): MountedRouter;
|
|
245
|
+
|
|
246
|
+
export { type IsActiveOptions, type MatchResult, type MountRouterOptions, type MountedRouter, type NavigateOptions, ROUTER_OUTLET_ID, type RouteBuilder, type RouteContext, type RouteDefinition, type RouteMatch, type Router, type RouterHistory, type RouterLocation, type RouterOptions, type ShellBuilder, createBrowserHistory, createMemoryHistory, createRouter, matchPattern, matchRoutes, mountRouter, normalizePath, routerOutlet, splitTarget };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { PageDSL, ContainerDSL } from '@streetui/dsl';
|
|
2
|
+
import { ReadonlySignal } from '@streetui/state';
|
|
3
|
+
import { StreetRenderer } from '@streetui/runtime';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* StreetUI Router — public type surface.
|
|
7
|
+
*
|
|
8
|
+
* The router sits ABOVE the DSL/compiler/runtime/renderer and composes them.
|
|
9
|
+
* It introduces no new rendering path and no second reactive system: route
|
|
10
|
+
* state is a StreetUI `signal`, route cleanup reuses the core `CleanupRegistry`,
|
|
11
|
+
* and each route renders as an ordinary compiled StreetUI application tree.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Context handed to a route builder when its route becomes active.
|
|
16
|
+
*
|
|
17
|
+
* `params` are the values captured from dynamic segments (`/users/:id`),
|
|
18
|
+
* `query` is the parsed query string, and `onCleanup` registers work to run
|
|
19
|
+
* when the router navigates away from this route (subscriptions, effects,
|
|
20
|
+
* timers). It is backed by a route-scoped `CleanupRegistry` — there is no
|
|
21
|
+
* separate cleanup mechanism.
|
|
22
|
+
*/
|
|
23
|
+
interface RouteContext {
|
|
24
|
+
/** Full matched pathname, e.g. `/users/123`. */
|
|
25
|
+
readonly path: string;
|
|
26
|
+
/** The route pattern that matched, e.g. `/users/:id` or `*`. */
|
|
27
|
+
readonly pattern: string;
|
|
28
|
+
/** Dynamic segment values captured from the path. */
|
|
29
|
+
readonly params: Readonly<Record<string, string>>;
|
|
30
|
+
/** Parsed query string (everything after `?`). */
|
|
31
|
+
readonly query: URLSearchParams;
|
|
32
|
+
/** Register a callback to run when navigating away from this route. */
|
|
33
|
+
onCleanup(fn: () => void): void;
|
|
34
|
+
}
|
|
35
|
+
/** Builds a route's page tree. Receives the page scope and the route context. */
|
|
36
|
+
type RouteBuilder = (page: PageDSL, ctx: RouteContext) => void;
|
|
37
|
+
/** A single route: a path pattern and the builder that renders it. */
|
|
38
|
+
interface RouteDefinition {
|
|
39
|
+
/**
|
|
40
|
+
* Path pattern. Supports:
|
|
41
|
+
* - static segments: `/`, `/docs`, `/docs/getting-started`
|
|
42
|
+
* - dynamic `:param`: `/users/:id`
|
|
43
|
+
* - catch-all wildcard: `*` (matches anything — use for a 404 route)
|
|
44
|
+
*/
|
|
45
|
+
readonly path: string;
|
|
46
|
+
readonly builder: RouteBuilder;
|
|
47
|
+
}
|
|
48
|
+
/** The result of resolving a location against the route table. */
|
|
49
|
+
interface RouteMatch {
|
|
50
|
+
/** The active pathname (no query string). */
|
|
51
|
+
readonly path: string;
|
|
52
|
+
/** The pattern of the matched route. */
|
|
53
|
+
readonly pattern: string;
|
|
54
|
+
/** Captured dynamic params. */
|
|
55
|
+
readonly params: Readonly<Record<string, string>>;
|
|
56
|
+
/** Parsed query string. */
|
|
57
|
+
readonly query: URLSearchParams;
|
|
58
|
+
/** The route definition that produced this match. */
|
|
59
|
+
readonly route: RouteDefinition;
|
|
60
|
+
/** True when this match came from the wildcard catch-all (`*`) — i.e. a 404. */
|
|
61
|
+
readonly isFallback: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Route matching — pure functions, no DOM, no reactivity.
|
|
66
|
+
*
|
|
67
|
+
* A pattern is matched segment-by-segment against a pathname:
|
|
68
|
+
* - a literal segment must equal the path segment,
|
|
69
|
+
* - a `:name` segment captures the path segment into `params.name`,
|
|
70
|
+
* - a `*` segment (or a whole-pattern `*`) is a catch-all that matches the
|
|
71
|
+
* remainder of the path and captures it into `params['*']`.
|
|
72
|
+
*
|
|
73
|
+
* Matching is intentionally small: no optional segments, no regex constraints,
|
|
74
|
+
* no nested route trees. Composition of layouts is done in the DSL, not here.
|
|
75
|
+
*/
|
|
76
|
+
/** Normalize a pathname: ensure a single leading slash, drop a trailing slash. */
|
|
77
|
+
declare function normalizePath(path: string): string;
|
|
78
|
+
/**
|
|
79
|
+
* Try to match a single pattern against a pathname.
|
|
80
|
+
* Returns the captured params on success, or `null` on no match.
|
|
81
|
+
*/
|
|
82
|
+
declare function matchPattern(pattern: string, pathname: string): Record<string, string> | null;
|
|
83
|
+
interface MatchResult<R> {
|
|
84
|
+
readonly route: R;
|
|
85
|
+
readonly params: Record<string, string>;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Match a pathname against an ordered list of routes. The first route whose
|
|
89
|
+
* pattern matches wins (definition order), so more specific routes should be
|
|
90
|
+
* listed before a `*` fallback.
|
|
91
|
+
*/
|
|
92
|
+
declare function matchRoutes<R extends {
|
|
93
|
+
path: string;
|
|
94
|
+
}>(routes: readonly R[], pathname: string): MatchResult<R> | null;
|
|
95
|
+
/** Split a `to` target into its pathname and (already-stripped) search string. */
|
|
96
|
+
declare function splitTarget(to: string): {
|
|
97
|
+
pathname: string;
|
|
98
|
+
search: string;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Router history — a small abstraction over the navigation source so the router
|
|
103
|
+
* can run both in the browser (real `window.history` + `popstate`) and in tests
|
|
104
|
+
* (an in-memory stack, fully deterministic, no globals).
|
|
105
|
+
*
|
|
106
|
+
* Internal navigation never triggers a full-page reload: the browser history
|
|
107
|
+
* uses `pushState`/`replaceState` and notifies listeners synchronously.
|
|
108
|
+
*/
|
|
109
|
+
interface RouterLocation {
|
|
110
|
+
/** Pathname, always normalized with a single leading slash. */
|
|
111
|
+
readonly pathname: string;
|
|
112
|
+
/** Query string without the leading `?`. */
|
|
113
|
+
readonly search: string;
|
|
114
|
+
}
|
|
115
|
+
interface RouterHistory {
|
|
116
|
+
/** The current location. */
|
|
117
|
+
location(): RouterLocation;
|
|
118
|
+
/** Push a new entry and notify listeners. */
|
|
119
|
+
push(pathname: string, search: string): void;
|
|
120
|
+
/** Replace the current entry and notify listeners. */
|
|
121
|
+
replace(pathname: string, search: string): void;
|
|
122
|
+
/** Go back one entry. */
|
|
123
|
+
back(): void;
|
|
124
|
+
/** Go forward one entry. */
|
|
125
|
+
forward(): void;
|
|
126
|
+
/** Subscribe to location changes. Returns an unsubscribe function. */
|
|
127
|
+
listen(cb: () => void): () => void;
|
|
128
|
+
/** Detach any global listeners (browser only). */
|
|
129
|
+
dispose(): void;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Browser history backed by `window.history`. `pushState`/`replaceState` do not
|
|
133
|
+
* emit `popstate`, so we notify listeners ourselves after those calls; genuine
|
|
134
|
+
* back/forward navigation arrives via the `popstate` event.
|
|
135
|
+
*/
|
|
136
|
+
declare function createBrowserHistory(): RouterHistory;
|
|
137
|
+
/**
|
|
138
|
+
* In-memory history for tests and non-DOM environments. Maintains an explicit
|
|
139
|
+
* stack and cursor so `back()`/`forward()` are deterministic.
|
|
140
|
+
*/
|
|
141
|
+
declare function createMemoryHistory(initial?: string): RouterHistory;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* StreetUI Router core — renderer-agnostic.
|
|
145
|
+
*
|
|
146
|
+
* Holds the route table, resolves the current location into a `RouteMatch`,
|
|
147
|
+
* and exposes that match as a StreetUI `signal`. Navigation is delegated to a
|
|
148
|
+
* `RouterHistory`; when the location changes (via `navigate`, `back`, `forward`,
|
|
149
|
+
* or a browser `popstate`) the router recomputes the match and updates the
|
|
150
|
+
* signal, which is how every consumer (`isActive`, the mount integration, any
|
|
151
|
+
* `derived` the app builds) stays in sync. No second reactive system.
|
|
152
|
+
*/
|
|
153
|
+
|
|
154
|
+
interface RouterOptions {
|
|
155
|
+
/** The route table. Order matters — the first matching pattern wins. */
|
|
156
|
+
readonly routes: readonly RouteDefinition[];
|
|
157
|
+
/**
|
|
158
|
+
* Navigation source. Defaults to a browser history. Pass a memory history
|
|
159
|
+
* for tests or non-DOM environments.
|
|
160
|
+
*/
|
|
161
|
+
readonly history?: RouterHistory;
|
|
162
|
+
/**
|
|
163
|
+
* Fallback route used when nothing else matches and no `*` route is present.
|
|
164
|
+
* Defaults to a built-in 404 page (a normal StreetUI tree — no special path).
|
|
165
|
+
*/
|
|
166
|
+
readonly notFound?: RouteDefinition;
|
|
167
|
+
}
|
|
168
|
+
interface NavigateOptions {
|
|
169
|
+
/** Replace the current history entry instead of pushing a new one. */
|
|
170
|
+
readonly replace?: boolean;
|
|
171
|
+
}
|
|
172
|
+
interface IsActiveOptions {
|
|
173
|
+
/** Require an exact pathname match rather than a prefix match. */
|
|
174
|
+
readonly exact?: boolean;
|
|
175
|
+
}
|
|
176
|
+
interface Router {
|
|
177
|
+
/** Reactive current match. Consumers subscribe via StreetUI signals. */
|
|
178
|
+
readonly currentRoute: ReadonlySignal<RouteMatch>;
|
|
179
|
+
/** Navigate to a target path (may include a query string). */
|
|
180
|
+
navigate(to: string, options?: NavigateOptions): void;
|
|
181
|
+
/** Go back one history entry. */
|
|
182
|
+
back(): void;
|
|
183
|
+
/** Go forward one history entry. */
|
|
184
|
+
forward(): void;
|
|
185
|
+
/** Reactive predicate: is `path` the active route (or a prefix of it)? */
|
|
186
|
+
isActive(path: string, options?: IsActiveOptions): ReadonlySignal<boolean>;
|
|
187
|
+
/** Tear down history listeners. */
|
|
188
|
+
destroy(): void;
|
|
189
|
+
}
|
|
190
|
+
declare function createRouter(options: RouterOptions): Router;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* StreetUI Router — DOM integration.
|
|
194
|
+
*
|
|
195
|
+
* `mountRouter` wires a `Router` to real DOM:
|
|
196
|
+
*
|
|
197
|
+
* 1. Mounts an optional persistent shell (layout + navigation) ONCE. The shell
|
|
198
|
+
* declares an outlet element (see `routerOutlet`) into which route content
|
|
199
|
+
* is rendered.
|
|
200
|
+
* 2. Subscribes to `router.currentRoute`. On every change it disposes the
|
|
201
|
+
* previous route (route-scoped `CleanupRegistry.run()` + `runtime.unmount()`)
|
|
202
|
+
* and mounts the new route's compiled application into the outlet.
|
|
203
|
+
* Only the outlet subtree is re-created — the shell persists.
|
|
204
|
+
* 3. Intercepts clicks on internal `<a>` elements for client-side navigation.
|
|
205
|
+
* External links (absolute URLs, `target="_blank"`, `mailto:`/`tel:`) keep
|
|
206
|
+
* their normal browser behaviour, and `link()` is used unchanged.
|
|
207
|
+
*
|
|
208
|
+
* Each route is an ordinary compiled StreetUI application — no special renderer
|
|
209
|
+
* path, no virtual DOM, no full-application rerender on navigation.
|
|
210
|
+
*/
|
|
211
|
+
|
|
212
|
+
/** Default id used for the route outlet element inside a shell. */
|
|
213
|
+
declare const ROUTER_OUTLET_ID = "streetui-router-outlet";
|
|
214
|
+
/**
|
|
215
|
+
* Declare the route outlet inside a shell builder. The router replaces this
|
|
216
|
+
* element's contents on every navigation.
|
|
217
|
+
*/
|
|
218
|
+
declare function routerOutlet(scope: ContainerDSL, id?: string): void;
|
|
219
|
+
type ShellBuilder = (shell: PageDSL, router: Router) => void;
|
|
220
|
+
interface MountRouterOptions {
|
|
221
|
+
/** Element to mount into. With a shell, the shell fills this; otherwise routes do. */
|
|
222
|
+
readonly container: Element;
|
|
223
|
+
/** Optional persistent layout. Must include a `routerOutlet(...)`. */
|
|
224
|
+
readonly shell?: ShellBuilder;
|
|
225
|
+
/** Id of the outlet element within the shell. Defaults to `ROUTER_OUTLET_ID`. */
|
|
226
|
+
readonly outletId?: string;
|
|
227
|
+
/** Override the renderer (e.g. a custom DOM adapter for tests). */
|
|
228
|
+
readonly renderer?: StreetRenderer;
|
|
229
|
+
/** Intercept internal `<a>` clicks for client-side navigation. Defaults to true. */
|
|
230
|
+
readonly interceptLinks?: boolean;
|
|
231
|
+
/**
|
|
232
|
+
* Hydrate server-rendered HTML already present in the container instead of
|
|
233
|
+
* mounting fresh. The shell and the *initial* route adopt the existing DOM;
|
|
234
|
+
* subsequent client-side navigations mount normally. Defaults to false.
|
|
235
|
+
*/
|
|
236
|
+
readonly hydrate?: boolean;
|
|
237
|
+
}
|
|
238
|
+
interface MountedRouter {
|
|
239
|
+
/** The element route content is rendered into. */
|
|
240
|
+
readonly outlet: Element;
|
|
241
|
+
/** Tear down the current route, the shell, link interception and the router. */
|
|
242
|
+
unmount(): void;
|
|
243
|
+
}
|
|
244
|
+
declare function mountRouter(router: Router, options: MountRouterOptions): MountedRouter;
|
|
245
|
+
|
|
246
|
+
export { type IsActiveOptions, type MatchResult, type MountRouterOptions, type MountedRouter, type NavigateOptions, ROUTER_OUTLET_ID, type RouteBuilder, type RouteContext, type RouteDefinition, type RouteMatch, type Router, type RouterHistory, type RouterLocation, type RouterOptions, type ShellBuilder, createBrowserHistory, createMemoryHistory, createRouter, matchPattern, matchRoutes, mountRouter, normalizePath, routerOutlet, splitTarget };
|