@relax.js/core 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.
Files changed (194) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +188 -0
  3. package/dist/DataLoader.d.ts +51 -0
  4. package/dist/DependencyInjection.d.ts +271 -0
  5. package/dist/DependencyInjectionOld.d.ts +35 -0
  6. package/dist/Metadata.d.ts +8 -0
  7. package/dist/SequentialId.d.ts +47 -0
  8. package/dist/_alt/src/MustardEngine.d.ts +30 -0
  9. package/dist/_alt/src/MustardParser.d.ts +63 -0
  10. package/dist/_alt/src/MustardParser2.d.ts +35 -0
  11. package/dist/_alt/src/pipes.d.ts +93 -0
  12. package/dist/_alt/src/template.d.ts +166 -0
  13. package/dist/_alt/src/tools.d.ts +4 -0
  14. package/dist/_alt/tests/pipes.tests.d.ts +1 -0
  15. package/dist/_alt/tests/template.tests.d.ts +1 -0
  16. package/dist/_alt/vitest.config.d.ts +2 -0
  17. package/dist/collections/Index.d.ts +1 -0
  18. package/dist/collections/LinkedList.d.ts +75 -0
  19. package/dist/collections/Pager.d.ts +15 -0
  20. package/dist/collections/index.js +2 -0
  21. package/dist/collections/index.js.map +7 -0
  22. package/dist/collections/index.mjs +2 -0
  23. package/dist/collections/index.mjs.map +7 -0
  24. package/dist/components/Table.d.ts +13 -0
  25. package/dist/components/index.d.ts +4 -0
  26. package/dist/components/index.js +128 -0
  27. package/dist/components/index.js.map +7 -0
  28. package/dist/components/index.mjs +128 -0
  29. package/dist/components/index.mjs.map +7 -0
  30. package/dist/components/lists/Table.d.ts +59 -0
  31. package/dist/components/lists/TreeView.d.ts +67 -0
  32. package/dist/components/lists/index.d.ts +2 -0
  33. package/dist/components/loader.d.ts +60 -0
  34. package/dist/components/menus/MenuItem.d.ts +30 -0
  35. package/dist/components/menus/TopMenu.d.ts +16 -0
  36. package/dist/components/menus/index.d.ts +2 -0
  37. package/dist/components/panels/tabs.d.ts +15 -0
  38. package/dist/di/index.d.ts +1 -0
  39. package/dist/di/index.js +2 -0
  40. package/dist/di/index.js.map +7 -0
  41. package/dist/di/index.mjs +2 -0
  42. package/dist/di/index.mjs.map +7 -0
  43. package/dist/elements/CopyAttributes.d.ts +2 -0
  44. package/dist/elements/dom.d.ts +18 -0
  45. package/dist/elements/index.d.ts +2 -0
  46. package/dist/elements/index.js +2 -0
  47. package/dist/elements/index.js.map +7 -0
  48. package/dist/elements/index.mjs +2 -0
  49. package/dist/elements/index.mjs.map +7 -0
  50. package/dist/errors.d.ts +71 -0
  51. package/dist/forms/FormReader.d.ts +182 -0
  52. package/dist/forms/FormValidator.d.ts +114 -0
  53. package/dist/forms/ValidationRules.d.ts +103 -0
  54. package/dist/forms/index.d.ts +4 -0
  55. package/dist/forms/index.js +2 -0
  56. package/dist/forms/index.js.map +7 -0
  57. package/dist/forms/index.mjs +2 -0
  58. package/dist/forms/index.mjs.map +7 -0
  59. package/dist/forms/setFormData.d.ts +49 -0
  60. package/dist/getParentComponent.d.ts +43 -0
  61. package/dist/html/TableRenderer.d.ts +44 -0
  62. package/dist/html/TreeBinder.d.ts +9 -0
  63. package/dist/html/html.d.ts +55 -0
  64. package/dist/html/index.d.ts +5 -0
  65. package/dist/html/index.js +2 -0
  66. package/dist/html/index.js.map +7 -0
  67. package/dist/html/index.mjs +2 -0
  68. package/dist/html/index.mjs.map +7 -0
  69. package/dist/html/template.d.ts +167 -0
  70. package/dist/http/ServerSentEvents.d.ts +116 -0
  71. package/dist/http/SimpleWebSocket.d.ts +153 -0
  72. package/dist/http/http.d.ts +177 -0
  73. package/dist/http/index.d.ts +3 -0
  74. package/dist/http/index.js +2 -0
  75. package/dist/http/index.js.map +7 -0
  76. package/dist/http/index.mjs +2 -0
  77. package/dist/http/index.mjs.map +7 -0
  78. package/dist/i18n/i18n.d.ts +105 -0
  79. package/dist/i18n/icu.d.ts +64 -0
  80. package/dist/i18n/index.d.ts +2 -0
  81. package/dist/i18n/index.js +2 -0
  82. package/dist/i18n/index.js.map +7 -0
  83. package/dist/i18n/index.mjs +2 -0
  84. package/dist/i18n/index.mjs.map +7 -0
  85. package/dist/index.d.ts +16 -0
  86. package/dist/index.js +5 -0
  87. package/dist/index.js.map +7 -0
  88. package/dist/index.mjs +5 -0
  89. package/dist/index.mjs.map +7 -0
  90. package/dist/lib/DataLoader.d.ts +51 -0
  91. package/dist/lib/DependencyInjection.d.ts +271 -0
  92. package/dist/lib/InvokeParent.d.ts +10 -0
  93. package/dist/lib/Pipes.d.ts +236 -0
  94. package/dist/lib/SequentialId.d.ts +47 -0
  95. package/dist/lib/collections/Index.d.ts +1 -0
  96. package/dist/lib/collections/LinkedList.d.ts +75 -0
  97. package/dist/lib/collections/Pager.d.ts +15 -0
  98. package/dist/lib/collections/TableRenderer.d.ts +44 -0
  99. package/dist/lib/di/index.d.ts +1 -0
  100. package/dist/lib/elements/CopyAttributes.d.ts +2 -0
  101. package/dist/lib/elements/dom.d.ts +18 -0
  102. package/dist/lib/elements/index.d.ts +2 -0
  103. package/dist/lib/errors.d.ts +71 -0
  104. package/dist/lib/forms/FormReader.d.ts +182 -0
  105. package/dist/lib/forms/FormValidator.d.ts +114 -0
  106. package/dist/lib/forms/ValidationRules.d.ts +103 -0
  107. package/dist/lib/forms/index.d.ts +4 -0
  108. package/dist/lib/forms/setFormData.d.ts +49 -0
  109. package/dist/lib/getParentComponent.d.ts +43 -0
  110. package/dist/lib/html/TableRenderer.d.ts +44 -0
  111. package/dist/lib/html/TreeBinder.d.ts +9 -0
  112. package/dist/lib/html/html.d.ts +55 -0
  113. package/dist/lib/html/html2.d.ts +55 -0
  114. package/dist/lib/html/index.d.ts +5 -0
  115. package/dist/lib/html/m.d.ts +167 -0
  116. package/dist/lib/html/m2.d.ts +8 -0
  117. package/dist/lib/html/m3.d.ts +0 -0
  118. package/dist/lib/html/template.d.ts +167 -0
  119. package/dist/lib/http/HttpClient.d.ts +153 -0
  120. package/dist/lib/http/ServerSentEvents.d.ts +116 -0
  121. package/dist/lib/http/SimpleWebSocket.d.ts +153 -0
  122. package/dist/lib/http/http.d.ts +177 -0
  123. package/dist/lib/http/index.d.ts +3 -0
  124. package/dist/lib/i18n/i18n.d.ts +105 -0
  125. package/dist/lib/i18n/icu.d.ts +64 -0
  126. package/dist/lib/i18n/index.d.ts +2 -0
  127. package/dist/lib/index.d.ts +16 -0
  128. package/dist/lib/routing/NavigateRouteEvent.d.ts +52 -0
  129. package/dist/lib/routing/RouteLink.d.ts +7 -0
  130. package/dist/lib/routing/Routing.d.ts +270 -0
  131. package/dist/lib/routing/RoutingTarget.d.ts +22 -0
  132. package/dist/lib/routing/index.d.ts +7 -0
  133. package/dist/lib/routing/navigation.d.ts +70 -0
  134. package/dist/lib/routing/routeMatching.d.ts +21 -0
  135. package/dist/lib/routing/routeTargetRegistry.d.ts +23 -0
  136. package/dist/lib/routing/types.d.ts +130 -0
  137. package/dist/lib/templates/NodeTemplate.d.ts +38 -0
  138. package/dist/lib/templates/accessorParser.d.ts +87 -0
  139. package/dist/lib/templates/parseTemplate.d.ts +6 -0
  140. package/dist/lib/templates/tokenizer.d.ts +76 -0
  141. package/dist/lib/tools.d.ts +30 -0
  142. package/dist/lib/utils/index.d.ts +4 -0
  143. package/dist/pipes.d.ts +236 -0
  144. package/dist/routing/NavigateRouteEvent.d.ts +52 -0
  145. package/dist/routing/RouteLink.d.ts +7 -0
  146. package/dist/routing/RoutingTarget.d.ts +22 -0
  147. package/dist/routing/index.d.ts +7 -0
  148. package/dist/routing/index.js +5 -0
  149. package/dist/routing/index.js.map +7 -0
  150. package/dist/routing/index.mjs +5 -0
  151. package/dist/routing/index.mjs.map +7 -0
  152. package/dist/routing/navigation.d.ts +70 -0
  153. package/dist/routing/routeMatching.d.ts +21 -0
  154. package/dist/routing/routeTargetRegistry.d.ts +23 -0
  155. package/dist/routing/types.d.ts +130 -0
  156. package/dist/templates/NodeTemplate.d.ts +38 -0
  157. package/dist/templates/accessorParser.d.ts +87 -0
  158. package/dist/templates/parseTemplate.d.ts +6 -0
  159. package/dist/templates/tokenizer.d.ts +76 -0
  160. package/dist/tools.d.ts +30 -0
  161. package/dist/utils/index.d.ts +4 -0
  162. package/dist/utils/index.js +2 -0
  163. package/dist/utils/index.js.map +7 -0
  164. package/dist/utils/index.mjs +2 -0
  165. package/dist/utils/index.mjs.map +7 -0
  166. package/docs/Architecture.md +333 -0
  167. package/docs/DependencyInjection.md +237 -0
  168. package/docs/Errors.md +87 -0
  169. package/docs/GettingStarted.md +231 -0
  170. package/docs/Pipes.md +211 -0
  171. package/docs/Translations.md +312 -0
  172. package/docs/WhyRelaxjs.md +336 -0
  173. package/docs/elements/dom.md +102 -0
  174. package/docs/forms/creating-form-components.md +924 -0
  175. package/docs/forms/form-api.md +94 -0
  176. package/docs/forms/forms.md +99 -0
  177. package/docs/forms/patterns.md +311 -0
  178. package/docs/forms/reading-writing.md +365 -0
  179. package/docs/forms/validation.md +351 -0
  180. package/docs/html/TableRenderer.md +292 -0
  181. package/docs/html/html.md +175 -0
  182. package/docs/html/index.md +54 -0
  183. package/docs/html/template.md +422 -0
  184. package/docs/http/HttpClient.md +459 -0
  185. package/docs/http/ServerSentEvents.md +184 -0
  186. package/docs/http/index.md +109 -0
  187. package/docs/i18n/i18n.md +309 -0
  188. package/docs/i18n/intl-standard.md +178 -0
  189. package/docs/routing/RouteLink.md +98 -0
  190. package/docs/routing/Routing.md +332 -0
  191. package/docs/routing/RoutingTarget.md +136 -0
  192. package/docs/routing/layouts.md +207 -0
  193. package/docs/utilities.md +143 -0
  194. package/package.json +93 -0
