@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,365 @@
1
+ # Reading & Writing Form Data
2
+
3
+ Functions for reading and writing form data with automatic type conversion.
4
+
5
+ ## mapFormToClass
6
+
7
+ Maps form field values to a class instance's properties with type conversion. Provides full type safety by populating an existing typed object.
8
+
9
+ ```typescript
10
+ import { mapFormToClass } from 'relaxjs/forms';
11
+
12
+ class UserDTO {
13
+ name: string = '';
14
+ email: string = '';
15
+ age: number = 0;
16
+ newsletter: boolean = false;
17
+ }
18
+
19
+ const form = document.querySelector('form');
20
+ const user = mapFormToClass(form, new UserDTO());
21
+ console.log(user.name, user.age, user.newsletter);
22
+ ```
23
+
24
+ ### Options
25
+
26
+ ```typescript
27
+ // Options object (inline, not a named interface)
28
+ {
29
+ throwOnMissingProperty?: boolean; // Throw if form field has no matching property
30
+ throwOnMissingField?: boolean; // Throw if class property has no matching field
31
+ }
32
+ ```
33
+
34
+ ### Strict Validation Mode
35
+
36
+ Catch mismatches between form fields and your data model:
37
+
38
+ ```typescript
39
+ const user = mapFormToClass(form, new UserDTO(), {
40
+ throwOnMissingProperty: true, // Catch typos in form field names
41
+ throwOnMissingField: true // Ensure all DTO fields are in form
42
+ });
43
+ ```
44
+
45
+ ### Type Conversion
46
+
47
+ Automatic conversion based on input types:
48
+
49
+ | Input Type | Converted To |
50
+ |------------|--------------|
51
+ | `checkbox` | `boolean` |
52
+ | `number` | `number` |
53
+ | `date` | `Date` |
54
+
55
+ ```typescript
56
+ class ProductDTO {
57
+ name: string = '';
58
+ price: number = 0;
59
+ inStock: boolean = false;
60
+ releaseDate: Date | null = null;
61
+ }
62
+
63
+ const product = mapFormToClass(form, new ProductDTO());
64
+ // product.price is number, product.inStock is boolean
65
+ ```
66
+
67
+ ## readData
68
+
69
+ Reads all form data into a plain object with automatic type conversion. Use when you don't need strict typing.
70
+
71
+ ```typescript
72
+ import { readData } from 'relaxjs/forms';
73
+
74
+ const form = document.querySelector('form');
75
+ const data = readData(form);
76
+ ```
77
+
78
+ ### Type Conversion
79
+
80
+ Type conversion is determined by:
81
+ 1. `data-type` attribute if present (`number`, `boolean`, `string`, `Date`)
82
+ 2. Input type (`checkbox`, `number`, `date`, etc.)
83
+ 3. Falls back to string
84
+
85
+ ```html
86
+ <form>
87
+ <input name="username" value="john" />
88
+ <input name="age" type="number" value="25" />
89
+ <input name="active" type="checkbox" checked />
90
+ <input name="salary" data-type="number" value="50000" />
91
+ <select name="colors" multiple>
92
+ <option value="red" selected>Red</option>
93
+ <option value="blue" selected>Blue</option>
94
+ </select>
95
+ </form>
96
+ ```
97
+
98
+ ```typescript
99
+ const data = readData(form);
100
+ // Returns: {
101
+ // username: 'john',
102
+ // age: 25,
103
+ // active: true,
104
+ // salary: 50000,
105
+ // colors: ['red', 'blue']
106
+ // }
107
+ ```
108
+
109
+ ### Checkbox Handling
110
+
111
+ Unchecked checkboxes are included as `false` in the result. Checked checkboxes with no explicit `value` attribute (which submit as `'on'` in FormData) are correctly converted to `true`.
112
+
113
+ ```html
114
+ <input name="active" type="checkbox" checked />
115
+ <input name="terms" type="checkbox" />
116
+ ```
117
+
118
+ ```typescript
119
+ const data = readData(form);
120
+ // Returns: { active: true, terms: false }
121
+ ```
122
+
123
+ ### Custom Form Components
124
+
125
+ Works with form-associated custom elements:
126
+
127
+ ```html
128
+ <form>
129
+ <r-input name="email" value="test@example.com"></r-input>
130
+ <r-checkbox name="terms" checked></r-checkbox>
131
+ </form>
132
+ ```
133
+
134
+ ```typescript
135
+ const data = readData(form);
136
+ // Returns: { email: 'test@example.com', terms: true }
137
+ ```
138
+
139
+ ## setFormData
140
+
141
+ Populates form fields from a data object using the `name` attribute.
142
+
143
+ ```typescript
144
+ import { setFormData } from 'relaxjs/forms';
145
+
146
+ const form = document.querySelector('form');
147
+ const data = { name: 'John', email: 'john@example.com' };
148
+ setFormData(form, data);
149
+ ```
150
+
151
+ ### Nested Objects (Dot Notation)
152
+
153
+ ```html
154
+ <form>
155
+ <input name="user.name" />
156
+ <input name="user.contact.email" />
157
+ </form>
158
+ ```
159
+
160
+ ```typescript
161
+ const data = {
162
+ user: {
163
+ name: 'John',
164
+ contact: {
165
+ email: 'john@example.com'
166
+ }
167
+ }
168
+ };
169
+ setFormData(form, data);
170
+ ```
171
+
172
+ ### Date and DateTime
173
+
174
+ `setFormData` formats `Date` objects to the correct string format for each input type:
175
+
176
+ ```html
177
+ <input type="date" name="birthday" />
178
+ <input type="datetime-local" name="meeting" />
179
+ ```
180
+
181
+ ```typescript
182
+ setFormData(form, {
183
+ birthday: new Date('2000-01-15'), // Sets "2000-01-15"
184
+ meeting: new Date('2024-06-15T14:30') // Sets "2024-06-15T14:30"
185
+ });
186
+ ```
187
+
188
+ ### Select Multiple
189
+
190
+ Array values are set directly on `<select multiple>` elements:
191
+
192
+ ```html
193
+ <select name="colors" multiple>
194
+ <option value="red">Red</option>
195
+ <option value="green">Green</option>
196
+ <option value="blue">Blue</option>
197
+ </select>
198
+ ```
199
+
200
+ ```typescript
201
+ setFormData(form, { colors: ['red', 'blue'] });
202
+ // Selects "red" and "blue", deselects "green"
203
+ ```
204
+
205
+ ### Simple Arrays ([] Notation)
206
+
207
+ For checkboxes, multi-select, and text inputs:
208
+
209
+ ```html
210
+ <form>
211
+ <input name="hobbies[]" type="checkbox" value="Reading" />
212
+ <input name="hobbies[]" type="checkbox" value="Cycling" />
213
+ <input name="hobbies[]" type="checkbox" value="Cooking" />
214
+ </form>
215
+ ```
216
+
217
+ ```typescript
218
+ const data = {
219
+ hobbies: ['Reading', 'Cooking']
220
+ };
221
+ setFormData(form, data);
222
+ // Checks "Reading" and "Cooking" checkboxes
223
+ ```
224
+
225
+ ### Array of Objects (Indexed Notation)
226
+
227
+ ```html
228
+ <form>
229
+ <input name="users[0].name" />
230
+ <input name="users[0].email" />
231
+ <input name="users[1].name" />
232
+ <input name="users[1].email" />
233
+ </form>
234
+ ```
235
+
236
+ ```typescript
237
+ const data = {
238
+ users: [
239
+ { name: 'John', email: 'john@example.com' },
240
+ { name: 'Jane', email: 'jane@example.com' }
241
+ ]
242
+ };
243
+ setFormData(form, data);
244
+ ```
245
+
246
+ ### Complex Data Structures
247
+
248
+ ```html
249
+ <form id="product-form">
250
+ <input name="product.name" placeholder="Product Name">
251
+ <input name="product.price" type="number" placeholder="Price">
252
+
253
+ <input name="categories[]" type="checkbox" value="electronics">
254
+ <input name="categories[]" type="checkbox" value="gadgets">
255
+
256
+ <input name="variants[0].size" placeholder="Size">
257
+ <input name="variants[0].color" placeholder="Color">
258
+ <input name="variants[1].size" placeholder="Size">
259
+ <input name="variants[1].color" placeholder="Color">
260
+
261
+ <input name="supplier.company" placeholder="Company">
262
+ <input name="supplier.contact.email" placeholder="Email">
263
+ </form>
264
+ ```
265
+
266
+ ```typescript
267
+ const productData = {
268
+ product: {
269
+ name: 'Wireless Headphones',
270
+ price: 99.99
271
+ },
272
+ categories: ['electronics', 'gadgets'],
273
+ variants: [
274
+ { size: 'M', color: 'black' },
275
+ { size: 'L', color: 'white' }
276
+ ],
277
+ supplier: {
278
+ company: 'TechCorp',
279
+ contact: {
280
+ email: 'orders@techcorp.com'
281
+ }
282
+ }
283
+ };
284
+
285
+ setFormData(form, productData);
286
+
287
+ // Later, extract the data
288
+ const extractedData = readData(form);
289
+ // Result matches the original structure
290
+ ```
291
+
292
+ ## Type Converters
293
+
294
+ ### Built-in Converters
295
+
296
+ | Converter | Input | Output |
297
+ |-----------|-------|--------|
298
+ | `BooleanConverter` | `'true'`, `'on'`, `'1'`, any positive number string -> `true`; `'false'`, `'off'`, `'0'` -> `false` | `boolean` |
299
+ | `NumberConverter` | Numeric string | `number` |
300
+ | `DateConverter` | ISO or locale-formatted date string | `Date` |
301
+
302
+ #### Locale-Aware Date Parsing
303
+
304
+ `DateConverter` detects the current locale and parses dates in the local format:
305
+
306
+ | Locale | Format | Example |
307
+ |--------|--------|---------|
308
+ | `en` (US) | `MM/DD/YYYY` | `01/15/2024` |
309
+ | `sv` (Swedish) | `YYYY-MM-DD` | `2024-01-15` |
310
+ | `de` (German) | `DD.MM.YYYY` | `15.01.2024` |
311
+
312
+ ISO format (`2024-01-15`) is always accepted regardless of locale.
313
+
314
+ ### Custom Type via `data-type` Attribute
315
+
316
+ Use the `data-type` attribute to override conversion for any field:
317
+
318
+ ```html
319
+ <input name="salary" data-type="number" value="50000" />
320
+ <input name="active" data-type="boolean" value="true" />
321
+ <input name="birthday" data-type="Date" value="2000-01-15" />
322
+ ```
323
+
324
+ ```typescript
325
+ const data = readData(form);
326
+ console.log(data.salary); // number: 50000
327
+ console.log(data.active); // boolean: true
328
+ console.log(data.birthday); // Date object
329
+ ```
330
+
331
+ Supported `data-type` values: `number`, `boolean`, `string`, `Date`.
332
+
333
+ ### Date and Time Handling
334
+
335
+ ```html
336
+ <input name="startDate" type="date">
337
+ <input name="startTime" type="time">
338
+ <input name="duration" type="time" step="900"> <!-- 15 min steps -->
339
+ ```
340
+
341
+ ```typescript
342
+ // Custom handling for combining date and time
343
+ document.querySelector('[name="startTime"]').getData = function() {
344
+ const dateInput = form.querySelector('[name="startDate"]') as HTMLInputElement;
345
+ const timeInput = this as HTMLInputElement;
346
+
347
+ if (dateInput.value && timeInput.value) {
348
+ return new Date(`${dateInput.value}T${timeInput.value}`);
349
+ }
350
+ return null;
351
+ };
352
+ ```
353
+
354
+ ### Input Type Conversion Table
355
+
356
+ | Input Type | Converted To |
357
+ |------------|--------------|
358
+ | `checkbox` | `boolean` |
359
+ | `number` | `number` |
360
+ | `date` | `Date` |
361
+ | `datetime-local` | `Date` |
362
+ | `month` | `Date` (first of month) |
363
+ | `week` | `{ year, week }` |
364
+ | `time` | `{ hours, minutes, seconds }` |
365
+ | Default | `string` |
@@ -0,0 +1,351 @@
1
+ # Form Validation
2
+
3
+ The `FormValidator` class provides form validation with HTML5 integration, error summaries, and custom validation support.
4
+
5
+ ## Basic Usage
6
+
7
+ ```typescript
8
+ import { FormValidator } from 'relaxjs/forms';
9
+
10
+ const form = document.querySelector('form');
11
+ const validator = new FormValidator(form, {
12
+ submitCallback: () => saveData()
13
+ });
14
+ ```
15
+
16
+ ## Configuration Options
17
+
18
+ ```typescript
19
+ interface ValidatorOptions {
20
+ autoValidate?: boolean; // Validate on every input event
21
+ useSummary?: boolean; // Show errors in summary element
22
+ customChecks?: (form: HTMLFormElement) => void; // Custom validation
23
+ preventDefault?: boolean; // Always prevent form submission
24
+ preventDefaultOnFailed?: boolean; // Prevent submission on failure (default: true)
25
+ submitCallback?: () => void; // Called when form is valid
26
+ }
27
+ ```
28
+
29
+ ## Auto-Validation
30
+
31
+ Enable real-time validation as users type:
32
+
33
+ ```typescript
34
+ const validator = new FormValidator(form, {
35
+ autoValidate: true,
36
+ useSummary: true
37
+ });
38
+ ```
39
+
40
+ ## Error Summary Display
41
+
42
+ Instead of browser tooltips, show errors in a summary element:
43
+
44
+ ```typescript
45
+ const validator = new FormValidator(form, {
46
+ useSummary: true,
47
+ submitCallback: () => handleSubmit()
48
+ });
49
+ ```
50
+
51
+ The error summary is automatically created and prepended to the form with:
52
+ - `role="alert"` for accessibility
53
+ - `aria-live="assertive"` for screen readers
54
+ - `class="error-summary"` for styling
55
+
56
+ ## Custom Validation
57
+
58
+ Add business logic validation beyond HTML5 constraints:
59
+
60
+ ```typescript
61
+ const validator = new FormValidator(form, {
62
+ useSummary: true,
63
+ customChecks: (form) => {
64
+ const password = form.querySelector('[name="password"]') as HTMLInputElement;
65
+ const confirm = form.querySelector('[name="confirmPassword"]') as HTMLInputElement;
66
+
67
+ if (password.value !== confirm.value) {
68
+ validator.addErrorToSummary('Password Confirmation', 'Passwords do not match');
69
+ }
70
+
71
+ const startDate = new Date(form.querySelector('[name="startDate"]').value);
72
+ const endDate = new Date(form.querySelector('[name="endDate"]').value);
73
+
74
+ if (endDate <= startDate) {
75
+ validator.addErrorToSummary('End Date', 'Must be after start date');
76
+ }
77
+ },
78
+ submitCallback: () => saveData()
79
+ });
80
+ ```
81
+
82
+ ## Manual Validation
83
+
84
+ Trigger validation programmatically:
85
+
86
+ ```typescript
87
+ if (validator.validateForm()) {
88
+ // Form is valid
89
+ proceedToNextStep();
90
+ }
91
+ ```
92
+
93
+ ## Error Summary Methods
94
+
95
+ ### addErrorToSummary
96
+
97
+ Add a single error to the summary:
98
+
99
+ ```typescript
100
+ validator.addErrorToSummary('Email', 'This email is already registered');
101
+ ```
102
+
103
+ ### displayErrorSummary
104
+
105
+ Display multiple errors at once:
106
+
107
+ ```typescript
108
+ validator.displayErrorSummary([
109
+ 'Name: This field is required',
110
+ 'Email: Invalid email format',
111
+ 'Age: Must be 18 or older'
112
+ ]);
113
+ ```
114
+
115
+ ### clearErrorSummary
116
+
117
+ Remove all errors from the summary:
118
+
119
+ ```typescript
120
+ validator.clearErrorSummary();
121
+ ```
122
+
123
+ ## Finding Forms
124
+
125
+ Use `FindForm` to locate a form relative to a custom element:
126
+
127
+ ```typescript
128
+ class MyComponent extends HTMLElement {
129
+ connectedCallback() {
130
+ const form = FormValidator.FindForm(this);
131
+ new FormValidator(form);
132
+ }
133
+ }
134
+ ```
135
+
136
+ `FindForm` searches:
137
+ 1. Parent element (if it's a form)
138
+ 2. Direct children
139
+
140
+ ## Built-in Validators
141
+
142
+ Declarative validation rules applied via the `data-validate` attribute. Multiple rules are space-separated.
143
+
144
+ ```html
145
+ <input name="age" type="number" data-validate="required range(0-120)" />
146
+ ```
147
+
148
+ Validation messages use the i18n system. Load the `r-validation` namespace for localized messages:
149
+
150
+ ```typescript
151
+ await loadNamespace('r-validation');
152
+ ```
153
+
154
+ Translation keys in the `r-validation` namespace:
155
+
156
+ | Key | Message (EN) | Message (SV) |
157
+ |-----|-------------|-------------|
158
+ | `required` | `This field is required.` | `Detta fält är obligatoriskt.` |
159
+ | `range` | `Number must be between {min} and {max}, was {actual}.` | `Talet måste vara mellan {min} och {max}, var {actual}.` |
160
+ | `digits` | `Please enter only digits.` | `Ange endast siffror.` |
161
+
162
+ ### required
163
+
164
+ Validates that the field has a non-empty value (whitespace-only fails).
165
+
166
+ ```html
167
+ <input name="username" data-validate="required" />
168
+ ```
169
+
170
+ ### range(min-max)
171
+
172
+ Validates that a numeric value falls within a range (inclusive). Supports negative numbers and decimals. Empty values are skipped (use with `required` if the field must be filled).
173
+
174
+ ```html
175
+ <input name="age" type="number" data-validate="range(0-120)" />
176
+ <input name="temperature" type="number" data-validate="range(-40-50)" />
177
+ <input name="ratio" type="number" data-validate="range(0.0-1.0)" />
178
+ ```
179
+
180
+ ### digits
181
+
182
+ Validates that the value contains only digits (0-9).
183
+
184
+ ```html
185
+ <input name="zipCode" data-validate="digits" />
186
+ ```
187
+
188
+ ## Custom Validators
189
+
190
+ Register your own validators using the `@RegisterValidator` decorator. The class must implement a `validate(value, context)` method.
191
+
192
+ ```typescript
193
+ import { RegisterValidator, ValidationContext } from 'relaxjs/forms';
194
+
195
+ @RegisterValidator('email')
196
+ class EmailValidation {
197
+ validate(value: string, context: ValidationContext) {
198
+ if (value && !value.includes('@')) {
199
+ context.addError('Invalid email address');
200
+ }
201
+ }
202
+ }
203
+ ```
204
+
205
+ Restrict a validator to specific input types with the second parameter:
206
+
207
+ ```typescript
208
+ @RegisterValidator('positive', ['number'])
209
+ class PositiveValidation {
210
+ validate(value: string, context: ValidationContext) {
211
+ if (value && parseFloat(value) < 0) {
212
+ context.addError('Value must be positive');
213
+ }
214
+ }
215
+ }
216
+ ```
217
+
218
+ ### ValidationContext
219
+
220
+ The context object passed to validators:
221
+
222
+ ```typescript
223
+ interface ValidationContext {
224
+ inputType: string; // The HTML input type
225
+ dataType?: string; // The data-type attribute value
226
+ addError(message: string): void; // Report a validation error
227
+ }
228
+ ```
229
+
230
+ ### Validator Registry
231
+
232
+ Look up registered validators programmatically:
233
+
234
+ ```typescript
235
+ import { getValidator } from 'relaxjs/forms';
236
+
237
+ const entry = getValidator('required');
238
+ // entry.validator is the class constructor
239
+ // entry.validInputTypes lists the input types this validator applies to
240
+ ```
241
+
242
+ ## File Upload Validation
243
+
244
+ ```typescript
245
+ class FileUploadForm {
246
+ private maxFileSize = 5 * 1024 * 1024; // 5MB
247
+ private allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
248
+
249
+ constructor(form: HTMLFormElement) {
250
+ const validator = new FormValidator(form, {
251
+ useSummary: true,
252
+ customChecks: (form) => this.validateFiles(form, validator),
253
+ submitCallback: () => this.handleSubmit()
254
+ });
255
+ }
256
+
257
+ validateFiles(form: HTMLFormElement, validator: FormValidator) {
258
+ const fileInputs = form.querySelectorAll('input[type="file"]');
259
+
260
+ fileInputs.forEach(input => {
261
+ const files = (input as HTMLInputElement).files;
262
+ if (!files) return;
263
+
264
+ Array.from(files).forEach(file => {
265
+ if (file.size > this.maxFileSize) {
266
+ validator.addErrorToSummary(
267
+ input.name,
268
+ `File "${file.name}" is too large (max 5MB)`
269
+ );
270
+ }
271
+
272
+ if (!this.allowedTypes.includes(file.type)) {
273
+ validator.addErrorToSummary(
274
+ input.name,
275
+ `File "${file.name}" has invalid type`
276
+ );
277
+ }
278
+ });
279
+ });
280
+ }
281
+
282
+ handleSubmit() {
283
+ const formData = new FormData(form);
284
+ // Upload files...
285
+ }
286
+ }
287
+ ```
288
+
289
+ ## Robust Error Handling
290
+
291
+ ```typescript
292
+ class RobustFormHandler {
293
+ private validator: FormValidator;
294
+
295
+ constructor(private form: HTMLFormElement) {
296
+ this.validator = new FormValidator(form, {
297
+ useSummary: true,
298
+ customChecks: (form) => this.comprehensiveValidation(form),
299
+ submitCallback: () => this.handleSubmissionWithRetry()
300
+ });
301
+ }
302
+
303
+ comprehensiveValidation(form: HTMLFormElement) {
304
+ try {
305
+ this.validateRequiredFields(form);
306
+ this.validateDataFormats(form);
307
+ this.validateBusinessRules(form);
308
+ } catch (error) {
309
+ console.error('Validation error:', error);
310
+ this.validator.addErrorToSummary('System', 'Validation failed. Please try again.');
311
+ }
312
+ }
313
+
314
+ validateDataFormats(form: HTMLFormElement) {
315
+ const emails = form.querySelectorAll('input[type="email"]');
316
+ emails.forEach(input => {
317
+ if (input.value && !this.isValidEmail(input.value)) {
318
+ this.validator.addErrorToSummary(input.name, 'Invalid email format');
319
+ }
320
+ });
321
+
322
+ const phones = form.querySelectorAll('input[data-type="phone"]');
323
+ phones.forEach(input => {
324
+ if (input.value && !this.isValidPhone(input.value)) {
325
+ this.validator.addErrorToSummary(input.name, 'Invalid phone number');
326
+ }
327
+ });
328
+ }
329
+
330
+ async handleSubmissionWithRetry() {
331
+ const maxRetries = 3;
332
+ let attempt = 0;
333
+
334
+ while (attempt < maxRetries) {
335
+ try {
336
+ const data = readData(this.form);
337
+ await this.submitToAPI(data);
338
+ this.showSuccess();
339
+ return;
340
+ } catch (error) {
341
+ attempt++;
342
+ if (attempt >= maxRetries) {
343
+ this.validator.addErrorToSummary('Submission', 'Failed to submit after multiple attempts');
344
+ } else {
345
+ await this.delay(1000 * attempt);
346
+ }
347
+ }
348
+ }
349
+ }
350
+ }
351
+ ```