@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
@@ -1,351 +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
- ```
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 '@relax.js/core/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 '@relax.js/core/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 '@relax.js/core/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
+ ```