@@ -0,0 +1,237 @@
1
+ # Dependency Injection
2
+
3
+ A lightweight IoC container for managing service dependencies in your application.
4
+
5
+ ## Overview
6
+
7
+ The DI system provides:
8
+ - Constructor injection
9
+ - Property injection
10
+ - Singleton and scoped lifetimes
11
+ - Decorator-based registration
12
+
13
+ ## Quick Start
14
+
15
+ ```typescript
16
+ import { ContainerService, container } from 'relaxjs/di';
17
+
18
+ @ContainerService()
19
+ class LoggerService {
20
+ log(message: string) {
21
+ console.log(`[LOG] ${message}`);
22
+ }
23
+ }
24
+
25
+ // Resolve and use
26
+ const logger = container.resolve(LoggerService);
27
+ logger.log('Hello!');
28
+ ```
29
+
30
+ ## Registration
31
+
32
+ Use the `@ContainerService` decorator to register services:
33
+
34
+ ```typescript
35
+ @ContainerService()
36
+ class ConfigService {
37
+ apiUrl = '/api/v1';
38
+ }
39
+
40
+ @ContainerService({ inject: [ConfigService] })
41
+ class ApiClient {
42
+ constructor(private config: ConfigService) {}
43
+
44
+ fetch(path: string) {
45
+ return fetch(this.config.apiUrl + path);
46
+ }
47
+ }
48
+ ```
49
+
50
+ ### Manual Registration
51
+
52
+ For cases where you can't use decorators (e.g. registering an existing instance):
53
+
54
+ ```typescript
55
+ import { serviceCollection } from 'relaxjs/di';
56
+
57
+ // Register with existing instance
58
+ const config = { apiUrl: '/api' };
59
+ serviceCollection.register(ConfigService, {
60
+ inject: [],
61
+ instance: config
62
+ });
63
+
64
+ // Register with custom key
65
+ serviceCollection.register(CacheService, {
66
+ key: 'primaryCache',
67
+ scope: 'global',
68
+ inject: []
69
+ });
70
+ ```
71
+
72
+ ## Scopes
73
+
74
+ ### Global (Singleton)
75
+
76
+ Same instance returned every time:
77
+
78
+ ```typescript
79
+ @ContainerService({ scope: 'global' })
80
+ class DatabaseConnection {
81
+ // ...
82
+ }
83
+
84
+ const db1 = container.resolve(DatabaseConnection);
85
+ const db2 = container.resolve(DatabaseConnection);
86
+ // db1 === db2
87
+ ```
88
+
89
+ ### Closest (Scoped)
90
+
91
+ New instance for each container scope:
92
+
93
+ ```typescript
94
+ @ContainerService({ scope: 'closest' })
95
+ class RequestContext {
96
+ // ...
97
+ }
98
+ ```
99
+
100
+ ## Constructor Injection
101
+
102
+ Dependencies are passed to the constructor in order:
103
+
104
+ ```typescript
105
+ @ContainerService({
106
+ inject: [LoggerService, ConfigService, DatabaseConnection]
107
+ })
108
+ class UserRepository {
109
+ constructor(
110
+ private logger: LoggerService,
111
+ private config: ConfigService,
112
+ private db: DatabaseConnection
113
+ ) {}
114
+ }
115
+ ```
116
+
117
+ ## Property Injection
118
+
119
+ ### Using the `@Inject` Decorator
120
+
121
+ The `@Inject` decorator resolves a dependency and assigns it to a class field:
122
+
123
+ ```typescript
124
+ import { ContainerService, Inject } from 'relaxjs/di';
125
+
126
+ @ContainerService({ inject: [] })
127
+ class OrderService {
128
+ @Inject(LoggerService)
129
+ private logger!: LoggerService;
130
+
131
+ @Inject('primaryCache') // resolve by string key
132
+ private cache!: CacheService;
133
+ }
134
+ ```
135
+
136
+ `@Inject` resolves the dependency immediately when the class is instantiated, so the field is available in all methods (including the constructor body).
137
+
138
+ ### Using the `properties` Option
139
+
140
+ Alternatively, declare property injection in the registration options:
141
+
142
+ ```typescript
143
+ @ContainerService({
144
+ inject: [],
145
+ properties: {
146
+ logger: LoggerService,
147
+ cache: 'primaryCache' // string key
148
+ }
149
+ })
150
+ class OrderService {
151
+ logger!: LoggerService;
152
+ cache!: CacheService;
153
+ }
154
+ ```
155
+
156
+ ## Integration with Web Components
157
+
158
+ ```typescript
159
+ @ContainerService({ inject: [UserService] })
160
+ class UserProfileComponent extends HTMLElement {
161
+ constructor(private userService: UserService) {
162
+ super();
163
+ }
164
+
165
+ connectedCallback() {
166
+ this.loadProfile();
167
+ }
168
+
169
+ async loadProfile() {
170
+ const user = await this.userService.getCurrentUser();
171
+ this.render(user);
172
+ }
173
+ }
174
+
175
+ customElements.define('user-profile', UserProfileComponent);
176
+ ```
177
+
178
+ ## Resolving Dependencies
179
+
180
+ ```typescript
181
+ // By class
182
+ const service = container.resolve(MyService);
183
+
184
+ // By string key
185
+ const cache = container.resolve<CacheService>('primaryCache');
186
+ ```
187
+
188
+ ## Error Handling
189
+
190
+ All DI errors go through the global [`onError`](Errors.md) handler, so you can log, display, or suppress them.
191
+
192
+ ### Resolution Errors
193
+
194
+ Thrown when `resolve()` cannot find a registered service.
195
+
196
+ | Field | Description |
197
+ |-------|-------------|
198
+ | `service` | The class name or string key that was requested |
199
+ | `registeredTypes` | Array of all registered class names |
200
+ | `registeredKeys` | Array of all registered string keys |
201
+
202
+ ```
203
+ RelaxError: Failed to resolve service 'MyService'
204
+ { service: 'MyService', registeredTypes: ['LoggerService', 'ConfigService'], registeredKeys: ['primaryCache'] }
205
+ ```
206
+
207
+ ### Registration Errors
208
+
209
+ Thrown during `register()` or `registerByType()` to catch configuration mistakes early.
210
+
211
+ | Error | Context | Cause |
212
+ |-------|---------|-------|
213
+ | Service name collision | `{ service }` | Two different classes registered with the same class name |
214
+ | Service key already registered | `{ key, existingClass, newClass }` | A string key is reused for a different class |
215
+ | Instance and inject conflict | `{ service }` | Both `instance` and non-empty `inject` provided (`inject` is ignored) |
216
+
217
+ ### Suppressing Errors
218
+
219
+ ```typescript
220
+ import { onError } from 'relaxjs/utils';
221
+
222
+ onError((error, ctx) => {
223
+ if (error.context.service === 'OptionalPlugin') {
224
+ ctx.suppress();
225
+ return;
226
+ }
227
+ console.error(error.message, error.context);
228
+ });
229
+ ```
230
+
231
+ ## Best Practices
232
+
233
+ 1. **Register early**: Register all services at application startup
234
+ 2. **Prefer constructor injection**: More explicit than property injection
235
+ 3. **Use global scope for stateless services**: Like loggers, config
236
+ 4. **Use closest scope for request-specific data**: Like user context
237
+ 5. **Avoid circular dependencies**: Restructure to use events or mediators
package/docs/Errors.md ADDED
@@ -0,0 +1,87 @@
1
+ # Error Handling
2
+
3
+ Global error handling for Relaxjs. Intercept errors before they throw, inspect structured context, and decide whether to suppress them.
4
+
5
+ ## Overview
6
+
7
+ By default, Relaxjs throws a `RelaxError` when something goes wrong internally. Register a handler with `onError()` to intercept these errors. The handler receives an `ErrorContext` that lets you control whether the error is thrown.
8
+
9
+ ## Quick Start
10
+
11
+ ```typescript
12
+ import { onError } from 'relaxjs/utils';
13
+
14
+ onError((error, ctx) => {
15
+ console.log(error.message);
16
+ console.log(error.context);
17
+ });
18
+ ```
19
+
20
+ Errors still throw after the handler runs. Call `ctx.suppress()` to prevent that:
21
+
22
+ ```typescript
23
+ onError((error, ctx) => {
24
+ logToService(error.message, error.context);
25
+ showToast(error.message);
26
+ ctx.suppress();
27
+ });
28
+ ```
29
+
30
+ ## RelaxError
31
+
32
+ Extends `Error` with a `context: Record<string, unknown>` field. The context contains specific information from the error source. For example, a routing error includes the route name, component tag, and route data. See each module's documentation for the context fields it provides.
33
+
34
+ ## ErrorContext
35
+
36
+ The second argument passed to the handler. Call `suppress()` to prevent the error from being thrown.
37
+
38
+ ```typescript
39
+ onError((error, ctx) => {
40
+ if (error.context.route === 'optional-sidebar') {
41
+ ctx.suppress();
42
+ return;
43
+ }
44
+
45
+ showErrorDialog(error.message);
46
+ });
47
+ ```
48
+
49
+ If the handler does not call `suppress()`, the `RelaxError` is thrown after the handler returns.
50
+
51
+ ## Using reportError
52
+
53
+ Application code can route errors through the same handler using `reportError()`. It returns the `RelaxError` to throw, or `null` if the handler suppressed it:
54
+
55
+ ```typescript
56
+ import { reportError } from 'relaxjs/utils';
57
+
58
+ const error = reportError('Payment processing failed', {
59
+ orderId: 123,
60
+ provider: 'stripe',
61
+ statusCode: 500,
62
+ });
63
+ if (error) throw error;
64
+ ```
65
+
66
+ ## API Reference
67
+
68
+ ### Functions
69
+
70
+ | Function | Description |
71
+ |----------|-------------|
72
+ | `onError(handler)` | Register a global error handler. Replaces any previous handler. |
73
+ | `reportError(message, context)` | Create and report a `RelaxError`. Returns the error to throw, or `null` if suppressed. |
74
+
75
+ ### Types
76
+
77
+ ```typescript
78
+ interface ErrorContext {
79
+ suppress(): void;
80
+ }
81
+
82
+ class RelaxError extends Error {
83
+ context: Record<string, unknown>;
84
+ }
85
+
86
+ type ErrorHandler = (error: RelaxError, ctx: ErrorContext) => void;
87
+ ```
@@ -0,0 +1,231 @@
1
+ Getting Started
2
+ ===============
3
+
4
+ Relaxjs is designed for gradual adoption. Start with full control using vanilla web components, then progressively add features like templating and routing as your app grows.
5
+
6
+ ## Installation
7
+
8
+ ```
9
+ npm i -S relaxjs
10
+ ```
11
+
12
+ ---
13
+
14
+ ## Level 1: Plain Web Components
15
+
16
+ Start with standard custom elements. No Relaxjs features are needed, just your components in HTML.
17
+
18
+ ```html
19
+ <!-- index.html -->
20
+ <body>
21
+ <my-header></my-header>
22
+ <my-content></my-content>
23
+ </body>
24
+ ```
25
+
26
+ ```ts
27
+ // my-header.ts
28
+ export class MyHeader extends HTMLElement {
29
+ connectedCallback() {
30
+ this.innerHTML = `<h1>My App</h1>`;
31
+ }
32
+ }
33
+ customElements.define('my-header', MyHeader);
34
+ ```
35
+
36
+ You're in complete control. Relaxjs components like `<r-input>`, `<r-button>`, etc. work alongside your own.
37
+
38
+ ---
39
+
40
+ ## Level 2: Add Templating with `html()`
41
+
42
+ When your components get complex, use `html()` for reactive templates and data binding.
43
+
44
+ ```ts
45
+ import { html } from 'relaxjs/html';
46
+
47
+ export class UserCard extends HTMLElement {
48
+ private user = { name: 'Alice', role: 'Admin' };
49
+
50
+ connectedCallback() {
51
+ const result = html`
52
+ <div class="card">
53
+ <h2>{{name}}</h2>
54
+ <span>{{role}}</span>
55
+ <button onclick=${() => this.edit()}>Edit</button>
56
+ </div>
57
+ `(this.user);
58
+ this.appendChild(result.fragment);
59
+ }
60
+
61
+ private edit() {
62
+ // Handle edit
63
+ }
64
+ }
65
+ customElements.define('user-card', UserCard);
66
+ ```
67
+
68
+ Still no routing. You control navigation yourself.
69
+
70
+ ---
71
+
72
+ ## Level 3: Manual Navigation
73
+
74
+ Swap content programmatically while staying in full control.
75
+
76
+ ```ts
77
+ import { html } from 'relaxjs/html';
78
+
79
+ export class AppShell extends HTMLElement {
80
+ private main!: HTMLElement;
81
+
82
+ connectedCallback() {
83
+ const result = html`
84
+ <nav>
85
+ <a onclick=${() => this.showMain()}>Home</a>
86
+ <a onclick=${() => this.showSettings()}>Settings</a>
87
+ </nav>
88
+ <main></main>
89
+ `({});
90
+ this.appendChild(result.fragment);
91
+ this.main = this.querySelector('main')!;
92
+ this.main.innerHTML = '<home-page></home-page>';
93
+ }
94
+
95
+ private showMain() {
96
+ this.main.innerHTML = '<home-page></home-page>';
97
+ }
98
+ private showSettings() {
99
+ this.main.innerHTML = '<settings-page></settings-page>';
100
+ }
101
+ }
102
+ customElements.define('app-shell', AppShell);
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Level 4: Simple Routing
108
+
109
+ When your app has multiple pages and you want URL-based navigation, add routing.
110
+
111
+ ```ts
112
+ import { Route, defineRoutes, startRouting } from 'relaxjs/routing';
113
+
114
+ const routes: Route[] = [
115
+ { name: 'home', path: '/', componentTagName: 'home-page' },
116
+ { name: 'settings', path: '/settings', componentTagName: 'settings-page' },
117
+ { name: 'profile', path: '/profile/:id', componentTagName: 'profile-page' },
118
+ ];
119
+
120
+ defineRoutes(routes);
121
+ startRouting();
122
+ ```
123
+
124
+ ```html
125
+ <!-- index.html -->
126
+ <body>
127
+ <nav>
128
+ <r-link name="home">Home</r-link>
129
+ <r-link name="settings">Settings</r-link>
130
+ <r-link name="profile" param-id="123">Profile</r-link>
131
+ </nav>
132
+ <r-route-target></r-route-target>
133
+ </body>
134
+ ```
135
+
136
+ The `<r-link>` component handles navigation by route name. Use `param-*` attributes for route parameters. The `<r-route-target>` renders the matched component.
137
+
138
+ ---
139
+
140
+ ## Level 5: Programmatic Navigation
141
+
142
+ Use `navigate()` for code-driven navigation.
143
+
144
+ ```ts
145
+ import { navigate } from 'relaxjs/routing';
146
+
147
+ // Navigate by route name with params
148
+ navigate('profile', { params: { id: '123' } });
149
+
150
+ // Navigate by path
151
+ navigate('/settings');
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Level 6: Route Guards
157
+
158
+ Protect routes with guards when you need authentication or authorization.
159
+
160
+ ```ts
161
+ // guards.ts
162
+ import { RouteGuard, RouteMatchResult, GuardResult, navigate } from 'relaxjs/routing';
163
+
164
+ export class AuthGuard implements RouteGuard {
165
+ check(route: RouteMatchResult): GuardResult {
166
+ const token = localStorage.getItem('token');
167
+ if (!token) {
168
+ navigate('login');
169
+ return GuardResult.Stop;
170
+ }
171
+ return GuardResult.Continue;
172
+ }
173
+ }
174
+ ```
175
+
176
+ ```ts
177
+ // app.ts
178
+ import { Route, defineRoutes } from 'relaxjs/routing';
179
+ import { AuthGuard } from './guards';
180
+
181
+ const authGuard = new AuthGuard();
182
+
183
+ const routes: Route[] = [
184
+ { name: 'login', path: '/login', componentTagName: 'login-page' },
185
+ { name: 'home', path: '/', componentTagName: 'home-page', guards: [authGuard] },
186
+ { name: 'admin', path: '/admin', componentTagName: 'admin-page', guards: [authGuard] },
187
+ ];
188
+
189
+ defineRoutes(routes);
190
+ ```
191
+
192
+ ---
193
+
194
+ ## Level 7: Layouts
195
+
196
+ Use different HTML pages for distinct UI structures. Each layout is a separate static HTML file.
197
+
198
+ ```
199
+ /index.html <- default layout (authenticated app)
200
+ /public.html <- public layout (login, register)
201
+ ```
202
+
203
+ ```ts
204
+ const routes: Route[] = [
205
+ // Routes using public.html
206
+ { name: 'login', path: '/login', componentTagName: 'login-page', layout: 'public' },
207
+ { name: 'register', path: '/register', componentTagName: 'register-page', layout: 'public' },
208
+
209
+ // Routes using index.html (default)
210
+ { name: 'dashboard', path: '/', componentTagName: 'dashboard-page' },
211
+ { name: 'settings', path: '/settings', componentTagName: 'settings-page' },
212
+ ];
213
+ ```
214
+
215
+ When navigating between layouts, the router redirects to the appropriate HTML file automatically.
216
+
217
+ ---
218
+
219
+ ## Summary
220
+
221
+ | Level | Features | Use When |
222
+ |-------|----------|----------|
223
+ | 1 | Plain components | Starting out, simple pages |
224
+ | 2 | `html()` templating | Complex component rendering |
225
+ | 3 | Manual navigation | Few views, full control needed |
226
+ | 4 | Basic routing | Multiple pages, URL sync needed |
227
+ | 5 | `navigate()` | Code-driven navigation |
228
+ | 6 | Guards | Auth/access control |
229
+ | 7 | Layouts | Shared UI structure |
230
+
231
+ Start at Level 1 and add features as your app requires them. You're always in control.