@coherent.js/forms 1.0.0 → 1.0.1

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/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/form-builder.js
2
+ import { render as renderToHTML } from "@coherent.js/core";
2
3
  var FormBuilder = class {
3
4
  constructor(options = {}) {
4
5
  this.options = {
@@ -286,17 +287,8 @@ var FormBuilder = class {
286
287
  /**
287
288
  * Convert form to HTML string
288
289
  */
289
- toHTML() {
290
- const fields = this.getFields();
291
- let html = `<form name="${this.options.name}">`;
292
- fields.forEach((field) => {
293
- html += `<div class="form-field">`;
294
- html += `<label for="${field.name}">${field.label}</label>`;
295
- html += `<input type="${field.type}" name="${field.name}" id="${field.name}">`;
296
- html += `</div>`;
297
- });
298
- html += `</form>`;
299
- return html;
290
+ toHTML(options = {}) {
291
+ return renderToHTML(this.buildForm(options));
300
292
  }
301
293
  /**
302
294
  * Mark field as touched
@@ -335,6 +327,29 @@ var FormBuilder = class {
335
327
  if (validatorNames) {
336
328
  inputProps["data-validators"] = validatorNames;
337
329
  }
330
+ if (field.type === "textarea" || field.type === "select") {
331
+ const { type: _type, value: _value, ...rest } = inputProps;
332
+ const props = { ...rest };
333
+ if (field.type === "textarea") {
334
+ return { textarea: { ...props, text: String(value) } };
335
+ }
336
+ const { placeholder: _placeholder, ...selectProps } = props;
337
+ return {
338
+ select: {
339
+ ...selectProps,
340
+ children: (field.options ?? []).map((option) => {
341
+ const { value: optionValue, label = optionValue } = typeof option === "object" && option !== null ? option : { value: option };
342
+ return {
343
+ option: {
344
+ value: optionValue,
345
+ selected: String(optionValue) === String(value) || void 0,
346
+ text: String(label)
347
+ }
348
+ };
349
+ })
350
+ }
351
+ };
352
+ }
338
353
  return {
339
354
  input: inputProps
340
355
  };
@@ -398,22 +413,53 @@ var FormBuilder = class {
398
413
  for (const [name] of this.fields) {
399
414
  fields.push(this.buildField(name));
400
415
  }
401
- if (options.submitButton !== false) {
416
+ const settings = { ...this.options, ...options };
417
+ if (settings.submitButton !== false) {
402
418
  fields.push({
403
419
  button: {
404
420
  type: "submit",
405
- text: options.submitText || "Submit",
421
+ text: settings.submitText || "Submit",
406
422
  className: "submit-button"
407
423
  }
408
424
  });
409
425
  }
410
- return {
411
- form: {
412
- onsubmit: "handleSubmit(event)",
413
- novalidate: true,
414
- children: fields
415
- }
416
- };
426
+ const form = {};
427
+ if (settings.action) form.action = settings.action;
428
+ if (settings.method) form.method = settings.method;
429
+ if (settings.name) form.name = settings.name;
430
+ if (settings.id) form.id = settings.id;
431
+ if (settings.className) form.className = settings.className;
432
+ if (settings.enctype) form.enctype = settings.enctype;
433
+ form.onsubmit = "handleSubmit(event)";
434
+ form.novalidate = true;
435
+ form.children = fields;
436
+ return { form };
437
+ }
438
+ /**
439
+ * Set the form action URL
440
+ */
441
+ setAction(action) {
442
+ this.options.action = action;
443
+ return this;
444
+ }
445
+ /**
446
+ * Set the form submission method
447
+ */
448
+ setMethod(method) {
449
+ this.options.method = method;
450
+ return this;
451
+ }
452
+ /**
453
+ * Build the form component (alias for buildForm)
454
+ */
455
+ build(options = {}) {
456
+ return this.buildForm(options);
457
+ }
458
+ /**
459
+ * Render the form to a component (alias for buildForm)
460
+ */
461
+ render(options = {}) {
462
+ return this.buildForm(options);
417
463
  }
418
464
  /**
419
465
  * Check if form is currently submitting
@@ -466,19 +512,199 @@ function createFormBuilder(options = {}) {
466
512
  }
467
513
  return form;
468
514
  }
469
- function buildForm(fields, options = {}) {
515
+ function buildForm(config = {}) {
516
+ const { fields = [], ...options } = Array.isArray(config) ? { fields: config } : config;
470
517
  const builder = new FormBuilder(options);
471
- for (const [name, config] of Object.entries(fields)) {
472
- builder.field(name, config);
518
+ if (Array.isArray(fields)) {
519
+ for (const field of fields) {
520
+ if (field && field.name) builder.field(field.name, field);
521
+ }
522
+ } else {
523
+ for (const [name, field] of Object.entries(fields)) {
524
+ builder.field(name, field);
525
+ }
473
526
  }
474
- return builder;
527
+ return builder.buildForm(options);
475
528
  }
476
529
 
477
- // src/validators.js
530
+ // src/validation.js
478
531
  var validators = {
532
+ required: (message = "This field is required") => (value) => {
533
+ if (value === null || value === void 0 || value === "") {
534
+ return message;
535
+ }
536
+ return null;
537
+ },
538
+ minLength: (min, message = `Minimum length is ${min}`) => (value) => {
539
+ if (value && value.length < min) {
540
+ return message;
541
+ }
542
+ return null;
543
+ },
544
+ maxLength: (max, message = `Maximum length is ${max}`) => (value) => {
545
+ if (value && value.length > max) {
546
+ return message;
547
+ }
548
+ return null;
549
+ },
550
+ min: (min, message = `Minimum value is ${min}`) => (value) => {
551
+ if (value !== null && value !== void 0 && Number(value) < min) {
552
+ return message;
553
+ }
554
+ return null;
555
+ },
556
+ max: (max, message = `Maximum value is ${max}`) => (value) => {
557
+ if (value !== null && value !== void 0 && Number(value) > max) {
558
+ return message;
559
+ }
560
+ return null;
561
+ },
562
+ email: (message = "Invalid email address") => (value) => {
563
+ if (value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
564
+ return message;
565
+ }
566
+ return null;
567
+ },
568
+ url: (message = "Invalid URL") => (value) => {
569
+ if (value) {
570
+ try {
571
+ new URL(value);
572
+ } catch {
573
+ return message;
574
+ }
575
+ }
576
+ return null;
577
+ },
578
+ pattern: (regex, message = "Invalid format") => (value) => {
579
+ if (value && !regex.test(value)) {
580
+ return message;
581
+ }
582
+ return null;
583
+ },
584
+ matches: (fieldName, message = "Fields do not match") => (value, formData) => {
585
+ if (value !== formData[fieldName]) {
586
+ return message;
587
+ }
588
+ return null;
589
+ },
590
+ oneOf: (options, message = "Invalid option") => (value) => {
591
+ if (value && !options.includes(value)) {
592
+ return message;
593
+ }
594
+ return null;
595
+ },
596
+ custom: (fn, message = "Validation failed") => (value, formData) => {
597
+ if (!fn(value, formData)) {
598
+ return message;
599
+ }
600
+ return null;
601
+ }
602
+ };
603
+ var FormValidator = class {
604
+ constructor(schema = {}) {
605
+ this.schema = schema;
606
+ this.errors = {};
607
+ this.touched = {};
608
+ }
609
+ /**
610
+ * Validate a single field
611
+ */
612
+ validateField(name, value, formData = {}) {
613
+ const fieldValidators = this.schema[name];
614
+ if (!fieldValidators) {
615
+ return null;
616
+ }
617
+ const validatorArray = Array.isArray(fieldValidators) ? fieldValidators : [fieldValidators];
618
+ for (const validator of validatorArray) {
619
+ const error = validator(value, formData);
620
+ if (error) {
621
+ return error;
622
+ }
623
+ }
624
+ return null;
625
+ }
626
+ /**
627
+ * Validate entire form
628
+ */
629
+ validate(formData) {
630
+ const errors = {};
631
+ let isValid = true;
632
+ for (const [name, value] of Object.entries(formData)) {
633
+ const error = this.validateField(name, value, formData);
634
+ if (error) {
635
+ errors[name] = error;
636
+ isValid = false;
637
+ }
638
+ }
639
+ for (const name of Object.keys(this.schema)) {
640
+ if (!(name in formData)) {
641
+ const error = this.validateField(name, void 0, formData);
642
+ if (error) {
643
+ errors[name] = error;
644
+ isValid = false;
645
+ }
646
+ }
647
+ }
648
+ this.errors = errors;
649
+ return { isValid, errors };
650
+ }
651
+ /**
652
+ * Mark field as touched
653
+ */
654
+ touch(name) {
655
+ this.touched[name] = true;
656
+ }
657
+ /**
658
+ * Check if field is touched
659
+ */
660
+ isTouched(name) {
661
+ return this.touched[name] || false;
662
+ }
663
+ /**
664
+ * Get error for field
665
+ */
666
+ getError(name) {
667
+ return this.errors[name] || null;
668
+ }
669
+ /**
670
+ * Check if field has error
671
+ */
672
+ hasError(name) {
673
+ return !!this.errors[name];
674
+ }
675
+ /**
676
+ * Clear errors
677
+ */
678
+ clearErrors() {
679
+ this.errors = {};
680
+ }
681
+ /**
682
+ * Clear touched state
683
+ */
684
+ clearTouched() {
685
+ this.touched = {};
686
+ }
687
+ /**
688
+ * Reset validator
689
+ */
690
+ reset() {
691
+ this.clearErrors();
692
+ this.clearTouched();
693
+ }
694
+ };
695
+ function createValidator(schema) {
696
+ return new FormValidator(schema);
697
+ }
698
+ function validate(formData, schema) {
699
+ const validator = new FormValidator(schema);
700
+ return validator.validate(formData);
701
+ }
702
+
703
+ // src/validators.js
704
+ var validators2 = {
479
705
  required: (value, options = {}) => {
480
706
  if (value === null || value === void 0 || value === "") {
481
- return options.message || validators.required.message || "This field is required";
707
+ return options.message || validators2.required.message || "This field is required";
482
708
  }
483
709
  return null;
484
710
  },
@@ -661,7 +887,7 @@ var validators = {
661
887
  },
662
888
  // Get a registered validator
663
889
  get: (name) => {
664
- return validators[name];
890
+ return validators2[name];
665
891
  },
666
892
  // Compose multiple validators
667
893
  compose: (validatorList) => {
@@ -729,19 +955,19 @@ var validators = {
729
955
  const stopOnFirstError = options.stopOnFirstError !== false;
730
956
  const chain = {
731
957
  required: (opts) => {
732
- validatorList.push((v, o, t, a) => validators.required(v, opts || o, t, a));
958
+ validatorList.push((v, o, t, a) => validators2.required(v, opts || o, t, a));
733
959
  return chain;
734
960
  },
735
961
  email: (opts) => {
736
- validatorList.push((v, o, t, a) => validators.email(v, opts || o, t, a));
962
+ validatorList.push((v, o, t, a) => validators2.email(v, opts || o, t, a));
737
963
  return chain;
738
964
  },
739
965
  minLength: (opts) => {
740
- validatorList.push((v, o, t, a) => validators.minLength(v, opts || o, t, a));
966
+ validatorList.push((v, o, t, a) => validators2.minLength(v, opts || o, t, a));
741
967
  return chain;
742
968
  },
743
969
  maxLength: (opts) => {
744
- validatorList.push((v, o, t, a) => validators.maxLength(v, opts || o, t, a));
970
+ validatorList.push((v, o, t, a) => validators2.maxLength(v, opts || o, t, a));
745
971
  return chain;
746
972
  },
747
973
  custom: (fn, message) => {
@@ -796,6 +1022,7 @@ function validateForm(formData, fieldValidators) {
796
1022
  return Object.keys(errors).length > 0 ? errors : null;
797
1023
  }
798
1024
  function registerValidator(name, validatorFn) {
1025
+ validators2[name] = validatorFn;
799
1026
  validators[name] = validatorFn;
800
1027
  }
801
1028
  function composeValidators(...validatorFns) {
@@ -842,8 +1069,8 @@ function hydrateForm(formSelector, options = {}) {
842
1069
  return validatorString.split(",").map((v) => {
843
1070
  const trimmed = v.trim();
844
1071
  const [name, ...params] = trimmed.split(":");
845
- if (validators[name]) {
846
- return params.length > 0 ? validators[name](...params.map((p) => isNaN(p) ? p : Number(p))) : validators[name];
1072
+ if (validators2[name]) {
1073
+ return params.length > 0 ? validators2[name](...params.map((p) => isNaN(p) ? p : Number(p))) : validators2[name];
847
1074
  }
848
1075
  return null;
849
1076
  }).filter(Boolean);
@@ -1067,179 +1294,6 @@ function hydrateForm(formSelector, options = {}) {
1067
1294
  })
1068
1295
  };
1069
1296
  }
1070
-
1071
- // src/validation.js
1072
- var validators2 = {
1073
- required: (message = "This field is required") => (value) => {
1074
- if (value === null || value === void 0 || value === "") {
1075
- return message;
1076
- }
1077
- return null;
1078
- },
1079
- minLength: (min, message = `Minimum length is ${min}`) => (value) => {
1080
- if (value && value.length < min) {
1081
- return message;
1082
- }
1083
- return null;
1084
- },
1085
- maxLength: (max, message = `Maximum length is ${max}`) => (value) => {
1086
- if (value && value.length > max) {
1087
- return message;
1088
- }
1089
- return null;
1090
- },
1091
- min: (min, message = `Minimum value is ${min}`) => (value) => {
1092
- if (value !== null && value !== void 0 && Number(value) < min) {
1093
- return message;
1094
- }
1095
- return null;
1096
- },
1097
- max: (max, message = `Maximum value is ${max}`) => (value) => {
1098
- if (value !== null && value !== void 0 && Number(value) > max) {
1099
- return message;
1100
- }
1101
- return null;
1102
- },
1103
- email: (message = "Invalid email address") => (value) => {
1104
- if (value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
1105
- return message;
1106
- }
1107
- return null;
1108
- },
1109
- url: (message = "Invalid URL") => (value) => {
1110
- if (value) {
1111
- try {
1112
- new URL(value);
1113
- } catch {
1114
- return message;
1115
- }
1116
- }
1117
- return null;
1118
- },
1119
- pattern: (regex, message = "Invalid format") => (value) => {
1120
- if (value && !regex.test(value)) {
1121
- return message;
1122
- }
1123
- return null;
1124
- },
1125
- matches: (fieldName, message = "Fields do not match") => (value, formData) => {
1126
- if (value !== formData[fieldName]) {
1127
- return message;
1128
- }
1129
- return null;
1130
- },
1131
- oneOf: (options, message = "Invalid option") => (value) => {
1132
- if (value && !options.includes(value)) {
1133
- return message;
1134
- }
1135
- return null;
1136
- },
1137
- custom: (fn, message = "Validation failed") => (value, formData) => {
1138
- if (!fn(value, formData)) {
1139
- return message;
1140
- }
1141
- return null;
1142
- }
1143
- };
1144
- var FormValidator = class {
1145
- constructor(schema = {}) {
1146
- this.schema = schema;
1147
- this.errors = {};
1148
- this.touched = {};
1149
- }
1150
- /**
1151
- * Validate a single field
1152
- */
1153
- validateField(name, value, formData = {}) {
1154
- const fieldValidators = this.schema[name];
1155
- if (!fieldValidators) {
1156
- return null;
1157
- }
1158
- const validatorArray = Array.isArray(fieldValidators) ? fieldValidators : [fieldValidators];
1159
- for (const validator of validatorArray) {
1160
- const error = validator(value, formData);
1161
- if (error) {
1162
- return error;
1163
- }
1164
- }
1165
- return null;
1166
- }
1167
- /**
1168
- * Validate entire form
1169
- */
1170
- validate(formData) {
1171
- const errors = {};
1172
- let isValid = true;
1173
- for (const [name, value] of Object.entries(formData)) {
1174
- const error = this.validateField(name, value, formData);
1175
- if (error) {
1176
- errors[name] = error;
1177
- isValid = false;
1178
- }
1179
- }
1180
- for (const name of Object.keys(this.schema)) {
1181
- if (!(name in formData)) {
1182
- const error = this.validateField(name, void 0, formData);
1183
- if (error) {
1184
- errors[name] = error;
1185
- isValid = false;
1186
- }
1187
- }
1188
- }
1189
- this.errors = errors;
1190
- return { isValid, errors };
1191
- }
1192
- /**
1193
- * Mark field as touched
1194
- */
1195
- touch(name) {
1196
- this.touched[name] = true;
1197
- }
1198
- /**
1199
- * Check if field is touched
1200
- */
1201
- isTouched(name) {
1202
- return this.touched[name] || false;
1203
- }
1204
- /**
1205
- * Get error for field
1206
- */
1207
- getError(name) {
1208
- return this.errors[name] || null;
1209
- }
1210
- /**
1211
- * Check if field has error
1212
- */
1213
- hasError(name) {
1214
- return !!this.errors[name];
1215
- }
1216
- /**
1217
- * Clear errors
1218
- */
1219
- clearErrors() {
1220
- this.errors = {};
1221
- }
1222
- /**
1223
- * Clear touched state
1224
- */
1225
- clearTouched() {
1226
- this.touched = {};
1227
- }
1228
- /**
1229
- * Reset validator
1230
- */
1231
- reset() {
1232
- this.clearErrors();
1233
- this.clearTouched();
1234
- }
1235
- };
1236
- function createValidator(schema) {
1237
- return new FormValidator(schema);
1238
- }
1239
- function validate(formData, schema) {
1240
- const validator = new FormValidator(schema);
1241
- return validator.validate(formData);
1242
- }
1243
1297
  export {
1244
1298
  FormBuilder,
1245
1299
  FormValidator,
@@ -1252,6 +1306,6 @@ export {
1252
1306
  validate,
1253
1307
  validateField,
1254
1308
  validateForm,
1255
- validators2 as validators
1309
+ validators
1256
1310
  };
1257
1311
  //# sourceMappingURL=index.js.map