@coherent.js/forms 1.0.1 → 1.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/dist/form-builder.js +86 -48
- package/dist/form-builder.js.map +2 -2
- package/dist/form-hydration.js +30 -5
- package/dist/form-hydration.js.map +4 -4
- package/dist/index.js +101 -53
- package/dist/index.js.map +3 -3
- package/package.json +3 -3
- package/types/index.d.ts +65 -3
package/dist/form-builder.js
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
// src/form-builder.js
|
|
2
2
|
import { render as renderToHTML } from "@coherent.js/core";
|
|
3
|
+
var DEFAULT_CLASS_NAMES = {
|
|
4
|
+
/** Wrapper around label, control and error */
|
|
5
|
+
field: "form-field",
|
|
6
|
+
label: "",
|
|
7
|
+
/** Base class on the control, before any per-field className */
|
|
8
|
+
control: "",
|
|
9
|
+
/** Added to the control while it has a visible error */
|
|
10
|
+
invalid: "error",
|
|
11
|
+
/** The error message element */
|
|
12
|
+
error: "error-message",
|
|
13
|
+
submit: "submit-button"
|
|
14
|
+
};
|
|
15
|
+
var VALID_ATTRIBUTE_NAME = /^[A-Za-z_:][-A-Za-z0-9_:.]*$/;
|
|
16
|
+
var EVENT_HANDLER_NAME = /^on/i;
|
|
17
|
+
function safeAttributes(attributes) {
|
|
18
|
+
if (!attributes || typeof attributes !== "object") return {};
|
|
19
|
+
const safe = {};
|
|
20
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
21
|
+
if (!VALID_ATTRIBUTE_NAME.test(name)) {
|
|
22
|
+
console.warn(`[coherent.js/forms] Ignoring invalid attribute name: ${JSON.stringify(name)}`);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (EVENT_HANDLER_NAME.test(name)) {
|
|
26
|
+
console.warn(
|
|
27
|
+
`[coherent.js/forms] Ignoring inline event handler "${name}". Attach handlers with hydrateForm instead.`
|
|
28
|
+
);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
safe[name] = value;
|
|
32
|
+
}
|
|
33
|
+
return safe;
|
|
34
|
+
}
|
|
35
|
+
function joinClasses(...names) {
|
|
36
|
+
return names.filter(Boolean).join(" ");
|
|
37
|
+
}
|
|
3
38
|
var FormBuilder = class {
|
|
4
39
|
constructor(options = {}) {
|
|
5
40
|
this.options = {
|
|
@@ -227,11 +262,8 @@ var FormBuilder = class {
|
|
|
227
262
|
*/
|
|
228
263
|
validate() {
|
|
229
264
|
const errors = {};
|
|
230
|
-
for (const [name
|
|
231
|
-
|
|
232
|
-
if (showCondition && !showCondition(this.values)) {
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
265
|
+
for (const [name] of this.fields) {
|
|
266
|
+
if (!this.isFieldVisible(name)) continue;
|
|
235
267
|
const error = this.validateField(name);
|
|
236
268
|
if (error) {
|
|
237
269
|
errors[name] = error;
|
|
@@ -299,7 +331,7 @@ var FormBuilder = class {
|
|
|
299
331
|
/**
|
|
300
332
|
* Build input component with validation metadata for hydration
|
|
301
333
|
*/
|
|
302
|
-
buildInput(name) {
|
|
334
|
+
buildInput(name, classNames = this.resolveClassNames()) {
|
|
303
335
|
const field = this.fields.get(name);
|
|
304
336
|
if (!field) return null;
|
|
305
337
|
const value = this.values[name] || "";
|
|
@@ -310,16 +342,26 @@ var FormBuilder = class {
|
|
|
310
342
|
if (typeof v === "string") return v;
|
|
311
343
|
return null;
|
|
312
344
|
}).filter(Boolean).join(",");
|
|
345
|
+
const controlClass = joinClasses(
|
|
346
|
+
classNames.control,
|
|
347
|
+
field.className,
|
|
348
|
+
error && isTouched ? classNames.invalid : null
|
|
349
|
+
);
|
|
313
350
|
const inputProps = {
|
|
351
|
+
// Spread first: name, id, type and the aria-* pair below are the
|
|
352
|
+
// builder's own invariants, and hydration keys off them.
|
|
353
|
+
...safeAttributes(field.attributes),
|
|
314
354
|
type: field.type,
|
|
315
355
|
name: field.name,
|
|
316
356
|
id: field.name,
|
|
317
357
|
value,
|
|
318
|
-
placeholder: field.placeholder,
|
|
319
358
|
"aria-invalid": error ? "true" : "false",
|
|
320
|
-
"aria-describedby": error ? `${name}-error` : void 0
|
|
321
|
-
className: error && isTouched ? "error" : ""
|
|
359
|
+
"aria-describedby": error ? `${name}-error` : void 0
|
|
322
360
|
};
|
|
361
|
+
if (field.placeholder) inputProps.placeholder = field.placeholder;
|
|
362
|
+
if (controlClass) inputProps.className = controlClass;
|
|
363
|
+
if (field.disabled) inputProps.disabled = true;
|
|
364
|
+
if (field.readonly) inputProps.readonly = true;
|
|
323
365
|
if (field.required) {
|
|
324
366
|
inputProps.required = true;
|
|
325
367
|
inputProps["data-required"] = "true";
|
|
@@ -357,71 +399,63 @@ var FormBuilder = class {
|
|
|
357
399
|
/**
|
|
358
400
|
* Build label component
|
|
359
401
|
*/
|
|
360
|
-
buildLabel(name) {
|
|
402
|
+
buildLabel(name, classNames = this.resolveClassNames()) {
|
|
361
403
|
const field = this.fields.get(name);
|
|
362
404
|
if (!field) return null;
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
text: field.label
|
|
367
|
-
}
|
|
368
|
-
};
|
|
405
|
+
const props = { for: field.name, text: field.label };
|
|
406
|
+
if (classNames.label) props.className = classNames.label;
|
|
407
|
+
return { label: props };
|
|
369
408
|
}
|
|
370
409
|
/**
|
|
371
410
|
* Build error component
|
|
372
411
|
*/
|
|
373
|
-
buildError(name) {
|
|
412
|
+
buildError(name, classNames = this.resolveClassNames()) {
|
|
374
413
|
const error = this.errors[name];
|
|
375
414
|
const isTouched = this.touched[name];
|
|
376
415
|
if (!error || !isTouched) return null;
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
416
|
+
const props = { id: `${name}-error`, role: "alert", text: error };
|
|
417
|
+
if (classNames.error) props.className = classNames.error;
|
|
418
|
+
return { div: props };
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Merge configured class names over the defaults
|
|
422
|
+
*/
|
|
423
|
+
resolveClassNames(overrides = {}) {
|
|
424
|
+
return { ...DEFAULT_CLASS_NAMES, ...this.options.classNames, ...overrides };
|
|
385
425
|
}
|
|
386
426
|
/**
|
|
387
427
|
* Build complete field component
|
|
388
428
|
*/
|
|
389
|
-
buildField(name) {
|
|
429
|
+
buildField(name, classNames = this.resolveClassNames()) {
|
|
390
430
|
const field = this.fields.get(name);
|
|
391
431
|
if (!field) return null;
|
|
392
432
|
const children = [
|
|
393
|
-
this.buildLabel(name),
|
|
394
|
-
this.buildInput(name)
|
|
433
|
+
this.buildLabel(name, classNames),
|
|
434
|
+
this.buildInput(name, classNames)
|
|
395
435
|
];
|
|
396
|
-
const error = this.buildError(name);
|
|
436
|
+
const error = this.buildError(name, classNames);
|
|
397
437
|
if (error) {
|
|
398
438
|
children.push(error);
|
|
399
439
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
"data-field": name,
|
|
404
|
-
children
|
|
405
|
-
}
|
|
406
|
-
};
|
|
440
|
+
const props = { "data-field": name, children };
|
|
441
|
+
if (classNames.field) props.className = classNames.field;
|
|
442
|
+
return { div: props };
|
|
407
443
|
}
|
|
408
444
|
/**
|
|
409
445
|
* Build entire form
|
|
410
446
|
*/
|
|
411
447
|
buildForm(options = {}) {
|
|
448
|
+
const settings = { ...this.options, ...options };
|
|
449
|
+
const classNames = this.resolveClassNames(options.classNames);
|
|
412
450
|
const fields = [];
|
|
413
451
|
for (const [name] of this.fields) {
|
|
414
|
-
|
|
452
|
+
if (!this.isFieldVisible(name)) continue;
|
|
453
|
+
fields.push(this.buildField(name, classNames));
|
|
415
454
|
}
|
|
416
|
-
const settings = { ...this.options, ...options };
|
|
417
455
|
if (settings.submitButton !== false) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
text: settings.submitText || "Submit",
|
|
422
|
-
className: "submit-button"
|
|
423
|
-
}
|
|
424
|
-
});
|
|
456
|
+
const button = { type: "submit", text: settings.submitText || "Submit" };
|
|
457
|
+
if (classNames.submit) button.className = classNames.submit;
|
|
458
|
+
fields.push({ button });
|
|
425
459
|
}
|
|
426
460
|
const form = {};
|
|
427
461
|
if (settings.action) form.action = settings.action;
|
|
@@ -430,8 +464,10 @@ var FormBuilder = class {
|
|
|
430
464
|
if (settings.id) form.id = settings.id;
|
|
431
465
|
if (settings.className) form.className = settings.className;
|
|
432
466
|
if (settings.enctype) form.enctype = settings.enctype;
|
|
433
|
-
|
|
434
|
-
|
|
467
|
+
if (settings.enhance) {
|
|
468
|
+
form.onsubmit = typeof settings.enhance === "string" ? settings.enhance : "handleSubmit(event)";
|
|
469
|
+
}
|
|
470
|
+
if (settings.novalidate === true) form.novalidate = true;
|
|
435
471
|
form.children = fields;
|
|
436
472
|
return { form };
|
|
437
473
|
}
|
|
@@ -479,11 +515,12 @@ var FormBuilder = class {
|
|
|
479
515
|
isFieldVisible(name) {
|
|
480
516
|
const field = this.fields.get(name);
|
|
481
517
|
if (!field) return false;
|
|
518
|
+
if (field.visible === false) return false;
|
|
482
519
|
const showCondition = field.showWhen || field.showIf;
|
|
483
520
|
if (showCondition) {
|
|
484
521
|
return showCondition(this.values);
|
|
485
522
|
}
|
|
486
|
-
return
|
|
523
|
+
return true;
|
|
487
524
|
}
|
|
488
525
|
/**
|
|
489
526
|
* Reset form
|
|
@@ -532,6 +569,7 @@ var form_builder_default = {
|
|
|
532
569
|
buildForm
|
|
533
570
|
};
|
|
534
571
|
export {
|
|
572
|
+
DEFAULT_CLASS_NAMES,
|
|
535
573
|
FormBuilder,
|
|
536
574
|
buildForm,
|
|
537
575
|
createFormBuilder,
|
package/dist/form-builder.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/form-builder.js"],
|
|
4
|
-
"sourcesContent": ["/**\n * Coherent.js Form Builder\n * \n * Utilities for building forms with Coherent.js\n * \n * @module forms/form-builder\n */\n\nimport { render as renderToHTML } from '@coherent.js/core';\n\n/**\n * Form Builder\n * Helps create form components with validation\n */\nexport class FormBuilder {\n constructor(options = {}) {\n this.options = {\n validateOnChange: true,\n validateOnBlur: true,\n name: options.name || 'form',\n ...options\n };\n \n this.fields = new Map();\n this.groups = new Map();\n this.values = {};\n this.errors = {};\n this.touched = {};\n this.initialValues = {};\n this.submitHandler = null;\n this.errorHandler = null;\n this._isSubmitting = false;\n }\n\n /**\n * Add a field to the form (alias for field)\n */\n addField(name, config = {}) {\n return this.field(name, config);\n }\n\n /**\n * Add a field to the form\n */\n field(name, config = {}) {\n const fieldConfig = {\n name,\n type: config.type || 'text',\n label: config.label || name,\n placeholder: config.placeholder || '',\n defaultValue: config.defaultValue || '',\n validators: config.validators || [],\n required: config.required || false,\n visible: config.visible !== false,\n showWhen: config.showWhen,\n ...config\n };\n \n this.fields.set(name, fieldConfig);\n\n // Set default value\n if (config.defaultValue !== undefined) {\n this.values[name] = config.defaultValue;\n this.initialValues[name] = config.defaultValue;\n }\n\n return this;\n }\n\n /**\n * Remove a field from the form\n */\n removeField(name) {\n this.fields.delete(name);\n delete this.values[name];\n delete this.errors[name];\n delete this.touched[name];\n return this;\n }\n\n /**\n * Update field configuration\n */\n updateField(name, config) {\n const field = this.fields.get(name);\n if (field) {\n this.fields.set(name, { ...field, ...config });\n }\n return this;\n }\n\n /**\n * Get all fields as array\n */\n getFields() {\n return Array.from(this.fields.values());\n }\n\n /**\n * Add a field group\n */\n addGroup(name, config = {}) {\n this.groups.set(name, {\n name,\n label: config.label || name,\n fields: config.fields || [],\n ...config\n });\n\n // Add fields in the group\n if (config.fields) {\n config.fields.forEach(fieldConfig => {\n this.addField(fieldConfig.name, fieldConfig);\n });\n }\n\n return this;\n }\n\n /**\n * Get field configuration\n */\n getField(name) {\n return this.fields.get(name);\n }\n\n /**\n * Set field value\n */\n setValue(name, value) {\n this.values[name] = value;\n this.touched[name] = true;\n\n const field = this.fields.get(name);\n if (field && (field.validateOnChange || this.options.validateOnChange)) {\n this.validateField(name);\n }\n }\n\n /**\n * Set multiple values\n */\n setValues(values) {\n Object.assign(this.values, values);\n return this;\n }\n\n /**\n * Get field value\n */\n getValue(name) {\n return this.values[name];\n }\n\n /**\n * Get all values\n */\n getValues() {\n return { ...this.values };\n }\n\n /**\n * Get field error\n */\n getFieldError(name) {\n return this.errors[name];\n }\n\n /**\n * Check if form has errors\n */\n hasErrors() {\n return Object.keys(this.errors).length > 0;\n }\n\n /**\n * Clear all errors\n */\n clearErrors() {\n this.errors = {};\n return this;\n }\n\n /**\n * Check if form is dirty (values changed from initial)\n */\n isDirty() {\n return Object.keys(this.values).some(key => {\n return this.values[key] !== this.initialValues[key];\n });\n }\n\n /**\n * Check if form is valid\n */\n isValid() {\n const result = this.validate();\n return Object.keys(result).length === 0;\n }\n\n /**\n * Validate a field\n */\n validateField(name) {\n const field = this.fields.get(name);\n if (!field) return null;\n\n // Skip validation for hidden fields (support both showWhen and showIf)\n const showCondition = field.showWhen || field.showIf;\n if (showCondition && !showCondition(this.values)) {\n delete this.errors[name];\n return null;\n }\n\n const value = this.values[name];\n \n // Check required\n if (field.required && (value === undefined || value === null || value === '')) {\n const error = 'This field is required';\n this.errors[name] = error;\n return error;\n }\n\n // Skip further validation if empty and not required\n if (!value && !field.required) {\n delete this.errors[name];\n return null;\n }\n\n // Type-based validation\n if (value) {\n if (field.type === 'email') {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n if (!emailRegex.test(value)) {\n const error = 'Please enter a valid email address';\n this.errors[name] = error;\n return error;\n }\n } else if (field.type === 'url') {\n try {\n new URL(value);\n } catch {\n const error = 'Please enter a valid URL';\n this.errors[name] = error;\n return error;\n }\n } else if (field.type === 'number') {\n if (isNaN(Number(value))) {\n const error = 'Please enter a valid number';\n this.errors[name] = error;\n return error;\n }\n }\n }\n\n // Check custom validate function\n if (field.validate) {\n const error = field.validate(value, this.values);\n if (error) {\n this.errors[name] = error;\n return error;\n }\n }\n\n // Run validators\n for (const validator of field.validators || []) {\n const error = validator(value, this.values);\n if (error) {\n this.errors[name] = error;\n return error;\n }\n }\n\n delete this.errors[name];\n return null;\n }\n\n /**\n * Validate all fields\n */\n validate() {\n const errors = {};\n\n for (const [name, field] of this.fields) {\n // Skip hidden fields (support both showWhen and showIf)\n const showCondition = field.showWhen || field.showIf;\n if (showCondition && !showCondition(this.values)) {\n continue;\n }\n\n const error = this.validateField(name);\n if (error) {\n errors[name] = error;\n }\n }\n\n this.errors = errors;\n return errors;\n }\n\n /**\n * Set submit handler\n */\n onSubmit(handler) {\n this.submitHandler = handler;\n return this;\n }\n\n /**\n * Set error handler\n */\n onError(handler) {\n this.errorHandler = handler;\n return this;\n }\n\n /**\n * Submit the form\n */\n async submit() {\n const errors = this.validate();\n \n if (Object.keys(errors).length > 0) {\n return { success: false, errors };\n }\n\n if (!this.submitHandler) {\n return { success: true, data: this.values };\n }\n\n this._isSubmitting = true;\n\n try {\n const result = await this.submitHandler(this.values);\n this._isSubmitting = false;\n return { success: true, data: result };\n } catch (error) {\n this._isSubmitting = false;\n if (this.errorHandler) {\n this.errorHandler(error);\n }\n return { success: false, error };\n }\n }\n\n /**\n * Serialize form data\n */\n serialize() {\n return { ...this.values };\n }\n\n /**\n * Convert form to HTML string\n */\n toHTML(options = {}) {\n // Delegates to buildForm() so there is one source of truth. The previous\n // hand-rolled string builder dropped action, method and every field\n // attribute beyond type/name/id, and interpolated values unescaped.\n return renderToHTML(this.buildForm(options));\n }\n\n /**\n * Mark field as touched\n */\n touch(name) {\n this.touched[name] = true;\n }\n\n /**\n * Build input component with validation metadata for hydration\n */\n buildInput(name) {\n const field = this.fields.get(name);\n if (!field) return null;\n\n const value = this.values[name] || '';\n const error = this.errors[name];\n const isTouched = this.touched[name];\n\n // Build validator names string for data-validators attribute\n const validatorNames = field.validators\n .map(v => {\n if (typeof v === 'function') return v.name || 'custom';\n if (typeof v === 'string') return v;\n return null;\n })\n .filter(Boolean)\n .join(',');\n\n const inputProps = {\n type: field.type,\n name: field.name,\n id: field.name,\n value: value,\n placeholder: field.placeholder,\n 'aria-invalid': error ? 'true' : 'false',\n 'aria-describedby': error ? `${name}-error` : undefined,\n className: error && isTouched ? 'error' : ''\n };\n\n // Add validation metadata for client-side hydration\n if (field.required) {\n inputProps.required = true;\n inputProps['data-required'] = 'true';\n }\n\n if (validatorNames) {\n inputProps['data-validators'] = validatorNames;\n }\n\n // Note: Event handlers are attached during hydration, not inline\n // This enables progressive enhancement and CSP compliance\n\n // textarea and select are elements, not input types. `type=\"textarea\"` is\n // not valid HTML \u2014 browsers render it as a single-line text box.\n if (field.type === 'textarea' || field.type === 'select') {\n const { type: _type, value: _value, ...rest } = inputProps;\n const props = { ...rest };\n\n if (field.type === 'textarea') {\n return { textarea: { ...props, text: String(value) } };\n }\n\n const { placeholder: _placeholder, ...selectProps } = props;\n\n return {\n select: {\n ...selectProps,\n children: (field.options ?? []).map(option => {\n const { value: optionValue, label = optionValue } =\n typeof option === 'object' && option !== null ? option : { value: option };\n\n return {\n option: {\n value: optionValue,\n selected: String(optionValue) === String(value) || undefined,\n text: String(label)\n }\n };\n })\n }\n };\n }\n\n return {\n input: inputProps\n };\n }\n\n /**\n * Build label component\n */\n buildLabel(name) {\n const field = this.fields.get(name);\n if (!field) return null;\n\n return {\n label: {\n for: field.name,\n text: field.label\n }\n };\n }\n\n /**\n * Build error component\n */\n buildError(name) {\n const error = this.errors[name];\n const isTouched = this.touched[name];\n\n if (!error || !isTouched) return null;\n\n return {\n div: {\n id: `${name}-error`,\n className: 'error-message',\n role: 'alert',\n text: error\n }\n };\n }\n\n /**\n * Build complete field component\n */\n buildField(name) {\n const field = this.fields.get(name);\n if (!field) return null;\n\n const children = [\n this.buildLabel(name),\n this.buildInput(name)\n ];\n\n const error = this.buildError(name);\n if (error) {\n children.push(error);\n }\n\n return {\n div: {\n className: 'form-field',\n 'data-field': name,\n children\n }\n };\n }\n\n /**\n * Build entire form\n */\n buildForm(options = {}) {\n const fields = [];\n\n for (const [name] of this.fields) {\n fields.push(this.buildField(name));\n }\n\n const settings = { ...this.options, ...options };\n\n if (settings.submitButton !== false) {\n fields.push({\n button: {\n type: 'submit',\n text: settings.submitText || 'Submit',\n className: 'submit-button'\n }\n });\n }\n\n const form = {};\n\n // Emitted before the handler so the markup reads action-first, and so a\n // form still submits to the right place without JavaScript.\n if (settings.action) form.action = settings.action;\n if (settings.method) form.method = settings.method;\n if (settings.name) form.name = settings.name;\n if (settings.id) form.id = settings.id;\n if (settings.className) form.className = settings.className;\n if (settings.enctype) form.enctype = settings.enctype;\n\n form.onsubmit = 'handleSubmit(event)';\n form.novalidate = true;\n form.children = fields;\n\n return { form };\n }\n\n /**\n * Set the form action URL\n */\n setAction(action) {\n this.options.action = action;\n return this;\n }\n\n /**\n * Set the form submission method\n */\n setMethod(method) {\n this.options.method = method;\n return this;\n }\n\n /**\n * Build the form component (alias for buildForm)\n */\n build(options = {}) {\n return this.buildForm(options);\n }\n\n /**\n * Render the form to a component (alias for buildForm)\n */\n render(options = {}) {\n return this.buildForm(options);\n }\n\n /**\n * Check if form is currently submitting\n */\n isSubmitting() {\n return this._isSubmitting;\n }\n\n /**\n * Get a field group\n */\n getGroup(name) {\n return this.groups.get(name);\n }\n\n /**\n * Check if a field is visible\n */\n isFieldVisible(name) {\n const field = this.fields.get(name);\n if (!field) return false;\n \n // Support both showWhen and showIf\n const showCondition = field.showWhen || field.showIf;\n if (showCondition) {\n return showCondition(this.values);\n }\n \n return field.visible !== false;\n }\n\n /**\n * Reset form\n */\n reset() {\n // Reset to initial values or empty strings\n this.values = {};\n for (const [name, field] of this.fields) {\n if (field.defaultValue !== undefined) {\n this.values[name] = field.defaultValue;\n } else {\n this.values[name] = '';\n }\n }\n this.errors = {};\n this.touched = {};\n this._isSubmitting = false;\n return this;\n }\n}\n\n/**\n * Create a form builder\n */\nexport function createFormBuilder(options = {}) {\n const form = new FormBuilder(options);\n\n // Add fields if provided\n if (options.fields) {\n options.fields.forEach(fieldConfig => {\n form.addField(fieldConfig.name, fieldConfig);\n });\n }\n\n return form;\n}\n\n/**\n * Build a form component from a configuration object.\n *\n * Returns a renderable component, as the type declarations have always said.\n * It previously returned the FormBuilder itself, so `render(buildForm(...))`\n * threw \"Invalid component structure\". Use createFormBuilder() when you want\n * the builder.\n *\n * @param {Object} config - Form configuration; `fields` may be an array of\n * field objects or an object keyed by field name. Remaining keys (action,\n * method, name, className, enctype, submitText) configure the form element.\n * @returns {Object} A Coherent.js component\n */\nexport function buildForm(config = {}) {\n const { fields = [], ...options } = Array.isArray(config) ? { fields: config } : config;\n const builder = new FormBuilder(options);\n\n if (Array.isArray(fields)) {\n for (const field of fields) {\n if (field && field.name) builder.field(field.name, field);\n }\n } else {\n for (const [name, field] of Object.entries(fields)) {\n builder.field(name, field);\n }\n }\n\n return builder.buildForm(options);\n}\n\nexport default {\n FormBuilder,\n createFormBuilder,\n buildForm\n};\n"],
|
|
5
|
-
"mappings": ";AAQA,SAAS,UAAU,oBAAoB;AAMhC,IAAM,cAAN,MAAkB;AAAA,EACvB,YAAY,UAAU,CAAC,GAAG;AACxB,SAAK,UAAU;AAAA,MACb,kBAAkB;AAAA,MAClB,gBAAgB;AAAA,MAChB,MAAM,QAAQ,QAAQ;AAAA,MACtB,GAAG;AAAA,IACL;AAEA,SAAK,SAAS,oBAAI,IAAI;AACtB,SAAK,SAAS,oBAAI,IAAI;AACtB,SAAK,SAAS,CAAC;AACf,SAAK,SAAS,CAAC;AACf,SAAK,UAAU,CAAC;AAChB,SAAK,gBAAgB,CAAC;AACtB,SAAK,gBAAgB;AACrB,SAAK,eAAe;AACpB,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,MAAM,SAAS,CAAC,GAAG;AAC1B,WAAO,KAAK,MAAM,MAAM,MAAM;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAM,SAAS,CAAC,GAAG;AACvB,UAAM,cAAc;AAAA,MAClB;AAAA,MACA,MAAM,OAAO,QAAQ;AAAA,MACrB,OAAO,OAAO,SAAS;AAAA,MACvB,aAAa,OAAO,eAAe;AAAA,MACnC,cAAc,OAAO,gBAAgB;AAAA,MACrC,YAAY,OAAO,cAAc,CAAC;AAAA,MAClC,UAAU,OAAO,YAAY;AAAA,MAC7B,SAAS,OAAO,YAAY;AAAA,MAC5B,UAAU,OAAO;AAAA,MACjB,GAAG;AAAA,IACL;AAEA,SAAK,OAAO,IAAI,MAAM,WAAW;AAGjC,QAAI,OAAO,iBAAiB,QAAW;AACrC,WAAK,OAAO,IAAI,IAAI,OAAO;AAC3B,WAAK,cAAc,IAAI,IAAI,OAAO;AAAA,IACpC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,MAAM;AAChB,SAAK,OAAO,OAAO,IAAI;AACvB,WAAO,KAAK,OAAO,IAAI;AACvB,WAAO,KAAK,OAAO,IAAI;AACvB,WAAO,KAAK,QAAQ,IAAI;AACxB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,MAAM,QAAQ;AACxB,UAAM,QAAQ,KAAK,OAAO,IAAI,IAAI;AAClC,QAAI,OAAO;AACT,WAAK,OAAO,IAAI,MAAM,EAAE,GAAG,OAAO,GAAG,OAAO,CAAC;AAAA,IAC/C;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY;AACV,WAAO,MAAM,KAAK,KAAK,OAAO,OAAO,CAAC;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,MAAM,SAAS,CAAC,GAAG;AAC1B,SAAK,OAAO,IAAI,MAAM;AAAA,MACpB;AAAA,MACA,OAAO,OAAO,SAAS;AAAA,MACvB,QAAQ,OAAO,UAAU,CAAC;AAAA,MAC1B,GAAG;AAAA,IACL,CAAC;AAGD,QAAI,OAAO,QAAQ;AACjB,aAAO,OAAO,QAAQ,iBAAe;AACnC,aAAK,SAAS,YAAY,MAAM,WAAW;AAAA,MAC7C,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,MAAM;AACb,WAAO,KAAK,OAAO,IAAI,IAAI;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,MAAM,OAAO;AACpB,SAAK,OAAO,IAAI,IAAI;AACpB,SAAK,QAAQ,IAAI,IAAI;AAErB,UAAM,QAAQ,KAAK,OAAO,IAAI,IAAI;AAClC,QAAI,UAAU,MAAM,oBAAoB,KAAK,QAAQ,mBAAmB;AACtE,WAAK,cAAc,IAAI;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAAQ;AAChB,WAAO,OAAO,KAAK,QAAQ,MAAM;AACjC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,MAAM;AACb,WAAO,KAAK,OAAO,IAAI;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY;AACV,WAAO,EAAE,GAAG,KAAK,OAAO;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,MAAM;AAClB,WAAO,KAAK,OAAO,IAAI;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY;AACV,WAAO,OAAO,KAAK,KAAK,MAAM,EAAE,SAAS;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc;AACZ,SAAK,SAAS,CAAC;AACf,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU;AACR,WAAO,OAAO,KAAK,KAAK,MAAM,EAAE,KAAK,SAAO;AAC1C,aAAO,KAAK,OAAO,GAAG,MAAM,KAAK,cAAc,GAAG;AAAA,IACpD,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU;AACR,UAAM,SAAS,KAAK,SAAS;AAC7B,WAAO,OAAO,KAAK,MAAM,EAAE,WAAW;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,MAAM;AAClB,UAAM,QAAQ,KAAK,OAAO,IAAI,IAAI;AAClC,QAAI,CAAC,MAAO,QAAO;AAGnB,UAAM,gBAAgB,MAAM,YAAY,MAAM;AAC9C,QAAI,iBAAiB,CAAC,cAAc,KAAK,MAAM,GAAG;AAChD,aAAO,KAAK,OAAO,IAAI;AACvB,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,KAAK,OAAO,IAAI;AAG9B,QAAI,MAAM,aAAa,UAAU,UAAa,UAAU,QAAQ,UAAU,KAAK;AAC7E,YAAM,QAAQ;AACd,WAAK,OAAO,IAAI,IAAI;AACpB,aAAO;AAAA,IACT;AAGA,QAAI,CAAC,SAAS,CAAC,MAAM,UAAU;AAC7B,aAAO,KAAK,OAAO,IAAI;AACvB,aAAO;AAAA,IACT;AAGA,QAAI,OAAO;AACT,UAAI,MAAM,SAAS,SAAS;AAC1B,cAAM,aAAa;AACnB,YAAI,CAAC,WAAW,KAAK,KAAK,GAAG;AAC3B,gBAAM,QAAQ;AACd,eAAK,OAAO,IAAI,IAAI;AACpB,iBAAO;AAAA,QACT;AAAA,MACF,WAAW,MAAM,SAAS,OAAO;AAC/B,YAAI;AACF,cAAI,IAAI,KAAK;AAAA,QACf,QAAQ;AACN,gBAAM,QAAQ;AACd,eAAK,OAAO,IAAI,IAAI;AACpB,iBAAO;AAAA,QACT;AAAA,MACF,WAAW,MAAM,SAAS,UAAU;AAClC,YAAI,MAAM,OAAO,KAAK,CAAC,GAAG;AACxB,gBAAM,QAAQ;AACd,eAAK,OAAO,IAAI,IAAI;AACpB,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAGA,QAAI,MAAM,UAAU;AAClB,YAAM,QAAQ,MAAM,SAAS,OAAO,KAAK,MAAM;AAC/C,UAAI,OAAO;AACT,aAAK,OAAO,IAAI,IAAI;AACpB,eAAO;AAAA,MACT;AAAA,IACF;AAGA,eAAW,aAAa,MAAM,cAAc,CAAC,GAAG;AAC9C,YAAM,QAAQ,UAAU,OAAO,KAAK,MAAM;AAC1C,UAAI,OAAO;AACT,aAAK,OAAO,IAAI,IAAI;AACpB,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO,KAAK,OAAO,IAAI;AACvB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW;AACT,UAAM,SAAS,CAAC;AAEhB,eAAW,CAAC,MAAM,KAAK,KAAK,KAAK,QAAQ;AAEvC,YAAM,gBAAgB,MAAM,YAAY,MAAM;AAC9C,UAAI,iBAAiB,CAAC,cAAc,KAAK,MAAM,GAAG;AAChD;AAAA,MACF;AAEA,YAAM,QAAQ,KAAK,cAAc,IAAI;AACrC,UAAI,OAAO;AACT,eAAO,IAAI,IAAI;AAAA,MACjB;AAAA,IACF;AAEA,SAAK,SAAS;AACd,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,SAAS;AAChB,SAAK,gBAAgB;AACrB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,SAAS;AACf,SAAK,eAAe;AACpB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS;AACb,UAAM,SAAS,KAAK,SAAS;AAE7B,QAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAAG;AAClC,aAAO,EAAE,SAAS,OAAO,OAAO;AAAA,IAClC;AAEA,QAAI,CAAC,KAAK,eAAe;AACvB,aAAO,EAAE,SAAS,MAAM,MAAM,KAAK,OAAO;AAAA,IAC5C;AAEA,SAAK,gBAAgB;AAErB,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,cAAc,KAAK,MAAM;AACnD,WAAK,gBAAgB;AACrB,aAAO,EAAE,SAAS,MAAM,MAAM,OAAO;AAAA,IACvC,SAAS,OAAO;AACd,WAAK,gBAAgB;AACrB,UAAI,KAAK,cAAc;AACrB,aAAK,aAAa,KAAK;AAAA,MACzB;AACA,aAAO,EAAE,SAAS,OAAO,MAAM;AAAA,IACjC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY;AACV,WAAO,EAAE,GAAG,KAAK,OAAO;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,UAAU,CAAC,GAAG;AAInB,WAAO,aAAa,KAAK,UAAU,OAAO,CAAC;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAM;AACV,SAAK,QAAQ,IAAI,IAAI;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAAM;AACf,UAAM,QAAQ,KAAK,OAAO,IAAI,IAAI;AAClC,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,QAAQ,KAAK,OAAO,IAAI,KAAK;AACnC,UAAM,QAAQ,KAAK,OAAO,IAAI;AAC9B,UAAM,YAAY,KAAK,QAAQ,IAAI;AAGnC,UAAM,iBAAiB,MAAM,WAC1B,IAAI,OAAK;AACR,UAAI,OAAO,MAAM,WAAY,QAAO,EAAE,QAAQ;AAC9C,UAAI,OAAO,MAAM,SAAU,QAAO;AAClC,aAAO;AAAA,IACT,CAAC,EACA,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,UAAM,aAAa;AAAA,MACjB,MAAM,MAAM;AAAA,MACZ,MAAM,MAAM;AAAA,MACZ,IAAI,MAAM;AAAA,MACV;AAAA,MACA,aAAa,MAAM;AAAA,MACnB,gBAAgB,QAAQ,SAAS;AAAA,MACjC,oBAAoB,QAAQ,GAAG,IAAI,WAAW;AAAA,MAC9C,WAAW,SAAS,YAAY,UAAU;AAAA,IAC5C;AAGA,QAAI,MAAM,UAAU;AAClB,iBAAW,WAAW;AACtB,iBAAW,eAAe,IAAI;AAAA,IAChC;AAEA,QAAI,gBAAgB;AAClB,iBAAW,iBAAiB,IAAI;AAAA,IAClC;AAOA,QAAI,MAAM,SAAS,cAAc,MAAM,SAAS,UAAU;AACxD,YAAM,EAAE,MAAM,OAAO,OAAO,QAAQ,GAAG,KAAK,IAAI;AAChD,YAAM,QAAQ,EAAE,GAAG,KAAK;AAExB,UAAI,MAAM,SAAS,YAAY;AAC7B,eAAO,EAAE,UAAU,EAAE,GAAG,OAAO,MAAM,OAAO,KAAK,EAAE,EAAE;AAAA,MACvD;AAEA,YAAM,EAAE,aAAa,cAAc,GAAG,YAAY,IAAI;AAEtD,aAAO;AAAA,QACL,QAAQ;AAAA,UACN,GAAG;AAAA,UACH,WAAW,MAAM,WAAW,CAAC,GAAG,IAAI,YAAU;AAC5C,kBAAM,EAAE,OAAO,aAAa,QAAQ,YAAY,IAC9C,OAAO,WAAW,YAAY,WAAW,OAAO,SAAS,EAAE,OAAO,OAAO;AAE3E,mBAAO;AAAA,cACL,QAAQ;AAAA,gBACN,OAAO;AAAA,gBACP,UAAU,OAAO,WAAW,MAAM,OAAO,KAAK,KAAK;AAAA,gBACnD,MAAM,OAAO,KAAK;AAAA,cACpB;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,OAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAAM;AACf,UAAM,QAAQ,KAAK,OAAO,IAAI,IAAI;AAClC,QAAI,CAAC,MAAO,QAAO;AAEnB,WAAO;AAAA,MACL,OAAO;AAAA,QACL,KAAK,MAAM;AAAA,QACX,MAAM,MAAM;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAAM;AACf,UAAM,QAAQ,KAAK,OAAO,IAAI;AAC9B,UAAM,YAAY,KAAK,QAAQ,IAAI;AAEnC,QAAI,CAAC,SAAS,CAAC,UAAW,QAAO;AAEjC,WAAO;AAAA,MACL,KAAK;AAAA,QACH,IAAI,GAAG,IAAI;AAAA,QACX,WAAW;AAAA,QACX,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAAM;AACf,UAAM,QAAQ,KAAK,OAAO,IAAI,IAAI;AAClC,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,WAAW;AAAA,MACf,KAAK,WAAW,IAAI;AAAA,MACpB,KAAK,WAAW,IAAI;AAAA,IACtB;AAEA,UAAM,QAAQ,KAAK,WAAW,IAAI;AAClC,QAAI,OAAO;AACT,eAAS,KAAK,KAAK;AAAA,IACrB;AAEA,WAAO;AAAA,MACL,KAAK;AAAA,QACH,WAAW;AAAA,QACX,cAAc;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,UAAU,CAAC,GAAG;AACtB,UAAM,SAAS,CAAC;AAEhB,eAAW,CAAC,IAAI,KAAK,KAAK,QAAQ;AAChC,aAAO,KAAK,KAAK,WAAW,IAAI,CAAC;AAAA,IACnC;AAEA,UAAM,WAAW,EAAE,GAAG,KAAK,SAAS,GAAG,QAAQ;AAE/C,QAAI,SAAS,iBAAiB,OAAO;AACnC,aAAO,KAAK;AAAA,QACV,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,MAAM,SAAS,cAAc;AAAA,UAC7B,WAAW;AAAA,QACb;AAAA,MACF,CAAC;AAAA,IACH;AAEA,UAAM,OAAO,CAAC;AAId,QAAI,SAAS,OAAQ,MAAK,SAAS,SAAS;AAC5C,QAAI,SAAS,OAAQ,MAAK,SAAS,SAAS;AAC5C,QAAI,SAAS,KAAM,MAAK,OAAO,SAAS;AACxC,QAAI,SAAS,GAAI,MAAK,KAAK,SAAS;AACpC,QAAI,SAAS,UAAW,MAAK,YAAY,SAAS;AAClD,QAAI,SAAS,QAAS,MAAK,UAAU,SAAS;AAE9C,SAAK,WAAW;AAChB,SAAK,aAAa;AAClB,SAAK,WAAW;AAEhB,WAAO,EAAE,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAAQ;AAChB,SAAK,QAAQ,SAAS;AACtB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAAQ;AAChB,SAAK,QAAQ,SAAS;AACtB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,CAAC,GAAG;AAClB,WAAO,KAAK,UAAU,OAAO;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,UAAU,CAAC,GAAG;AACnB,WAAO,KAAK,UAAU,OAAO;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe;AACb,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,MAAM;AACb,WAAO,KAAK,OAAO,IAAI,IAAI;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,MAAM;AACnB,UAAM,QAAQ,KAAK,OAAO,IAAI,IAAI;AAClC,QAAI,CAAC,MAAO,QAAO;AAGnB,UAAM,gBAAgB,MAAM,YAAY,MAAM;AAC9C,QAAI,eAAe;AACjB,aAAO,cAAc,KAAK,MAAM;AAAA,IAClC;AAEA,WAAO,MAAM,YAAY;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ;AAEN,SAAK,SAAS,CAAC;AACf,eAAW,CAAC,MAAM,KAAK,KAAK,KAAK,QAAQ;AACvC,UAAI,MAAM,iBAAiB,QAAW;AACpC,aAAK,OAAO,IAAI,IAAI,MAAM;AAAA,MAC5B,OAAO;AACL,aAAK,OAAO,IAAI,IAAI;AAAA,MACtB;AAAA,IACF;AACA,SAAK,SAAS,CAAC;AACf,SAAK,UAAU,CAAC;AAChB,SAAK,gBAAgB;AACrB,WAAO;AAAA,EACT;AACF;AAKO,SAAS,kBAAkB,UAAU,CAAC,GAAG;AAC9C,QAAM,OAAO,IAAI,YAAY,OAAO;AAGpC,MAAI,QAAQ,QAAQ;AAClB,YAAQ,OAAO,QAAQ,iBAAe;AACpC,WAAK,SAAS,YAAY,MAAM,WAAW;AAAA,IAC7C,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAeO,SAAS,UAAU,SAAS,CAAC,GAAG;AACrC,QAAM,EAAE,SAAS,CAAC,GAAG,GAAG,QAAQ,IAAI,MAAM,QAAQ,MAAM,IAAI,EAAE,QAAQ,OAAO,IAAI;AACjF,QAAM,UAAU,IAAI,YAAY,OAAO;AAEvC,MAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,eAAW,SAAS,QAAQ;AAC1B,UAAI,SAAS,MAAM,KAAM,SAAQ,MAAM,MAAM,MAAM,KAAK;AAAA,IAC1D;AAAA,EACF,OAAO;AACL,eAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AAClD,cAAQ,MAAM,MAAM,KAAK;AAAA,IAC3B;AAAA,EACF;AAEA,SAAO,QAAQ,UAAU,OAAO;AAClC;AAEA,IAAO,uBAAQ;AAAA,EACb;AAAA,EACA;AAAA,EACA;AACF;",
|
|
4
|
+
"sourcesContent": ["/**\n * Coherent.js Form Builder\n * \n * Utilities for building forms with Coherent.js\n * \n * @module forms/form-builder\n */\n\nimport { render as renderToHTML } from '@coherent.js/core';\n\n/**\n * Class applied to each structural slot. Consumers override any subset via\n * the `classNames` option; hydrateForm accepts `invalid` and `error` so the\n * client writes the same names the server rendered.\n */\nexport const DEFAULT_CLASS_NAMES = {\n /** Wrapper around label, control and error */\n field: 'form-field',\n label: '',\n /** Base class on the control, before any per-field className */\n control: '',\n /** Added to the control while it has a visible error */\n invalid: 'error',\n /** The error message element */\n error: 'error-message',\n submit: 'submit-button'\n};\n\n/**\n * Attribute names are interpolated into the markup unescaped by\n * formatAttributes, so a passthrough has to reject anything that is not a\n * plain name \u2014 otherwise `{'x onclick=alert(1)': ''}` injects an attribute.\n */\nconst VALID_ATTRIBUTE_NAME = /^[A-Za-z_:][-A-Za-z0-9_:.]*$/;\n\n/**\n * `onclick` and friends are syntactically valid names, and a string value\n * renders as an inline handler \u2014 script execution from whatever produced the\n * field config, and the thing this builder stopped emitting on the form\n * itself. Handlers belong in hydration.\n */\nconst EVENT_HANDLER_NAME = /^on/i;\n\nfunction safeAttributes(attributes) {\n if (!attributes || typeof attributes !== 'object') return {};\n\n const safe = {};\n for (const [name, value] of Object.entries(attributes)) {\n if (!VALID_ATTRIBUTE_NAME.test(name)) {\n console.warn(`[coherent.js/forms] Ignoring invalid attribute name: ${JSON.stringify(name)}`);\n continue;\n }\n if (EVENT_HANDLER_NAME.test(name)) {\n console.warn(\n `[coherent.js/forms] Ignoring inline event handler \"${name}\". ` +\n 'Attach handlers with hydrateForm instead.'\n );\n continue;\n }\n safe[name] = value;\n }\n return safe;\n}\n\n/** Join class names, dropping empties so no element carries `class=\"\"`. */\nfunction joinClasses(...names) {\n return names.filter(Boolean).join(' ');\n}\n\n/**\n * Form Builder\n * Helps create form components with validation\n */\nexport class FormBuilder {\n constructor(options = {}) {\n this.options = {\n validateOnChange: true,\n validateOnBlur: true,\n name: options.name || 'form',\n ...options\n };\n \n this.fields = new Map();\n this.groups = new Map();\n this.values = {};\n this.errors = {};\n this.touched = {};\n this.initialValues = {};\n this.submitHandler = null;\n this.errorHandler = null;\n this._isSubmitting = false;\n }\n\n /**\n * Add a field to the form (alias for field)\n */\n addField(name, config = {}) {\n return this.field(name, config);\n }\n\n /**\n * Add a field to the form\n */\n field(name, config = {}) {\n const fieldConfig = {\n name,\n type: config.type || 'text',\n label: config.label || name,\n placeholder: config.placeholder || '',\n defaultValue: config.defaultValue || '',\n validators: config.validators || [],\n required: config.required || false,\n visible: config.visible !== false,\n showWhen: config.showWhen,\n ...config\n };\n \n this.fields.set(name, fieldConfig);\n\n // Set default value\n if (config.defaultValue !== undefined) {\n this.values[name] = config.defaultValue;\n this.initialValues[name] = config.defaultValue;\n }\n\n return this;\n }\n\n /**\n * Remove a field from the form\n */\n removeField(name) {\n this.fields.delete(name);\n delete this.values[name];\n delete this.errors[name];\n delete this.touched[name];\n return this;\n }\n\n /**\n * Update field configuration\n */\n updateField(name, config) {\n const field = this.fields.get(name);\n if (field) {\n this.fields.set(name, { ...field, ...config });\n }\n return this;\n }\n\n /**\n * Get all fields as array\n */\n getFields() {\n return Array.from(this.fields.values());\n }\n\n /**\n * Add a field group\n */\n addGroup(name, config = {}) {\n this.groups.set(name, {\n name,\n label: config.label || name,\n fields: config.fields || [],\n ...config\n });\n\n // Add fields in the group\n if (config.fields) {\n config.fields.forEach(fieldConfig => {\n this.addField(fieldConfig.name, fieldConfig);\n });\n }\n\n return this;\n }\n\n /**\n * Get field configuration\n */\n getField(name) {\n return this.fields.get(name);\n }\n\n /**\n * Set field value\n */\n setValue(name, value) {\n this.values[name] = value;\n this.touched[name] = true;\n\n const field = this.fields.get(name);\n if (field && (field.validateOnChange || this.options.validateOnChange)) {\n this.validateField(name);\n }\n }\n\n /**\n * Set multiple values\n */\n setValues(values) {\n Object.assign(this.values, values);\n return this;\n }\n\n /**\n * Get field value\n */\n getValue(name) {\n return this.values[name];\n }\n\n /**\n * Get all values\n */\n getValues() {\n return { ...this.values };\n }\n\n /**\n * Get field error\n */\n getFieldError(name) {\n return this.errors[name];\n }\n\n /**\n * Check if form has errors\n */\n hasErrors() {\n return Object.keys(this.errors).length > 0;\n }\n\n /**\n * Clear all errors\n */\n clearErrors() {\n this.errors = {};\n return this;\n }\n\n /**\n * Check if form is dirty (values changed from initial)\n */\n isDirty() {\n return Object.keys(this.values).some(key => {\n return this.values[key] !== this.initialValues[key];\n });\n }\n\n /**\n * Check if form is valid\n */\n isValid() {\n const result = this.validate();\n return Object.keys(result).length === 0;\n }\n\n /**\n * Validate a field\n */\n validateField(name) {\n const field = this.fields.get(name);\n if (!field) return null;\n\n // Skip validation for hidden fields (support both showWhen and showIf)\n const showCondition = field.showWhen || field.showIf;\n if (showCondition && !showCondition(this.values)) {\n delete this.errors[name];\n return null;\n }\n\n const value = this.values[name];\n \n // Check required\n if (field.required && (value === undefined || value === null || value === '')) {\n const error = 'This field is required';\n this.errors[name] = error;\n return error;\n }\n\n // Skip further validation if empty and not required\n if (!value && !field.required) {\n delete this.errors[name];\n return null;\n }\n\n // Type-based validation\n if (value) {\n if (field.type === 'email') {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n if (!emailRegex.test(value)) {\n const error = 'Please enter a valid email address';\n this.errors[name] = error;\n return error;\n }\n } else if (field.type === 'url') {\n try {\n new URL(value);\n } catch {\n const error = 'Please enter a valid URL';\n this.errors[name] = error;\n return error;\n }\n } else if (field.type === 'number') {\n if (isNaN(Number(value))) {\n const error = 'Please enter a valid number';\n this.errors[name] = error;\n return error;\n }\n }\n }\n\n // Check custom validate function\n if (field.validate) {\n const error = field.validate(value, this.values);\n if (error) {\n this.errors[name] = error;\n return error;\n }\n }\n\n // Run validators\n for (const validator of field.validators || []) {\n const error = validator(value, this.values);\n if (error) {\n this.errors[name] = error;\n return error;\n }\n }\n\n delete this.errors[name];\n return null;\n }\n\n /**\n * Validate all fields\n */\n validate() {\n const errors = {};\n\n for (const [name] of this.fields) {\n // The same predicate buildForm() renders by, so a field that is not on\n // the page can never block submission.\n if (!this.isFieldVisible(name)) continue;\n\n const error = this.validateField(name);\n if (error) {\n errors[name] = error;\n }\n }\n\n this.errors = errors;\n return errors;\n }\n\n /**\n * Set submit handler\n */\n onSubmit(handler) {\n this.submitHandler = handler;\n return this;\n }\n\n /**\n * Set error handler\n */\n onError(handler) {\n this.errorHandler = handler;\n return this;\n }\n\n /**\n * Submit the form\n */\n async submit() {\n const errors = this.validate();\n \n if (Object.keys(errors).length > 0) {\n return { success: false, errors };\n }\n\n if (!this.submitHandler) {\n return { success: true, data: this.values };\n }\n\n this._isSubmitting = true;\n\n try {\n const result = await this.submitHandler(this.values);\n this._isSubmitting = false;\n return { success: true, data: result };\n } catch (error) {\n this._isSubmitting = false;\n if (this.errorHandler) {\n this.errorHandler(error);\n }\n return { success: false, error };\n }\n }\n\n /**\n * Serialize form data\n */\n serialize() {\n return { ...this.values };\n }\n\n /**\n * Convert form to HTML string\n */\n toHTML(options = {}) {\n // Delegates to buildForm() so there is one source of truth. The previous\n // hand-rolled string builder dropped action, method and every field\n // attribute beyond type/name/id, and interpolated values unescaped.\n return renderToHTML(this.buildForm(options));\n }\n\n /**\n * Mark field as touched\n */\n touch(name) {\n this.touched[name] = true;\n }\n\n /**\n * Build input component with validation metadata for hydration\n */\n buildInput(name, classNames = this.resolveClassNames()) {\n const field = this.fields.get(name);\n if (!field) return null;\n\n const value = this.values[name] || '';\n const error = this.errors[name];\n const isTouched = this.touched[name];\n\n // Build validator names string for data-validators attribute\n const validatorNames = field.validators\n .map(v => {\n if (typeof v === 'function') return v.name || 'custom';\n if (typeof v === 'string') return v;\n return null;\n })\n .filter(Boolean)\n .join(',');\n\n const controlClass = joinClasses(\n classNames.control,\n field.className,\n error && isTouched ? classNames.invalid : null\n );\n\n const inputProps = {\n // Spread first: name, id, type and the aria-* pair below are the\n // builder's own invariants, and hydration keys off them.\n ...safeAttributes(field.attributes),\n type: field.type,\n name: field.name,\n id: field.name,\n value: value,\n 'aria-invalid': error ? 'true' : 'false',\n 'aria-describedby': error ? `${name}-error` : undefined\n };\n\n if (field.placeholder) inputProps.placeholder = field.placeholder;\n if (controlClass) inputProps.className = controlClass;\n if (field.disabled) inputProps.disabled = true;\n if (field.readonly) inputProps.readonly = true;\n\n // Add validation metadata for client-side hydration\n if (field.required) {\n inputProps.required = true;\n inputProps['data-required'] = 'true';\n }\n\n if (validatorNames) {\n inputProps['data-validators'] = validatorNames;\n }\n\n // Note: Event handlers are attached during hydration, not inline\n // This enables progressive enhancement and CSP compliance\n\n // textarea and select are elements, not input types. `type=\"textarea\"` is\n // not valid HTML \u2014 browsers render it as a single-line text box.\n if (field.type === 'textarea' || field.type === 'select') {\n const { type: _type, value: _value, ...rest } = inputProps;\n const props = { ...rest };\n\n if (field.type === 'textarea') {\n return { textarea: { ...props, text: String(value) } };\n }\n\n const { placeholder: _placeholder, ...selectProps } = props;\n\n return {\n select: {\n ...selectProps,\n children: (field.options ?? []).map(option => {\n const { value: optionValue, label = optionValue } =\n typeof option === 'object' && option !== null ? option : { value: option };\n\n return {\n option: {\n value: optionValue,\n selected: String(optionValue) === String(value) || undefined,\n text: String(label)\n }\n };\n })\n }\n };\n }\n\n return {\n input: inputProps\n };\n }\n\n /**\n * Build label component\n */\n buildLabel(name, classNames = this.resolveClassNames()) {\n const field = this.fields.get(name);\n if (!field) return null;\n\n const props = { for: field.name, text: field.label };\n if (classNames.label) props.className = classNames.label;\n\n return { label: props };\n }\n\n /**\n * Build error component\n */\n buildError(name, classNames = this.resolveClassNames()) {\n const error = this.errors[name];\n const isTouched = this.touched[name];\n\n if (!error || !isTouched) return null;\n\n const props = { id: `${name}-error`, role: 'alert', text: error };\n if (classNames.error) props.className = classNames.error;\n\n return { div: props };\n }\n\n /**\n * Merge configured class names over the defaults\n */\n resolveClassNames(overrides = {}) {\n return { ...DEFAULT_CLASS_NAMES, ...this.options.classNames, ...overrides };\n }\n\n /**\n * Build complete field component\n */\n buildField(name, classNames = this.resolveClassNames()) {\n const field = this.fields.get(name);\n if (!field) return null;\n\n const children = [\n this.buildLabel(name, classNames),\n this.buildInput(name, classNames)\n ];\n\n const error = this.buildError(name, classNames);\n if (error) {\n children.push(error);\n }\n\n // data-field is the structural hook hydrateForm uses to find the wrapper,\n // so classes stay entirely the consumer's to choose.\n const props = { 'data-field': name, children };\n if (classNames.field) props.className = classNames.field;\n\n return { div: props };\n }\n\n /**\n * Build entire form\n */\n buildForm(options = {}) {\n const settings = { ...this.options, ...options };\n const classNames = this.resolveClassNames(options.classNames);\n const fields = [];\n\n for (const [name] of this.fields) {\n // validate() has always skipped fields hidden by showWhen/showIf; render\n // agreed with it only by accident, because nothing was ever hidden.\n if (!this.isFieldVisible(name)) continue;\n fields.push(this.buildField(name, classNames));\n }\n\n if (settings.submitButton !== false) {\n const button = { type: 'submit', text: settings.submitText || 'Submit' };\n if (classNames.submit) button.className = classNames.submit;\n fields.push({ button });\n }\n\n const form = {};\n\n if (settings.action) form.action = settings.action;\n if (settings.method) form.method = settings.method;\n if (settings.name) form.name = settings.name;\n if (settings.id) form.id = settings.id;\n if (settings.className) form.className = settings.className;\n if (settings.enctype) form.enctype = settings.enctype;\n\n // Plain HTML by default: the form posts to `action` and the browser runs\n // its own validation with JavaScript off. hydrateForm binds its own submit\n // listener, so it never needed the inline handler this used to emit \u2014 and\n // an inline handler breaks under a strict CSP besides.\n if (settings.enhance) {\n form.onsubmit = typeof settings.enhance === 'string'\n ? settings.enhance\n : 'handleSubmit(event)';\n }\n\n if (settings.novalidate === true) form.novalidate = true;\n\n form.children = fields;\n\n return { form };\n }\n\n /**\n * Set the form action URL\n */\n setAction(action) {\n this.options.action = action;\n return this;\n }\n\n /**\n * Set the form submission method\n */\n setMethod(method) {\n this.options.method = method;\n return this;\n }\n\n /**\n * Build the form component (alias for buildForm)\n */\n build(options = {}) {\n return this.buildForm(options);\n }\n\n /**\n * Render the form to a component (alias for buildForm)\n */\n render(options = {}) {\n return this.buildForm(options);\n }\n\n /**\n * Check if form is currently submitting\n */\n isSubmitting() {\n return this._isSubmitting;\n }\n\n /**\n * Get a field group\n */\n getGroup(name) {\n return this.groups.get(name);\n }\n\n /**\n * Check if a field is visible\n */\n isFieldVisible(name) {\n const field = this.fields.get(name);\n if (!field) return false;\n\n // visible:false wins outright \u2014 a showWhen that happens to return true\n // must not resurrect a field the caller switched off.\n if (field.visible === false) return false;\n\n // Support both showWhen and showIf\n const showCondition = field.showWhen || field.showIf;\n if (showCondition) {\n return showCondition(this.values);\n }\n\n return true;\n }\n\n /**\n * Reset form\n */\n reset() {\n // Reset to initial values or empty strings\n this.values = {};\n for (const [name, field] of this.fields) {\n if (field.defaultValue !== undefined) {\n this.values[name] = field.defaultValue;\n } else {\n this.values[name] = '';\n }\n }\n this.errors = {};\n this.touched = {};\n this._isSubmitting = false;\n return this;\n }\n}\n\n/**\n * Create a form builder\n */\nexport function createFormBuilder(options = {}) {\n const form = new FormBuilder(options);\n\n // Add fields if provided\n if (options.fields) {\n options.fields.forEach(fieldConfig => {\n form.addField(fieldConfig.name, fieldConfig);\n });\n }\n\n return form;\n}\n\n/**\n * Build a form component from a configuration object.\n *\n * Returns a renderable component, as the type declarations have always said.\n * It previously returned the FormBuilder itself, so `render(buildForm(...))`\n * threw \"Invalid component structure\". Use createFormBuilder() when you want\n * the builder.\n *\n * @param {Object} config - Form configuration; `fields` may be an array of\n * field objects or an object keyed by field name. Remaining keys (action,\n * method, name, className, enctype, submitText) configure the form element.\n * @returns {Object} A Coherent.js component\n */\nexport function buildForm(config = {}) {\n const { fields = [], ...options } = Array.isArray(config) ? { fields: config } : config;\n const builder = new FormBuilder(options);\n\n if (Array.isArray(fields)) {\n for (const field of fields) {\n if (field && field.name) builder.field(field.name, field);\n }\n } else {\n for (const [name, field] of Object.entries(fields)) {\n builder.field(name, field);\n }\n }\n\n return builder.buildForm(options);\n}\n\nexport default {\n FormBuilder,\n createFormBuilder,\n buildForm\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/form-hydration.js
CHANGED
|
@@ -300,6 +300,21 @@ var validators = {
|
|
|
300
300
|
}
|
|
301
301
|
};
|
|
302
302
|
|
|
303
|
+
// src/form-builder.js
|
|
304
|
+
import { render as renderToHTML } from "@coherent.js/core";
|
|
305
|
+
var DEFAULT_CLASS_NAMES = {
|
|
306
|
+
/** Wrapper around label, control and error */
|
|
307
|
+
field: "form-field",
|
|
308
|
+
label: "",
|
|
309
|
+
/** Base class on the control, before any per-field className */
|
|
310
|
+
control: "",
|
|
311
|
+
/** Added to the control while it has a visible error */
|
|
312
|
+
invalid: "error",
|
|
313
|
+
/** The error message element */
|
|
314
|
+
error: "error-message",
|
|
315
|
+
submit: "submit-button"
|
|
316
|
+
};
|
|
317
|
+
|
|
303
318
|
// src/form-hydration.js
|
|
304
319
|
function hydrateForm(formSelector, options = {}) {
|
|
305
320
|
if (typeof document === "undefined") {
|
|
@@ -317,7 +332,14 @@ function hydrateForm(formSelector, options = {}) {
|
|
|
317
332
|
validateOnSubmit: true,
|
|
318
333
|
showErrorsOnTouch: true,
|
|
319
334
|
debounce: 300,
|
|
320
|
-
...options
|
|
335
|
+
...options,
|
|
336
|
+
// Must match whatever the server rendered, so pass the same names given to
|
|
337
|
+
// buildForm's `classNames` when they were customised.
|
|
338
|
+
classNames: {
|
|
339
|
+
invalid: DEFAULT_CLASS_NAMES.invalid,
|
|
340
|
+
error: DEFAULT_CLASS_NAMES.error,
|
|
341
|
+
...options.classNames
|
|
342
|
+
}
|
|
321
343
|
};
|
|
322
344
|
const state = {
|
|
323
345
|
values: {},
|
|
@@ -361,10 +383,10 @@ function hydrateForm(formSelector, options = {}) {
|
|
|
361
383
|
function createErrorElement(name, inputElement) {
|
|
362
384
|
const errorDiv = document.createElement("div");
|
|
363
385
|
errorDiv.id = `${name}-error`;
|
|
364
|
-
errorDiv.className =
|
|
386
|
+
if (opts.classNames.error) errorDiv.className = opts.classNames.error;
|
|
365
387
|
errorDiv.setAttribute("role", "alert");
|
|
366
388
|
errorDiv.style.display = "none";
|
|
367
|
-
const fieldWrapper = inputElement.closest("
|
|
389
|
+
const fieldWrapper = inputElement.closest("[data-field]") || inputElement.parentElement;
|
|
368
390
|
fieldWrapper.appendChild(errorDiv);
|
|
369
391
|
return errorDiv;
|
|
370
392
|
}
|
|
@@ -413,6 +435,9 @@ function hydrateForm(formSelector, options = {}) {
|
|
|
413
435
|
displayError(name, error);
|
|
414
436
|
return !error;
|
|
415
437
|
}
|
|
438
|
+
function invalidClasses() {
|
|
439
|
+
return opts.classNames.invalid ? opts.classNames.invalid.trim().split(/\s+/) : [];
|
|
440
|
+
}
|
|
416
441
|
function displayError(name, error) {
|
|
417
442
|
const field = state.fields.get(name);
|
|
418
443
|
if (!field) return;
|
|
@@ -421,12 +446,12 @@ function hydrateForm(formSelector, options = {}) {
|
|
|
421
446
|
errorElement.textContent = error;
|
|
422
447
|
errorElement.style.display = "block";
|
|
423
448
|
element.setAttribute("aria-invalid", "true");
|
|
424
|
-
element.classList.add(
|
|
449
|
+
invalidClasses().forEach((name2) => element.classList.add(name2));
|
|
425
450
|
} else {
|
|
426
451
|
errorElement.textContent = "";
|
|
427
452
|
errorElement.style.display = "none";
|
|
428
453
|
element.setAttribute("aria-invalid", "false");
|
|
429
|
-
element.classList.remove(
|
|
454
|
+
invalidClasses().forEach((name2) => element.classList.remove(name2));
|
|
430
455
|
}
|
|
431
456
|
}
|
|
432
457
|
function validateForm() {
|