@solidrt/router 0.0.62
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/AGENTS.md +96 -0
- package/README.md +257 -0
- package/package.json +21 -0
- package/src/index.ts +17 -0
- package/src/route.ts +332 -0
- package/src/router.tsx +450 -0
- package/src/stack.ts +249 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# @solidrt/router - agent notes
|
|
2
|
+
|
|
3
|
+
Routing for SolidRT apps: a typed route tree, a stack, one back step, links
|
|
4
|
+
in and the location out. Optional: an app with one screen, or one that
|
|
5
|
+
routes by hand over core's `onBack`/`onLink`, needs none of it. Prose is in
|
|
6
|
+
README.md; the typed API is src/route.ts (the headless tree and matching)
|
|
7
|
+
and src/router.tsx (the Solid binding).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
bun add @solidrt/router # peers: @solidrt/core, @solidjs/signals
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Shape
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
let item = createRoute({ // a value: typed params
|
|
19
|
+
path: "/item/$id",
|
|
20
|
+
params: { parse: (raw) => ({ id: Number(raw.id) }) }, // throw = no match
|
|
21
|
+
component: Item,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
<Router initial="/"> // once, inside <Window>
|
|
25
|
+
<Route path="/" component={Shell}> // Shell renders <Outlet />
|
|
26
|
+
<Route path="/" component={Home} />
|
|
27
|
+
<Route path="/settings" component={Settings} />
|
|
28
|
+
<Route route={item} /> // the value, placed
|
|
29
|
+
<Route path="/$" component={NotFound} />
|
|
30
|
+
</Route>
|
|
31
|
+
</Router>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- JSX for the tree; a route value (`createRoute`) where a param needs a
|
|
35
|
+
type or validation, placed with `<Route route={...}>`. A JSX `<Route>`
|
|
36
|
+
takes `path` and `component` only. Values can also form the whole tree
|
|
37
|
+
(`createRootRoute({ children: [...] })`, `createRoute({ ..., children })`)
|
|
38
|
+
with `createRouter({ tree })` at module scope and `<Router router={...}>`;
|
|
39
|
+
the player does this.
|
|
40
|
+
- Targets: `useNavigate()("/item/42")` by path (matched at run time, refused
|
|
41
|
+
with a warning when nothing matches), or by value, checked at compile
|
|
42
|
+
time: `navigate(home)`, `navigate({ route: item, params: { id: 42 } })`.
|
|
43
|
+
- `useParams()()` is the screen's raw strings; `useParams(item)()` is typed
|
|
44
|
+
by the value. Both reactive. `useLocation()`, `useNavigate()`,
|
|
45
|
+
`useRouter()`, `createLink(target)`, `useBlocker(fn)`.
|
|
46
|
+
- `router.entries()` is the stack as paths; pass a saved one as `initial`.
|
|
47
|
+
- Tabs: `<Route path="/" tabs component={Main}>` makes each child a tab
|
|
48
|
+
with a stack of its own, kept mounted and hidden while another shows;
|
|
49
|
+
full-window screens are siblings of the tabs route, above the bar. `Main`
|
|
50
|
+
renders `<Outlet />` and draws the bar from `useTabs()` (`{ paths,
|
|
51
|
+
active(), select(path) }`, e.g. wired to components' `NavShell`). A tab
|
|
52
|
+
is named by its literal root path. Select = show as left, re-select =
|
|
53
|
+
back to root; back pops above the bar, then the tab, then goes to the
|
|
54
|
+
first tab, then the platform. With tabs `entries()` is `{ stack, tabs }`.
|
|
55
|
+
- Path syntax: literal, `$name`, `$name?` (last), `$` for the rest (a `$`
|
|
56
|
+
route last under the root is the not-found screen). `/` is an index
|
|
57
|
+
route, or with children a pathless layout. Children match in
|
|
58
|
+
declaration order.
|
|
59
|
+
|
|
60
|
+
## Traps
|
|
61
|
+
|
|
62
|
+
- A `<Route>` is not an element: it evaluates to a route value. Only
|
|
63
|
+
`<Route>` elements go under `<Router>` or another `<Route>`; anything
|
|
64
|
+
else there throws at mount.
|
|
65
|
+
- A route value has one place in one tree: placing it twice (two `<Route
|
|
66
|
+
route={x}>`, or a `children:` list and a `<Route route={x}>`) throws.
|
|
67
|
+
- With a JSX tree the router exists only inside `<Router>`: screens reach
|
|
68
|
+
it through `useNavigate()` / `useRouter()`, not a module-scope import.
|
|
69
|
+
With a value tree at module scope, screens that import route values from
|
|
70
|
+
the module that imports them form a cycle ESM handles as long as a
|
|
71
|
+
screen reads a route only inside a handler or a hook call, never at
|
|
72
|
+
module scope.
|
|
73
|
+
- Route components take no props. State a screen shares with others is a
|
|
74
|
+
module-scope signal (see the player's parts/app-state.ts), not a prop.
|
|
75
|
+
- A layout route's component must render `<Outlet />` or its child never
|
|
76
|
+
appears; a route without a component is an outlet alone.
|
|
77
|
+
- `<Router>` registers its `onBack` before anything it renders. A dialog
|
|
78
|
+
that registers its own `onBack` inside a screen still runs first, as
|
|
79
|
+
before; only use `useBlocker` for guards that must hold a navigation
|
|
80
|
+
(unsaved changes), not for closing overlays.
|
|
81
|
+
- `navigate(..., { reset: true })` for "flow finished, this is home"; a
|
|
82
|
+
plain push there leaves the flow's screens under home for back to revisit.
|
|
83
|
+
- A hidden tab is laid out to nothing: its reactive code keeps running,
|
|
84
|
+
but `getBoundingBox` and friends return null there and nothing in it is
|
|
85
|
+
painted, hit or focusable. A screen that measures itself checks
|
|
86
|
+
`useTabs().active()` against its tab's path, or tolerates null.
|
|
87
|
+
- The tabs route and each tab render in a full-size `<view>` (flex 1,
|
|
88
|
+
column): put the tabs route under laid-out parents only.
|
|
89
|
+
- Nothing above a tabs route takes a param, a tab's path is literal, one
|
|
90
|
+
tabs route per tree and never inside a tab: all thrown at tree build.
|
|
91
|
+
- Links are untrusted: put validation in `params.parse`, never in the
|
|
92
|
+
screen. `linkToPath` strips a custom scheme whole (`myapp://a/b` is
|
|
93
|
+
`/a/b`) and an http(s) host.
|
|
94
|
+
- Tooling: `open_link` / `POST /__control__/link` opens a screen,
|
|
95
|
+
`get_location` / `GET /__control__/link` reads the path the router
|
|
96
|
+
publishes, `srt render --link <path>` renders that screen.
|
package/README.md
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Router
|
|
2
|
+
|
|
3
|
+
`@solidrt/router` makes screens addressable. A route tree maps a path like
|
|
4
|
+
`/settings/theme` to a screen, a stack holds where the app is, and the same
|
|
5
|
+
path is what a link from the OS, an MCP tool, `srt render --link` or a saved
|
|
6
|
+
session carries. It is a layer over core: core reports links (`onLink`,
|
|
7
|
+
`env.launchLink`) and the back intent (`onBack`), the router decides what
|
|
8
|
+
they mean. An app can route by hand without it.
|
|
9
|
+
|
|
10
|
+
Not a web router. There is no address bar, no forward, no history API, no
|
|
11
|
+
code splitting, no data loading. What is left is small, and typed.
|
|
12
|
+
|
|
13
|
+
## Routes
|
|
14
|
+
|
|
15
|
+
The tree is JSX, one `<Route>` per screen, nested for layouts:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { Router, Route, Outlet, useParams, useNavigate } from "@solidrt/router"
|
|
19
|
+
|
|
20
|
+
render(() => (
|
|
21
|
+
<Window>
|
|
22
|
+
<Router initial="/">
|
|
23
|
+
<Route path="/" component={Shell}>
|
|
24
|
+
<Route path="/" component={Home} />
|
|
25
|
+
<Route path="/settings" component={SettingsLayout}>
|
|
26
|
+
<Route path="/$section" component={Section} />
|
|
27
|
+
</Route>
|
|
28
|
+
<Route path="/$" component={NotFound} />
|
|
29
|
+
</Route>
|
|
30
|
+
</Router>
|
|
31
|
+
</Window>
|
|
32
|
+
))
|
|
33
|
+
|
|
34
|
+
function Section() {
|
|
35
|
+
let params = useParams() // () => { section: string }
|
|
36
|
+
return <text>{params().section}</text>
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
A route's `path` is its own piece below the parent: literal segments,
|
|
41
|
+
`$name` for a param, `$name?` for an optional one at the end, `$` for the
|
|
42
|
+
rest of the path (captured as `rest`; a `$` route last under the root is
|
|
43
|
+
the not-found screen). `/` adds nothing: an index route under a layout, or
|
|
44
|
+
with children a pathless layout (the `Shell` above). A layout route's
|
|
45
|
+
component renders its child with `<Outlet />`; a route without a component
|
|
46
|
+
is an `<Outlet />` alone. Children match in declaration order, first match
|
|
47
|
+
wins, so a catch-all goes last.
|
|
48
|
+
|
|
49
|
+
A `<Route>` is not an element: it evaluates to a route value that its
|
|
50
|
+
parent places in the tree, once, when `<Router>` mounts. The router made
|
|
51
|
+
this way is reached through the hooks (`useNavigate`, `useRouter`).
|
|
52
|
+
|
|
53
|
+
## Params are validated
|
|
54
|
+
|
|
55
|
+
A path arrives as strings, and from outside the app they are untrusted. A
|
|
56
|
+
JSX route hands them to its screen as they came (`useParams()` above). When
|
|
57
|
+
a param has a shape, a route is a value with a `parse`, placed in the JSX
|
|
58
|
+
tree where it belongs:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { createRoute } from "@solidrt/router"
|
|
62
|
+
|
|
63
|
+
let item = createRoute({
|
|
64
|
+
path: "/item/$id",
|
|
65
|
+
params: { parse: (raw) => ({ id: parseId(raw.id) }) },
|
|
66
|
+
component: Item,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
<Route route={item} /> // in the tree, next to the JSX routes
|
|
70
|
+
|
|
71
|
+
function Item() {
|
|
72
|
+
let params = useParams(item) // () => { id: number }
|
|
73
|
+
return <text>{params().id}</text>
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`params.parse` is the one place strings become typed values: return them,
|
|
78
|
+
or throw, and the route does not match (the next sibling is tried, then
|
|
79
|
+
the catch-all). `useParams(route)` is typed by the route it names, so there
|
|
80
|
+
is no way to read a value that did not pass. That is the rule: JSX for the
|
|
81
|
+
tree, a route value where a param needs a type. A JSX `<Route>` takes no
|
|
82
|
+
`params`.
|
|
83
|
+
|
|
84
|
+
Params are reactive: navigating to the same route with other params updates
|
|
85
|
+
`params()` without remounting the screen. An ancestor's params are in the
|
|
86
|
+
match too; read them through the ancestor, `useParams(settings)`.
|
|
87
|
+
|
|
88
|
+
### The tree as values
|
|
89
|
+
|
|
90
|
+
The whole tree can be values instead, with the router made at module scope
|
|
91
|
+
so screens import it directly; the player app does this. Children are a
|
|
92
|
+
list, in matching order, and a route has one place in one tree:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
let root = createRootRoute({
|
|
96
|
+
component: Shell,
|
|
97
|
+
children: [home, settings, item, notFound],
|
|
98
|
+
})
|
|
99
|
+
let router = createRouter({ tree: root, initial: "/" })
|
|
100
|
+
|
|
101
|
+
<Router router={router} />
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`<Route route={x}>` and a `children:` list are the same placement; a JSX
|
|
105
|
+
`<Route route={settings}>` can nest more `<Route>` elements under a value
|
|
106
|
+
that already has children of its own.
|
|
107
|
+
|
|
108
|
+
## Navigation
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
let navigate = useNavigate()
|
|
112
|
+
navigate(home) // a route without params
|
|
113
|
+
navigate({ route: section, params: { section: "theme" } }) // one with params, checked
|
|
114
|
+
navigate("/settings/theme") // a path, as a link would carry it
|
|
115
|
+
navigate(home, { replace: true }) // swap the top instead of pushing
|
|
116
|
+
navigate(home, { reset: true }) // start the stack over here (a flow finished)
|
|
117
|
+
router.back() // pop
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The route forms are checked at compile time: a removed screen or a missing
|
|
121
|
+
param is a build error. The path form is matched at run time and refused,
|
|
122
|
+
with a warning, when nothing matches.
|
|
123
|
+
|
|
124
|
+
Every navigation is a signal write, so Solid's transition semantics apply:
|
|
125
|
+
a new screen whose async reads are still pending keeps the previous one on
|
|
126
|
+
screen, and `isPending(() => router.location())` says so. Data lives in
|
|
127
|
+
the screen (`createMemo(() => db.query(...))` under `<Loading>`, failures
|
|
128
|
+
in `<Errored>`); the router has no data layer.
|
|
129
|
+
|
|
130
|
+
`createLink(target)` gives `{ href, go }` for a pressable; styling is the
|
|
131
|
+
caller's.
|
|
132
|
+
|
|
133
|
+
## Tabs
|
|
134
|
+
|
|
135
|
+
A `tabs` route makes each of its children a tab: a screen with a stack of
|
|
136
|
+
its own, kept mounted while another tab shows. Full-window screens (a
|
|
137
|
+
viewer, a login) go above the tab bar, as siblings of the tabs route.
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
import { useTabs } from "@solidrt/router"
|
|
141
|
+
|
|
142
|
+
<Router>
|
|
143
|
+
<Route path="/" tabs component={Main}>
|
|
144
|
+
<Route path="/feed">
|
|
145
|
+
<Route path="/" component={Feed} />
|
|
146
|
+
<Route path="/post/$id" component={Post} />
|
|
147
|
+
</Route>
|
|
148
|
+
<Route path="/search" component={Search} />
|
|
149
|
+
<Route path="/profile" component={Profile} />
|
|
150
|
+
</Route>
|
|
151
|
+
<Route path="/viewer/$id" component={Viewer} />
|
|
152
|
+
<Route path="/$" component={NotFound} />
|
|
153
|
+
</Router>
|
|
154
|
+
|
|
155
|
+
function Main() {
|
|
156
|
+
let tabs = useTabs() // { paths, active(), select(path) }
|
|
157
|
+
return (
|
|
158
|
+
<NavShell items={items} value={tabs.active()} onChange={(v) => tabs.select(v as string)}>
|
|
159
|
+
<Outlet />
|
|
160
|
+
</NavShell>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
A tab is named by its root path (`/feed`), the router's usual unit: what a
|
|
166
|
+
link carries, what a `NavShell` item holds. A tab's path is literal and
|
|
167
|
+
nothing above the tabs route takes a param; a tree holds one tabs route,
|
|
168
|
+
not inside another. `createRoute({ tabs: true, children })` is the value
|
|
169
|
+
form. The router draws no bar: `NavShell` from components does, wired in
|
|
170
|
+
app code as above, or any component of the app's own.
|
|
171
|
+
|
|
172
|
+
The tabs behave as native tab bars do:
|
|
173
|
+
|
|
174
|
+
- Selecting another tab shows it as it was left. Selecting the tab that
|
|
175
|
+
shows takes it back to its root. `navigate` to a tab's root path is the
|
|
176
|
+
same as selecting it.
|
|
177
|
+
- Navigating to a screen in a tab shows that tab and pushes the screen on
|
|
178
|
+
its stack. Navigating to a screen outside the tabs pushes it above the
|
|
179
|
+
tab bar, full window; back returns to the tabs, active tab intact.
|
|
180
|
+
- Back pops the screen above the tab bar if there is one, else the active
|
|
181
|
+
tab's stack while it is deeper than its root, else switches a non-first
|
|
182
|
+
tab to the first tab, and on the first tab's root leaves the event to
|
|
183
|
+
the platform.
|
|
184
|
+
- `replace` swaps the top of the stack that holds the target (the tab bar
|
|
185
|
+
itself, when a full-window screen replaces it); `reset` clears every
|
|
186
|
+
stack and lands the target with its tab's root beneath it.
|
|
187
|
+
- A link arriving while the app runs is a navigation. A launch link into a
|
|
188
|
+
tab lands with the tab's root beneath it, the other tabs on their roots.
|
|
189
|
+
- `useBlocker` in a tab holds the moves that change that tab's stack; in a
|
|
190
|
+
component outside the tabs, those that change the parent stack. Showing
|
|
191
|
+
another tab changes no stack and asks no blocker.
|
|
192
|
+
|
|
193
|
+
A tab mounts the first time it shows and stays mounted, hidden while
|
|
194
|
+
another shows: never painted, hit or focused, its reactive code still
|
|
195
|
+
running. Inside a tab `useLocation` and `useParams` read the tab's own
|
|
196
|
+
stack whether or not it shows, and `useTabs().active()` compared with the
|
|
197
|
+
tab's path says whether it does, for a screen to pause video or per-frame
|
|
198
|
+
work. The tabs route and each tab render in a full-size `<view>` (flex 1,
|
|
199
|
+
column) of their own. The tabs stay mounted under a full-window screen as
|
|
200
|
+
long as the routes above the tabs route stay matched, which they do when
|
|
201
|
+
that screen is a sibling of the tabs route.
|
|
202
|
+
|
|
203
|
+
With tabs, `entries()` is `{ stack, tabs: { active, stacks } }`: the parent
|
|
204
|
+
stack, in which the tabs route's own path stands for the tab bar, and a
|
|
205
|
+
stack per tab. `initial` takes that back; a path or a list of paths still
|
|
206
|
+
works too.
|
|
207
|
+
|
|
208
|
+
## Back
|
|
209
|
+
|
|
210
|
+
`<Router>` registers one `onBack` step: it pops while the stack is deeper
|
|
211
|
+
than one entry, and at the root leaves the event to the platform default
|
|
212
|
+
(background on Android, exit elsewhere). With tabs the step is the one
|
|
213
|
+
described above. It registers before anything it
|
|
214
|
+
renders, so a dialog or a screen that registers its own `onBack` gets the
|
|
215
|
+
event first, as before.
|
|
216
|
+
|
|
217
|
+
`useBlocker(fn)` guards leaving the current entry while the component is
|
|
218
|
+
mounted: back, navigate and replace ask it first. Return `false` to stay,
|
|
219
|
+
`true` to go, or a promise that decides later (a confirm dialog). A
|
|
220
|
+
blocked back is prevented and never falls through to the platform.
|
|
221
|
+
|
|
222
|
+
## Links in, location out
|
|
223
|
+
|
|
224
|
+
`createRouter` reads `env.launchLink`: a link the app was started with wins
|
|
225
|
+
over `initial` and lands as a stack of one. `<Router>` subscribes to
|
|
226
|
+
`onLink`: a link arriving while the app runs pushes onto the stack. A
|
|
227
|
+
custom-scheme link keeps everything after the scheme (`myapp://settings/theme`
|
|
228
|
+
is `/settings/theme`); an http(s) link drops its host; a query or fragment
|
|
229
|
+
is dropped. A link that matches nothing lands on the catch-all route, or is
|
|
230
|
+
ignored with a warning.
|
|
231
|
+
|
|
232
|
+
`<Router>` reports the current path through `reportLocation` from
|
|
233
|
+
`srt:dev`, so `GET /__control__/link` and the `get_location` MCP tool read
|
|
234
|
+
where the app is, and `POST /__control__/link` or `open_link` opens a
|
|
235
|
+
screen directly. `srt render --link /settings` renders that screen. A hot
|
|
236
|
+
reload starts the rebuilt app at the path it was on: the runtime hands the
|
|
237
|
+
last reported path to the new run as `env.launchLink`.
|
|
238
|
+
|
|
239
|
+
## Restore
|
|
240
|
+
|
|
241
|
+
The stack is plain data:
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
onSuspend(() => file("nav.json").write(JSON.stringify(router.entries())))
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
To resume, read it back before creating the router and pass it as
|
|
248
|
+
`initial` (an array is a saved stack). Whether to resume is the app's call
|
|
249
|
+
(`env.launch === "restored"`); a launch link still wins.
|
|
250
|
+
|
|
251
|
+
## Reference
|
|
252
|
+
|
|
253
|
+
- `<Router initial?>` with `<Route path? component? tabs?>` / `<Route route>` children, or `<Router router />`; `<Outlet />`
|
|
254
|
+
- `createRoute({ path, params?, component?, children?, tabs? })`, `createRootRoute({ component?, children? })`
|
|
255
|
+
- `createRouter({ tree, initial? })` returns `{ entries, location, navigate, back, href }`
|
|
256
|
+
- `useRouter()`, `useLocation()`, `useParams()` / `useParams(route)`, `useNavigate()`, `createLink(target, options?)`, `useBlocker(fn)`, `useTabs()`
|
|
257
|
+
- `matchPath(root, path)`, `formatPath(route, params)`, `linkToPath(link)` for tooling and tests
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@solidrt/router",
|
|
3
|
+
"version": "0.0.62",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"funding": "https://github.com/sponsors/wellawaretech",
|
|
6
|
+
"author": "Antoine van Wel",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "src/index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.ts"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"src/",
|
|
14
|
+
"README.md",
|
|
15
|
+
"AGENTS.md"
|
|
16
|
+
],
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"@solidjs/signals": "2.0.0-rc.9",
|
|
19
|
+
"@solidrt/core": "0.0.62"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { createRootRoute, createRoute, matchPath, formatPath, formatPattern, linkToPath } from "./route.ts"
|
|
2
|
+
export type { AnyRoute, ParamsOf, RouteOptions, RawParams, Match } from "./route.ts"
|
|
3
|
+
export {
|
|
4
|
+
createRouter,
|
|
5
|
+
Router,
|
|
6
|
+
Route,
|
|
7
|
+
Outlet,
|
|
8
|
+
useRouter,
|
|
9
|
+
useLocation,
|
|
10
|
+
useParams,
|
|
11
|
+
useNavigate,
|
|
12
|
+
createLink,
|
|
13
|
+
useBlocker,
|
|
14
|
+
useTabs,
|
|
15
|
+
} from "./router.tsx"
|
|
16
|
+
export type { Router as RouterInstance, RouterProps, RouteProps, Location, NavTarget, NavigateOptions, Blocker, RouterOptions, Tabs } from "./router.tsx"
|
|
17
|
+
export type { Entries, TabsState } from "./stack.ts"
|