@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
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# Why Relaxjs?
|
|
2
|
+
|
|
3
|
+
A deeper look at the differences between modern frameworks and the Relaxjs approach.
|
|
4
|
+
|
|
5
|
+
## Architecture Over Tooling
|
|
6
|
+
|
|
7
|
+
Reactive frameworks often market themselves as solutions to complex problems like cascading updates or high-frequency data. Relaxjs takes the stance that these are architectural challenges, not tooling deficiencies.
|
|
8
|
+
|
|
9
|
+
By choosing Relaxjs, you reject the idea that a library should magically fix "bad" data flow. Instead, you accept the responsibility of designing a clean architecture. Relaxjs offers less "safety net" for poor architecture in exchange for total control and performance.
|
|
10
|
+
|
|
11
|
+
If you believe that complexity should be managed by better design patterns, not by heavier JavaScript bundles, then Relaxjs is the right tool.
|
|
12
|
+
|
|
13
|
+
### The "Spreadsheet" Problem
|
|
14
|
+
|
|
15
|
+
**The framework pitch:** "If Cell C depends on B, and B depends on A, you need a reactive engine to automatically recalculate everything when A changes, or you'll lose sync."
|
|
16
|
+
|
|
17
|
+
**The reality:** Explicit communication is safer than implicit magic. Use standard Events or Signals. If Component A changes, it dispatches an event. Component B listens, updates, and dispatches its own event. You don't need a hidden dependency graph; you need a clear event taxonomy. By keeping updates explicit, you eliminate the "spaghetti code" of invisible side effects that plague large reactive apps.
|
|
18
|
+
|
|
19
|
+
See [Typed Events for Decoupling](#1-typed-events-for-decoupling) for implementation details.
|
|
20
|
+
|
|
21
|
+
### The "Thrashing" Problem
|
|
22
|
+
|
|
23
|
+
**The framework pitch:** "If your backend sends 50 updates per second (like a stock ticker), you need a Virtual DOM to batch them and prevent the UI from freezing."
|
|
24
|
+
|
|
25
|
+
**The reality:** Fix the leak, don't buy a bigger bucket. Sending 50 DOM updates per second is inefficient regardless of the framework.
|
|
26
|
+
|
|
27
|
+
Solve the root cause: throttle or debounce the data at the source (the socket or the API handler) to match the human perception limit (~60fps). If you feed the DOM efficient data, direct updates are faster than any Virtual DOM could ever be.
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
class StockTicker extends HTMLElement {
|
|
31
|
+
private socket: WebSocket;
|
|
32
|
+
private pending: Map<string, number> = new Map();
|
|
33
|
+
private frameId: number | null = null;
|
|
34
|
+
|
|
35
|
+
connectedCallback() {
|
|
36
|
+
this.socket = new WebSocket('/stocks');
|
|
37
|
+
this.socket.onmessage = (e) => {
|
|
38
|
+
const { symbol, price } = JSON.parse(e.data);
|
|
39
|
+
this.pending.set(symbol, price); // Coalesce updates
|
|
40
|
+
this.scheduleRender();
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private scheduleRender() {
|
|
45
|
+
if (this.frameId) return;
|
|
46
|
+
this.frameId = requestAnimationFrame(() => {
|
|
47
|
+
this.frameId = null;
|
|
48
|
+
for (const [symbol, price] of this.pending) {
|
|
49
|
+
this.updatePrice(symbol, price);
|
|
50
|
+
}
|
|
51
|
+
this.pending.clear();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### The "Prop Drilling" Problem
|
|
58
|
+
|
|
59
|
+
**The framework pitch:** "Passing data down through 10 layers of components is tedious. You need 'Context' or 'Global Stores' to teleport data to the bottom."
|
|
60
|
+
|
|
61
|
+
**The reality:** Deep nesting is a symptom of rigid composition. Instead of nesting logic inside logic, use Slots to compose your UI at the top level. Flatten the hierarchy. If `App` owns the data and `Avatar` needs it, put `Avatar` inside a slot of `Header`. This removes the need for intermediate components to act as "data mules."
|
|
62
|
+
|
|
63
|
+
See [Composition over Injection](#3-composition-over-injection) for implementation details.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Virtual DOM Diffing vs Direct DOM Manipulation
|
|
68
|
+
|
|
69
|
+
**Framework approach:** React, Vue, and others maintain a virtual representation of the DOM in memory. When state changes, they diff the virtual tree against the previous version and batch updates to the real DOM.
|
|
70
|
+
|
|
71
|
+
**Relaxjs approach:** You manipulate the DOM directly. When you call `element.textContent = 'new value'`, that's exactly what happens. There is no intermediate representation, no diffing algorithm, no batched updates.
|
|
72
|
+
|
|
73
|
+
**Won't this cause flickering?** No. Browsers already batch DOM updates within the same JavaScript task. Multiple DOM changes in synchronous code are rendered together in a single paint. The browser's [rendering pipeline](https://developer.mozilla.org/en-US/docs/Web/Performance/How_browsers_work#render) handles this automatically, and it's what virtual DOM implementations rely on underneath anyway.
|
|
74
|
+
|
|
75
|
+
**Why this matters:**
|
|
76
|
+
- Debugging is straightforward. Set a breakpoint and see exactly what changes
|
|
77
|
+
- No "stale closure" bugs from captured state in render functions
|
|
78
|
+
- Performance is predictable: one DOM operation = one DOM operation
|
|
79
|
+
- No framework-specific DevTools required to understand what's happening
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// What you write is what happens
|
|
83
|
+
// Browser batches these into a single repaint
|
|
84
|
+
this.nameSpan.textContent = user.name;
|
|
85
|
+
this.emailSpan.textContent = user.email;
|
|
86
|
+
this.avatar.src = user.avatarUrl;
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Reactive State Management vs Explicit Updates
|
|
90
|
+
|
|
91
|
+
**Framework approach:** Frameworks track dependencies automatically. Change a reactive variable and the framework figures out what needs to re-render. This requires proxies, signals, or compiler transforms to intercept property access.
|
|
92
|
+
|
|
93
|
+
**Relaxjs approach:** You decide when and what to update. There's no magic tracking. If you want the UI to change, you change it.
|
|
94
|
+
|
|
95
|
+
**Isn't that tedious?** Not with Relaxjs utilities. Functions like `setFormData()` populate entire forms from objects. `readData()` extracts typed data back. You get explicit control without the boilerplate.
|
|
96
|
+
|
|
97
|
+
**Why this matters:**
|
|
98
|
+
- No accidental re-renders from touching a tracked property
|
|
99
|
+
- No debugging sessions trying to understand "why did this re-render?"
|
|
100
|
+
- Memory usage is predictable, with no subscription graphs or dependency tracking overhead
|
|
101
|
+
- Works naturally with any data structure, including classes with methods
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// Explicit updates made easy with Relaxjs utilities
|
|
105
|
+
async function loadUser(id: string) {
|
|
106
|
+
const user = await fetch(`/api/users/${id}`).then(r => r.json());
|
|
107
|
+
setFormData(this.form, user); // One call updates the entire form
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function saveUser() {
|
|
111
|
+
const user = readData(this.form); // Typed object, ready for the backend
|
|
112
|
+
await fetch('/api/users', { method: 'POST', body: JSON.stringify(user) });
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Custom Template Syntax vs Standard HTML
|
|
117
|
+
|
|
118
|
+
**Framework approach:** JSX, Angular templates, Vue SFCs. Each framework has its own syntax that requires compilation. Editors need plugins for proper syntax highlighting and error checking.
|
|
119
|
+
|
|
120
|
+
**Relaxjs approach:** Standard HTML. Your templates are either HTML strings, `<template>` elements, or DOM APIs. No compilation step required for templates.
|
|
121
|
+
|
|
122
|
+
**Why this matters:**
|
|
123
|
+
- Any HTML you know works, with no learning curve for template syntax
|
|
124
|
+
- No build step required for template processing
|
|
125
|
+
- Server-rendered HTML works without hydration complexity
|
|
126
|
+
- Copy HTML from anywhere and it just works
|
|
127
|
+
|
|
128
|
+
```html
|
|
129
|
+
<!-- Standard HTML, no special syntax -->
|
|
130
|
+
<form>
|
|
131
|
+
<input name="email" type="email" required>
|
|
132
|
+
<button type="submit">Save</button>
|
|
133
|
+
</form>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Framework Lifecycle vs Native Web Component Lifecycle
|
|
137
|
+
|
|
138
|
+
**Framework approach:** Each framework defines its own lifecycle: `useEffect`, `onMounted`, `ngOnInit`, `componentDidMount`. These have framework-specific timing guarantees and cleanup patterns.
|
|
139
|
+
|
|
140
|
+
**Relaxjs approach:** Web Components have a [standard lifecycle](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks) defined by the browser: `connectedCallback`, `disconnectedCallback`, `attributeChangedCallback`. These are part of the web platform.
|
|
141
|
+
|
|
142
|
+
**Why this matters:**
|
|
143
|
+
- Lifecycle behavior is documented on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Web_components), not framework docs
|
|
144
|
+
- Skills transfer to any Web Component project
|
|
145
|
+
- No framework version upgrades changing lifecycle timing
|
|
146
|
+
- Browser DevTools understand the lifecycle natively
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
class MyComponent extends HTMLElement {
|
|
150
|
+
connectedCallback() {
|
|
151
|
+
// Called when added to DOM (standard browser behavior)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
disconnectedCallback() {
|
|
155
|
+
// Called when removed (clean up here)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Magic Re-renders vs Explicit Control
|
|
161
|
+
|
|
162
|
+
**Framework approach:** Change state and the framework decides what to re-render. This often involves reconciliation algorithms, shouldComponentUpdate checks, or fine-grained reactivity tracking.
|
|
163
|
+
|
|
164
|
+
**Relaxjs approach:** Nothing re-renders unless you explicitly update it. The DOM stays exactly as you left it until you change it.
|
|
165
|
+
|
|
166
|
+
**Why this matters:**
|
|
167
|
+
- No wasted renders because you only update what needs updating
|
|
168
|
+
- No memoization dance (`useMemo`, `useCallback`, `React.memo`)
|
|
169
|
+
- Third-party libraries can't trigger unexpected re-renders
|
|
170
|
+
- Animations and transitions work naturally without fighting the framework
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// Only updates what changed
|
|
174
|
+
if (user.name !== previousName) {
|
|
175
|
+
this.nameElement.textContent = user.name;
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Custom Rendering Pipeline vs Native Async/Await
|
|
180
|
+
|
|
181
|
+
**Framework approach:** Frameworks manage their own rendering schedules. React has concurrent mode and Suspense. Vue has the async component and `nextTick`. These interact in complex ways with async operations.
|
|
182
|
+
|
|
183
|
+
**Relaxjs approach:** JavaScript's native `async/await` works exactly as expected. Fetch data, await the result, update the DOM. No framework scheduler to consider.
|
|
184
|
+
|
|
185
|
+
**A note on async lifecycle methods:** Web Component lifecycle callbacks like `connectedCallback` are synchronous. Marking them `async` works, but the browser doesn't await the returned Promise. The element connects immediately, and your async code continues afterward. This is actually ideal for loading patterns:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
connectedCallback() {
|
|
189
|
+
this.showLoading(); // Runs synchronously when element connects
|
|
190
|
+
this.loadData(); // Kicks off async work
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private async loadData() {
|
|
194
|
+
try {
|
|
195
|
+
const data = await fetch('/api/data').then(r => r.json());
|
|
196
|
+
this.render(data);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
this.showError(error);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Why this matters:**
|
|
204
|
+
- `async/await` behaves identically to any other JavaScript code
|
|
205
|
+
- No special handling for loading states; just use a boolean
|
|
206
|
+
- No Suspense boundaries or fallback components required
|
|
207
|
+
- Error handling with standard `try/catch`
|
|
208
|
+
|
|
209
|
+
## "But What About Complex State?"
|
|
210
|
+
|
|
211
|
+
Reactive frameworks justify their complexity with scenarios like:
|
|
212
|
+
|
|
213
|
+
- Shopping cart displayed in header, sidebar, and checkout
|
|
214
|
+
- Form fields with cascading dependencies
|
|
215
|
+
- Real-time updates across many components
|
|
216
|
+
- Deeply nested components needing shared data
|
|
217
|
+
|
|
218
|
+
These aren't technology limitations. They're architectural problems. Follow three principles and they vanish:
|
|
219
|
+
|
|
220
|
+
### 1. Typed Events for Decoupling
|
|
221
|
+
|
|
222
|
+
**Framework solution:** Redux, Pinia, RxJS services. These are global stores that track subscriptions and trigger re-renders automatically.
|
|
223
|
+
|
|
224
|
+
**Web Component solution:** Event classes. Create a class extending `Event` with typed properties. Components dispatch events; interested components listen. Full intellisense, JSDoc support, and searchability.
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
/** Dispatched when the shopping cart changes. */
|
|
228
|
+
class CartUpdatedEvent extends Event {
|
|
229
|
+
static readonly type = 'cart-updated';
|
|
230
|
+
|
|
231
|
+
constructor(
|
|
232
|
+
public readonly items: CartItem[],
|
|
233
|
+
public readonly total: number
|
|
234
|
+
) {
|
|
235
|
+
super(CartUpdatedEvent.type, { bubbles: true });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Register in global event map for addEventListener type inference
|
|
240
|
+
declare global {
|
|
241
|
+
interface HTMLElementEventMap {
|
|
242
|
+
[CartUpdatedEvent.type]: CartUpdatedEvent;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Service dispatches typed event
|
|
247
|
+
class CartService {
|
|
248
|
+
add(item: CartItem) {
|
|
249
|
+
this.items.push(item);
|
|
250
|
+
document.dispatchEvent(new CartUpdatedEvent(this.items, this.calculateTotal()));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Components listen with full type safety
|
|
255
|
+
class HeaderCart extends HTMLElement {
|
|
256
|
+
connectedCallback() {
|
|
257
|
+
document.addEventListener(CartUpdatedEvent.type, this.onCartUpdated);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
disconnectedCallback() {
|
|
261
|
+
document.removeEventListener(CartUpdatedEvent.type, this.onCartUpdated);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private onCartUpdated = (e: CartUpdatedEvent) => {
|
|
265
|
+
// e.items and e.total are typed, so intellisense works
|
|
266
|
+
this.badge.textContent = e.items.length.toString();
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Components stay decoupled. Events are documented with JSDoc and fully typed. Search for `CartUpdatedEvent` to find all producers and consumers.
|
|
272
|
+
|
|
273
|
+
### 2. Sensible Data Flow
|
|
274
|
+
|
|
275
|
+
**Framework solution:** Computed properties, signals, `useMemo`. These create automatic dependency graphs that recalculate derived values.
|
|
276
|
+
|
|
277
|
+
**Web Component solution:** Question why this complexity exists on the client. Send computed totals from the server instead of computing everywhere. If you need client-side derivation, calculate it explicitly when the source changes.
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
setItems(items: OrderItem[]) {
|
|
281
|
+
this.items = items;
|
|
282
|
+
|
|
283
|
+
// Calculate derived values right here
|
|
284
|
+
const subtotal = items.reduce((sum, i) => sum + i.price * i.qty, 0);
|
|
285
|
+
const tax = subtotal * 0.1;
|
|
286
|
+
const total = subtotal + tax;
|
|
287
|
+
|
|
288
|
+
this.updateDisplay(subtotal, tax, total);
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Complex reactive graphs usually signal too much logic scattered across components. Simplify by centralizing: compute derived values in one service and dispatch events with the results. Components just display pre-computed data.
|
|
293
|
+
|
|
294
|
+
### 3. Composition over Injection
|
|
295
|
+
|
|
296
|
+
**Framework solution:** Context, provide/inject, hierarchical injectors. These are mechanisms to skip prop drilling through intermediate components.
|
|
297
|
+
|
|
298
|
+
**Web Component solution:** Slots. Compose the UI so the data owner places components directly, without intermediaries.
|
|
299
|
+
|
|
300
|
+
```html
|
|
301
|
+
<!-- Instead of drilling data: Layout → Sidebar → UserPanel → Avatar -->
|
|
302
|
+
|
|
303
|
+
<!-- Compose directly: page owns user, places avatar in layout slot -->
|
|
304
|
+
<app-layout>
|
|
305
|
+
<user-avatar slot="header"></user-avatar>
|
|
306
|
+
<nav-menu slot="sidebar"></nav-menu>
|
|
307
|
+
<page-content slot="body"></page-content>
|
|
308
|
+
</app-layout>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The layout doesn't know about users, it provides slots. The page knows about users and places the avatar directly. No drilling, no context providers.
|
|
312
|
+
|
|
313
|
+
### The Comparison
|
|
314
|
+
|
|
315
|
+
| Problem | Framework approach | Web Component approach |
|
|
316
|
+
|---------|-------------------|------------------------|
|
|
317
|
+
| Shared state | Redux, Pinia, stores | Typed DOM events |
|
|
318
|
+
| Derived values | Computed properties, signals | Calculate when source changes |
|
|
319
|
+
| Prop drilling | Context, provide/inject | Slots for composition |
|
|
320
|
+
| Form dependencies | Reactive forms, watchers | Explicit event handlers |
|
|
321
|
+
|
|
322
|
+
### The Bottom Line
|
|
323
|
+
|
|
324
|
+
The limitation isn't in Web Components. It's in whether developers architect solutions or reach for abstractions that hide complexity.
|
|
325
|
+
|
|
326
|
+
With these principles, your codebase is faster and easier to debug. Every update has a clear cause. Every event has a clear source. If you've spent hours debugging why something re-rendered (or didn't), Relaxjs offers a simpler path.
|
|
327
|
+
|
|
328
|
+
## Summary
|
|
329
|
+
|
|
330
|
+
| Aspect | Framework | Relaxjs |
|
|
331
|
+
|--------|-----------|---------|
|
|
332
|
+
| Learning curve | Framework-specific concepts | Web platform APIs |
|
|
333
|
+
| Debugging | Framework DevTools required | Browser DevTools sufficient |
|
|
334
|
+
| Bundle size | Framework runtime included | Just your code |
|
|
335
|
+
| Upgrades | Migration guides, breaking changes | Stable browser APIs |
|
|
336
|
+
| Skills | Framework-specific | Transferable to any project |
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Form Utilities
|
|
2
|
+
|
|
3
|
+
A TypeScript library for form validation, data mapping, and form manipulation with strong typing support.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The form utilities provide:
|
|
8
|
+
|
|
9
|
+
- **[Validation](validation.md)** - Form validation with HTML5 integration, error summaries, and custom validators
|
|
10
|
+
- **[Reading & Writing](reading-writing.md)** - Read and write form data with automatic type conversion
|
|
11
|
+
- **[Form API](form-api.md)** - How RelaxJS supports the Form-Associated Custom Elements standard
|
|
12
|
+
- **[Patterns](patterns.md)** - Common patterns for multi-step forms, file uploads, and state management
|
|
13
|
+
- **[Creating Form Components](creating-form-components.md)** - Guide for building custom form-associated components
|
|
14
|
+
|
|
15
|
+
## Form Components Support
|
|
16
|
+
|
|
17
|
+
All form utilities fully support **form-associated custom elements** (the HTML Form API). Custom web components created with `ElementInternals` and `formAssociated = true` integrate seamlessly with:
|
|
18
|
+
|
|
19
|
+
- `setFormData()` - Populates custom form components like standard HTML inputs
|
|
20
|
+
- `readData()` - Extracts values from custom form components
|
|
21
|
+
- `mapFormToClass()` - Maps custom form component values to class properties
|
|
22
|
+
- `FormValidator` - Validates custom form components with native and custom validation
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### Basic Form Validation
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
const form = document.querySelector('form');
|
|
30
|
+
const validator = new FormValidator(form, {
|
|
31
|
+
useSummary: true,
|
|
32
|
+
autoValidate: true
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Reading Form Data
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
const form = document.querySelector('form');
|
|
40
|
+
const data = readData(form);
|
|
41
|
+
// Returns typed object with automatic conversion
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Writing Form Data
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
const form = document.querySelector('form');
|
|
48
|
+
const userData = {
|
|
49
|
+
name: 'John',
|
|
50
|
+
email: 'john@example.com',
|
|
51
|
+
preferences: {
|
|
52
|
+
notifications: true
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
setFormData(form, userData);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Mapping to Class Instance
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
class UserDTO {
|
|
62
|
+
name: string = '';
|
|
63
|
+
email: string = '';
|
|
64
|
+
age: number = 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const form = document.querySelector('form');
|
|
68
|
+
const user = mapFormToClass(form, new UserDTO());
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Complete Example
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
class UserRegistration {
|
|
75
|
+
name: string = '';
|
|
76
|
+
email: string = '';
|
|
77
|
+
age: number = 0;
|
|
78
|
+
isSubscribed: boolean = false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const form = document.querySelector('#registration-form');
|
|
82
|
+
const user = new UserRegistration();
|
|
83
|
+
|
|
84
|
+
const validator = new FormValidator(form, {
|
|
85
|
+
useSummary: true,
|
|
86
|
+
customChecks: (form) => {
|
|
87
|
+
const password = form.querySelector('[name="password"]') as HTMLInputElement;
|
|
88
|
+
const confirm = form.querySelector('[name="confirmPassword"]') as HTMLInputElement;
|
|
89
|
+
|
|
90
|
+
if (password.value !== confirm.value) {
|
|
91
|
+
validator.addErrorToSummary('Password Confirmation', 'Passwords do not match');
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
submitCallback: () => {
|
|
95
|
+
mapFormToClass(form, user);
|
|
96
|
+
console.log('User data:', user);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
```
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# html Template Engine
|
|
2
|
+
|
|
3
|
+
A lightweight HTML template engine with update capabilities. Creates templates that can be re-rendered with new data without recreating DOM nodes.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { html } from '@relax.js/core/html';
|
|
9
|
+
|
|
10
|
+
// Create a template
|
|
11
|
+
const userCard = html`
|
|
12
|
+
<div class="user-card">
|
|
13
|
+
<h2>{{name}}</h2>
|
|
14
|
+
<p>{{email}}</p>
|
|
15
|
+
</div>
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
// Render with data
|
|
19
|
+
const result = userCard({ name: 'John', email: 'john@example.com' });
|
|
20
|
+
container.appendChild(result.fragment);
|
|
21
|
+
|
|
22
|
+
// Update with new data (no DOM recreation)
|
|
23
|
+
result.update({ name: 'Jane', email: 'jane@example.com' });
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
### Mustache Bindings
|
|
29
|
+
|
|
30
|
+
Use `{{property}}` syntax to bind context properties to text content or attributes:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
const template = html`
|
|
34
|
+
<a href="{{url}}" class="{{linkClass}}">{{linkText}}</a>
|
|
35
|
+
`;
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Template Literal Substitutions
|
|
39
|
+
|
|
40
|
+
Use `${}` for static values or functions:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
const staticClass = 'container';
|
|
44
|
+
const template = html`<div class="${staticClass}">{{content}}</div>`;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Event Handlers
|
|
48
|
+
|
|
49
|
+
Bind event handlers with context:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
const row = html`
|
|
53
|
+
<tr>
|
|
54
|
+
<td>{{name}}</td>
|
|
55
|
+
<td>
|
|
56
|
+
<button onclick=${function() { this.onEdit(this.id); }}>Edit</button>
|
|
57
|
+
</td>
|
|
58
|
+
</tr>
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
const result = row({
|
|
62
|
+
id: 42,
|
|
63
|
+
name: 'Item',
|
|
64
|
+
onEdit(id) { console.log('Edit:', id); }
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Context Functions
|
|
69
|
+
|
|
70
|
+
Call methods from the context object:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const template = html`<div>{{formatPrice}}</div>`;
|
|
74
|
+
|
|
75
|
+
const result = template({
|
|
76
|
+
price: 99.99,
|
|
77
|
+
formatPrice() {
|
|
78
|
+
return `$${this.price.toFixed(2)}`;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Function Arguments
|
|
84
|
+
|
|
85
|
+
Pass arguments to context functions using the pipe syntax:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const template = html`<div>{{greet|name}}</div>`;
|
|
89
|
+
|
|
90
|
+
const result = template({
|
|
91
|
+
name: 'John',
|
|
92
|
+
greet(name) { return `Hello, ${name}!`; }
|
|
93
|
+
});
|
|
94
|
+
// Result: <div>Hello, John!</div>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The value after `|` is resolved from the context and passed to the function. Multiple arguments use comma separation:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const template = html`<div>{{format|name,title}}</div>`;
|
|
101
|
+
|
|
102
|
+
const result = template({
|
|
103
|
+
name: 'John',
|
|
104
|
+
title: 'Developer',
|
|
105
|
+
format(name, title) { return `${name} - ${title}`; }
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
For value transformations using pipes (like `uppercase`, `currency`), use [compileTemplate](template.md) instead.
|
|
110
|
+
|
|
111
|
+
## API
|
|
112
|
+
|
|
113
|
+
### `html` (tagged template literal)
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
function html(
|
|
117
|
+
templateStrings: TemplateStringsArray,
|
|
118
|
+
...substitutions: any[]
|
|
119
|
+
): (context: any) => RenderTemplate
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Creates a template function that accepts a context object and returns a `RenderTemplate`.
|
|
123
|
+
|
|
124
|
+
### `RenderTemplate`
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
interface RenderTemplate {
|
|
128
|
+
fragment: DocumentFragment;
|
|
129
|
+
update(context: any): void;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
- `fragment`: The rendered DOM fragment. Add to the DOM once.
|
|
134
|
+
- `update(context)`: Re-renders with new data without recreating DOM nodes.
|
|
135
|
+
|
|
136
|
+
## Design
|
|
137
|
+
|
|
138
|
+
This template engine is designed for **single-use updateable templates**:
|
|
139
|
+
|
|
140
|
+
1. Create the template once
|
|
141
|
+
2. Render and add `fragment` to the DOM
|
|
142
|
+
3. Call `update()` to push changes to the existing nodes
|
|
143
|
+
|
|
144
|
+
For reusable templates that create multiple independent instances, use `compileTemplate` from [template](template.md).
|
|
145
|
+
|
|
146
|
+
## Property vs Attribute Binding
|
|
147
|
+
|
|
148
|
+
The engine automatically detects when to set element properties vs attributes:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// Sets input.value property (not attribute)
|
|
152
|
+
const input = html`<input value="{{val}}">`;
|
|
153
|
+
|
|
154
|
+
// Sets data-id attribute
|
|
155
|
+
const div = html`<div data-id="{{id}}"></div>`;
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Custom Elements
|
|
159
|
+
|
|
160
|
+
Custom elements are automatically upgraded when encountered in templates.
|
|
161
|
+
|
|
162
|
+
## Limitations
|
|
163
|
+
|
|
164
|
+
The `html` template is optimized for simple, updateable templates. For advanced features, use [compileTemplate](template.md):
|
|
165
|
+
|
|
166
|
+
| Feature | `html` | `compileTemplate` |
|
|
167
|
+
|---------|--------|-------------------|
|
|
168
|
+
| Mustache bindings | Yes | Yes |
|
|
169
|
+
| Event handlers | Yes | No |
|
|
170
|
+
| In-place updates | Yes | Yes |
|
|
171
|
+
| Nested paths (`user.name`) | No | Yes |
|
|
172
|
+
| Loops (`loop="item in items"`) | No | Yes |
|
|
173
|
+
| Conditionals (`if`, `unless`) | No | Yes |
|
|
174
|
+
| Pipe transformations | No | Yes |
|
|
175
|
+
| Function calls with args | Yes (via `\|`) | Yes |
|