@coherent.js/forms 1.0.0-beta.8 → 1.0.0-rc.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
@@ -458,7 +458,13 @@ var FormBuilder = class {
458
458
  }
459
459
  };
460
460
  function createFormBuilder(options = {}) {
461
- return new FormBuilder(options);
461
+ const form = new FormBuilder(options);
462
+ if (options.fields) {
463
+ options.fields.forEach((fieldConfig) => {
464
+ form.addField(fieldConfig.name, fieldConfig);
465
+ });
466
+ }
467
+ return form;
462
468
  }
463
469
  function buildForm(fields, options = {}) {
464
470
  const builder = new FormBuilder(options);
@@ -941,8 +947,8 @@ function hydrateForm(formSelector, options = {}) {
941
947
  }
942
948
  return isValid;
943
949
  }
944
- function handleChange(event2) {
945
- const input = event2.target;
950
+ function handleChange(event) {
951
+ const input = event.target;
946
952
  const name = input.getAttribute("name");
947
953
  if (!state.fields.has(name)) return;
948
954
  state.values[name] = getFieldValue(input);
@@ -957,8 +963,8 @@ function hydrateForm(formSelector, options = {}) {
957
963
  debounceTimers.set(name, timer);
958
964
  }
959
965
  }
960
- function handleBlur(event2) {
961
- const input = event2.target;
966
+ function handleBlur(event) {
967
+ const input = event.target;
962
968
  const name = input.getAttribute("name");
963
969
  if (!state.fields.has(name)) return;
964
970
  state.touched[name] = true;
@@ -966,8 +972,8 @@ function hydrateForm(formSelector, options = {}) {
966
972
  validateField2(name);
967
973
  }
968
974
  }
969
- function handleSubmit(event2) {
970
- event2.preventDefault();
975
+ function handleSubmit(event) {
976
+ event.preventDefault();
971
977
  for (const name of state.fields.keys()) {
972
978
  state.touched[name] = true;
973
979
  }
@@ -985,7 +991,7 @@ function hydrateForm(formSelector, options = {}) {
985
991
  state.isSubmitting = true;
986
992
  const submitData = { ...state.values };
987
993
  if (options.onSubmit) {
988
- const result = options.onSubmit(submitData, event2);
994
+ const result = options.onSubmit(submitData, event);
989
995
  if (result === false) {
990
996
  state.isSubmitting = false;
991
997
  return;
@@ -1234,627 +1240,18 @@ function validate(formData, schema) {
1234
1240
  const validator = new FormValidator(schema);
1235
1241
  return validator.validate(formData);
1236
1242
  }
1237
-
1238
- // src/forms.js
1239
- function createForm(options = {}) {
1240
- const opts = {
1241
- fields: {},
1242
- validation: {
1243
- strategy: "blur",
1244
- debounce: 300,
1245
- async: true,
1246
- stopOnFirstError: false,
1247
- revalidateOn: ["change", "blur"],
1248
- ...options.validation
1249
- },
1250
- errors: {
1251
- format: "detailed",
1252
- display: "inline",
1253
- customFormatter: null,
1254
- ...options.errors
1255
- },
1256
- submission: {
1257
- preventDefault: true,
1258
- validateBeforeSubmit: true,
1259
- disableOnSubmit: true,
1260
- resetOnSuccess: false,
1261
- onSuccess: null,
1262
- onError: null,
1263
- ...options.submission
1264
- },
1265
- state: {
1266
- trackDirty: true,
1267
- trackTouched: true,
1268
- trackVisited: true,
1269
- initialValues: {},
1270
- resetValues: null,
1271
- ...options.state
1272
- },
1273
- middleware: options.middleware || [],
1274
- ...options
1275
- };
1276
- const state = {
1277
- values: { ...opts.state.initialValues },
1278
- errors: {},
1279
- touched: {},
1280
- dirty: {},
1281
- visited: {},
1282
- isSubmitting: false,
1283
- isValidating: false,
1284
- submitCount: 0,
1285
- asyncValidations: /* @__PURE__ */ new Map()
1286
- };
1287
- const fields = /* @__PURE__ */ new Map();
1288
- const stats = {
1289
- validations: 0,
1290
- asyncValidations: 0,
1291
- submissions: 0,
1292
- successfulSubmissions: 0,
1293
- failedSubmissions: 0,
1294
- middlewareExecutions: 0
1295
- };
1296
- function registerField(name, config) {
1297
- fields.set(name, {
1298
- name,
1299
- type: config.type || "text",
1300
- validators: config.validators || [],
1301
- transform: config.transform || {},
1302
- validateWhen: config.validateWhen,
1303
- required: config.required || false,
1304
- defaultValue: config.defaultValue,
1305
- ...config
1306
- });
1307
- if (!(name in state.values)) {
1308
- state.values[name] = config.defaultValue !== void 0 ? config.defaultValue : "";
1309
- }
1310
- state.errors[name] = [];
1311
- state.touched[name] = false;
1312
- state.dirty[name] = false;
1313
- state.visited[name] = false;
1314
- }
1315
- function getField(name) {
1316
- return fields.get(name);
1317
- }
1318
- function setFieldValue(name, value, shouldValidate = true) {
1319
- const field = fields.get(name);
1320
- if (!field) {
1321
- console.warn(`Field ${name} not registered`);
1322
- return;
1323
- }
1324
- if (field.transform?.input) {
1325
- value = field.transform.input(value);
1326
- }
1327
- state.values[name] = value;
1328
- if (opts.state.trackDirty) {
1329
- const initialValue = opts.state.initialValues[name];
1330
- state.dirty[name] = value !== initialValue;
1331
- }
1332
- if (shouldValidate && opts.validation.revalidateOn.includes("change")) {
1333
- validateField2(name);
1334
- }
1335
- }
1336
- function getFieldValue(name) {
1337
- return state.values[name];
1338
- }
1339
- function setFieldTouched(name, touched = true) {
1340
- if (!opts.state.trackTouched) return;
1341
- state.touched[name] = touched;
1342
- if (touched && opts.validation.revalidateOn.includes("blur")) {
1343
- validateField2(name);
1344
- }
1345
- }
1346
- function setFieldVisited(name, visited = true) {
1347
- if (!opts.state.trackVisited) return;
1348
- state.visited[name] = visited;
1349
- }
1350
- async function validateField2(name) {
1351
- const field = fields.get(name);
1352
- if (!field) return { valid: true, errors: [] };
1353
- stats.validations++;
1354
- if (field.validateWhen && !field.validateWhen(state.values)) {
1355
- state.errors[name] = [];
1356
- return { valid: true, errors: [] };
1357
- }
1358
- const value = state.values[name];
1359
- const errors = [];
1360
- if (state.asyncValidations.has(name)) {
1361
- clearTimeout(state.asyncValidations.get(name));
1362
- }
1363
- if (field.required && (value === "" || value === null || value === void 0)) {
1364
- errors.push({
1365
- field: name,
1366
- type: "required",
1367
- message: `${name} is required`
1368
- });
1369
- state.errors[name] = errors;
1370
- return { valid: false, errors };
1371
- }
1372
- for (const validator of field.validators) {
1373
- try {
1374
- let result;
1375
- if (validator.validate.constructor.name === "AsyncFunction" || opts.validation.async) {
1376
- stats.asyncValidations++;
1377
- if (validator.debounce || opts.validation.debounce) {
1378
- await new Promise((resolve) => {
1379
- const timeoutId = setTimeout(resolve, validator.debounce || opts.validation.debounce);
1380
- state.asyncValidations.set(name, timeoutId);
1381
- });
1382
- }
1383
- result = await validator.validate(value, state.values);
1384
- } else {
1385
- result = validator.validate(value, state.values);
1386
- }
1387
- if (result !== true && result !== void 0 && result !== null) {
1388
- errors.push({
1389
- field: name,
1390
- type: validator.name || "custom",
1391
- message: validator.message || result || "Validation failed"
1392
- });
1393
- if (opts.validation.stopOnFirstError) {
1394
- break;
1395
- }
1396
- }
1397
- } catch (error) {
1398
- errors.push({
1399
- field: name,
1400
- type: "error",
1401
- message: error.message || "Validation error"
1402
- });
1403
- if (opts.validation.stopOnFirstError) {
1404
- break;
1405
- }
1406
- }
1407
- }
1408
- state.errors[name] = errors;
1409
- return { valid: errors.length === 0, errors };
1410
- }
1411
- async function validateForm2() {
1412
- state.isValidating = true;
1413
- const validationPromises = Array.from(fields.keys()).map(
1414
- (name) => validateField2(name)
1415
- );
1416
- const results = await Promise.all(validationPromises);
1417
- state.isValidating = false;
1418
- const allErrors = results.reduce((acc, result, index) => {
1419
- const fieldName = Array.from(fields.keys())[index];
1420
- if (result.errors.length > 0) {
1421
- acc[fieldName] = result.errors;
1422
- }
1423
- return acc;
1424
- }, {});
1425
- const isValid2 = Object.keys(allErrors).length === 0;
1426
- return {
1427
- valid: isValid2,
1428
- errors: allErrors
1429
- };
1430
- }
1431
- async function executeMiddleware(action, data) {
1432
- if (opts.middleware.length === 0) return data;
1433
- stats.middlewareExecutions++;
1434
- let result = data;
1435
- for (const middleware of opts.middleware) {
1436
- try {
1437
- const next = () => result;
1438
- result = await middleware(action, result, next, state);
1439
- } catch (error) {
1440
- console.error("Middleware error:", error);
1441
- throw error;
1442
- }
1443
- }
1444
- return result;
1445
- }
1446
- function applyTransformations(values) {
1447
- const transformed = { ...values };
1448
- fields.forEach((field, name) => {
1449
- if (field.transform?.output && name in transformed) {
1450
- transformed[name] = field.transform.output(transformed[name]);
1451
- }
1452
- });
1453
- return transformed;
1454
- }
1455
- async function handleSubmit(onSubmit) {
1456
- stats.submissions++;
1457
- try {
1458
- if (opts.submission.preventDefault && typeof event !== "undefined") {
1459
- event.preventDefault();
1460
- }
1461
- if (opts.submission.disableOnSubmit) {
1462
- state.isSubmitting = true;
1463
- }
1464
- let values = { ...state.values };
1465
- values = await executeMiddleware("beforeSubmit", values);
1466
- if (opts.submission.validateBeforeSubmit) {
1467
- const validation = await validateForm2();
1468
- if (!validation.valid) {
1469
- if (opts.submission.onError) {
1470
- opts.submission.onError(validation.errors);
1471
- }
1472
- stats.failedSubmissions++;
1473
- return { success: false, errors: validation.errors };
1474
- }
1475
- }
1476
- values = applyTransformations(values);
1477
- values = await executeMiddleware("afterValidation", values);
1478
- const result = await onSubmit(values);
1479
- await executeMiddleware("afterSubmit", result);
1480
- state.submitCount++;
1481
- stats.successfulSubmissions++;
1482
- if (opts.submission.resetOnSuccess) {
1483
- reset();
1484
- }
1485
- if (opts.submission.onSuccess) {
1486
- opts.submission.onSuccess(result);
1487
- }
1488
- return { success: true, data: result };
1489
- } catch (error) {
1490
- stats.failedSubmissions++;
1491
- if (opts.submission.onError) {
1492
- opts.submission.onError(error);
1493
- }
1494
- await executeMiddleware("onError", error);
1495
- return { success: false, error };
1496
- } finally {
1497
- state.isSubmitting = false;
1498
- }
1499
- }
1500
- function reset(values) {
1501
- const resetValues = values || opts.state.resetValues || opts.state.initialValues;
1502
- state.values = { ...resetValues };
1503
- state.errors = {};
1504
- state.touched = {};
1505
- state.dirty = {};
1506
- state.visited = {};
1507
- state.submitCount = 0;
1508
- fields.forEach((field, name) => {
1509
- if (!(name in state.values)) {
1510
- state.values[name] = field.defaultValue !== void 0 ? field.defaultValue : "";
1511
- }
1512
- state.errors[name] = [];
1513
- state.touched[name] = false;
1514
- state.dirty[name] = false;
1515
- state.visited[name] = false;
1516
- });
1517
- }
1518
- function getErrors(fieldName) {
1519
- if (fieldName) {
1520
- return state.errors[fieldName] || [];
1521
- }
1522
- if (opts.errors.customFormatter) {
1523
- return opts.errors.customFormatter(state.errors);
1524
- }
1525
- if (opts.errors.format === "simple") {
1526
- return Object.values(state.errors).flat().map((e) => e.message);
1527
- }
1528
- return state.errors;
1529
- }
1530
- function isValid() {
1531
- return Object.values(state.errors).every((errors) => errors.length === 0);
1532
- }
1533
- function isDirty(fieldName) {
1534
- if (fieldName) {
1535
- return state.dirty[fieldName] || false;
1536
- }
1537
- return Object.values(state.dirty).some((dirty) => dirty);
1538
- }
1539
- function isTouched(fieldName) {
1540
- if (fieldName) {
1541
- return state.touched[fieldName] || false;
1542
- }
1543
- return Object.values(state.touched).some((touched) => touched);
1544
- }
1545
- function getValues() {
1546
- return { ...state.values };
1547
- }
1548
- function setValues(values, shouldValidate = false) {
1549
- Object.entries(values).forEach(([name, value]) => {
1550
- setFieldValue(name, value, shouldValidate);
1551
- });
1552
- }
1553
- function getState() {
1554
- return {
1555
- values: { ...state.values },
1556
- errors: { ...state.errors },
1557
- touched: { ...state.touched },
1558
- dirty: { ...state.dirty },
1559
- visited: { ...state.visited },
1560
- isSubmitting: state.isSubmitting,
1561
- isValidating: state.isValidating,
1562
- submitCount: state.submitCount,
1563
- isValid: isValid(),
1564
- isDirty: isDirty(),
1565
- isTouched: isTouched()
1566
- };
1567
- }
1568
- function getStats() {
1569
- return {
1570
- ...stats,
1571
- fieldsRegistered: fields.size,
1572
- activeAsyncValidations: state.asyncValidations.size
1573
- };
1574
- }
1575
- Object.entries(opts.fields).forEach(([name, config]) => {
1576
- registerField(name, config);
1577
- });
1578
- return {
1579
- registerField,
1580
- getField,
1581
- setFieldValue,
1582
- getFieldValue,
1583
- setFieldTouched,
1584
- setFieldVisited,
1585
- validateField: validateField2,
1586
- validateForm: validateForm2,
1587
- handleSubmit,
1588
- reset,
1589
- getErrors,
1590
- isValid,
1591
- isDirty,
1592
- isTouched,
1593
- getValues,
1594
- setValues,
1595
- getState,
1596
- getStats,
1597
- // Expose state for testing
1598
- _state: state
1599
- };
1600
- }
1601
- var formValidators = {
1602
- required: {
1603
- name: "required",
1604
- validate: (value) => {
1605
- return value !== "" && value !== null && value !== void 0;
1606
- },
1607
- message: "This field is required"
1608
- },
1609
- email: {
1610
- name: "email",
1611
- validate: (value) => {
1612
- if (!value) return true;
1613
- const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1614
- return regex.test(value);
1615
- },
1616
- message: "Please enter a valid email address"
1617
- },
1618
- minLength: (min) => ({
1619
- name: "minLength",
1620
- validate: (value) => {
1621
- if (!value) return true;
1622
- return String(value).length >= min;
1623
- },
1624
- message: `Must be at least ${min} characters`
1625
- }),
1626
- maxLength: (max) => ({
1627
- name: "maxLength",
1628
- validate: (value) => {
1629
- if (!value) return true;
1630
- return String(value).length <= max;
1631
- },
1632
- message: `Must be no more than ${max} characters`
1633
- }),
1634
- pattern: (regex, message) => ({
1635
- name: "pattern",
1636
- validate: (value) => {
1637
- if (!value) return true;
1638
- return regex.test(value);
1639
- },
1640
- message: message || "Invalid format"
1641
- }),
1642
- asyncEmail: {
1643
- name: "asyncEmail",
1644
- validate: async (value) => {
1645
- if (!value) return true;
1646
- await new Promise((resolve) => setTimeout(resolve, 100));
1647
- const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1648
- return regex.test(value);
1649
- },
1650
- message: "Please enter a valid email address",
1651
- debounce: 500
1652
- },
1653
- asyncUnique: (checkFn) => ({
1654
- name: "asyncUnique",
1655
- validate: async (value) => {
1656
- if (!value) return true;
1657
- return await checkFn(value);
1658
- },
1659
- message: "This value is already taken",
1660
- debounce: 500
1661
- })
1662
- };
1663
- var enhancedForm = createForm();
1664
-
1665
- // src/advanced-validation.js
1666
- import { ReactiveState, observable, computed } from "@coherent.js/state/src/reactive-state.js";
1667
- import { globalErrorHandler } from "@coherent.js/core/src/utils/error-handler.js";
1668
- var validationRules = {
1669
- required: (value, params = true) => {
1670
- if (!params) return true;
1671
- const isEmpty = value === null || value === void 0 || value === "" || Array.isArray(value) && value.length === 0;
1672
- return !isEmpty || "This field is required";
1673
- },
1674
- min: (value, minValue) => {
1675
- if (value === null || value === void 0 || value === "") return true;
1676
- const num = Number(value);
1677
- return isNaN(num) || num >= minValue || `Value must be at least ${minValue}`;
1678
- },
1679
- max: (value, maxValue) => {
1680
- if (value === null || value === void 0 || value === "") return true;
1681
- const num = Number(value);
1682
- return isNaN(num) || num <= maxValue || `Value must be no more than ${maxValue}`;
1683
- },
1684
- minLength: (value, minLen) => {
1685
- if (value === null || value === void 0) return true;
1686
- const str = String(value);
1687
- return str.length >= minLen || `Must be at least ${minLen} characters`;
1688
- },
1689
- maxLength: (value, maxLen) => {
1690
- if (value === null || value === void 0) return true;
1691
- const str = String(value);
1692
- return str.length <= maxLen || `Must be no more than ${maxLen} characters`;
1693
- },
1694
- pattern: (value, regex, message = "Invalid format") => {
1695
- if (value === null || value === void 0 || value === "") return true;
1696
- const pattern = typeof regex === "string" ? new RegExp(regex) : regex;
1697
- return pattern.test(String(value)) || message;
1698
- },
1699
- email: (value) => {
1700
- if (value === null || value === void 0 || value === "") return true;
1701
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1702
- return emailRegex.test(String(value)) || "Please enter a valid email address";
1703
- },
1704
- url: (value) => {
1705
- if (value === null || value === void 0 || value === "") return true;
1706
- try {
1707
- new URL(String(value));
1708
- return true;
1709
- } catch {
1710
- return "Please enter a valid URL";
1711
- }
1712
- },
1713
- numeric: (value) => {
1714
- if (value === null || value === void 0 || value === "") return true;
1715
- return !isNaN(Number(value)) || "Must be a valid number";
1716
- },
1717
- integer: (value) => {
1718
- if (value === null || value === void 0 || value === "") return true;
1719
- const num = Number(value);
1720
- return Number.isInteger(num) || "Must be a whole number";
1721
- },
1722
- alpha: (value) => {
1723
- if (value === null || value === void 0 || value === "") return true;
1724
- return /^[a-zA-Z]+$/.test(String(value)) || "Must contain only letters";
1725
- },
1726
- alphanumeric: (value) => {
1727
- if (value === null || value === void 0 || value === "") return true;
1728
- return /^[a-zA-Z0-9]+$/.test(String(value)) || "Must contain only letters and numbers";
1729
- },
1730
- equals: (value, otherValue, fieldName = "other field") => {
1731
- return value === otherValue || `Must match ${fieldName}`;
1732
- },
1733
- oneOf: (value, options, message = "Invalid selection") => {
1734
- if (value === null || value === void 0 || value === "") return true;
1735
- return options.includes(value) || message;
1736
- },
1737
- custom: (value, validator, ...args) => {
1738
- if (typeof validator !== "function") {
1739
- throw new Error("Custom validator must be a function");
1740
- }
1741
- return validator(value, ...args);
1742
- }
1743
- };
1744
- var binding = {
1745
- /**
1746
- * Two-way data binding for input elements
1747
- */
1748
- model(form, fieldName, options = {}) {
1749
- return {
1750
- value: form.getField(fieldName),
1751
- oninput: (event2) => {
1752
- const value = options.number ? Number(event2.target.value) : event2.target.value;
1753
- form.setField(fieldName, value);
1754
- },
1755
- onblur: () => {
1756
- form.handleBlur(fieldName);
1757
- }
1758
- };
1759
- },
1760
- /**
1761
- * Checkbox binding
1762
- */
1763
- checkbox(form, fieldName) {
1764
- return {
1765
- checked: Boolean(form.getField(fieldName)),
1766
- onchange: (event2) => {
1767
- form.setField(fieldName, event2.target.checked);
1768
- },
1769
- onblur: () => {
1770
- form.handleBlur(fieldName);
1771
- }
1772
- };
1773
- },
1774
- /**
1775
- * Select dropdown binding
1776
- */
1777
- select(form, fieldName, options = {}) {
1778
- return {
1779
- value: form.getField(fieldName),
1780
- onchange: (event2) => {
1781
- const value = options.multiple ? Array.from(event2.target.selectedOptions, (opt) => opt.value) : event2.target.value;
1782
- form.setField(fieldName, value);
1783
- },
1784
- onblur: () => {
1785
- form.handleBlur(fieldName);
1786
- }
1787
- };
1788
- },
1789
- /**
1790
- * Radio button binding
1791
- */
1792
- radio(form, fieldName, value) {
1793
- return {
1794
- checked: form.getField(fieldName) === value,
1795
- value,
1796
- onchange: (event2) => {
1797
- if (event2.target.checked) {
1798
- form.setField(fieldName, value);
1799
- }
1800
- }
1801
- };
1802
- }
1803
- };
1804
- var formComponents = {
1805
- /**
1806
- * Validation _error display
1807
- */
1808
- ValidationError({ form, field, className = "validation-_error" }) {
1809
- const validator = form.getValidator(field);
1810
- if (!validator || validator.errors.value.length === 0) {
1811
- return null;
1812
- }
1813
- return {
1814
- div: {
1815
- className,
1816
- children: validator.errors.value.map((_error) => ({
1817
- span: { text: _error, className: "_error-message" }
1818
- }))
1819
- }
1820
- };
1821
- },
1822
- /**
1823
- * Form field wrapper with validation
1824
- */
1825
- FormField({ form, field, label, children, showErrors = true }) {
1826
- const validator = form.getValidator(field);
1827
- const state = validator ? validator.getState() : {};
1828
- return {
1829
- div: {
1830
- className: `form-field ${state.hasError ? "has-_error" : ""} ${state.isTouched ? "touched" : ""}`,
1831
- children: [
1832
- label ? { label: { text: label, htmlFor: field } } : null,
1833
- children,
1834
- showErrors && state.hasError ? formComponents.ValidationError({ form, field }) : null
1835
- ].filter(Boolean)
1836
- }
1837
- };
1838
- }
1839
- };
1840
1243
  export {
1841
1244
  FormBuilder,
1842
1245
  FormValidator,
1843
- binding,
1844
1246
  buildForm,
1845
1247
  composeValidators,
1846
- createForm,
1847
1248
  createFormBuilder,
1848
1249
  createValidator,
1849
- enhancedForm,
1850
- formComponents,
1851
- formValidators,
1852
1250
  hydrateForm,
1853
1251
  registerValidator,
1854
1252
  validate,
1855
1253
  validateField,
1856
1254
  validateForm,
1857
- validationRules,
1858
1255
  validators2 as validators
1859
1256
  };
1860
1257
  //# sourceMappingURL=index.js.map