@relax.js/core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +188 -0
  3. package/dist/DataLoader.d.ts +51 -0
  4. package/dist/DependencyInjection.d.ts +271 -0
  5. package/dist/DependencyInjectionOld.d.ts +35 -0
  6. package/dist/Metadata.d.ts +8 -0
  7. package/dist/SequentialId.d.ts +47 -0
  8. package/dist/_alt/src/MustardEngine.d.ts +30 -0
  9. package/dist/_alt/src/MustardParser.d.ts +63 -0
  10. package/dist/_alt/src/MustardParser2.d.ts +35 -0
  11. package/dist/_alt/src/pipes.d.ts +93 -0
  12. package/dist/_alt/src/template.d.ts +166 -0
  13. package/dist/_alt/src/tools.d.ts +4 -0
  14. package/dist/_alt/tests/pipes.tests.d.ts +1 -0
  15. package/dist/_alt/tests/template.tests.d.ts +1 -0
  16. package/dist/_alt/vitest.config.d.ts +2 -0
  17. package/dist/collections/Index.d.ts +1 -0
  18. package/dist/collections/LinkedList.d.ts +75 -0
  19. package/dist/collections/Pager.d.ts +15 -0
  20. package/dist/collections/index.js +2 -0
  21. package/dist/collections/index.js.map +7 -0
  22. package/dist/collections/index.mjs +2 -0
  23. package/dist/collections/index.mjs.map +7 -0
  24. package/dist/components/Table.d.ts +13 -0
  25. package/dist/components/index.d.ts +4 -0
  26. package/dist/components/index.js +128 -0
  27. package/dist/components/index.js.map +7 -0
  28. package/dist/components/index.mjs +128 -0
  29. package/dist/components/index.mjs.map +7 -0
  30. package/dist/components/lists/Table.d.ts +59 -0
  31. package/dist/components/lists/TreeView.d.ts +67 -0
  32. package/dist/components/lists/index.d.ts +2 -0
  33. package/dist/components/loader.d.ts +60 -0
  34. package/dist/components/menus/MenuItem.d.ts +30 -0
  35. package/dist/components/menus/TopMenu.d.ts +16 -0
  36. package/dist/components/menus/index.d.ts +2 -0
  37. package/dist/components/panels/tabs.d.ts +15 -0
  38. package/dist/di/index.d.ts +1 -0
  39. package/dist/di/index.js +2 -0
  40. package/dist/di/index.js.map +7 -0
  41. package/dist/di/index.mjs +2 -0
  42. package/dist/di/index.mjs.map +7 -0
  43. package/dist/elements/CopyAttributes.d.ts +2 -0
  44. package/dist/elements/dom.d.ts +18 -0
  45. package/dist/elements/index.d.ts +2 -0
  46. package/dist/elements/index.js +2 -0
  47. package/dist/elements/index.js.map +7 -0
  48. package/dist/elements/index.mjs +2 -0
  49. package/dist/elements/index.mjs.map +7 -0
  50. package/dist/errors.d.ts +71 -0
  51. package/dist/forms/FormReader.d.ts +182 -0
  52. package/dist/forms/FormValidator.d.ts +114 -0
  53. package/dist/forms/ValidationRules.d.ts +103 -0
  54. package/dist/forms/index.d.ts +4 -0
  55. package/dist/forms/index.js +2 -0
  56. package/dist/forms/index.js.map +7 -0
  57. package/dist/forms/index.mjs +2 -0
  58. package/dist/forms/index.mjs.map +7 -0
  59. package/dist/forms/setFormData.d.ts +49 -0
  60. package/dist/getParentComponent.d.ts +43 -0
  61. package/dist/html/TableRenderer.d.ts +44 -0
  62. package/dist/html/TreeBinder.d.ts +9 -0
  63. package/dist/html/html.d.ts +55 -0
  64. package/dist/html/index.d.ts +5 -0
  65. package/dist/html/index.js +2 -0
  66. package/dist/html/index.js.map +7 -0
  67. package/dist/html/index.mjs +2 -0
  68. package/dist/html/index.mjs.map +7 -0
  69. package/dist/html/template.d.ts +167 -0
  70. package/dist/http/ServerSentEvents.d.ts +116 -0
  71. package/dist/http/SimpleWebSocket.d.ts +153 -0
  72. package/dist/http/http.d.ts +177 -0
  73. package/dist/http/index.d.ts +3 -0
  74. package/dist/http/index.js +2 -0
  75. package/dist/http/index.js.map +7 -0
  76. package/dist/http/index.mjs +2 -0
  77. package/dist/http/index.mjs.map +7 -0
  78. package/dist/i18n/i18n.d.ts +105 -0
  79. package/dist/i18n/icu.d.ts +64 -0
  80. package/dist/i18n/index.d.ts +2 -0
  81. package/dist/i18n/index.js +2 -0
  82. package/dist/i18n/index.js.map +7 -0
  83. package/dist/i18n/index.mjs +2 -0
  84. package/dist/i18n/index.mjs.map +7 -0
  85. package/dist/index.d.ts +16 -0
  86. package/dist/index.js +5 -0
  87. package/dist/index.js.map +7 -0
  88. package/dist/index.mjs +5 -0
  89. package/dist/index.mjs.map +7 -0
  90. package/dist/lib/DataLoader.d.ts +51 -0
  91. package/dist/lib/DependencyInjection.d.ts +271 -0
  92. package/dist/lib/InvokeParent.d.ts +10 -0
  93. package/dist/lib/Pipes.d.ts +236 -0
  94. package/dist/lib/SequentialId.d.ts +47 -0
  95. package/dist/lib/collections/Index.d.ts +1 -0
  96. package/dist/lib/collections/LinkedList.d.ts +75 -0
  97. package/dist/lib/collections/Pager.d.ts +15 -0
  98. package/dist/lib/collections/TableRenderer.d.ts +44 -0
  99. package/dist/lib/di/index.d.ts +1 -0
  100. package/dist/lib/elements/CopyAttributes.d.ts +2 -0
  101. package/dist/lib/elements/dom.d.ts +18 -0
  102. package/dist/lib/elements/index.d.ts +2 -0
  103. package/dist/lib/errors.d.ts +71 -0
  104. package/dist/lib/forms/FormReader.d.ts +182 -0
  105. package/dist/lib/forms/FormValidator.d.ts +114 -0
  106. package/dist/lib/forms/ValidationRules.d.ts +103 -0
  107. package/dist/lib/forms/index.d.ts +4 -0
  108. package/dist/lib/forms/setFormData.d.ts +49 -0
  109. package/dist/lib/getParentComponent.d.ts +43 -0
  110. package/dist/lib/html/TableRenderer.d.ts +44 -0
  111. package/dist/lib/html/TreeBinder.d.ts +9 -0
  112. package/dist/lib/html/html.d.ts +55 -0
  113. package/dist/lib/html/html2.d.ts +55 -0
  114. package/dist/lib/html/index.d.ts +5 -0
  115. package/dist/lib/html/m.d.ts +167 -0
  116. package/dist/lib/html/m2.d.ts +8 -0
  117. package/dist/lib/html/m3.d.ts +0 -0
  118. package/dist/lib/html/template.d.ts +167 -0
  119. package/dist/lib/http/HttpClient.d.ts +153 -0
  120. package/dist/lib/http/ServerSentEvents.d.ts +116 -0
  121. package/dist/lib/http/SimpleWebSocket.d.ts +153 -0
  122. package/dist/lib/http/http.d.ts +177 -0
  123. package/dist/lib/http/index.d.ts +3 -0
  124. package/dist/lib/i18n/i18n.d.ts +105 -0
  125. package/dist/lib/i18n/icu.d.ts +64 -0
  126. package/dist/lib/i18n/index.d.ts +2 -0
  127. package/dist/lib/index.d.ts +16 -0
  128. package/dist/lib/routing/NavigateRouteEvent.d.ts +52 -0
  129. package/dist/lib/routing/RouteLink.d.ts +7 -0
  130. package/dist/lib/routing/Routing.d.ts +270 -0
  131. package/dist/lib/routing/RoutingTarget.d.ts +22 -0
  132. package/dist/lib/routing/index.d.ts +7 -0
  133. package/dist/lib/routing/navigation.d.ts +70 -0
  134. package/dist/lib/routing/routeMatching.d.ts +21 -0
  135. package/dist/lib/routing/routeTargetRegistry.d.ts +23 -0
  136. package/dist/lib/routing/types.d.ts +130 -0
  137. package/dist/lib/templates/NodeTemplate.d.ts +38 -0
  138. package/dist/lib/templates/accessorParser.d.ts +87 -0
  139. package/dist/lib/templates/parseTemplate.d.ts +6 -0
  140. package/dist/lib/templates/tokenizer.d.ts +76 -0
  141. package/dist/lib/tools.d.ts +30 -0
  142. package/dist/lib/utils/index.d.ts +4 -0
  143. package/dist/pipes.d.ts +236 -0
  144. package/dist/routing/NavigateRouteEvent.d.ts +52 -0
  145. package/dist/routing/RouteLink.d.ts +7 -0
  146. package/dist/routing/RoutingTarget.d.ts +22 -0
  147. package/dist/routing/index.d.ts +7 -0
  148. package/dist/routing/index.js +5 -0
  149. package/dist/routing/index.js.map +7 -0
  150. package/dist/routing/index.mjs +5 -0
  151. package/dist/routing/index.mjs.map +7 -0
  152. package/dist/routing/navigation.d.ts +70 -0
  153. package/dist/routing/routeMatching.d.ts +21 -0
  154. package/dist/routing/routeTargetRegistry.d.ts +23 -0
  155. package/dist/routing/types.d.ts +130 -0
  156. package/dist/templates/NodeTemplate.d.ts +38 -0
  157. package/dist/templates/accessorParser.d.ts +87 -0
  158. package/dist/templates/parseTemplate.d.ts +6 -0
  159. package/dist/templates/tokenizer.d.ts +76 -0
  160. package/dist/tools.d.ts +30 -0
  161. package/dist/utils/index.d.ts +4 -0
  162. package/dist/utils/index.js +2 -0
  163. package/dist/utils/index.js.map +7 -0
  164. package/dist/utils/index.mjs +2 -0
  165. package/dist/utils/index.mjs.map +7 -0
  166. package/docs/Architecture.md +333 -0
  167. package/docs/DependencyInjection.md +237 -0
  168. package/docs/Errors.md +87 -0
  169. package/docs/GettingStarted.md +231 -0
  170. package/docs/Pipes.md +211 -0
  171. package/docs/Translations.md +312 -0
  172. package/docs/WhyRelaxjs.md +336 -0
  173. package/docs/elements/dom.md +102 -0
  174. package/docs/forms/creating-form-components.md +924 -0
  175. package/docs/forms/form-api.md +94 -0
  176. package/docs/forms/forms.md +99 -0
  177. package/docs/forms/patterns.md +311 -0
  178. package/docs/forms/reading-writing.md +365 -0
  179. package/docs/forms/validation.md +351 -0
  180. package/docs/html/TableRenderer.md +292 -0
  181. package/docs/html/html.md +175 -0
  182. package/docs/html/index.md +54 -0
  183. package/docs/html/template.md +422 -0
  184. package/docs/http/HttpClient.md +459 -0
  185. package/docs/http/ServerSentEvents.md +184 -0
  186. package/docs/http/index.md +109 -0
  187. package/docs/i18n/i18n.md +309 -0
  188. package/docs/i18n/intl-standard.md +178 -0
  189. package/docs/routing/RouteLink.md +98 -0
  190. package/docs/routing/Routing.md +332 -0
  191. package/docs/routing/RoutingTarget.md +136 -0
  192. package/docs/routing/layouts.md +207 -0
  193. package/docs/utilities.md +143 -0
  194. package/package.json +93 -0
