@relax.js/core 1.0.2 → 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/collections/Index.d.ts +2 -0
- package/dist/collections/index.js +1 -1
- package/dist/collections/index.js.map +4 -4
- package/dist/collections/index.mjs +1 -1
- package/dist/collections/index.mjs.map +4 -4
- 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.d.ts +0 -2
- package/dist/index.js +3 -3
- package/dist/index.js.map +4 -4
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +4 -4
- 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/Architecture.md
CHANGED
|
@@ -1,333 +1,333 @@
|
|
|
1
|
-
# Architecture Overview
|
|
2
|
-
|
|
3
|
-
Relaxjs is a library for building web applications with Web Components. It emphasizes explicit control, direct DOM manipulation, and predictable behavior.
|
|
4
|
-
|
|
5
|
-
## Philosophy
|
|
6
|
-
|
|
7
|
-
Unlike modern SPA frameworks, Relaxjs takes an explicit approach:
|
|
8
|
-
|
|
9
|
-
- **No virtual DOM**: The real DOM is the source of truth
|
|
10
|
-
- **No hidden reactivity**: Changes happen when you explicitly make them
|
|
11
|
-
- **No synthetic lifecycle**: Uses native Web Component lifecycle hooks
|
|
12
|
-
- **Full developer control**: You always know what triggered what
|
|
13
|
-
|
|
14
|
-
## Core Modules
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
├── Routing # SPA navigation with guards and layouts
|
|
19
|
-
├── Forms # Data binding, validation, type conversion
|
|
20
|
-
├── DI # Dependency injection container
|
|
21
|
-
├── HTTP # Fetch wrapper and WebSocket client
|
|
22
|
-
├── i18n # Internationalization with ICU support
|
|
23
|
-
├── Templates # HTML templating with data binding
|
|
24
|
-
└── Components # Pre-built UI components
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Module Integration
|
|
28
|
-
|
|
29
|
-
### Application Startup
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
import { defineRoutes, startRouting } from '
|
|
33
|
-
import { setLocale } from '
|
|
34
|
-
import { serviceCollection } from '
|
|
35
|
-
|
|
36
|
-
class Application extends HTMLElement {
|
|
37
|
-
async connectedCallback() {
|
|
38
|
-
// 1. Register services
|
|
39
|
-
serviceCollection.registerByType(ApiService, { inject: [] });
|
|
40
|
-
serviceCollection.registerByType(AuthService, { inject: [ApiService] });
|
|
41
|
-
|
|
42
|
-
// 2. Set locale
|
|
43
|
-
await setLocale(navigator.language);
|
|
44
|
-
|
|
45
|
-
// 3. Define routes
|
|
46
|
-
defineRoutes([
|
|
47
|
-
{ name: 'home', path: '/', componentTagName: 'app-home' },
|
|
48
|
-
{ name: 'login', path: '/login', componentTagName: 'login-page' }
|
|
49
|
-
]);
|
|
50
|
-
|
|
51
|
-
// 4. Start routing
|
|
52
|
-
startRouting();
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
customElements.define('app-main', Application);
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Component Pattern
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
class UserProfile extends HTMLElement {
|
|
63
|
-
private form: HTMLFormElement;
|
|
64
|
-
private validator: FormValidator;
|
|
65
|
-
|
|
66
|
-
connectedCallback() {
|
|
67
|
-
this.innerHTML = `
|
|
68
|
-
<form>
|
|
69
|
-
<input name="name" required>
|
|
70
|
-
<input name="email" type="email" required>
|
|
71
|
-
<button type="submit">Save</button>
|
|
72
|
-
</form>
|
|
73
|
-
`;
|
|
74
|
-
|
|
75
|
-
this.form = this.querySelector('form')!;
|
|
76
|
-
this.validator = new FormValidator(this.form, {
|
|
77
|
-
submitCallback: () => this.save()
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
this.loadUser();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async loadUser() {
|
|
84
|
-
const api = container.resolve(ApiService);
|
|
85
|
-
const user = await api.get('/user/profile');
|
|
86
|
-
setFormData(this.form, user.as<User>());
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async save() {
|
|
90
|
-
const data = readData(this.form);
|
|
91
|
-
const api = container.resolve(ApiService);
|
|
92
|
-
await api.put('/user/profile', JSON.stringify(data));
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
customElements.define('user-profile', UserProfile);
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Data Flow
|
|
100
|
-
|
|
101
|
-
```
|
|
102
|
-
┌─────────────────────────────────────────────────────────┐
|
|
103
|
-
│ User Action │
|
|
104
|
-
└─────────────────────────────────────────────────────────┘
|
|
105
|
-
│
|
|
106
|
-
▼
|
|
107
|
-
┌─────────────────────────────────────────────────────────┐
|
|
108
|
-
│ Event Handler │
|
|
109
|
-
│ │
|
|
110
|
-
│ element.addEventListener('click', (e) => { │
|
|
111
|
-
│ // Explicit action │
|
|
112
|
-
│ }); │
|
|
113
|
-
└─────────────────────────────────────────────────────────┘
|
|
114
|
-
│
|
|
115
|
-
▼
|
|
116
|
-
┌─────────────────────────────────────────────────────────┐
|
|
117
|
-
│ DOM Update │
|
|
118
|
-
│ │
|
|
119
|
-
│ // Direct manipulation │
|
|
120
|
-
│ element.textContent = newValue; │
|
|
121
|
-
│ element.setAttribute('data-state', 'active'); │
|
|
122
|
-
└─────────────────────────────────────────────────────────┘
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## Routing Architecture
|
|
126
|
-
|
|
127
|
-
```
|
|
128
|
-
┌──────────────┐ navigate() ┌──────────────────┐
|
|
129
|
-
│ User │ ────────────────► │ Router │
|
|
130
|
-
│ Action │ │ │
|
|
131
|
-
└──────────────┘ │ - Match route │
|
|
132
|
-
│ - Check guards │
|
|
133
|
-
│ - Update URL │
|
|
134
|
-
└────────┬─────────┘
|
|
135
|
-
│
|
|
136
|
-
NavigateRouteEvent
|
|
137
|
-
│
|
|
138
|
-
▼
|
|
139
|
-
┌──────────────────┐
|
|
140
|
-
│ r-route-target│
|
|
141
|
-
│ │
|
|
142
|
-
│ - Load component│
|
|
143
|
-
│ - Pass params │
|
|
144
|
-
└──────────────────┘
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### Multiple Targets
|
|
148
|
-
|
|
149
|
-
```html
|
|
150
|
-
<body>
|
|
151
|
-
<r-route-target>
|
|
152
|
-
<!-- Main content renders here -->
|
|
153
|
-
</r-route-target>
|
|
154
|
-
|
|
155
|
-
<r-route-target name="modal">
|
|
156
|
-
<!-- Modal content renders here -->
|
|
157
|
-
</r-route-target>
|
|
158
|
-
|
|
159
|
-
<r-route-target name="sidebar">
|
|
160
|
-
<!-- Sidebar content renders here -->
|
|
161
|
-
</r-route-target>
|
|
162
|
-
</body>
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
// Navigate to specific target
|
|
167
|
-
navigate('userDetails', { params: { id: '123' }, target: 'modal' });
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
## Form Data Flow
|
|
171
|
-
|
|
172
|
-
```
|
|
173
|
-
┌─────────────┐ setFormData() ┌─────────────────────────────────────┐
|
|
174
|
-
│ Data │ ──────────────────► │ Form Elements & Custom Components │
|
|
175
|
-
│ Object │ │ │
|
|
176
|
-
└─────────────┘ │ <input> <select> <textarea> │
|
|
177
|
-
│ <r-input> <r-checkbox> (FORM API) │
|
|
178
|
-
▲ └────────┬────────────────────────────┘
|
|
179
|
-
│ │
|
|
180
|
-
│ readData() User edits
|
|
181
|
-
│ │
|
|
182
|
-
│ ▼
|
|
183
|
-
┌─────┴───────┐ FormValidator ┌─────────────────┐
|
|
184
|
-
│ Updated │ ◄─────────────────── │ Validation │
|
|
185
|
-
│ Object │ │ │
|
|
186
|
-
└─────────────┘ │ - HTML5 │
|
|
187
|
-
│ - Custom rules │
|
|
188
|
-
│ - FORM API │
|
|
189
|
-
│ components │
|
|
190
|
-
└─────────────────┘
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
### Form Component Support
|
|
194
|
-
|
|
195
|
-
The form utilities support both native HTML form elements and modern form-associated custom elements (those implementing the FORM API with `ElementInternals`). This means:
|
|
196
|
-
|
|
197
|
-
- **Native elements**: `<input>`, `<select>`, `<textarea>` work as expected
|
|
198
|
-
- **Custom components**: Web components like `<r-input>`, `<r-checkbox>`, `<r-select>` that use `ElementInternals` and `formAssociated = true` are fully integrated
|
|
199
|
-
- **No API differences**: Both types are handled identically by `setFormData()`, `readData()`, `mapFormToClass()`, and `FormValidator`
|
|
200
|
-
|
|
201
|
-
## Template Rendering
|
|
202
|
-
|
|
203
|
-
### Static Templates (html)
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
const template = html`
|
|
207
|
-
<div class="card">
|
|
208
|
-
<h2>${'title'}</h2>
|
|
209
|
-
<p>${'description'}</p>
|
|
210
|
-
</div>
|
|
211
|
-
`;
|
|
212
|
-
|
|
213
|
-
// Creates new DOM each time
|
|
214
|
-
const fragment = template({ title: 'Hello', description: 'World' });
|
|
215
|
-
container.appendChild(fragment);
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### Updateable Templates (html)
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
const template = html`
|
|
222
|
-
<div class="user">
|
|
223
|
-
<span>{{name}}</span>
|
|
224
|
-
<span>{{score|number}}</span>
|
|
225
|
-
</div>
|
|
226
|
-
`;
|
|
227
|
-
|
|
228
|
-
const rendered = template({ name: 'John', score: 42 });
|
|
229
|
-
container.appendChild(rendered.fragment);
|
|
230
|
-
|
|
231
|
-
// Later, update without recreating DOM
|
|
232
|
-
rendered.update({ name: 'Jane', score: 100 });
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
## Service Architecture
|
|
236
|
-
|
|
237
|
-
```
|
|
238
|
-
┌────────────────────────────────────────────────────────┐
|
|
239
|
-
│ ServiceCollection │
|
|
240
|
-
│ │
|
|
241
|
-
│ Register services with metadata: │
|
|
242
|
-
│ - Constructor dependencies │
|
|
243
|
-
│ - Property injections │
|
|
244
|
-
│ - Scope (global/closest) │
|
|
245
|
-
└───────────────────────────┬────────────────────────────┘
|
|
246
|
-
│
|
|
247
|
-
│ Configuration
|
|
248
|
-
▼
|
|
249
|
-
┌────────────────────────────────────────────────────────┐
|
|
250
|
-
│ ServiceContainer │
|
|
251
|
-
│ │
|
|
252
|
-
│ Resolve services: │
|
|
253
|
-
│ 1. Check cache (for singletons) │
|
|
254
|
-
│ 2. Resolve dependencies │
|
|
255
|
-
│ 3. Create instance │
|
|
256
|
-
│ 4. Inject properties │
|
|
257
|
-
│ 5. Cache if global scope │
|
|
258
|
-
└────────────────────────────────────────────────────────┘
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
## File Organization
|
|
262
|
-
|
|
263
|
-
Recommended project structure:
|
|
264
|
-
|
|
265
|
-
```
|
|
266
|
-
src/
|
|
267
|
-
├── components/
|
|
268
|
-
│ ├── forms/ # Form-related components
|
|
269
|
-
│ ├── lists/ # List/table components
|
|
270
|
-
│ └── shared/ # Shared UI components
|
|
271
|
-
├── pages/ # Route components
|
|
272
|
-
├── services/ # Business logic services
|
|
273
|
-
├── guards/ # Route guards
|
|
274
|
-
├── models/ # TypeScript interfaces/types
|
|
275
|
-
└── app.ts # Application entry point
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
## Best Practices
|
|
279
|
-
|
|
280
|
-
1. **Use native HTML when possible**: Don't create components for things HTML already does well
|
|
281
|
-
|
|
282
|
-
2. **Keep components small**: Each component should do one thing
|
|
283
|
-
|
|
284
|
-
3. **Form Components Use FORM API**: When building custom form components, use the HTML Form API with `ElementInternals` and `formAssociated = true`. All RelaxJS form utilities automatically support these components without any special handling:
|
|
285
|
-
|
|
286
|
-
```typescript
|
|
287
|
-
class CustomCheckbox extends HTMLElement {
|
|
288
|
-
static formAssociated = true;
|
|
289
|
-
|
|
290
|
-
private internals: ElementInternals;
|
|
291
|
-
|
|
292
|
-
constructor() {
|
|
293
|
-
super();
|
|
294
|
-
this.internals = this.attachInternals();
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
connectedCallback() {
|
|
298
|
-
const checkbox = document.createElement('input');
|
|
299
|
-
checkbox.type = 'checkbox';
|
|
300
|
-
this.appendChild(checkbox);
|
|
301
|
-
|
|
302
|
-
checkbox.addEventListener('change', () => {
|
|
303
|
-
this.internals.setFormValue(checkbox.checked ? 'on' : '');
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
get checked() { return (this.querySelector('input') as HTMLInputElement).checked; }
|
|
308
|
-
set checked(v) { (this.querySelector('input') as HTMLInputElement).checked = v; }
|
|
309
|
-
|
|
310
|
-
get value() { return this.getAttribute('value') || 'on'; }
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
customElements.define('r-checkbox', CustomCheckbox);
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
Once defined, use it seamlessly with form utilities:
|
|
317
|
-
|
|
318
|
-
```typescript
|
|
319
|
-
const form = document.querySelector('form');
|
|
320
|
-
const data = { termsAccepted: true };
|
|
321
|
-
setFormData(form, data); // Works with <r-checkbox name="termsAccepted"></r-checkbox>
|
|
322
|
-
|
|
323
|
-
const extracted = readData(form); // Extracts values from custom components
|
|
324
|
-
const validator = new FormValidator(form); // Validates custom components
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
3. **Explicit over implicit**: Prefer explicit method calls over magic bindings
|
|
328
|
-
|
|
329
|
-
4. **Type everything**: Use TypeScript interfaces for all data structures
|
|
330
|
-
|
|
331
|
-
5. **CSS variables for theming**: Use semantic variable names
|
|
332
|
-
|
|
333
|
-
6. **Form-associated components**: Use `ElementInternals` for custom form controls
|
|
1
|
+
# Architecture Overview
|
|
2
|
+
|
|
3
|
+
Relaxjs is a library for building web applications with Web Components. It emphasizes explicit control, direct DOM manipulation, and predictable behavior.
|
|
4
|
+
|
|
5
|
+
## Philosophy
|
|
6
|
+
|
|
7
|
+
Unlike modern SPA frameworks, Relaxjs takes an explicit approach:
|
|
8
|
+
|
|
9
|
+
- **No virtual DOM**: The real DOM is the source of truth
|
|
10
|
+
- **No hidden reactivity**: Changes happen when you explicitly make them
|
|
11
|
+
- **No synthetic lifecycle**: Uses native Web Component lifecycle hooks
|
|
12
|
+
- **Full developer control**: You always know what triggered what
|
|
13
|
+
|
|
14
|
+
## Core Modules
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
@relax.js/core
|
|
18
|
+
├── Routing # SPA navigation with guards and layouts
|
|
19
|
+
├── Forms # Data binding, validation, type conversion
|
|
20
|
+
├── DI # Dependency injection container
|
|
21
|
+
├── HTTP # Fetch wrapper and WebSocket client
|
|
22
|
+
├── i18n # Internationalization with ICU support
|
|
23
|
+
├── Templates # HTML templating with data binding
|
|
24
|
+
└── Components # Pre-built UI components
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Module Integration
|
|
28
|
+
|
|
29
|
+
### Application Startup
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { defineRoutes, startRouting } from '@relax.js/core/routing';
|
|
33
|
+
import { setLocale } from '@relax.js/core/i18n';
|
|
34
|
+
import { serviceCollection } from '@relax.js/core/di';
|
|
35
|
+
|
|
36
|
+
class Application extends HTMLElement {
|
|
37
|
+
async connectedCallback() {
|
|
38
|
+
// 1. Register services
|
|
39
|
+
serviceCollection.registerByType(ApiService, { inject: [] });
|
|
40
|
+
serviceCollection.registerByType(AuthService, { inject: [ApiService] });
|
|
41
|
+
|
|
42
|
+
// 2. Set locale
|
|
43
|
+
await setLocale(navigator.language);
|
|
44
|
+
|
|
45
|
+
// 3. Define routes
|
|
46
|
+
defineRoutes([
|
|
47
|
+
{ name: 'home', path: '/', componentTagName: 'app-home' },
|
|
48
|
+
{ name: 'login', path: '/login', componentTagName: 'login-page' }
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
// 4. Start routing
|
|
52
|
+
startRouting();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
customElements.define('app-main', Application);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Component Pattern
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
class UserProfile extends HTMLElement {
|
|
63
|
+
private form: HTMLFormElement;
|
|
64
|
+
private validator: FormValidator;
|
|
65
|
+
|
|
66
|
+
connectedCallback() {
|
|
67
|
+
this.innerHTML = `
|
|
68
|
+
<form>
|
|
69
|
+
<input name="name" required>
|
|
70
|
+
<input name="email" type="email" required>
|
|
71
|
+
<button type="submit">Save</button>
|
|
72
|
+
</form>
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
this.form = this.querySelector('form')!;
|
|
76
|
+
this.validator = new FormValidator(this.form, {
|
|
77
|
+
submitCallback: () => this.save()
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
this.loadUser();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async loadUser() {
|
|
84
|
+
const api = container.resolve(ApiService);
|
|
85
|
+
const user = await api.get('/user/profile');
|
|
86
|
+
setFormData(this.form, user.as<User>());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async save() {
|
|
90
|
+
const data = readData(this.form);
|
|
91
|
+
const api = container.resolve(ApiService);
|
|
92
|
+
await api.put('/user/profile', JSON.stringify(data));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
customElements.define('user-profile', UserProfile);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Data Flow
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
┌─────────────────────────────────────────────────────────┐
|
|
103
|
+
│ User Action │
|
|
104
|
+
└─────────────────────────────────────────────────────────┘
|
|
105
|
+
│
|
|
106
|
+
▼
|
|
107
|
+
┌─────────────────────────────────────────────────────────┐
|
|
108
|
+
│ Event Handler │
|
|
109
|
+
│ │
|
|
110
|
+
│ element.addEventListener('click', (e) => { │
|
|
111
|
+
│ // Explicit action │
|
|
112
|
+
│ }); │
|
|
113
|
+
└─────────────────────────────────────────────────────────┘
|
|
114
|
+
│
|
|
115
|
+
▼
|
|
116
|
+
┌─────────────────────────────────────────────────────────┐
|
|
117
|
+
│ DOM Update │
|
|
118
|
+
│ │
|
|
119
|
+
│ // Direct manipulation │
|
|
120
|
+
│ element.textContent = newValue; │
|
|
121
|
+
│ element.setAttribute('data-state', 'active'); │
|
|
122
|
+
└─────────────────────────────────────────────────────────┘
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Routing Architecture
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
┌──────────────┐ navigate() ┌──────────────────┐
|
|
129
|
+
│ User │ ────────────────► │ Router │
|
|
130
|
+
│ Action │ │ │
|
|
131
|
+
└──────────────┘ │ - Match route │
|
|
132
|
+
│ - Check guards │
|
|
133
|
+
│ - Update URL │
|
|
134
|
+
└────────┬─────────┘
|
|
135
|
+
│
|
|
136
|
+
NavigateRouteEvent
|
|
137
|
+
│
|
|
138
|
+
▼
|
|
139
|
+
┌──────────────────┐
|
|
140
|
+
│ r-route-target│
|
|
141
|
+
│ │
|
|
142
|
+
│ - Load component│
|
|
143
|
+
│ - Pass params │
|
|
144
|
+
└──────────────────┘
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Multiple Targets
|
|
148
|
+
|
|
149
|
+
```html
|
|
150
|
+
<body>
|
|
151
|
+
<r-route-target>
|
|
152
|
+
<!-- Main content renders here -->
|
|
153
|
+
</r-route-target>
|
|
154
|
+
|
|
155
|
+
<r-route-target name="modal">
|
|
156
|
+
<!-- Modal content renders here -->
|
|
157
|
+
</r-route-target>
|
|
158
|
+
|
|
159
|
+
<r-route-target name="sidebar">
|
|
160
|
+
<!-- Sidebar content renders here -->
|
|
161
|
+
</r-route-target>
|
|
162
|
+
</body>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// Navigate to specific target
|
|
167
|
+
navigate('userDetails', { params: { id: '123' }, target: 'modal' });
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Form Data Flow
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
┌─────────────┐ setFormData() ┌─────────────────────────────────────┐
|
|
174
|
+
│ Data │ ──────────────────► │ Form Elements & Custom Components │
|
|
175
|
+
│ Object │ │ │
|
|
176
|
+
└─────────────┘ │ <input> <select> <textarea> │
|
|
177
|
+
│ <r-input> <r-checkbox> (FORM API) │
|
|
178
|
+
▲ └────────┬────────────────────────────┘
|
|
179
|
+
│ │
|
|
180
|
+
│ readData() User edits
|
|
181
|
+
│ │
|
|
182
|
+
│ ▼
|
|
183
|
+
┌─────┴───────┐ FormValidator ┌─────────────────┐
|
|
184
|
+
│ Updated │ ◄─────────────────── │ Validation │
|
|
185
|
+
│ Object │ │ │
|
|
186
|
+
└─────────────┘ │ - HTML5 │
|
|
187
|
+
│ - Custom rules │
|
|
188
|
+
│ - FORM API │
|
|
189
|
+
│ components │
|
|
190
|
+
└─────────────────┘
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Form Component Support
|
|
194
|
+
|
|
195
|
+
The form utilities support both native HTML form elements and modern form-associated custom elements (those implementing the FORM API with `ElementInternals`). This means:
|
|
196
|
+
|
|
197
|
+
- **Native elements**: `<input>`, `<select>`, `<textarea>` work as expected
|
|
198
|
+
- **Custom components**: Web components like `<r-input>`, `<r-checkbox>`, `<r-select>` that use `ElementInternals` and `formAssociated = true` are fully integrated
|
|
199
|
+
- **No API differences**: Both types are handled identically by `setFormData()`, `readData()`, `mapFormToClass()`, and `FormValidator`
|
|
200
|
+
|
|
201
|
+
## Template Rendering
|
|
202
|
+
|
|
203
|
+
### Static Templates (html)
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
const template = html`
|
|
207
|
+
<div class="card">
|
|
208
|
+
<h2>${'title'}</h2>
|
|
209
|
+
<p>${'description'}</p>
|
|
210
|
+
</div>
|
|
211
|
+
`;
|
|
212
|
+
|
|
213
|
+
// Creates new DOM each time
|
|
214
|
+
const fragment = template({ title: 'Hello', description: 'World' });
|
|
215
|
+
container.appendChild(fragment);
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Updateable Templates (html)
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
const template = html`
|
|
222
|
+
<div class="user">
|
|
223
|
+
<span>{{name}}</span>
|
|
224
|
+
<span>{{score|number}}</span>
|
|
225
|
+
</div>
|
|
226
|
+
`;
|
|
227
|
+
|
|
228
|
+
const rendered = template({ name: 'John', score: 42 });
|
|
229
|
+
container.appendChild(rendered.fragment);
|
|
230
|
+
|
|
231
|
+
// Later, update without recreating DOM
|
|
232
|
+
rendered.update({ name: 'Jane', score: 100 });
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Service Architecture
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
┌────────────────────────────────────────────────────────┐
|
|
239
|
+
│ ServiceCollection │
|
|
240
|
+
│ │
|
|
241
|
+
│ Register services with metadata: │
|
|
242
|
+
│ - Constructor dependencies │
|
|
243
|
+
│ - Property injections │
|
|
244
|
+
│ - Scope (global/closest) │
|
|
245
|
+
└───────────────────────────┬────────────────────────────┘
|
|
246
|
+
│
|
|
247
|
+
│ Configuration
|
|
248
|
+
▼
|
|
249
|
+
┌────────────────────────────────────────────────────────┐
|
|
250
|
+
│ ServiceContainer │
|
|
251
|
+
│ │
|
|
252
|
+
│ Resolve services: │
|
|
253
|
+
│ 1. Check cache (for singletons) │
|
|
254
|
+
│ 2. Resolve dependencies │
|
|
255
|
+
│ 3. Create instance │
|
|
256
|
+
│ 4. Inject properties │
|
|
257
|
+
│ 5. Cache if global scope │
|
|
258
|
+
└────────────────────────────────────────────────────────┘
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## File Organization
|
|
262
|
+
|
|
263
|
+
Recommended project structure:
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
src/
|
|
267
|
+
├── components/
|
|
268
|
+
│ ├── forms/ # Form-related components
|
|
269
|
+
│ ├── lists/ # List/table components
|
|
270
|
+
│ └── shared/ # Shared UI components
|
|
271
|
+
├── pages/ # Route components
|
|
272
|
+
├── services/ # Business logic services
|
|
273
|
+
├── guards/ # Route guards
|
|
274
|
+
├── models/ # TypeScript interfaces/types
|
|
275
|
+
└── app.ts # Application entry point
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Best Practices
|
|
279
|
+
|
|
280
|
+
1. **Use native HTML when possible**: Don't create components for things HTML already does well
|
|
281
|
+
|
|
282
|
+
2. **Keep components small**: Each component should do one thing
|
|
283
|
+
|
|
284
|
+
3. **Form Components Use FORM API**: When building custom form components, use the HTML Form API with `ElementInternals` and `formAssociated = true`. All RelaxJS form utilities automatically support these components without any special handling:
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
class CustomCheckbox extends HTMLElement {
|
|
288
|
+
static formAssociated = true;
|
|
289
|
+
|
|
290
|
+
private internals: ElementInternals;
|
|
291
|
+
|
|
292
|
+
constructor() {
|
|
293
|
+
super();
|
|
294
|
+
this.internals = this.attachInternals();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
connectedCallback() {
|
|
298
|
+
const checkbox = document.createElement('input');
|
|
299
|
+
checkbox.type = 'checkbox';
|
|
300
|
+
this.appendChild(checkbox);
|
|
301
|
+
|
|
302
|
+
checkbox.addEventListener('change', () => {
|
|
303
|
+
this.internals.setFormValue(checkbox.checked ? 'on' : '');
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
get checked() { return (this.querySelector('input') as HTMLInputElement).checked; }
|
|
308
|
+
set checked(v) { (this.querySelector('input') as HTMLInputElement).checked = v; }
|
|
309
|
+
|
|
310
|
+
get value() { return this.getAttribute('value') || 'on'; }
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
customElements.define('r-checkbox', CustomCheckbox);
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Once defined, use it seamlessly with form utilities:
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
const form = document.querySelector('form');
|
|
320
|
+
const data = { termsAccepted: true };
|
|
321
|
+
setFormData(form, data); // Works with <r-checkbox name="termsAccepted"></r-checkbox>
|
|
322
|
+
|
|
323
|
+
const extracted = readData(form); // Extracts values from custom components
|
|
324
|
+
const validator = new FormValidator(form); // Validates custom components
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
3. **Explicit over implicit**: Prefer explicit method calls over magic bindings
|
|
328
|
+
|
|
329
|
+
4. **Type everything**: Use TypeScript interfaces for all data structures
|
|
330
|
+
|
|
331
|
+
5. **CSS variables for theming**: Use semantic variable names
|
|
332
|
+
|
|
333
|
+
6. **Form-associated components**: Use `ElementInternals` for custom form controls
|