@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.
Files changed (240) hide show
  1. package/README.md +194 -188
  2. package/dist/DependencyInjection.d.ts +42 -24
  3. package/dist/collections/Index.d.ts +2 -0
  4. package/dist/collections/index.js +1 -1
  5. package/dist/collections/index.js.map +4 -4
  6. package/dist/collections/index.mjs +1 -1
  7. package/dist/collections/index.mjs.map +4 -4
  8. package/dist/di/index.js +1 -1
  9. package/dist/di/index.js.map +3 -3
  10. package/dist/di/index.mjs +1 -1
  11. package/dist/di/index.mjs.map +3 -3
  12. package/dist/errors.d.ts +20 -0
  13. package/dist/forms/FormValidator.d.ts +1 -20
  14. package/dist/forms/ValidationRules.d.ts +2 -0
  15. package/dist/forms/index.js +1 -1
  16. package/dist/forms/index.js.map +4 -4
  17. package/dist/forms/index.mjs +1 -1
  18. package/dist/forms/index.mjs.map +4 -4
  19. package/dist/html/TableRenderer.d.ts +1 -0
  20. package/dist/html/index.js.map +2 -2
  21. package/dist/html/index.mjs.map +2 -2
  22. package/dist/html/template.d.ts +4 -0
  23. package/dist/http/http.d.ts +1 -0
  24. package/dist/http/index.js.map +2 -2
  25. package/dist/http/index.mjs.map +2 -2
  26. package/dist/index.d.ts +0 -2
  27. package/dist/index.js +3 -3
  28. package/dist/index.js.map +4 -4
  29. package/dist/index.mjs +3 -3
  30. package/dist/index.mjs.map +4 -4
  31. package/dist/routing/index.js +3 -3
  32. package/dist/routing/index.js.map +3 -3
  33. package/dist/routing/index.mjs +3 -3
  34. package/dist/routing/index.mjs.map +3 -3
  35. package/dist/routing/routeTargetRegistry.d.ts +1 -0
  36. package/dist/routing/types.d.ts +2 -1
  37. package/dist/templates/NodeTemplate.d.ts +2 -0
  38. package/dist/utils/index.js +1 -1
  39. package/dist/utils/index.js.map +2 -2
  40. package/dist/utils/index.mjs +1 -1
  41. package/dist/utils/index.mjs.map +2 -2
  42. package/docs/Architecture.md +333 -333
  43. package/docs/DependencyInjection.md +277 -237
  44. package/docs/Errors.md +87 -87
  45. package/docs/GettingStarted.md +231 -231
  46. package/docs/Pipes.md +5 -5
  47. package/docs/Translations.md +167 -312
  48. package/docs/WhyRelaxjs.md +336 -336
  49. package/docs/api/.nojekyll +1 -0
  50. package/docs/api/assets/hierarchy.js +1 -0
  51. package/docs/api/assets/highlight.css +120 -0
  52. package/docs/api/assets/icons.js +18 -0
  53. package/docs/api/assets/icons.svg +1 -0
  54. package/docs/api/assets/main.js +60 -0
  55. package/docs/api/assets/navigation.js +1 -0
  56. package/docs/api/assets/search.js +1 -0
  57. package/docs/api/assets/style.css +1633 -0
  58. package/docs/api/classes/http.WebSocketClient.html +26 -0
  59. package/docs/api/classes/i18n.LocaleChangeEvent.html +66 -0
  60. package/docs/api/classes/index.Blueprint.html +3 -0
  61. package/docs/api/classes/index.BoundNode.html +3 -0
  62. package/docs/api/classes/index.DigitsValidation.html +10 -0
  63. package/docs/api/classes/index.FormValidator.html +32 -0
  64. package/docs/api/classes/index.HttpError.html +13 -0
  65. package/docs/api/classes/index.LinkedList.html +26 -0
  66. package/docs/api/classes/index.NavigateRouteEvent.html +76 -0
  67. package/docs/api/classes/index.Node.html +15 -0
  68. package/docs/api/classes/index.PageSelectedEvent.html +61 -0
  69. package/docs/api/classes/index.Pager.html +4 -0
  70. package/docs/api/classes/index.RangeValidation.html +15 -0
  71. package/docs/api/classes/index.RelaxError.html +17 -0
  72. package/docs/api/classes/index.RequiredValidation.html +10 -0
  73. package/docs/api/classes/index.RouteError.html +11 -0
  74. package/docs/api/classes/index.RouteGuardError.html +12 -0
  75. package/docs/api/classes/index.RouteLink.html +779 -0
  76. package/docs/api/classes/index.RouteTarget.html +788 -0
  77. package/docs/api/classes/index.SSEClient.html +13 -0
  78. package/docs/api/classes/index.SSEDataEvent.html +63 -0
  79. package/docs/api/classes/index.ServiceCollection.html +28 -0
  80. package/docs/api/classes/index.ServiceContainer.html +24 -0
  81. package/docs/api/classes/index.SortChangeEvent.html +61 -0
  82. package/docs/api/classes/index.TableRenderer.html +5 -0
  83. package/docs/api/classes/index.TableSorter.html +4 -0
  84. package/docs/api/enums/index.GuardResult.html +9 -0
  85. package/docs/api/functions/elements.formError.html +6 -0
  86. package/docs/api/functions/elements.selectOne.html +6 -0
  87. package/docs/api/functions/i18n.getCurrentLocale.html +3 -0
  88. package/docs/api/functions/i18n.loadNamespace.html +7 -0
  89. package/docs/api/functions/i18n.loadNamespaces.html +6 -0
  90. package/docs/api/functions/i18n.onMissingTranslation.html +7 -0
  91. package/docs/api/functions/i18n.setLocale.html +7 -0
  92. package/docs/api/functions/i18n.setMessageFormatter.html +7 -0
  93. package/docs/api/functions/i18n.t.html +9 -0
  94. package/docs/api/functions/index.BooleanConverter.html +6 -0
  95. package/docs/api/functions/index.ContainerService.html +13 -0
  96. package/docs/api/functions/index.DateConverter.html +11 -0
  97. package/docs/api/functions/index.Inject.html +16 -0
  98. package/docs/api/functions/index.NumberConverter.html +5 -0
  99. package/docs/api/functions/index.RegisterValidator.html +7 -0
  100. package/docs/api/functions/index.applyPipes.html +17 -0
  101. package/docs/api/functions/index.asyncHandler.html +11 -0
  102. package/docs/api/functions/index.capitalizePipe.html +4 -0
  103. package/docs/api/functions/index.clearPendingNavigations.html +1 -0
  104. package/docs/api/functions/index.compileTemplate.html +26 -0
  105. package/docs/api/functions/index.configure.html +5 -0
  106. package/docs/api/functions/index.createBluePrint.html +1 -0
  107. package/docs/api/functions/index.createConverterFromDataType.html +4 -0
  108. package/docs/api/functions/index.createConverterFromInputType.html +5 -0
  109. package/docs/api/functions/index.createPipeRegistry.html +12 -0
  110. package/docs/api/functions/index.currencyPipe.html +9 -0
  111. package/docs/api/functions/index.datePipe.html +9 -0
  112. package/docs/api/functions/index.daysAgoPipe.html +8 -0
  113. package/docs/api/functions/index.defaultPipe.html +5 -0
  114. package/docs/api/functions/index.defineRoutes.html +8 -0
  115. package/docs/api/functions/index.del.html +8 -0
  116. package/docs/api/functions/index.findRouteByName.html +5 -0
  117. package/docs/api/functions/index.findRouteByUrl.html +4 -0
  118. package/docs/api/functions/index.firstPipe.html +4 -0
  119. package/docs/api/functions/index.generateSequentialId.html +21 -0
  120. package/docs/api/functions/index.get.html +9 -0
  121. package/docs/api/functions/index.getDataConverter.html +11 -0
  122. package/docs/api/functions/index.getParentComponent.html +18 -0
  123. package/docs/api/functions/index.getValidator.html +4 -0
  124. package/docs/api/functions/index.html.html +19 -0
  125. package/docs/api/functions/index.joinPipe.html +5 -0
  126. package/docs/api/functions/index.keysPipe.html +4 -0
  127. package/docs/api/functions/index.lastPipe.html +4 -0
  128. package/docs/api/functions/index.lowercasePipe.html +4 -0
  129. package/docs/api/functions/index.mapFormToClass.html +17 -0
  130. package/docs/api/functions/index.matchRoute.html +5 -0
  131. package/docs/api/functions/index.navigate.html +8 -0
  132. package/docs/api/functions/index.onError.html +8 -0
  133. package/docs/api/functions/index.piecesPipe.html +8 -0
  134. package/docs/api/functions/index.post.html +9 -0
  135. package/docs/api/functions/index.printRoutes.html +2 -0
  136. package/docs/api/functions/index.put.html +9 -0
  137. package/docs/api/functions/index.readData.html +17 -0
  138. package/docs/api/functions/index.registerRouteTarget.html +9 -0
  139. package/docs/api/functions/index.reportError.html +10 -0
  140. package/docs/api/functions/index.request.html +8 -0
  141. package/docs/api/functions/index.resolveValue.html +18 -0
  142. package/docs/api/functions/index.setFetch.html +6 -0
  143. package/docs/api/functions/index.setFormData.html +17 -0
  144. package/docs/api/functions/index.shortenPipe.html +5 -0
  145. package/docs/api/functions/index.startRouting.html +6 -0
  146. package/docs/api/functions/index.ternaryPipe.html +6 -0
  147. package/docs/api/functions/index.trimPipe.html +4 -0
  148. package/docs/api/functions/index.unregisterRouteTarget.html +3 -0
  149. package/docs/api/functions/index.uppercasePipe.html +4 -0
  150. package/docs/api/hierarchy.html +1 -0
  151. package/docs/api/index.html +323 -0
  152. package/docs/api/interfaces/http.SimpleDataEvent.html +3 -0
  153. package/docs/api/interfaces/http.WebSocketAbstraction.html +9 -0
  154. package/docs/api/interfaces/http.WebSocketCodec.html +4 -0
  155. package/docs/api/interfaces/http.WebSocketOptions.html +20 -0
  156. package/docs/api/interfaces/index.CompiledTemplate.html +10 -0
  157. package/docs/api/interfaces/index.DataLoader.html +19 -0
  158. package/docs/api/interfaces/index.EngineConfig.html +11 -0
  159. package/docs/api/interfaces/index.ErrorContext.html +4 -0
  160. package/docs/api/interfaces/index.FormReaderOptions.html +8 -0
  161. package/docs/api/interfaces/index.HttpOptions.html +16 -0
  162. package/docs/api/interfaces/index.HttpResponse.html +17 -0
  163. package/docs/api/interfaces/index.LoadRoute.html +7 -0
  164. package/docs/api/interfaces/index.NavigateOptions.html +7 -0
  165. package/docs/api/interfaces/index.PipeRegistry.html +12 -0
  166. package/docs/api/interfaces/index.RegistrationOptions.html +22 -0
  167. package/docs/api/interfaces/index.RenderTemplate.html +7 -0
  168. package/docs/api/interfaces/index.RequestOptions.html +11 -0
  169. package/docs/api/interfaces/index.Routable.html +10 -0
  170. package/docs/api/interfaces/index.Route.html +13 -0
  171. package/docs/api/interfaces/index.RouteGuard.html +2 -0
  172. package/docs/api/interfaces/index.RouteValue.html +6 -0
  173. package/docs/api/interfaces/index.SSEOptions.html +24 -0
  174. package/docs/api/interfaces/index.ValidationContext.html +8 -0
  175. package/docs/api/interfaces/index.ValidatorOptions.html +14 -0
  176. package/docs/api/media/Architecture.md +333 -0
  177. package/docs/api/media/DependencyInjection.md +277 -0
  178. package/docs/api/media/GettingStarted.md +231 -0
  179. package/docs/api/media/HttpClient.md +459 -0
  180. package/docs/api/media/Pipes.md +211 -0
  181. package/docs/api/media/Routing.md +332 -0
  182. package/docs/api/media/WhyRelaxjs.md +336 -0
  183. package/docs/api/media/forms.md +99 -0
  184. package/docs/api/media/html.md +175 -0
  185. package/docs/api/media/i18n.md +354 -0
  186. package/docs/api/media/utilities.md +143 -0
  187. package/docs/api/media/validation.md +351 -0
  188. package/docs/api/modules/collections_Index.html +1 -0
  189. package/docs/api/modules/di.html +1 -0
  190. package/docs/api/modules/elements.html +1 -0
  191. package/docs/api/modules/forms.html +1 -0
  192. package/docs/api/modules/html.html +1 -0
  193. package/docs/api/modules/http.html +1 -0
  194. package/docs/api/modules/i18n.html +1 -0
  195. package/docs/api/modules/index.html +1 -0
  196. package/docs/api/modules/routing.html +1 -0
  197. package/docs/api/modules/utils.html +1 -0
  198. package/docs/api/modules.html +1 -0
  199. package/docs/api/types/http.WebSocketFactory.html +2 -0
  200. package/docs/api/types/i18n.MessageFormatter.html +3 -0
  201. package/docs/api/types/i18n.MissingTranslationHandler.html +1 -0
  202. package/docs/api/types/index.Constructor.html +7 -0
  203. package/docs/api/types/index.ConverterFunc.html +2 -0
  204. package/docs/api/types/index.DataType.html +2 -0
  205. package/docs/api/types/index.InputType.html +2 -0
  206. package/docs/api/types/index.PipeFunction.html +6 -0
  207. package/docs/api/types/index.RouteData.html +1 -0
  208. package/docs/api/types/index.RouteMatchResult.html +9 -0
  209. package/docs/api/types/index.RouteParamType.html +1 -0
  210. package/docs/api/types/index.RouteSegmentType.html +2 -0
  211. package/docs/api/types/index.SSEEventFactory.html +5 -0
  212. package/docs/api/types/index.ServiceScope.html +10 -0
  213. package/docs/api/types/index.SortColumn.html +3 -0
  214. package/docs/api/variables/i18n.formatICU.html +3 -0
  215. package/docs/api/variables/index.container.html +6 -0
  216. package/docs/api/variables/index.defaultPipes.html +6 -0
  217. package/docs/api/variables/index.internalRoutes.html +1 -0
  218. package/docs/api/variables/index.serviceCollection.html +6 -0
  219. package/docs/api.json +93171 -0
  220. package/docs/elements/dom.md +102 -102
  221. package/docs/forms/creating-form-components.md +924 -924
  222. package/docs/forms/form-api.md +94 -94
  223. package/docs/forms/forms.md +99 -99
  224. package/docs/forms/patterns.md +311 -311
  225. package/docs/forms/reading-writing.md +365 -365
  226. package/docs/forms/validation.md +351 -351
  227. package/docs/html/TableRenderer.md +291 -291
  228. package/docs/html/html.md +175 -175
  229. package/docs/html/index.md +54 -54
  230. package/docs/html/template.md +422 -422
  231. package/docs/http/HttpClient.md +459 -459
  232. package/docs/http/ServerSentEvents.md +184 -184
  233. package/docs/http/index.md +109 -109
  234. package/docs/i18n/i18n.md +49 -4
  235. package/docs/i18n/intl-standard.md +178 -178
  236. package/docs/routing/RouteLink.md +98 -98
  237. package/docs/routing/Routing.md +332 -332
  238. package/docs/routing/layouts.md +207 -207
  239. package/docs/utilities.md +143 -143
  240. 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 |