@relax.js/core 1.0.3 → 1.0.4
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/README.md +194 -188
- package/dist/DependencyInjection.d.ts +42 -24
- package/dist/di/index.js +1 -1
- package/dist/di/index.js.map +3 -3
- package/dist/di/index.mjs +1 -1
- package/dist/di/index.mjs.map +3 -3
- package/dist/errors.d.ts +20 -0
- package/dist/forms/FormValidator.d.ts +1 -20
- package/dist/forms/ValidationRules.d.ts +2 -0
- package/dist/forms/index.js +1 -1
- package/dist/forms/index.js.map +4 -4
- package/dist/forms/index.mjs +1 -1
- package/dist/forms/index.mjs.map +4 -4
- package/dist/html/TableRenderer.d.ts +1 -0
- package/dist/html/index.js.map +2 -2
- package/dist/html/index.mjs.map +2 -2
- package/dist/html/template.d.ts +4 -0
- package/dist/http/http.d.ts +1 -0
- package/dist/http/index.js.map +2 -2
- package/dist/http/index.mjs.map +2 -2
- package/dist/index.js +3 -3
- package/dist/index.js.map +3 -3
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +3 -3
- package/dist/routing/index.js +3 -3
- package/dist/routing/index.js.map +3 -3
- package/dist/routing/index.mjs +3 -3
- package/dist/routing/index.mjs.map +3 -3
- package/dist/routing/routeTargetRegistry.d.ts +1 -0
- package/dist/routing/types.d.ts +2 -1
- package/dist/templates/NodeTemplate.d.ts +2 -0
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +2 -2
- package/dist/utils/index.mjs +1 -1
- package/dist/utils/index.mjs.map +2 -2
- package/docs/Architecture.md +333 -333
- package/docs/DependencyInjection.md +277 -237
- package/docs/Errors.md +87 -87
- package/docs/GettingStarted.md +231 -231
- package/docs/Pipes.md +5 -5
- package/docs/Translations.md +167 -312
- package/docs/WhyRelaxjs.md +336 -336
- package/docs/api/.nojekyll +1 -0
- package/docs/api/assets/hierarchy.js +1 -0
- package/docs/api/assets/highlight.css +120 -0
- package/docs/api/assets/icons.js +18 -0
- package/docs/api/assets/icons.svg +1 -0
- package/docs/api/assets/main.js +60 -0
- package/docs/api/assets/navigation.js +1 -0
- package/docs/api/assets/search.js +1 -0
- package/docs/api/assets/style.css +1633 -0
- package/docs/api/classes/http.WebSocketClient.html +26 -0
- package/docs/api/classes/i18n.LocaleChangeEvent.html +66 -0
- package/docs/api/classes/index.Blueprint.html +3 -0
- package/docs/api/classes/index.BoundNode.html +3 -0
- package/docs/api/classes/index.DigitsValidation.html +10 -0
- package/docs/api/classes/index.FormValidator.html +32 -0
- package/docs/api/classes/index.HttpError.html +13 -0
- package/docs/api/classes/index.LinkedList.html +26 -0
- package/docs/api/classes/index.NavigateRouteEvent.html +76 -0
- package/docs/api/classes/index.Node.html +15 -0
- package/docs/api/classes/index.PageSelectedEvent.html +61 -0
- package/docs/api/classes/index.Pager.html +4 -0
- package/docs/api/classes/index.RangeValidation.html +15 -0
- package/docs/api/classes/index.RelaxError.html +17 -0
- package/docs/api/classes/index.RequiredValidation.html +10 -0
- package/docs/api/classes/index.RouteError.html +11 -0
- package/docs/api/classes/index.RouteGuardError.html +12 -0
- package/docs/api/classes/index.RouteLink.html +779 -0
- package/docs/api/classes/index.RouteTarget.html +788 -0
- package/docs/api/classes/index.SSEClient.html +13 -0
- package/docs/api/classes/index.SSEDataEvent.html +63 -0
- package/docs/api/classes/index.ServiceCollection.html +28 -0
- package/docs/api/classes/index.ServiceContainer.html +24 -0
- package/docs/api/classes/index.SortChangeEvent.html +61 -0
- package/docs/api/classes/index.TableRenderer.html +5 -0
- package/docs/api/classes/index.TableSorter.html +4 -0
- package/docs/api/enums/index.GuardResult.html +9 -0
- package/docs/api/functions/elements.formError.html +6 -0
- package/docs/api/functions/elements.selectOne.html +6 -0
- package/docs/api/functions/i18n.getCurrentLocale.html +3 -0
- package/docs/api/functions/i18n.loadNamespace.html +7 -0
- package/docs/api/functions/i18n.loadNamespaces.html +6 -0
- package/docs/api/functions/i18n.onMissingTranslation.html +7 -0
- package/docs/api/functions/i18n.setLocale.html +7 -0
- package/docs/api/functions/i18n.setMessageFormatter.html +7 -0
- package/docs/api/functions/i18n.t.html +9 -0
- package/docs/api/functions/index.BooleanConverter.html +6 -0
- package/docs/api/functions/index.ContainerService.html +13 -0
- package/docs/api/functions/index.DateConverter.html +11 -0
- package/docs/api/functions/index.Inject.html +16 -0
- package/docs/api/functions/index.NumberConverter.html +5 -0
- package/docs/api/functions/index.RegisterValidator.html +7 -0
- package/docs/api/functions/index.applyPipes.html +17 -0
- package/docs/api/functions/index.asyncHandler.html +11 -0
- package/docs/api/functions/index.capitalizePipe.html +4 -0
- package/docs/api/functions/index.clearPendingNavigations.html +1 -0
- package/docs/api/functions/index.compileTemplate.html +26 -0
- package/docs/api/functions/index.configure.html +5 -0
- package/docs/api/functions/index.createBluePrint.html +1 -0
- package/docs/api/functions/index.createConverterFromDataType.html +4 -0
- package/docs/api/functions/index.createConverterFromInputType.html +5 -0
- package/docs/api/functions/index.createPipeRegistry.html +12 -0
- package/docs/api/functions/index.currencyPipe.html +9 -0
- package/docs/api/functions/index.datePipe.html +9 -0
- package/docs/api/functions/index.daysAgoPipe.html +8 -0
- package/docs/api/functions/index.defaultPipe.html +5 -0
- package/docs/api/functions/index.defineRoutes.html +8 -0
- package/docs/api/functions/index.del.html +8 -0
- package/docs/api/functions/index.findRouteByName.html +5 -0
- package/docs/api/functions/index.findRouteByUrl.html +4 -0
- package/docs/api/functions/index.firstPipe.html +4 -0
- package/docs/api/functions/index.generateSequentialId.html +21 -0
- package/docs/api/functions/index.get.html +9 -0
- package/docs/api/functions/index.getDataConverter.html +11 -0
- package/docs/api/functions/index.getParentComponent.html +18 -0
- package/docs/api/functions/index.getValidator.html +4 -0
- package/docs/api/functions/index.html.html +19 -0
- package/docs/api/functions/index.joinPipe.html +5 -0
- package/docs/api/functions/index.keysPipe.html +4 -0
- package/docs/api/functions/index.lastPipe.html +4 -0
- package/docs/api/functions/index.lowercasePipe.html +4 -0
- package/docs/api/functions/index.mapFormToClass.html +17 -0
- package/docs/api/functions/index.matchRoute.html +5 -0
- package/docs/api/functions/index.navigate.html +8 -0
- package/docs/api/functions/index.onError.html +8 -0
- package/docs/api/functions/index.piecesPipe.html +8 -0
- package/docs/api/functions/index.post.html +9 -0
- package/docs/api/functions/index.printRoutes.html +2 -0
- package/docs/api/functions/index.put.html +9 -0
- package/docs/api/functions/index.readData.html +17 -0
- package/docs/api/functions/index.registerRouteTarget.html +9 -0
- package/docs/api/functions/index.reportError.html +10 -0
- package/docs/api/functions/index.request.html +8 -0
- package/docs/api/functions/index.resolveValue.html +18 -0
- package/docs/api/functions/index.setFetch.html +6 -0
- package/docs/api/functions/index.setFormData.html +17 -0
- package/docs/api/functions/index.shortenPipe.html +5 -0
- package/docs/api/functions/index.startRouting.html +6 -0
- package/docs/api/functions/index.ternaryPipe.html +6 -0
- package/docs/api/functions/index.trimPipe.html +4 -0
- package/docs/api/functions/index.unregisterRouteTarget.html +3 -0
- package/docs/api/functions/index.uppercasePipe.html +4 -0
- package/docs/api/hierarchy.html +1 -0
- package/docs/api/index.html +323 -0
- package/docs/api/interfaces/http.SimpleDataEvent.html +3 -0
- package/docs/api/interfaces/http.WebSocketAbstraction.html +9 -0
- package/docs/api/interfaces/http.WebSocketCodec.html +4 -0
- package/docs/api/interfaces/http.WebSocketOptions.html +20 -0
- package/docs/api/interfaces/index.CompiledTemplate.html +10 -0
- package/docs/api/interfaces/index.DataLoader.html +19 -0
- package/docs/api/interfaces/index.EngineConfig.html +11 -0
- package/docs/api/interfaces/index.ErrorContext.html +4 -0
- package/docs/api/interfaces/index.FormReaderOptions.html +8 -0
- package/docs/api/interfaces/index.HttpOptions.html +16 -0
- package/docs/api/interfaces/index.HttpResponse.html +17 -0
- package/docs/api/interfaces/index.LoadRoute.html +7 -0
- package/docs/api/interfaces/index.NavigateOptions.html +7 -0
- package/docs/api/interfaces/index.PipeRegistry.html +12 -0
- package/docs/api/interfaces/index.RegistrationOptions.html +22 -0
- package/docs/api/interfaces/index.RenderTemplate.html +7 -0
- package/docs/api/interfaces/index.RequestOptions.html +11 -0
- package/docs/api/interfaces/index.Routable.html +10 -0
- package/docs/api/interfaces/index.Route.html +13 -0
- package/docs/api/interfaces/index.RouteGuard.html +2 -0
- package/docs/api/interfaces/index.RouteValue.html +6 -0
- package/docs/api/interfaces/index.SSEOptions.html +24 -0
- package/docs/api/interfaces/index.ValidationContext.html +8 -0
- package/docs/api/interfaces/index.ValidatorOptions.html +14 -0
- package/docs/api/media/Architecture.md +333 -0
- package/docs/api/media/DependencyInjection.md +277 -0
- package/docs/api/media/GettingStarted.md +231 -0
- package/docs/api/media/HttpClient.md +459 -0
- package/docs/api/media/Pipes.md +211 -0
- package/docs/api/media/Routing.md +332 -0
- package/docs/api/media/WhyRelaxjs.md +336 -0
- package/docs/api/media/forms.md +99 -0
- package/docs/api/media/html.md +175 -0
- package/docs/api/media/i18n.md +354 -0
- package/docs/api/media/utilities.md +143 -0
- package/docs/api/media/validation.md +351 -0
- package/docs/api/modules/collections_Index.html +1 -0
- package/docs/api/modules/di.html +1 -0
- package/docs/api/modules/elements.html +1 -0
- package/docs/api/modules/forms.html +1 -0
- package/docs/api/modules/html.html +1 -0
- package/docs/api/modules/http.html +1 -0
- package/docs/api/modules/i18n.html +1 -0
- package/docs/api/modules/index.html +1 -0
- package/docs/api/modules/routing.html +1 -0
- package/docs/api/modules/utils.html +1 -0
- package/docs/api/modules.html +1 -0
- package/docs/api/types/http.WebSocketFactory.html +2 -0
- package/docs/api/types/i18n.MessageFormatter.html +3 -0
- package/docs/api/types/i18n.MissingTranslationHandler.html +1 -0
- package/docs/api/types/index.Constructor.html +7 -0
- package/docs/api/types/index.ConverterFunc.html +2 -0
- package/docs/api/types/index.DataType.html +2 -0
- package/docs/api/types/index.InputType.html +2 -0
- package/docs/api/types/index.PipeFunction.html +6 -0
- package/docs/api/types/index.RouteData.html +1 -0
- package/docs/api/types/index.RouteMatchResult.html +9 -0
- package/docs/api/types/index.RouteParamType.html +1 -0
- package/docs/api/types/index.RouteSegmentType.html +2 -0
- package/docs/api/types/index.SSEEventFactory.html +5 -0
- package/docs/api/types/index.ServiceScope.html +10 -0
- package/docs/api/types/index.SortColumn.html +3 -0
- package/docs/api/variables/i18n.formatICU.html +3 -0
- package/docs/api/variables/index.container.html +6 -0
- package/docs/api/variables/index.defaultPipes.html +6 -0
- package/docs/api/variables/index.internalRoutes.html +1 -0
- package/docs/api/variables/index.serviceCollection.html +6 -0
- package/docs/api.json +93171 -0
- package/docs/elements/dom.md +102 -102
- package/docs/forms/creating-form-components.md +924 -924
- package/docs/forms/form-api.md +94 -94
- package/docs/forms/forms.md +99 -99
- package/docs/forms/patterns.md +311 -311
- package/docs/forms/reading-writing.md +365 -365
- package/docs/forms/validation.md +351 -351
- package/docs/html/TableRenderer.md +291 -291
- package/docs/html/html.md +175 -175
- package/docs/html/index.md +54 -54
- package/docs/html/template.md +422 -422
- package/docs/http/HttpClient.md +459 -459
- package/docs/http/ServerSentEvents.md +184 -184
- package/docs/http/index.md +109 -109
- package/docs/i18n/i18n.md +49 -4
- package/docs/i18n/intl-standard.md +178 -178
- package/docs/routing/RouteLink.md +98 -98
- package/docs/routing/Routing.md +332 -332
- package/docs/routing/layouts.md +207 -207
- package/docs/utilities.md +143 -143
- package/package.json +4 -3
package/docs/routing/Routing.md
CHANGED
|
@@ -1,332 +1,332 @@
|
|
|
1
|
-
# Routing
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
A client-side routing system for single-page applications. The router:
|
|
6
|
-
|
|
7
|
-
- Matches URLs to route configurations
|
|
8
|
-
- Extracts typed parameters from URL segments
|
|
9
|
-
- Manages browser history (back/forward navigation)
|
|
10
|
-
- Dispatches `NavigateRouteEvent` to render components
|
|
11
|
-
- Supports multiple rendering targets (main content, sidebars, modals)
|
|
12
|
-
- Handles layout switching between different HTML shells
|
|
13
|
-
|
|
14
|
-
The router itself only handles matching and navigation. Rendering is handled by [`<r-route-target>`](RoutingTarget.md) components that listen for navigation events.
|
|
15
|
-
|
|
16
|
-
## Quick Start
|
|
17
|
-
|
|
18
|
-
```typescript
|
|
19
|
-
import { Route, defineRoutes, startRouting } from '
|
|
20
|
-
|
|
21
|
-
const routes: Route[] = [
|
|
22
|
-
{ name: 'home', path: '/', componentTagName: 'home-page' },
|
|
23
|
-
{ name: 'users', path: '/users', componentTagName: 'user-list' },
|
|
24
|
-
{ name: 'user', path: '/users/:id', componentTagName: 'user-profile' },
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
defineRoutes(routes);
|
|
28
|
-
startRouting();
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
```html
|
|
32
|
-
<body>
|
|
33
|
-
<nav>
|
|
34
|
-
<r-link name="home">Home</r-link>
|
|
35
|
-
<r-link name="users">Users</r-link>
|
|
36
|
-
</nav>
|
|
37
|
-
<main>
|
|
38
|
-
<r-route-target></r-route-target>
|
|
39
|
-
</main>
|
|
40
|
-
</body>
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Route Definition
|
|
44
|
-
|
|
45
|
-
Each route specifies how a URL maps to a component:
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
interface Route {
|
|
49
|
-
name?: string; // Identifier for programmatic navigation
|
|
50
|
-
path: string; // URL pattern with parameters
|
|
51
|
-
componentTagName?: string; // Custom element tag name
|
|
52
|
-
component?: WebComponentConstructor; // Or class reference
|
|
53
|
-
target?: string; // Named r-route-target (default: unnamed)
|
|
54
|
-
layout?: string; // HTML file for different shells
|
|
55
|
-
guards?: RouteGuard[]; // Access control
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### URL Pattern Syntax
|
|
60
|
-
|
|
61
|
-
| Syntax | Type | Example | Matches |
|
|
62
|
-
|--------|------|---------|---------|
|
|
63
|
-
| `text` | Static segment | `/users` | Exactly "users" |
|
|
64
|
-
| `:name` | String parameter | `/users/:id` | `/users/john` → `{ id: 'john' }` |
|
|
65
|
-
| `;name` | Number parameter | `/orders/;orderId` | `/orders/123` → `{ orderId: 123 }` |
|
|
66
|
-
|
|
67
|
-
Number parameters (`;`) validate that the segment contains only digits and convert to `number` type. String parameters (`:`) accept any value and remain as `string`.
|
|
68
|
-
|
|
69
|
-
```typescript
|
|
70
|
-
const routes: Route[] = [
|
|
71
|
-
{ name: 'home', path: '/' },
|
|
72
|
-
{ name: 'user', path: '/users/:userName' }, // String: userName
|
|
73
|
-
{ name: 'order', path: '/orders/;orderId' }, // Number: orderId
|
|
74
|
-
{ name: 'category', path: '/shop/:category/items' }, // Mixed static + param
|
|
75
|
-
];
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Navigation
|
|
79
|
-
|
|
80
|
-
### Using RouteLink Component
|
|
81
|
-
|
|
82
|
-
The simplest way to navigate is with [`<r-link>`](RouteLink.md):
|
|
83
|
-
|
|
84
|
-
```html
|
|
85
|
-
<r-link name="home">Home</r-link>
|
|
86
|
-
<r-link name="user" param-userName="john">View Profile</r-link>
|
|
87
|
-
<r-link name="order" param-orderId="123">Order Details</r-link>
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Programmatic Navigation
|
|
91
|
-
|
|
92
|
-
Use `navigate()` for navigation from code:
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
import { navigate } from '
|
|
96
|
-
|
|
97
|
-
// By route name with parameters
|
|
98
|
-
navigate('user', { params: { userName: 'john' } });
|
|
99
|
-
navigate('order', { params: { orderId: 123 } });
|
|
100
|
-
|
|
101
|
-
// By URL directly
|
|
102
|
-
navigate('/users/john');
|
|
103
|
-
navigate('/orders/123');
|
|
104
|
-
|
|
105
|
-
// To a specific target
|
|
106
|
-
navigate('preview', { params: { id: '42' }, target: 'modal' });
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## Multiple Targets
|
|
110
|
-
|
|
111
|
-
Routes can render in different [`<r-route-target>`](RoutingTarget.md) elements:
|
|
112
|
-
|
|
113
|
-
```html
|
|
114
|
-
<div class="layout">
|
|
115
|
-
<aside>
|
|
116
|
-
<r-route-target name="sidebar"></r-route-target>
|
|
117
|
-
</aside>
|
|
118
|
-
<main>
|
|
119
|
-
<r-route-target></r-route-target>
|
|
120
|
-
</main>
|
|
121
|
-
</div>
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
const routes: Route[] = [
|
|
126
|
-
{ name: 'home', path: '/', componentTagName: 'home-page' },
|
|
127
|
-
{ name: 'menu', path: '/menu', target: 'sidebar', componentTagName: 'nav-menu' },
|
|
128
|
-
];
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
Routes without a `target` property render in the default (unnamed) target.
|
|
132
|
-
|
|
133
|
-
## Browser History
|
|
134
|
-
|
|
135
|
-
The router integrates with the browser's History API:
|
|
136
|
-
|
|
137
|
-
- `navigate()` calls `history.pushState()` to add entries
|
|
138
|
-
- Back/forward buttons trigger navigation to previous routes (components receive `loadRoute` again)
|
|
139
|
-
- `startRouting()` reads the current URL and navigates on page load
|
|
140
|
-
|
|
141
|
-
URLs display the route path (e.g., `/users/john`), not the HTML file.
|
|
142
|
-
|
|
143
|
-
## Layouts
|
|
144
|
-
|
|
145
|
-
Different parts of your application may need different HTML shells (navigation, sidebar presence, etc.). See [Layouts](layouts.md) for details.
|
|
146
|
-
|
|
147
|
-
```typescript
|
|
148
|
-
const routes: Route[] = [
|
|
149
|
-
{ name: 'login', path: '/login', componentTagName: 'login-page', layout: 'public' },
|
|
150
|
-
{ name: 'dashboard', path: '/dashboard', componentTagName: 'dashboard-page' }, // default layout
|
|
151
|
-
];
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
When navigating between layouts, the router redirects to the appropriate HTML file and resumes navigation.
|
|
155
|
-
|
|
156
|
-
## Route Guards
|
|
157
|
-
|
|
158
|
-
Guards control access to routes:
|
|
159
|
-
|
|
160
|
-
```typescript
|
|
161
|
-
import { RouteGuard, GuardResult, RouteMatchResult } from '
|
|
162
|
-
|
|
163
|
-
class AuthGuard implements RouteGuard {
|
|
164
|
-
check(route: RouteMatchResult): GuardResult {
|
|
165
|
-
if (isAuthenticated()) {
|
|
166
|
-
return GuardResult.Continue; // Check other guards
|
|
167
|
-
}
|
|
168
|
-
navigate('login');
|
|
169
|
-
return GuardResult.Stop; // Prevent navigation
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const routes: Route[] = [
|
|
174
|
-
{ name: 'dashboard', path: '/dashboard', componentTagName: 'dashboard-page', guards: [new AuthGuard()] },
|
|
175
|
-
];
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Guard Results
|
|
179
|
-
|
|
180
|
-
| Result | Behavior |
|
|
181
|
-
|--------|----------|
|
|
182
|
-
| `Allow` | Proceed immediately, skip remaining guards |
|
|
183
|
-
| `Continue` | Check next guard (or proceed if none left) |
|
|
184
|
-
| `Stop` | Cancel navigation silently |
|
|
185
|
-
| `Deny` | Cancel navigation and throw `RouteGuardError` |
|
|
186
|
-
|
|
187
|
-
## Events
|
|
188
|
-
|
|
189
|
-
Navigation dispatches `NavigateRouteEvent` on `document`:
|
|
190
|
-
|
|
191
|
-
```typescript
|
|
192
|
-
import { NavigateRouteEvent } from '
|
|
193
|
-
|
|
194
|
-
document.addEventListener('rlx.navigateRoute', (e: NavigateRouteEvent) => {
|
|
195
|
-
console.log('Route:', e.route.name);
|
|
196
|
-
console.log('Params:', e.routeData);
|
|
197
|
-
console.log('Target:', e.routeTarget ?? 'default');
|
|
198
|
-
});
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
This event is typed in `HTMLElementEventMap` for full TypeScript support.
|
|
202
|
-
|
|
203
|
-
## Route Matching
|
|
204
|
-
|
|
205
|
-
For advanced use cases, match routes without navigating:
|
|
206
|
-
|
|
207
|
-
```typescript
|
|
208
|
-
import { matchRoute, findRouteByUrl, findRouteByName } from '
|
|
209
|
-
|
|
210
|
-
// Match by URL
|
|
211
|
-
const result = matchRoute(routes, '/users/john');
|
|
212
|
-
// { route: {...}, params: { userName: 'john' }, urlSegments: ['users', 'john'] }
|
|
213
|
-
|
|
214
|
-
// Match by name
|
|
215
|
-
const result = matchRoute(routes, 'user', { userName: 'john' });
|
|
216
|
-
|
|
217
|
-
// Direct functions
|
|
218
|
-
findRouteByUrl(routes, '/users/john');
|
|
219
|
-
findRouteByName(routes, 'user', { userName: 'john' });
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
## Receiving Route Parameters
|
|
223
|
-
|
|
224
|
-
Components rendered by `<r-route-target>` can receive route parameters in two ways.
|
|
225
|
-
|
|
226
|
-
### loadRoute (async initialization)
|
|
227
|
-
|
|
228
|
-
Implement `loadRoute()` to run async setup before the component is added to the DOM. The component is not visible until `loadRoute()` completes.
|
|
229
|
-
|
|
230
|
-
```typescript
|
|
231
|
-
import { LoadRoute, RouteData } from '
|
|
232
|
-
|
|
233
|
-
class OrderDetail extends HTMLElement implements LoadRoute<{ orderId: number }> {
|
|
234
|
-
private order: Order;
|
|
235
|
-
|
|
236
|
-
async loadRoute(data: { orderId: number }) {
|
|
237
|
-
this.order = await fetchOrder(data.orderId);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
connectedCallback() {
|
|
241
|
-
this.render(this.order);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### routeData (typed property)
|
|
247
|
-
|
|
248
|
-
Implement `Routable` to receive parameters as a typed property. The property is optional since it's set by the router after construction.
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
import { Routable } from '
|
|
252
|
-
|
|
253
|
-
class UserProfile extends HTMLElement implements Routable<{ userName: string }> {
|
|
254
|
-
routeData?: { userName: string };
|
|
255
|
-
}
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
For convention-based usage without the interface, declare `routeData` directly on your component. The router always assigns it regardless.
|
|
259
|
-
|
|
260
|
-
Both can be combined. `loadRoute()` runs first, then `routeData` is assigned.
|
|
261
|
-
|
|
262
|
-
## Error Handling
|
|
263
|
-
|
|
264
|
-
```typescript
|
|
265
|
-
import { RouteError, RouteGuardError } from '
|
|
266
|
-
|
|
267
|
-
try {
|
|
268
|
-
navigate('unknown-route');
|
|
269
|
-
} catch (e) {
|
|
270
|
-
if (e instanceof RouteGuardError) {
|
|
271
|
-
console.log('Access denied');
|
|
272
|
-
} else if (e instanceof RouteError) {
|
|
273
|
-
console.log('Route not found');
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
When no route matches, the error message lists all available routes for debugging.
|
|
279
|
-
|
|
280
|
-
Routing errors are reported through the global [error handler](../Errors.md). The error context contains:
|
|
281
|
-
|
|
282
|
-
| Field | Description |
|
|
283
|
-
|-------|-------------|
|
|
284
|
-
| `route` | Route name |
|
|
285
|
-
| `componentTagName` | Custom element tag name |
|
|
286
|
-
| `component` | Component class name (if using class reference) |
|
|
287
|
-
| `routeData` | Parameters extracted from the URL |
|
|
288
|
-
|
|
289
|
-
## API Reference
|
|
290
|
-
|
|
291
|
-
### Functions
|
|
292
|
-
|
|
293
|
-
| Function | Description |
|
|
294
|
-
|----------|-------------|
|
|
295
|
-
| `defineRoutes(routes)` | Register routes at startup |
|
|
296
|
-
| `startRouting()` | Initialize router and navigate to current URL |
|
|
297
|
-
| `navigate(nameOrUrl, options?)` | Navigate to a route |
|
|
298
|
-
| `matchRoute(routes, nameOrUrl, params?)` | Match without navigating |
|
|
299
|
-
| `findRouteByUrl(routes, path)` | Match by URL pattern |
|
|
300
|
-
| `findRouteByName(routes, name, params)` | Match by route name |
|
|
301
|
-
|
|
302
|
-
### Types
|
|
303
|
-
|
|
304
|
-
```typescript
|
|
305
|
-
type RouteParamType = string | number;
|
|
306
|
-
type RouteData = Record<string, RouteParamType>;
|
|
307
|
-
|
|
308
|
-
interface NavigateOptions {
|
|
309
|
-
params?: Record<string, string | number>;
|
|
310
|
-
target?: string;
|
|
311
|
-
routes?: Route[]; // Override registered routes
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
interface RouteMatchResult {
|
|
315
|
-
route: Route;
|
|
316
|
-
params: RouteData;
|
|
317
|
-
urlSegments: string[];
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
enum GuardResult {
|
|
321
|
-
Allow, // Proceed, skip remaining guards
|
|
322
|
-
Deny, // Throw RouteGuardError
|
|
323
|
-
Continue, // Check next guard
|
|
324
|
-
Stop // Cancel silently
|
|
325
|
-
}
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
## Related
|
|
329
|
-
|
|
330
|
-
- [`<r-route-target>`](RoutingTarget.md) - Renders routed components
|
|
331
|
-
- [`<r-link>`](RouteLink.md) - Declarative navigation links
|
|
332
|
-
- [Layouts](layouts.md) - Multiple HTML shells
|
|
1
|
+
# Routing
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
A client-side routing system for single-page applications. The router:
|
|
6
|
+
|
|
7
|
+
- Matches URLs to route configurations
|
|
8
|
+
- Extracts typed parameters from URL segments
|
|
9
|
+
- Manages browser history (back/forward navigation)
|
|
10
|
+
- Dispatches `NavigateRouteEvent` to render components
|
|
11
|
+
- Supports multiple rendering targets (main content, sidebars, modals)
|
|
12
|
+
- Handles layout switching between different HTML shells
|
|
13
|
+
|
|
14
|
+
The router itself only handles matching and navigation. Rendering is handled by [`<r-route-target>`](RoutingTarget.md) components that listen for navigation events.
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { Route, defineRoutes, startRouting } from '@relax.js/core/routing';
|
|
20
|
+
|
|
21
|
+
const routes: Route[] = [
|
|
22
|
+
{ name: 'home', path: '/', componentTagName: 'home-page' },
|
|
23
|
+
{ name: 'users', path: '/users', componentTagName: 'user-list' },
|
|
24
|
+
{ name: 'user', path: '/users/:id', componentTagName: 'user-profile' },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
defineRoutes(routes);
|
|
28
|
+
startRouting();
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<body>
|
|
33
|
+
<nav>
|
|
34
|
+
<r-link name="home">Home</r-link>
|
|
35
|
+
<r-link name="users">Users</r-link>
|
|
36
|
+
</nav>
|
|
37
|
+
<main>
|
|
38
|
+
<r-route-target></r-route-target>
|
|
39
|
+
</main>
|
|
40
|
+
</body>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Route Definition
|
|
44
|
+
|
|
45
|
+
Each route specifies how a URL maps to a component:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
interface Route {
|
|
49
|
+
name?: string; // Identifier for programmatic navigation
|
|
50
|
+
path: string; // URL pattern with parameters
|
|
51
|
+
componentTagName?: string; // Custom element tag name
|
|
52
|
+
component?: WebComponentConstructor; // Or class reference
|
|
53
|
+
target?: string; // Named r-route-target (default: unnamed)
|
|
54
|
+
layout?: string; // HTML file for different shells
|
|
55
|
+
guards?: RouteGuard[]; // Access control
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### URL Pattern Syntax
|
|
60
|
+
|
|
61
|
+
| Syntax | Type | Example | Matches |
|
|
62
|
+
|--------|------|---------|---------|
|
|
63
|
+
| `text` | Static segment | `/users` | Exactly "users" |
|
|
64
|
+
| `:name` | String parameter | `/users/:id` | `/users/john` → `{ id: 'john' }` |
|
|
65
|
+
| `;name` | Number parameter | `/orders/;orderId` | `/orders/123` → `{ orderId: 123 }` |
|
|
66
|
+
|
|
67
|
+
Number parameters (`;`) validate that the segment contains only digits and convert to `number` type. String parameters (`:`) accept any value and remain as `string`.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
const routes: Route[] = [
|
|
71
|
+
{ name: 'home', path: '/' },
|
|
72
|
+
{ name: 'user', path: '/users/:userName' }, // String: userName
|
|
73
|
+
{ name: 'order', path: '/orders/;orderId' }, // Number: orderId
|
|
74
|
+
{ name: 'category', path: '/shop/:category/items' }, // Mixed static + param
|
|
75
|
+
];
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Navigation
|
|
79
|
+
|
|
80
|
+
### Using RouteLink Component
|
|
81
|
+
|
|
82
|
+
The simplest way to navigate is with [`<r-link>`](RouteLink.md):
|
|
83
|
+
|
|
84
|
+
```html
|
|
85
|
+
<r-link name="home">Home</r-link>
|
|
86
|
+
<r-link name="user" param-userName="john">View Profile</r-link>
|
|
87
|
+
<r-link name="order" param-orderId="123">Order Details</r-link>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Programmatic Navigation
|
|
91
|
+
|
|
92
|
+
Use `navigate()` for navigation from code:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { navigate } from '@relax.js/core/routing';
|
|
96
|
+
|
|
97
|
+
// By route name with parameters
|
|
98
|
+
navigate('user', { params: { userName: 'john' } });
|
|
99
|
+
navigate('order', { params: { orderId: 123 } });
|
|
100
|
+
|
|
101
|
+
// By URL directly
|
|
102
|
+
navigate('/users/john');
|
|
103
|
+
navigate('/orders/123');
|
|
104
|
+
|
|
105
|
+
// To a specific target
|
|
106
|
+
navigate('preview', { params: { id: '42' }, target: 'modal' });
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Multiple Targets
|
|
110
|
+
|
|
111
|
+
Routes can render in different [`<r-route-target>`](RoutingTarget.md) elements:
|
|
112
|
+
|
|
113
|
+
```html
|
|
114
|
+
<div class="layout">
|
|
115
|
+
<aside>
|
|
116
|
+
<r-route-target name="sidebar"></r-route-target>
|
|
117
|
+
</aside>
|
|
118
|
+
<main>
|
|
119
|
+
<r-route-target></r-route-target>
|
|
120
|
+
</main>
|
|
121
|
+
</div>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const routes: Route[] = [
|
|
126
|
+
{ name: 'home', path: '/', componentTagName: 'home-page' },
|
|
127
|
+
{ name: 'menu', path: '/menu', target: 'sidebar', componentTagName: 'nav-menu' },
|
|
128
|
+
];
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Routes without a `target` property render in the default (unnamed) target.
|
|
132
|
+
|
|
133
|
+
## Browser History
|
|
134
|
+
|
|
135
|
+
The router integrates with the browser's History API:
|
|
136
|
+
|
|
137
|
+
- `navigate()` calls `history.pushState()` to add entries
|
|
138
|
+
- Back/forward buttons trigger navigation to previous routes (components receive `loadRoute` again)
|
|
139
|
+
- `startRouting()` reads the current URL and navigates on page load
|
|
140
|
+
|
|
141
|
+
URLs display the route path (e.g., `/users/john`), not the HTML file.
|
|
142
|
+
|
|
143
|
+
## Layouts
|
|
144
|
+
|
|
145
|
+
Different parts of your application may need different HTML shells (navigation, sidebar presence, etc.). See [Layouts](layouts.md) for details.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
const routes: Route[] = [
|
|
149
|
+
{ name: 'login', path: '/login', componentTagName: 'login-page', layout: 'public' },
|
|
150
|
+
{ name: 'dashboard', path: '/dashboard', componentTagName: 'dashboard-page' }, // default layout
|
|
151
|
+
];
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
When navigating between layouts, the router redirects to the appropriate HTML file and resumes navigation.
|
|
155
|
+
|
|
156
|
+
## Route Guards
|
|
157
|
+
|
|
158
|
+
Guards control access to routes:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { RouteGuard, GuardResult, RouteMatchResult } from '@relax.js/core/routing';
|
|
162
|
+
|
|
163
|
+
class AuthGuard implements RouteGuard {
|
|
164
|
+
check(route: RouteMatchResult): GuardResult {
|
|
165
|
+
if (isAuthenticated()) {
|
|
166
|
+
return GuardResult.Continue; // Check other guards
|
|
167
|
+
}
|
|
168
|
+
navigate('login');
|
|
169
|
+
return GuardResult.Stop; // Prevent navigation
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const routes: Route[] = [
|
|
174
|
+
{ name: 'dashboard', path: '/dashboard', componentTagName: 'dashboard-page', guards: [new AuthGuard()] },
|
|
175
|
+
];
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Guard Results
|
|
179
|
+
|
|
180
|
+
| Result | Behavior |
|
|
181
|
+
|--------|----------|
|
|
182
|
+
| `Allow` | Proceed immediately, skip remaining guards |
|
|
183
|
+
| `Continue` | Check next guard (or proceed if none left) |
|
|
184
|
+
| `Stop` | Cancel navigation silently |
|
|
185
|
+
| `Deny` | Cancel navigation and throw `RouteGuardError` |
|
|
186
|
+
|
|
187
|
+
## Events
|
|
188
|
+
|
|
189
|
+
Navigation dispatches `NavigateRouteEvent` on `document`:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { NavigateRouteEvent } from '@relax.js/core/routing';
|
|
193
|
+
|
|
194
|
+
document.addEventListener('rlx.navigateRoute', (e: NavigateRouteEvent) => {
|
|
195
|
+
console.log('Route:', e.route.name);
|
|
196
|
+
console.log('Params:', e.routeData);
|
|
197
|
+
console.log('Target:', e.routeTarget ?? 'default');
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
This event is typed in `HTMLElementEventMap` for full TypeScript support.
|
|
202
|
+
|
|
203
|
+
## Route Matching
|
|
204
|
+
|
|
205
|
+
For advanced use cases, match routes without navigating:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { matchRoute, findRouteByUrl, findRouteByName } from '@relax.js/core/routing';
|
|
209
|
+
|
|
210
|
+
// Match by URL
|
|
211
|
+
const result = matchRoute(routes, '/users/john');
|
|
212
|
+
// { route: {...}, params: { userName: 'john' }, urlSegments: ['users', 'john'] }
|
|
213
|
+
|
|
214
|
+
// Match by name
|
|
215
|
+
const result = matchRoute(routes, 'user', { userName: 'john' });
|
|
216
|
+
|
|
217
|
+
// Direct functions
|
|
218
|
+
findRouteByUrl(routes, '/users/john');
|
|
219
|
+
findRouteByName(routes, 'user', { userName: 'john' });
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Receiving Route Parameters
|
|
223
|
+
|
|
224
|
+
Components rendered by `<r-route-target>` can receive route parameters in two ways.
|
|
225
|
+
|
|
226
|
+
### loadRoute (async initialization)
|
|
227
|
+
|
|
228
|
+
Implement `loadRoute()` to run async setup before the component is added to the DOM. The component is not visible until `loadRoute()` completes.
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import { LoadRoute, RouteData } from '@relax.js/core/routing';
|
|
232
|
+
|
|
233
|
+
class OrderDetail extends HTMLElement implements LoadRoute<{ orderId: number }> {
|
|
234
|
+
private order: Order;
|
|
235
|
+
|
|
236
|
+
async loadRoute(data: { orderId: number }) {
|
|
237
|
+
this.order = await fetchOrder(data.orderId);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
connectedCallback() {
|
|
241
|
+
this.render(this.order);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### routeData (typed property)
|
|
247
|
+
|
|
248
|
+
Implement `Routable` to receive parameters as a typed property. The property is optional since it's set by the router after construction.
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { Routable } from '@relax.js/core/routing';
|
|
252
|
+
|
|
253
|
+
class UserProfile extends HTMLElement implements Routable<{ userName: string }> {
|
|
254
|
+
routeData?: { userName: string };
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
For convention-based usage without the interface, declare `routeData` directly on your component. The router always assigns it regardless.
|
|
259
|
+
|
|
260
|
+
Both can be combined. `loadRoute()` runs first, then `routeData` is assigned.
|
|
261
|
+
|
|
262
|
+
## Error Handling
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { RouteError, RouteGuardError } from '@relax.js/core/routing';
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
navigate('unknown-route');
|
|
269
|
+
} catch (e) {
|
|
270
|
+
if (e instanceof RouteGuardError) {
|
|
271
|
+
console.log('Access denied');
|
|
272
|
+
} else if (e instanceof RouteError) {
|
|
273
|
+
console.log('Route not found');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
When no route matches, the error message lists all available routes for debugging.
|
|
279
|
+
|
|
280
|
+
Routing errors are reported through the global [error handler](../Errors.md). The error context contains:
|
|
281
|
+
|
|
282
|
+
| Field | Description |
|
|
283
|
+
|-------|-------------|
|
|
284
|
+
| `route` | Route name |
|
|
285
|
+
| `componentTagName` | Custom element tag name |
|
|
286
|
+
| `component` | Component class name (if using class reference) |
|
|
287
|
+
| `routeData` | Parameters extracted from the URL |
|
|
288
|
+
|
|
289
|
+
## API Reference
|
|
290
|
+
|
|
291
|
+
### Functions
|
|
292
|
+
|
|
293
|
+
| Function | Description |
|
|
294
|
+
|----------|-------------|
|
|
295
|
+
| `defineRoutes(routes)` | Register routes at startup |
|
|
296
|
+
| `startRouting()` | Initialize router and navigate to current URL |
|
|
297
|
+
| `navigate(nameOrUrl, options?)` | Navigate to a route |
|
|
298
|
+
| `matchRoute(routes, nameOrUrl, params?)` | Match without navigating |
|
|
299
|
+
| `findRouteByUrl(routes, path)` | Match by URL pattern |
|
|
300
|
+
| `findRouteByName(routes, name, params)` | Match by route name |
|
|
301
|
+
|
|
302
|
+
### Types
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
type RouteParamType = string | number;
|
|
306
|
+
type RouteData = Record<string, RouteParamType>;
|
|
307
|
+
|
|
308
|
+
interface NavigateOptions {
|
|
309
|
+
params?: Record<string, string | number>;
|
|
310
|
+
target?: string;
|
|
311
|
+
routes?: Route[]; // Override registered routes
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
interface RouteMatchResult {
|
|
315
|
+
route: Route;
|
|
316
|
+
params: RouteData;
|
|
317
|
+
urlSegments: string[];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
enum GuardResult {
|
|
321
|
+
Allow, // Proceed, skip remaining guards
|
|
322
|
+
Deny, // Throw RouteGuardError
|
|
323
|
+
Continue, // Check next guard
|
|
324
|
+
Stop // Cancel silently
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Related
|
|
329
|
+
|
|
330
|
+
- [`<r-route-target>`](RoutingTarget.md) - Renders routed components
|
|
331
|
+
- [`<r-link>`](RouteLink.md) - Declarative navigation links
|
|
332
|
+
- [Layouts](layouts.md) - Multiple HTML shells
|