@@ -0,0 +1,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,102 @@
1
+ # DOM Helpers
2
+
3
+ Utility functions for finding elements and setting form validation errors.
4
+
5
+ ## `selectOne`
6
+
7
+ Finds exactly one element matching a selector. Throws if no match is found.
8
+
9
+ When the selector starts with `#`, it tries matching by `id` first, then falls back to the `name` attribute. This makes it convenient for form fields where `name` is more common than `id`.
10
+
11
+ ```typescript
12
+ import { selectOne } from 'relaxjs/elements';
13
+
14
+ // Standard CSS selector
15
+ const container = selectOne('.main-content');
16
+
17
+ // # looks up id, then name
18
+ const email = selectOne<HTMLInputElement>('#email');
19
+
20
+ // Scope to a parent element
21
+ const field = selectOne<HTMLInputElement>('#username', form);
22
+ ```
23
+
24
+ The default return type is `HTMLElement`. Pass a type parameter for more specific types:
25
+
26
+ ```typescript
27
+ const input = selectOne<HTMLInputElement>('#age');
28
+ input.valueAsNumber; // OK, typed as HTMLInputElement
29
+ ```
30
+
31
+ ## `formError`
32
+
33
+ Sets a validation error message on a form field. Supports both native form elements (`<input>`, `<select>`, `<textarea>`, `<button>`) and form-associated custom elements that use `ElementInternals`. Throws if the element is not a form field.
34
+
35
+ ```typescript
36
+ import { formError } from 'relaxjs/elements';
37
+
38
+ // Set an error
39
+ formError('#email', 'Please enter a valid email');
40
+
41
+ // Clear an error
42
+ formError('#email', '');
43
+
44
+ // Scope to a specific form
45
+ formError('#quantity', 'Must be positive', orderForm);
46
+ ```
47
+
48
+ ### Native form fields
49
+
50
+ Calls `setCustomValidity(message)` and `reportValidity()` directly on the element.
51
+
52
+ ### Form-associated custom elements
53
+
54
+ Detects elements whose constructor has `static formAssociated = true` and exposes `setCustomValidity`. This covers custom elements built with the `ElementInternals` API that proxy validation methods.
55
+
56
+ ### Non-form elements
57
+
58
+ Throws an error if the matched element is neither a native form field nor a form-associated custom element.
59
+
60
+ ## `getParentComponent`
61
+
62
+ Finds the closest parent element of a specific Web Component type. Traverses up the DOM tree looking for an ancestor matching the given constructor.
63
+
64
+ ```typescript
65
+ import { getParentComponent } from 'relaxjs/elements';
66
+ ```
67
+
68
+ ### Finding a Parent Container
69
+
70
+ ```typescript
71
+ class ListItem extends HTMLElement {
72
+ connectedCallback() {
73
+ const list = getParentComponent(this, ListContainer);
74
+ if (list) {
75
+ list.registerItem(this);
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ ### Accessing Parent Methods
82
+
83
+ ```typescript
84
+ class TabPanel extends HTMLElement {
85
+ activate() {
86
+ const tabs = getParentComponent(this, TabContainer);
87
+ tabs?.selectPanel(this);
88
+ }
89
+ }
90
+ ```
91
+
92
+ ### Handling Missing Parent
93
+
94
+ Returns `null` if no matching parent is found:
95
+
96
+ ```typescript
97
+ const form = getParentComponent(input, FormContainer);
98
+ if (!form) {
99
+ console.warn('Input must be inside a FormContainer');
100
+ return;
101
+ }
102
+ ```