@nuralyui/form 0.1.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.
- package/bundle.js +154 -0
- package/controllers/submission.controller.d.ts +60 -0
- package/controllers/submission.controller.d.ts.map +1 -0
- package/controllers/submission.controller.js +220 -0
- package/controllers/submission.controller.js.map +1 -0
- package/controllers/validation.controller.d.ts +87 -0
- package/controllers/validation.controller.d.ts.map +1 -0
- package/controllers/validation.controller.js +236 -0
- package/controllers/validation.controller.js.map +1 -0
- package/form.component.d.ts +279 -0
- package/form.component.js +644 -0
- package/form.style.d.ts +7 -0
- package/form.style.js +68 -0
- package/form.types.d.ts +92 -0
- package/form.types.js +38 -0
- package/index.d.ts +10 -0
- package/index.js +13 -0
- package/interfaces/validation.interface.d.ts +118 -0
- package/interfaces/validation.interface.d.ts.map +1 -0
- package/interfaces/validation.interface.js +7 -0
- package/interfaces/validation.interface.js.map +1 -0
- package/mixins/form-field-integration.mixin.d.ts +22 -0
- package/mixins/form-field-integration.mixin.d.ts.map +1 -0
- package/mixins/form-field-integration.mixin.js +78 -0
- package/mixins/form-field-integration.mixin.js.map +1 -0
- package/mixins/form-field-validation.mixin.d.ts +35 -0
- package/mixins/form-field-validation.mixin.d.ts.map +1 -0
- package/mixins/form-field-validation.mixin.js +300 -0
- package/mixins/form-field-validation.mixin.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2023 Nuraly, Laabidi Aymen
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
7
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
9
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
10
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
11
|
+
};
|
|
12
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
13
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
14
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
15
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
16
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
17
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
18
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
import { LitElement, html } from 'lit';
|
|
22
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
23
|
+
import { styles } from './form.style.js';
|
|
24
|
+
import { FormValidationState, FormSubmissionState, FORM_EVENTS } from './form.types.js';
|
|
25
|
+
import { NuralyUIBaseMixin } from '../../shared/base-mixin.js';
|
|
26
|
+
import { FormValidationController } from './controllers/validation.controller.js';
|
|
27
|
+
import { FormSubmissionController } from './controllers/submission.controller.js';
|
|
28
|
+
/**
|
|
29
|
+
* Comprehensive form component with field management and validation API
|
|
30
|
+
*
|
|
31
|
+
* Key Features:
|
|
32
|
+
* - Coordinates validation across all form fields (does NOT validate itself)
|
|
33
|
+
* - Handles form submission with built-in validation checks
|
|
34
|
+
* - Provides form state management and events
|
|
35
|
+
* - Integrates with existing component validation controllers
|
|
36
|
+
* - Supports both programmatic and user-driven interactions
|
|
37
|
+
* - Comprehensive API for field manipulation and validation
|
|
38
|
+
*
|
|
39
|
+
* @example Basic Usage
|
|
40
|
+
* ```html
|
|
41
|
+
* <nr-form @nr-form-submit-success="${handleSuccess}" validate-on-change>
|
|
42
|
+
* <nr-input name="username" required></nr-input>
|
|
43
|
+
* <nr-input name="email" type="email" required></nr-input>
|
|
44
|
+
* <nr-button type="submit">Submit</nr-button>
|
|
45
|
+
* </nr-form>
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example Programmatic Usage
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const form = document.querySelector('nr-form');
|
|
51
|
+
*
|
|
52
|
+
* // Set field values
|
|
53
|
+
* form.setFieldsValue({ username: 'john', email: 'john@example.com' });
|
|
54
|
+
*
|
|
55
|
+
* // Get field values
|
|
56
|
+
* const values = form.getFieldsValue();
|
|
57
|
+
*
|
|
58
|
+
* // Validate and submit
|
|
59
|
+
* try {
|
|
60
|
+
* const values = await form.finish();
|
|
61
|
+
* console.log('Form submitted:', values);
|
|
62
|
+
* } catch (errors) {
|
|
63
|
+
* console.log('Validation failed:', errors);
|
|
64
|
+
* }
|
|
65
|
+
*
|
|
66
|
+
* // Reset specific fields
|
|
67
|
+
* form.resetFields(['username']);
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @fires nr-form-validation-changed - Validation state changes
|
|
71
|
+
* @fires nr-form-field-changed - Individual field changes
|
|
72
|
+
* @fires nr-form-submit-attempt - Form submission attempted
|
|
73
|
+
* @fires nr-form-submit-success - Form submitted successfully
|
|
74
|
+
* @fires nr-form-submit-error - Form submission failed
|
|
75
|
+
* @fires nr-form-reset - Form was reset
|
|
76
|
+
*
|
|
77
|
+
* @slot default - Form content (inputs, buttons, etc.)
|
|
78
|
+
*/
|
|
79
|
+
let NrFormElement = class NrFormElement extends NuralyUIBaseMixin(LitElement) {
|
|
80
|
+
constructor() {
|
|
81
|
+
super(...arguments);
|
|
82
|
+
/** Form configuration */
|
|
83
|
+
this.config = {
|
|
84
|
+
validateOnChange: false,
|
|
85
|
+
validateOnBlur: true,
|
|
86
|
+
showErrorsImmediately: false,
|
|
87
|
+
preventInvalidSubmission: true,
|
|
88
|
+
resetOnSuccess: false,
|
|
89
|
+
validationDelay: 300
|
|
90
|
+
};
|
|
91
|
+
/** Enable real-time validation on field changes */
|
|
92
|
+
this.validateOnChange = false; // Default to false
|
|
93
|
+
/** Enable validation on field blur */
|
|
94
|
+
this.validateOnBlur = true;
|
|
95
|
+
/** Prevent form submission if validation fails */
|
|
96
|
+
this.preventInvalidSubmission = true;
|
|
97
|
+
/** Reset form after successful submission */
|
|
98
|
+
this.resetOnSuccess = false;
|
|
99
|
+
/** Form method for native submission */
|
|
100
|
+
this.method = 'POST';
|
|
101
|
+
/** Form encoding type */
|
|
102
|
+
this.enctype = 'multipart/form-data';
|
|
103
|
+
/** Disable the entire form */
|
|
104
|
+
this.disabled = false;
|
|
105
|
+
/** Form validation state */
|
|
106
|
+
this._validationState = FormValidationState.Pristine;
|
|
107
|
+
/** Form submission state */
|
|
108
|
+
this._submissionState = FormSubmissionState.Idle;
|
|
109
|
+
/** Validation controller */
|
|
110
|
+
this.validationController = new FormValidationController(this);
|
|
111
|
+
/** Submission controller */
|
|
112
|
+
this.submissionController = new FormSubmissionController(this);
|
|
113
|
+
/**
|
|
114
|
+
* Handle validation state changes
|
|
115
|
+
*/
|
|
116
|
+
this.handleValidationChanged = (event) => {
|
|
117
|
+
const customEvent = event;
|
|
118
|
+
const result = customEvent.detail.validationResult;
|
|
119
|
+
if (result) {
|
|
120
|
+
this._validationState = result.isValid ?
|
|
121
|
+
FormValidationState.Valid :
|
|
122
|
+
FormValidationState.Invalid;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** Get current validation state */
|
|
127
|
+
get validationState() {
|
|
128
|
+
return this._validationState;
|
|
129
|
+
}
|
|
130
|
+
/** Get current submission state */
|
|
131
|
+
get submissionState() {
|
|
132
|
+
return this._submissionState;
|
|
133
|
+
}
|
|
134
|
+
connectedCallback() {
|
|
135
|
+
super.connectedCallback();
|
|
136
|
+
this.setupFormObserver();
|
|
137
|
+
}
|
|
138
|
+
disconnectedCallback() {
|
|
139
|
+
super.disconnectedCallback();
|
|
140
|
+
this.cleanupFormObserver();
|
|
141
|
+
}
|
|
142
|
+
willUpdate(changedProperties) {
|
|
143
|
+
super.willUpdate(changedProperties);
|
|
144
|
+
// Update config when properties change
|
|
145
|
+
if (changedProperties.has('validateOnChange') ||
|
|
146
|
+
changedProperties.has('validateOnBlur') ||
|
|
147
|
+
changedProperties.has('preventInvalidSubmission') ||
|
|
148
|
+
changedProperties.has('resetOnSuccess')) {
|
|
149
|
+
this.config = Object.assign(Object.assign({}, this.config), { validateOnChange: this.validateOnChange, validateOnBlur: this.validateOnBlur, preventInvalidSubmission: this.preventInvalidSubmission, resetOnSuccess: this.resetOnSuccess });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
firstUpdated() {
|
|
153
|
+
this.registerExistingFields();
|
|
154
|
+
this.setupFormEvents();
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Setup mutation observer to detect new form fields
|
|
158
|
+
*/
|
|
159
|
+
setupFormObserver() {
|
|
160
|
+
// Implementation for observing DOM changes to register new fields
|
|
161
|
+
const observer = new MutationObserver((mutations) => {
|
|
162
|
+
mutations.forEach(mutation => {
|
|
163
|
+
mutation.addedNodes.forEach(node => {
|
|
164
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
165
|
+
this.registerFieldsInElement(node);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
observer.observe(this, {
|
|
171
|
+
childList: true,
|
|
172
|
+
subtree: true
|
|
173
|
+
});
|
|
174
|
+
this._formObserver = observer;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Cleanup mutation observer
|
|
178
|
+
*/
|
|
179
|
+
cleanupFormObserver() {
|
|
180
|
+
const observer = this._formObserver;
|
|
181
|
+
if (observer) {
|
|
182
|
+
observer.disconnect();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Register existing form fields
|
|
187
|
+
*/
|
|
188
|
+
registerExistingFields() {
|
|
189
|
+
this.registerFieldsInElement(this);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Register form fields in an element
|
|
193
|
+
*/
|
|
194
|
+
registerFieldsInElement(element) {
|
|
195
|
+
const selectors = [
|
|
196
|
+
'nr-input', 'nr-select', 'nr-radio', 'nr-checkbox',
|
|
197
|
+
'nr-textarea', 'nr-timepicker', 'nr-datepicker'
|
|
198
|
+
];
|
|
199
|
+
selectors.forEach(selector => {
|
|
200
|
+
const fields = element.querySelectorAll(selector);
|
|
201
|
+
fields.forEach(field => {
|
|
202
|
+
if (field.getAttribute('name')) {
|
|
203
|
+
this.validationController.registerField(field);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Setup form events
|
|
210
|
+
*/
|
|
211
|
+
setupFormEvents() {
|
|
212
|
+
// Listen for form submission
|
|
213
|
+
this.addEventListener('submit', this.handleFormSubmit);
|
|
214
|
+
// Listen for form reset
|
|
215
|
+
this.addEventListener('reset', this.handleFormReset);
|
|
216
|
+
// Listen for validation events
|
|
217
|
+
this.addEventListener(FORM_EVENTS.VALIDATION_CHANGED, this.handleValidationChanged);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Handle form submission
|
|
221
|
+
*/
|
|
222
|
+
handleFormSubmit(event) {
|
|
223
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
224
|
+
event.preventDefault();
|
|
225
|
+
if (this.disabled) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
const submissionData = yield this.submissionController.submitForm();
|
|
230
|
+
if (this.resetOnSuccess) {
|
|
231
|
+
this.reset();
|
|
232
|
+
}
|
|
233
|
+
// If action is specified, perform native submission
|
|
234
|
+
if (this.action) {
|
|
235
|
+
this.performNativeSubmission(submissionData.formData);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
console.error('Form submission failed:', error);
|
|
240
|
+
// Error events are already dispatched by submission controller
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Handle form reset
|
|
246
|
+
*/
|
|
247
|
+
handleFormReset(event) {
|
|
248
|
+
event.preventDefault();
|
|
249
|
+
this.reset();
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Perform native form submission
|
|
253
|
+
*/
|
|
254
|
+
performNativeSubmission(formData) {
|
|
255
|
+
const form = document.createElement('form');
|
|
256
|
+
form.action = this.action;
|
|
257
|
+
form.method = this.method;
|
|
258
|
+
form.enctype = this.enctype;
|
|
259
|
+
if (this.target)
|
|
260
|
+
form.target = this.target;
|
|
261
|
+
// Add form data as hidden inputs
|
|
262
|
+
for (const [name, value] of formData.entries()) {
|
|
263
|
+
const input = document.createElement('input');
|
|
264
|
+
input.type = 'hidden';
|
|
265
|
+
input.name = name;
|
|
266
|
+
input.value = value;
|
|
267
|
+
form.appendChild(input);
|
|
268
|
+
}
|
|
269
|
+
document.body.appendChild(form);
|
|
270
|
+
form.submit();
|
|
271
|
+
document.body.removeChild(form);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Validate the form
|
|
275
|
+
*/
|
|
276
|
+
validate() {
|
|
277
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
278
|
+
const result = yield this.validationController.validateForm();
|
|
279
|
+
return result.isValid;
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Submit the form programmatically
|
|
284
|
+
*/
|
|
285
|
+
submit(customData) {
|
|
286
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
287
|
+
yield this.submissionController.submitForm(customData);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Reset the form
|
|
292
|
+
*/
|
|
293
|
+
reset() {
|
|
294
|
+
this.validationController.reset();
|
|
295
|
+
this.submissionController.resetSubmission();
|
|
296
|
+
this._validationState = FormValidationState.Pristine;
|
|
297
|
+
this._submissionState = FormSubmissionState.Idle;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Check if form is valid
|
|
301
|
+
*/
|
|
302
|
+
get isValid() {
|
|
303
|
+
return this.validationController.isValid();
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Check if form is submitting
|
|
307
|
+
*/
|
|
308
|
+
get isSubmitting() {
|
|
309
|
+
return this.submissionController.isSubmitting();
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get form data
|
|
313
|
+
*/
|
|
314
|
+
getFormData() {
|
|
315
|
+
return this.submissionController.collectFormData();
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Get invalid fields
|
|
319
|
+
*/
|
|
320
|
+
getInvalidFields() {
|
|
321
|
+
return this.validationController.getInvalidFields();
|
|
322
|
+
}
|
|
323
|
+
// ============================================
|
|
324
|
+
// FORM API METHODS
|
|
325
|
+
// ============================================
|
|
326
|
+
/**
|
|
327
|
+
* Get values of all fields
|
|
328
|
+
* @returns Object containing all field values
|
|
329
|
+
*/
|
|
330
|
+
getFieldsValue(nameList) {
|
|
331
|
+
const formData = this.getFormData();
|
|
332
|
+
const values = formData.jsonData;
|
|
333
|
+
if (nameList && nameList.length > 0) {
|
|
334
|
+
const filteredValues = {};
|
|
335
|
+
nameList.forEach(name => {
|
|
336
|
+
if (name in values) {
|
|
337
|
+
filteredValues[name] = values[name];
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
return filteredValues;
|
|
341
|
+
}
|
|
342
|
+
return values;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Get value of specific field
|
|
346
|
+
* @param name Field name
|
|
347
|
+
* @returns Field value
|
|
348
|
+
*/
|
|
349
|
+
getFieldValue(name) {
|
|
350
|
+
const values = this.getFieldsValue();
|
|
351
|
+
return values[name];
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Set values of fields
|
|
355
|
+
* @param values Object containing field values to set
|
|
356
|
+
*/
|
|
357
|
+
setFieldsValue(values) {
|
|
358
|
+
Object.entries(values).forEach(([name, value]) => {
|
|
359
|
+
this.setFieldValue(name, value);
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Set value of specific field
|
|
364
|
+
* @param name Field name
|
|
365
|
+
* @param value Field value
|
|
366
|
+
*/
|
|
367
|
+
setFieldValue(name, value) {
|
|
368
|
+
const fields = this.validationController.getFields();
|
|
369
|
+
const field = fields.find(f => f.name === name);
|
|
370
|
+
if (field && field.element) {
|
|
371
|
+
field.element.value = value;
|
|
372
|
+
field.value = value;
|
|
373
|
+
// Trigger change event to update validation
|
|
374
|
+
field.element.dispatchEvent(new Event('change', { bubbles: true }));
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Validate specific fields
|
|
379
|
+
* @param nameList Array of field names to validate, if empty validates all
|
|
380
|
+
* @returns Promise with validation result
|
|
381
|
+
*/
|
|
382
|
+
validateFields(nameList) {
|
|
383
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
384
|
+
const result = yield this.validationController.validateForm();
|
|
385
|
+
if (nameList && nameList.length > 0) {
|
|
386
|
+
// Filter validation errors for specific fields
|
|
387
|
+
const filteredErrors = {};
|
|
388
|
+
nameList.forEach(name => {
|
|
389
|
+
if (result.validationErrors[name]) {
|
|
390
|
+
filteredErrors[name] = result.validationErrors[name];
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
if (Object.keys(filteredErrors).length > 0) {
|
|
394
|
+
throw new Error(JSON.stringify(filteredErrors));
|
|
395
|
+
}
|
|
396
|
+
return this.getFieldsValue(nameList);
|
|
397
|
+
}
|
|
398
|
+
if (!result.isValid) {
|
|
399
|
+
throw new Error(JSON.stringify(result.validationErrors));
|
|
400
|
+
}
|
|
401
|
+
return this.getFieldsValue();
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Reset specific fields
|
|
406
|
+
* @param nameList Array of field names to reset, if empty resets all
|
|
407
|
+
*/
|
|
408
|
+
resetFields(nameList) {
|
|
409
|
+
if (!nameList || nameList.length === 0) {
|
|
410
|
+
this.reset();
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const fields = this.validationController.getFields();
|
|
414
|
+
nameList.forEach(name => {
|
|
415
|
+
const field = fields.find(f => f.name === name);
|
|
416
|
+
if (field && field.element) {
|
|
417
|
+
// Reset value
|
|
418
|
+
field.element.value = '';
|
|
419
|
+
field.value = '';
|
|
420
|
+
field.touched = false;
|
|
421
|
+
field.dirty = false;
|
|
422
|
+
field.isValid = true;
|
|
423
|
+
field.validationMessage = '';
|
|
424
|
+
// Trigger change event
|
|
425
|
+
field.element.dispatchEvent(new Event('change', { bubbles: true }));
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Get field error
|
|
431
|
+
* @param name Field name
|
|
432
|
+
* @returns Field error message or null
|
|
433
|
+
*/
|
|
434
|
+
getFieldError(name) {
|
|
435
|
+
const fields = this.validationController.getFields();
|
|
436
|
+
const field = fields.find(f => f.name === name);
|
|
437
|
+
return field && !field.isValid ? field.validationMessage : null;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Get all field errors
|
|
441
|
+
* @param nameList Array of field names, if empty returns all
|
|
442
|
+
* @returns Object containing field errors
|
|
443
|
+
*/
|
|
444
|
+
getFieldsError(nameList) {
|
|
445
|
+
const fields = this.validationController.getFields();
|
|
446
|
+
const errors = {};
|
|
447
|
+
const targetFields = nameList
|
|
448
|
+
? fields.filter(f => nameList.includes(f.name))
|
|
449
|
+
: fields;
|
|
450
|
+
targetFields.forEach(field => {
|
|
451
|
+
errors[field.name] = field.isValid ? null : field.validationMessage;
|
|
452
|
+
});
|
|
453
|
+
return errors;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Check if field has been touched
|
|
457
|
+
* @param name Field name
|
|
458
|
+
* @returns Whether field has been touched
|
|
459
|
+
*/
|
|
460
|
+
isFieldTouched(name) {
|
|
461
|
+
const fields = this.validationController.getFields();
|
|
462
|
+
const field = fields.find(f => f.name === name);
|
|
463
|
+
return field ? field.touched : false;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Check if any fields have been touched
|
|
467
|
+
* @param nameList Array of field names, if empty checks all
|
|
468
|
+
* @returns Whether any of the specified fields have been touched
|
|
469
|
+
*/
|
|
470
|
+
isFieldsTouched(nameList) {
|
|
471
|
+
const fields = this.validationController.getFields();
|
|
472
|
+
const targetFields = nameList
|
|
473
|
+
? fields.filter(f => nameList.includes(f.name))
|
|
474
|
+
: fields;
|
|
475
|
+
return targetFields.some(field => field.touched);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Check if field value has been modified
|
|
479
|
+
* @param name Field name
|
|
480
|
+
* @returns Whether field has been modified
|
|
481
|
+
*/
|
|
482
|
+
isFieldDirty(name) {
|
|
483
|
+
const fields = this.validationController.getFields();
|
|
484
|
+
const field = fields.find(f => f.name === name);
|
|
485
|
+
return field ? field.dirty : false;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Check if any fields have been modified
|
|
489
|
+
* @param nameList Array of field names, if empty checks all
|
|
490
|
+
* @returns Whether any of the specified fields have been modified
|
|
491
|
+
*/
|
|
492
|
+
isFieldsDirty(nameList) {
|
|
493
|
+
const fields = this.validationController.getFields();
|
|
494
|
+
const targetFields = nameList
|
|
495
|
+
? fields.filter(f => nameList.includes(f.name))
|
|
496
|
+
: fields;
|
|
497
|
+
return targetFields.some(field => field.dirty);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Get field instance
|
|
501
|
+
* @param name Field name
|
|
502
|
+
* @returns Field element or null
|
|
503
|
+
*/
|
|
504
|
+
getFieldInstance(name) {
|
|
505
|
+
const fields = this.validationController.getFields();
|
|
506
|
+
const field = fields.find(f => f.name === name);
|
|
507
|
+
return field ? field.element : null;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Scroll to first error field
|
|
511
|
+
* @returns Whether scrolled to a field
|
|
512
|
+
*/
|
|
513
|
+
scrollToField(name) {
|
|
514
|
+
if (name) {
|
|
515
|
+
const fieldElement = this.getFieldInstance(name);
|
|
516
|
+
if (fieldElement) {
|
|
517
|
+
fieldElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
518
|
+
if (typeof fieldElement.focus === 'function') {
|
|
519
|
+
fieldElement.focus();
|
|
520
|
+
}
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
// Scroll to first invalid field
|
|
526
|
+
return this.validationController.focusFirstInvalidField();
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Submit form and validate
|
|
530
|
+
* @returns Promise with form values
|
|
531
|
+
*/
|
|
532
|
+
finish() {
|
|
533
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
534
|
+
try {
|
|
535
|
+
const values = yield this.validateFields();
|
|
536
|
+
yield this.submit();
|
|
537
|
+
return values;
|
|
538
|
+
}
|
|
539
|
+
catch (error) {
|
|
540
|
+
this.scrollToField();
|
|
541
|
+
throw error;
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Get field names that have validation errors
|
|
547
|
+
* @returns Array of field names with errors
|
|
548
|
+
*/
|
|
549
|
+
getFieldsWithErrors() {
|
|
550
|
+
const fields = this.validationController.getFields();
|
|
551
|
+
return fields
|
|
552
|
+
.filter(field => !field.isValid)
|
|
553
|
+
.map(field => field.name);
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Check if form has any validation errors
|
|
557
|
+
* @returns Whether form has errors
|
|
558
|
+
*/
|
|
559
|
+
hasErrors() {
|
|
560
|
+
return this.getFieldsWithErrors().length > 0;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Get summary of form state
|
|
564
|
+
* @returns Object with form state information
|
|
565
|
+
*/
|
|
566
|
+
getFormState() {
|
|
567
|
+
const fields = this.validationController.getFields();
|
|
568
|
+
return {
|
|
569
|
+
isValid: this.isValid,
|
|
570
|
+
isSubmitting: this.isSubmitting,
|
|
571
|
+
hasErrors: this.hasErrors(),
|
|
572
|
+
errorCount: this.getFieldsWithErrors().length,
|
|
573
|
+
fieldCount: fields.length,
|
|
574
|
+
touchedFields: fields.filter(f => f.touched).map(f => f.name),
|
|
575
|
+
dirtyFields: fields.filter(f => f.dirty).map(f => f.name),
|
|
576
|
+
invalidFields: this.getFieldsWithErrors()
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Set form loading state (useful for async operations)
|
|
581
|
+
* @param loading Whether form is in loading state
|
|
582
|
+
*/
|
|
583
|
+
setLoading(loading) {
|
|
584
|
+
this.disabled = loading;
|
|
585
|
+
this.requestUpdate();
|
|
586
|
+
}
|
|
587
|
+
render() {
|
|
588
|
+
return html `
|
|
589
|
+
<form
|
|
590
|
+
action="${this.action || ''}"
|
|
591
|
+
method="${this.method.toLowerCase()}"
|
|
592
|
+
enctype="${this.enctype}"
|
|
593
|
+
target="${this.target || ''}"
|
|
594
|
+
class="form-wrapper"
|
|
595
|
+
data-disabled="${this.disabled}"
|
|
596
|
+
novalidate
|
|
597
|
+
>
|
|
598
|
+
<slot></slot>
|
|
599
|
+
</form>
|
|
600
|
+
`;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
NrFormElement.styles = styles;
|
|
604
|
+
__decorate([
|
|
605
|
+
property({ type: Object })
|
|
606
|
+
], NrFormElement.prototype, "config", void 0);
|
|
607
|
+
__decorate([
|
|
608
|
+
property({ type: Boolean, attribute: 'validate-on-change' })
|
|
609
|
+
], NrFormElement.prototype, "validateOnChange", void 0);
|
|
610
|
+
__decorate([
|
|
611
|
+
property({ type: Boolean, attribute: 'validate-on-blur' })
|
|
612
|
+
], NrFormElement.prototype, "validateOnBlur", void 0);
|
|
613
|
+
__decorate([
|
|
614
|
+
property({ type: Boolean, attribute: 'prevent-invalid-submission' })
|
|
615
|
+
], NrFormElement.prototype, "preventInvalidSubmission", void 0);
|
|
616
|
+
__decorate([
|
|
617
|
+
property({ type: Boolean, attribute: 'reset-on-success' })
|
|
618
|
+
], NrFormElement.prototype, "resetOnSuccess", void 0);
|
|
619
|
+
__decorate([
|
|
620
|
+
property({ type: String })
|
|
621
|
+
], NrFormElement.prototype, "action", void 0);
|
|
622
|
+
__decorate([
|
|
623
|
+
property({ type: String })
|
|
624
|
+
], NrFormElement.prototype, "method", void 0);
|
|
625
|
+
__decorate([
|
|
626
|
+
property({ type: String, attribute: 'enctype' })
|
|
627
|
+
], NrFormElement.prototype, "enctype", void 0);
|
|
628
|
+
__decorate([
|
|
629
|
+
property({ type: String })
|
|
630
|
+
], NrFormElement.prototype, "target", void 0);
|
|
631
|
+
__decorate([
|
|
632
|
+
property({ type: Boolean, reflect: true })
|
|
633
|
+
], NrFormElement.prototype, "disabled", void 0);
|
|
634
|
+
__decorate([
|
|
635
|
+
state()
|
|
636
|
+
], NrFormElement.prototype, "_validationState", void 0);
|
|
637
|
+
__decorate([
|
|
638
|
+
state()
|
|
639
|
+
], NrFormElement.prototype, "_submissionState", void 0);
|
|
640
|
+
NrFormElement = __decorate([
|
|
641
|
+
customElement('nr-form')
|
|
642
|
+
], NrFormElement);
|
|
643
|
+
export { NrFormElement };
|
|
644
|
+
//# sourceMappingURL=form.component.js.map
|