@coherent.js/forms 1.1.2 → 2.0.0-rc.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.
@@ -5,79 +5,383 @@ function isEmailShaped(value) {
5
5
  return typeof value === "string" && value.length <= EMAIL_MAX_LENGTH && EMAIL_PATTERN.test(value);
6
6
  }
7
7
 
8
- // src/validation.js
9
- var validators = {
10
- required: (message = "This field is required") => (value) => {
11
- if (value === null || value === void 0 || value === "") {
12
- return message;
8
+ // src/rules.js
9
+ var BUILTIN = /* @__PURE__ */ Symbol("coherent.forms.builtin");
10
+ var DESCRIPTORS = /* @__PURE__ */ new WeakMap();
11
+ var HELPER_NAMES = /* @__PURE__ */ new Set(["get", "compose", "debounce", "cancellable", "when", "chain"]);
12
+ var isEmpty = (value) => value === null || value === void 0 || value === "";
13
+ var isOptionsObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof RegExp);
14
+ function argsFromString(params, kind, text) {
15
+ if (text === void 0 || text.trim() === "") return [];
16
+ if (params.length === 0) return [text.trim()];
17
+ if (kind === "list") {
18
+ return [text.split(",").map((part) => part.trim()).filter(Boolean)];
19
+ }
20
+ if (kind === "regexp") {
21
+ try {
22
+ return [new RegExp(text)];
23
+ } catch {
24
+ return null;
13
25
  }
14
- return null;
15
- },
16
- minLength: (min, message = `Minimum length is ${min}`) => (value) => {
17
- if (value && value.length < min) {
18
- return message;
26
+ }
27
+ const comma = text.indexOf(",");
28
+ const raw = (comma === -1 ? text : text.slice(0, comma)).trim();
29
+ const message = comma === -1 ? "" : text.slice(comma + 1).trim();
30
+ let arg = raw;
31
+ if (kind === "number") {
32
+ arg = Number(raw);
33
+ if (raw === "" || Number.isNaN(arg)) return null;
34
+ }
35
+ return message ? [arg, message] : [arg];
36
+ }
37
+ function defineRule(name, { params = [], stringArg = "string", valid, message }) {
38
+ const fromOptions = (options = {}) => {
39
+ const config = { message: options.message };
40
+ for (const [key, ...aliases] of params) {
41
+ config[key] = [key, ...aliases].map((alias) => options[alias]).find((v) => v !== void 0);
19
42
  }
20
- return null;
21
- },
22
- maxLength: (max, message = `Maximum length is ${max}`) => (value) => {
23
- if (value && value.length > max) {
24
- return message;
43
+ return config;
44
+ };
45
+ const check = (value, config, formData) => {
46
+ if (valid(value, config, formData || {})) return null;
47
+ return config.message || builtin.message || message(config);
48
+ };
49
+ const factory = (...args) => {
50
+ const config = { message: args[params.length] };
51
+ params.forEach(([key], index) => {
52
+ config[key] = args[index];
53
+ });
54
+ const rule = (value, formData, _translator, allValues) => check(value, config, allValues ?? formData);
55
+ DESCRIPTORS.set(rule, { name, args });
56
+ return rule;
57
+ };
58
+ const isDirectCall = (args) => {
59
+ if (args.length >= 2) {
60
+ return params.length === 0 || isOptionsObject(args[1]);
25
61
  }
26
- return null;
27
- },
28
- min: (min, message = `Minimum value is ${min}`) => (value) => {
29
- if (value !== null && value !== void 0 && Number(value) < min) {
30
- return message;
62
+ if (args.length === 1 && params.length === 0) {
63
+ const [arg] = args;
64
+ return arg === "" || arg !== void 0 && arg !== null && typeof arg !== "string";
31
65
  }
32
- return null;
33
- },
34
- max: (max, message = `Maximum value is ${max}`) => (value) => {
35
- if (value !== null && value !== void 0 && Number(value) > max) {
36
- return message;
66
+ return false;
67
+ };
68
+ function builtin(...args) {
69
+ if (isDirectCall(args)) {
70
+ const [value, options, , allValues] = args;
71
+ return check(value, fromOptions(isOptionsObject(options) ? options : {}), allValues);
37
72
  }
38
- return null;
39
- },
40
- email: (message = "Invalid email address") => (value) => {
41
- if (value && !isEmailShaped(value)) {
42
- return message;
73
+ return factory(...args);
74
+ }
75
+ Object.defineProperty(builtin, "name", { value: name });
76
+ builtin[BUILTIN] = {
77
+ name,
78
+ factory,
79
+ argsFromString: (text) => argsFromString(params, stringArg, text)
80
+ };
81
+ return builtin;
82
+ }
83
+ var toNumber = (value) => typeof value === "number" ? value : Number(value);
84
+ function testRegExp(regex, value) {
85
+ if (!(regex instanceof RegExp)) return true;
86
+ regex.lastIndex = 0;
87
+ return regex.test(String(value));
88
+ }
89
+ function fileMatchesType(file, allowedTypes) {
90
+ const fileType = file.type;
91
+ const fileExt = file.name ? file.name.split(".").pop().toLowerCase() : "";
92
+ return allowedTypes.some((type) => {
93
+ if (type.startsWith(".")) {
94
+ return fileExt === type.slice(1).toLowerCase();
43
95
  }
44
- return null;
45
- },
46
- url: (message = "Invalid URL") => (value) => {
47
- if (value) {
96
+ if (type.includes("/")) {
97
+ if (type.endsWith("/*")) {
98
+ return fileType.startsWith(type.replace("/*", "/"));
99
+ }
100
+ return fileType === type;
101
+ }
102
+ return fileExt === type.toLowerCase();
103
+ });
104
+ }
105
+ var validators = {
106
+ required: defineRule("required", {
107
+ valid: (value) => !isEmpty(value),
108
+ message: () => "This field is required"
109
+ }),
110
+ email: defineRule("email", {
111
+ valid: (value) => isEmpty(value) || isEmailShaped(value),
112
+ message: () => "Invalid email address"
113
+ }),
114
+ url: defineRule("url", {
115
+ valid: (value) => {
116
+ if (isEmpty(value)) return true;
48
117
  try {
49
118
  new URL(value);
119
+ return true;
50
120
  } catch {
51
- return message;
121
+ return false;
52
122
  }
53
- }
54
- return null;
123
+ },
124
+ message: () => "Invalid URL"
125
+ }),
126
+ minLength: defineRule("minLength", {
127
+ params: [["min", "minLength"]],
128
+ stringArg: "number",
129
+ valid: (value, { min }) => !value || !(value.length < (min ?? 0)),
130
+ message: ({ min }) => `Minimum length is ${min}`
131
+ }),
132
+ maxLength: defineRule("maxLength", {
133
+ params: [["max", "maxLength"]],
134
+ stringArg: "number",
135
+ valid: (value, { max }) => !value || !(value.length > (max ?? Infinity)),
136
+ message: ({ max }) => `Maximum length is ${max}`
137
+ }),
138
+ // Empty values pass (combine with `required`); a non-numeric value fails.
139
+ min: defineRule("min", {
140
+ params: [["min"]],
141
+ stringArg: "number",
142
+ valid: (value, { min }) => {
143
+ if (isEmpty(value)) return true;
144
+ const number = toNumber(value);
145
+ return !Number.isNaN(number) && !(number < (min ?? -Infinity));
146
+ },
147
+ message: ({ min }) => `Minimum value is ${min}`
148
+ }),
149
+ max: defineRule("max", {
150
+ params: [["max"]],
151
+ stringArg: "number",
152
+ valid: (value, { max }) => {
153
+ if (isEmpty(value)) return true;
154
+ const number = toNumber(value);
155
+ return !Number.isNaN(number) && !(number > (max ?? Infinity));
156
+ },
157
+ message: ({ max }) => `Maximum value is ${max}`
158
+ }),
159
+ pattern: defineRule("pattern", {
160
+ params: [["pattern", "regex"]],
161
+ stringArg: "regexp",
162
+ valid: (value, { pattern }) => isEmpty(value) || testRegExp(pattern, value),
163
+ message: () => "Invalid format"
164
+ }),
165
+ /** Equal to another field, even when empty */
166
+ matches: defineRule("matches", {
167
+ params: [["field", "fieldName"]],
168
+ valid: (value, { field }, formData) => value === formData[field],
169
+ message: () => "Fields do not match"
170
+ }),
171
+ /** Equal to another field; an empty value passes */
172
+ match: defineRule("match", {
173
+ params: [["field", "fieldName"]],
174
+ valid: (value, { field }, formData) => !value || value === formData[field],
175
+ message: ({ field }) => `Must match ${field}`
176
+ }),
177
+ oneOf: defineRule("oneOf", {
178
+ params: [["options", "values"]],
179
+ stringArg: "list",
180
+ valid: (value, { options }) => !value || !Array.isArray(options) || options.includes(value),
181
+ message: () => "Invalid option"
182
+ }),
183
+ custom: defineRule("custom", {
184
+ params: [["validator", "fn"]],
185
+ valid: (value, { validator }, formData) => typeof validator !== "function" || Boolean(validator(value, formData)),
186
+ message: () => "Validation failed"
187
+ }),
188
+ number: defineRule("number", {
189
+ valid: (value) => isEmpty(value) || !Number.isNaN(Number(value)),
190
+ message: () => "Must be a valid number"
191
+ }),
192
+ integer: defineRule("integer", {
193
+ valid: (value) => isEmpty(value) || Number.isInteger(Number(value)),
194
+ message: () => "Must be a whole number"
195
+ }),
196
+ phone: defineRule("phone", {
197
+ valid: (value) => !value || /^[\d\s\-+()]+$/.test(value) && value.replace(/\D/g, "").length >= 10,
198
+ message: () => "Please enter a valid phone number"
199
+ }),
200
+ date: defineRule("date", {
201
+ valid: (value) => !value || !Number.isNaN(new Date(value).getTime()),
202
+ message: () => "Please enter a valid date"
203
+ }),
204
+ alpha: defineRule("alpha", {
205
+ valid: (value) => !value || /^[a-zA-Z]+$/.test(value),
206
+ message: () => "Must contain only letters"
207
+ }),
208
+ alphanumeric: defineRule("alphanumeric", {
209
+ valid: (value) => !value || /^[a-zA-Z0-9]+$/.test(value),
210
+ message: () => "Must contain only letters and numbers"
211
+ }),
212
+ uppercase: defineRule("uppercase", {
213
+ valid: (value) => !value || value === String(value).toUpperCase(),
214
+ message: () => "Must be uppercase"
215
+ }),
216
+ fileType: defineRule("fileType", {
217
+ params: [["accept", "types"]],
218
+ stringArg: "list",
219
+ valid: (value, { accept }) => !value || value.type === void 0 || fileMatchesType(value, accept || []),
220
+ message: ({ accept }) => `File type must be one of: ${(accept || []).join(", ")}`
221
+ }),
222
+ fileSize: defineRule("fileSize", {
223
+ params: [["maxSize"]],
224
+ stringArg: "number",
225
+ valid: (value, { maxSize }) => !value || value.size === void 0 || !(value.size > (maxSize ?? Infinity)),
226
+ message: ({ maxSize }) => `File size must be less than ${((maxSize ?? Infinity) / (1024 * 1024)).toFixed(2)}MB`
227
+ }),
228
+ fileExtension: defineRule("fileExtension", {
229
+ params: [["extensions"]],
230
+ stringArg: "list",
231
+ valid: (value, { extensions }) => {
232
+ if (!value) return true;
233
+ const fileName = value.name || value;
234
+ const ext = `.${String(fileName).split(".").pop().toLowerCase()}`;
235
+ return (extensions || []).some((allowed) => ext === allowed.toLowerCase());
236
+ },
237
+ message: ({ extensions }) => `File extension must be one of: ${(extensions || []).join(", ")}`
238
+ }),
239
+ /** A registered validator or built-in by name */
240
+ get: (name) => Object.hasOwn(validators, name) ? validators[name] : void 0,
241
+ /** Combine validators into one returning the first error */
242
+ compose: (validatorList) => {
243
+ const rules = validatorList.map(resolveValidator);
244
+ return (value, options, translator, allValues) => {
245
+ for (const rule of rules) {
246
+ const error = rule ? rule(value, options, translator, allValues) : null;
247
+ if (error) {
248
+ return error;
249
+ }
250
+ }
251
+ return null;
252
+ };
55
253
  },
56
- pattern: (regex, message = "Invalid format") => (value) => {
57
- if (value && !regex.test(value)) {
58
- return message;
59
- }
60
- return null;
254
+ /** Debounce an async validator */
255
+ debounce: (validator, delay = 300) => {
256
+ let timeoutId;
257
+ return (value) => {
258
+ return new Promise((resolve) => {
259
+ clearTimeout(timeoutId);
260
+ timeoutId = setTimeout(async () => {
261
+ const result = await validator(value);
262
+ resolve(result);
263
+ }, delay);
264
+ });
265
+ };
61
266
  },
62
- matches: (fieldName, message = "Fields do not match") => (value, formData) => {
63
- if (value !== formData[fieldName]) {
64
- return message;
65
- }
66
- return null;
267
+ /** Cancellable async validator */
268
+ cancellable: (validator) => {
269
+ let abortController;
270
+ const wrapped = async (value) => {
271
+ if (abortController) {
272
+ abortController.abort();
273
+ }
274
+ abortController = typeof AbortController !== "undefined" ? new AbortController() : null;
275
+ try {
276
+ return await validator(value, abortController ? abortController.signal : null);
277
+ } catch (error) {
278
+ if (error.name === "AbortError") {
279
+ return null;
280
+ }
281
+ throw error;
282
+ }
283
+ };
284
+ wrapped.cancel = () => {
285
+ if (abortController) {
286
+ abortController.abort();
287
+ }
288
+ };
289
+ return wrapped;
67
290
  },
68
- oneOf: (options, message = "Invalid option") => (value) => {
69
- if (value && !options.includes(value)) {
70
- return message;
71
- }
72
- return null;
291
+ /** Run `validator` only when `condition` holds */
292
+ when: (condition, validator) => {
293
+ const rule = resolveValidator(validator);
294
+ return (value, options = {}, translator, allValues = {}) => {
295
+ const context = options.min !== void 0 || options.max !== void 0 ? allValues : options;
296
+ const shouldValidate = typeof condition === "function" ? condition(value, context) : condition;
297
+ if (!shouldValidate) {
298
+ return null;
299
+ }
300
+ return rule ? rule(value, options, translator, allValues) : null;
301
+ };
73
302
  },
74
- custom: (fn, message = "Validation failed") => (value, formData) => {
75
- if (!fn(value, formData)) {
76
- return message;
77
- }
78
- return null;
303
+ /** Validator chain builder */
304
+ chain: (options = {}) => {
305
+ const validatorList = [];
306
+ const stopOnFirstError = options.stopOnFirstError !== false;
307
+ const direct = (name) => (opts) => {
308
+ validatorList.push((v, o, t, a) => validators[name](v, opts || o || {}, t, a));
309
+ return chain;
310
+ };
311
+ const chain = {
312
+ required: direct("required"),
313
+ email: direct("email"),
314
+ minLength: direct("minLength"),
315
+ maxLength: direct("maxLength"),
316
+ custom: (fn, message) => {
317
+ validatorList.push((v, o, t, a) => {
318
+ const result = fn(v, a);
319
+ return result === null || result === true || result === void 0 ? null : message || result;
320
+ });
321
+ return chain;
322
+ },
323
+ validate: (value, opts, translator, allValues) => {
324
+ if (stopOnFirstError) {
325
+ for (const validator of validatorList) {
326
+ const error = validator(value, opts, translator, allValues);
327
+ if (error) {
328
+ return error;
329
+ }
330
+ }
331
+ return null;
332
+ }
333
+ const errors = [];
334
+ for (const validator of validatorList) {
335
+ const error = validator(value, opts, translator, allValues);
336
+ if (error) {
337
+ errors.push(error);
338
+ }
339
+ }
340
+ return errors.length > 0 ? errors : null;
341
+ }
342
+ };
343
+ return chain;
79
344
  }
80
345
  };
346
+ function lookup(name) {
347
+ const found = Object.hasOwn(validators, name) && !HELPER_NAMES.has(name) ? validators[name] : null;
348
+ return typeof found === "function" ? found : null;
349
+ }
350
+ function resolveString(entry) {
351
+ const exact = lookup(entry);
352
+ const colon = exact ? -1 : entry.indexOf(":");
353
+ const name = colon === -1 ? entry : entry.slice(0, colon).trim();
354
+ const found = exact ?? lookup(name);
355
+ if (!found) return null;
356
+ const text = colon === -1 ? void 0 : entry.slice(colon + 1);
357
+ if (!found[BUILTIN]) {
358
+ return text === void 0 ? { name, args: [], validator: found } : null;
359
+ }
360
+ const args = found[BUILTIN].argsFromString(text);
361
+ if (!args) return null;
362
+ return { name: found[BUILTIN].name, args, validator: found[BUILTIN].factory(...args) };
363
+ }
364
+ function resolveValidator(entry) {
365
+ if (typeof entry === "string") {
366
+ return resolveString(entry)?.validator ?? null;
367
+ }
368
+ if (typeof entry !== "function") return null;
369
+ return entry[BUILTIN] ? entry[BUILTIN].factory() : entry;
370
+ }
371
+ function wrapValidator(validatorFn, message) {
372
+ return (value, options, translator, allValues) => {
373
+ const result = validatorFn(value, options, translator, allValues);
374
+ if (typeof result === "string") {
375
+ return result;
376
+ }
377
+ if (!result) {
378
+ return null;
379
+ }
380
+ return message || "Validation failed";
381
+ };
382
+ }
383
+
384
+ // src/validation.js
81
385
  var FormValidator = class {
82
386
  constructor(schema = {}) {
83
387
  this.schema = schema;
@@ -93,8 +397,9 @@ var FormValidator = class {
93
397
  return null;
94
398
  }
95
399
  const validatorArray = Array.isArray(fieldValidators) ? fieldValidators : [fieldValidators];
96
- for (const validator of validatorArray) {
97
- const error = validator(value, formData);
400
+ for (const entry of validatorArray) {
401
+ const validator = resolveValidator(entry);
402
+ const error = validator ? validator(value, formData) : null;
98
403
  if (error) {
99
404
  return error;
100
405
  }
@@ -170,8 +475,11 @@ var FormValidator = class {
170
475
  this.clearTouched();
171
476
  }
172
477
  };
173
- function createValidator(schema) {
174
- return new FormValidator(schema);
478
+ function createValidator(schemaOrFn, message) {
479
+ if (typeof schemaOrFn === "function") {
480
+ return wrapValidator(schemaOrFn, message);
481
+ }
482
+ return new FormValidator(schemaOrFn);
175
483
  }
176
484
  function validate(formData, schema) {
177
485
  const validator = new FormValidator(schema);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/patterns.js", "../src/validation.js"],
4
- "sourcesContent": ["/**\n * Shared validation patterns.\n *\n * Internal to @coherent.js/forms \u2014 not re-exported from index.js.\n */\n\n/**\n * Email shape check: a local part, then a dotted domain.\n *\n * Domain labels use `[^\\s@.]` rather than `[^\\s@]` so that the literal dot\n * separators are the only thing that can match a dot. Allowing `[^\\s@]+` on\n * both sides of `\\.` makes the split ambiguous, and a non-matching subject\n * with many dots (\"a@\" + \"a.\" * n + \" \") then costs O(n\u00B2) backtracking \u2014\n * CodeQL js/polynomial-redos.\n *\n * This is a shape check, not RFC 5322 conformance. Deliverability is only\n * ever established by sending mail.\n */\nexport const EMAIL_PATTERN = /^[^\\s@]+@[^\\s@.]+(?:\\.[^\\s@.]+)+$/;\n\n/**\n * Longest address RFC 5321 permits, used to bound work before matching.\n */\nexport const EMAIL_MAX_LENGTH = 254;\n\n/**\n * Test whether a value has the shape of an email address.\n *\n * @param {unknown} value - Value to check\n * @returns {boolean} True if the value looks like an email address\n */\nexport function isEmailShaped(value) {\n return (\n typeof value === 'string' &&\n value.length <= EMAIL_MAX_LENGTH &&\n EMAIL_PATTERN.test(value)\n );\n}\n", "/**\n * Coherent.js Form Validation\n * \n * Comprehensive validation utilities for forms\n * \n * @module forms/validation\n */\n\nimport { isEmailShaped } from './patterns.js';\n\n/**\n * Built-in validators\n */\nexport const validators = {\n required: (message = 'This field is required') => (value) => {\n if (value === null || value === undefined || value === '') {\n return message;\n }\n return null;\n },\n\n minLength: (min, message = `Minimum length is ${min}`) => (value) => {\n if (value && value.length < min) {\n return message;\n }\n return null;\n },\n\n maxLength: (max, message = `Maximum length is ${max}`) => (value) => {\n if (value && value.length > max) {\n return message;\n }\n return null;\n },\n\n min: (min, message = `Minimum value is ${min}`) => (value) => {\n if (value !== null && value !== undefined && Number(value) < min) {\n return message;\n }\n return null;\n },\n\n max: (max, message = `Maximum value is ${max}`) => (value) => {\n if (value !== null && value !== undefined && Number(value) > max) {\n return message;\n }\n return null;\n },\n\n email: (message = 'Invalid email address') => (value) => {\n if (value && !isEmailShaped(value)) {\n return message;\n }\n return null;\n },\n\n url: (message = 'Invalid URL') => (value) => {\n if (value) {\n try {\n new URL(value);\n } catch {\n return message;\n }\n }\n return null;\n },\n\n pattern: (regex, message = 'Invalid format') => (value) => {\n if (value && !regex.test(value)) {\n return message;\n }\n return null;\n },\n\n matches: (fieldName, message = 'Fields do not match') => (value, formData) => {\n if (value !== formData[fieldName]) {\n return message;\n }\n return null;\n },\n\n oneOf: (options, message = 'Invalid option') => (value) => {\n if (value && !options.includes(value)) {\n return message;\n }\n return null;\n },\n\n custom: (fn, message = 'Validation failed') => (value, formData) => {\n if (!fn(value, formData)) {\n return message;\n }\n return null;\n }\n};\n\n/**\n * Form Validator\n * Manages form validation state\n */\nexport class FormValidator {\n constructor(schema = {}) {\n this.schema = schema;\n this.errors = {};\n this.touched = {};\n }\n\n /**\n * Validate a single field\n */\n validateField(name, value, formData = {}) {\n const fieldValidators = this.schema[name];\n \n if (!fieldValidators) {\n return null;\n }\n\n const validatorArray = Array.isArray(fieldValidators) ? fieldValidators : [fieldValidators];\n\n for (const validator of validatorArray) {\n const error = validator(value, formData);\n if (error) {\n return error;\n }\n }\n\n return null;\n }\n\n /**\n * Validate entire form\n */\n validate(formData) {\n const errors = {};\n let isValid = true;\n\n for (const [name, value] of Object.entries(formData)) {\n const error = this.validateField(name, value, formData);\n if (error) {\n errors[name] = error;\n isValid = false;\n }\n }\n\n // Check for required fields not in formData\n for (const name of Object.keys(this.schema)) {\n if (!(name in formData)) {\n const error = this.validateField(name, undefined, formData);\n if (error) {\n errors[name] = error;\n isValid = false;\n }\n }\n }\n\n this.errors = errors;\n return { isValid, errors };\n }\n\n /**\n * Mark field as touched\n */\n touch(name) {\n this.touched[name] = true;\n }\n\n /**\n * Check if field is touched\n */\n isTouched(name) {\n return this.touched[name] || false;\n }\n\n /**\n * Get error for field\n */\n getError(name) {\n return this.errors[name] || null;\n }\n\n /**\n * Check if field has error\n */\n hasError(name) {\n return !!this.errors[name];\n }\n\n /**\n * Clear errors\n */\n clearErrors() {\n this.errors = {};\n }\n\n /**\n * Clear touched state\n */\n clearTouched() {\n this.touched = {};\n }\n\n /**\n * Reset validator\n */\n reset() {\n this.clearErrors();\n this.clearTouched();\n }\n}\n\n/**\n * Create a form validator\n */\nexport function createValidator(schema) {\n return new FormValidator(schema);\n}\n\n/**\n * Validate form data against schema\n */\nexport function validate(formData, schema) {\n const validator = new FormValidator(schema);\n return validator.validate(formData);\n}\n\nexport default {\n validators,\n FormValidator,\n createValidator,\n validate\n};\n"],
5
- "mappings": "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",
3
+ "sources": ["../src/patterns.js", "../src/rules.js", "../src/validation.js"],
4
+ "sourcesContent": ["/**\n * Shared validation patterns.\n *\n * Internal to @coherent.js/forms \u2014 not re-exported from index.js.\n */\n\n/**\n * Email shape check: a local part, then a dotted domain.\n *\n * Domain labels use `[^\\s@.]` rather than `[^\\s@]` so that the literal dot\n * separators are the only thing that can match a dot. Allowing `[^\\s@]+` on\n * both sides of `\\.` makes the split ambiguous, and a non-matching subject\n * with many dots (\"a@\" + \"a.\" * n + \" \") then costs O(n\u00B2) backtracking \u2014\n * CodeQL js/polynomial-redos.\n *\n * This is a shape check, not RFC 5322 conformance. Deliverability is only\n * ever established by sending mail.\n */\nexport const EMAIL_PATTERN = /^[^\\s@]+@[^\\s@.]+(?:\\.[^\\s@.]+)+$/;\n\n/**\n * Longest address RFC 5321 permits, used to bound work before matching.\n */\nexport const EMAIL_MAX_LENGTH = 254;\n\n/**\n * Test whether a value has the shape of an email address.\n *\n * @param {unknown} value - Value to check\n * @returns {boolean} True if the value looks like an email address\n */\nexport function isEmailShaped(value) {\n return (\n typeof value === 'string' &&\n value.length <= EMAIL_MAX_LENGTH &&\n EMAIL_PATTERN.test(value)\n );\n}\n", "/**\n * Coherent.js Forms - validation rules\n *\n * The single `validators` registry behind `@coherent.js/forms`,\n * `@coherent.js/forms/validation` and `@coherent.js/forms/validators`.\n * Internal: import it through one of those entry points.\n *\n * ## Calling convention\n *\n * A **validator** is `(value, formData) => string | null`: an error message,\n * or `null` when the value passes. Schemas, `validateField`, `validateForm`,\n * `FormBuilder` fields and `hydrateForm` all run validators this way.\n *\n * Each built-in is a **factory** that returns a validator:\n *\n * validators.required() validators.required('Required!')\n * validators.minLength(8) validators.minLength(8, 'Too short')\n *\n * A built-in may also be listed without calling it (`[validators.required]`);\n * runners call the factory with its defaults first. A string names a built-in\n * or registered validator, and a built-in's name may carry its arguments:\n * `'minLength:8'` is `validators.minLength(8)` (see argsFromString).\n *\n * For backward compatibility a built-in can still be called **directly** with\n * a value and an options object, returning the error or `null`:\n *\n * validators.minLength('abc', { min: 5 }) // 'Minimum length is 5'\n * validators.email(null) // null\n *\n * A lone argument that could be a message \u2014 a non-empty string, `undefined`\n * or `null` \u2014 is always read as a factory call, so `validators.email('a@b.c')`\n * returns a validator; call `validators.email('a@b.c', {})` (or\n * `validators.email()('a@b.c')`) to check a value directly.\n *\n * @module forms/rules\n */\n\nimport { isEmailShaped } from './patterns.js';\n\n/** Built-in factories, so runners can tell them from plain validators. */\nconst BUILTIN = Symbol('coherent.forms.builtin');\n\n/**\n * Validators whose origin is known, so the form builder can describe them to\n * the client: validator \u2192 `{ name, args }`.\n */\nconst DESCRIPTORS = new WeakMap();\n\n/** Names on `validators` that are helpers, not validation rules. */\nconst HELPER_NAMES = new Set(['get', 'compose', 'debounce', 'cancellable', 'when', 'chain']);\n\nconst isEmpty = value => value === null || value === undefined || value === '';\n\nconst isOptionsObject = value =>\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n !(value instanceof RegExp);\n\n/**\n * Factory arguments for the text after the colon of a `'name:\u2026'` string\n * entry, or `null` when it does not fit the rule.\n *\n * - No parameter (`required`, `email`, \u2026): the text is the message \u2014\n * `'required:Please enter your name'`.\n * - `'list'` (`oneOf`, `fileType`, `fileExtension`): every comma-separated\n * value goes into the one array parameter \u2014 `'oneOf:red,green,blue'`.\n * - `'regexp'` (`pattern`): the whole text is the regular expression source,\n * commas included \u2014 `'pattern:^[a-z]{2,8}$'`.\n * - Otherwise the text up to the first comma is the parameter (a number for\n * `'number'`, so `'minLength:abc'` is rejected) and the rest, commas\n * included, is the message \u2014 `'minLength:8,Use at least 8 characters'`.\n *\n * @param {Array} params - The rule's factory parameters\n * @param {'string'|'number'|'list'|'regexp'} kind - How the text supplies them\n * @param {string|undefined} text - Text after the colon (`undefined`: none)\n * @returns {Array|null}\n */\nfunction argsFromString(params, kind, text) {\n if (text === undefined || text.trim() === '') return [];\n if (params.length === 0) return [text.trim()];\n\n if (kind === 'list') {\n return [text.split(',').map(part => part.trim()).filter(Boolean)];\n }\n if (kind === 'regexp') {\n try {\n return [new RegExp(text)];\n } catch {\n return null;\n }\n }\n\n const comma = text.indexOf(',');\n const raw = (comma === -1 ? text : text.slice(0, comma)).trim();\n const message = comma === -1 ? '' : text.slice(comma + 1).trim();\n\n let arg = raw;\n if (kind === 'number') {\n arg = Number(raw);\n if (raw === '' || Number.isNaN(arg)) return null;\n }\n return message ? [arg, message] : [arg];\n}\n\n/**\n * Define a built-in rule.\n *\n * @param {string} name - Registry name\n * @param {Object} spec\n * @param {Array<[string, ...string[]]>} [spec.params] - Factory parameters, in\n * order, each as `[configKey, ...optionAliases]` for direct calls\n * @param {'string'|'number'|'list'|'regexp'} [spec.stringArg] - How the\n * `'name:\u2026'` string form supplies the parameter (see argsFromString)\n * @param {(value: unknown, config: Object, formData: Object) => boolean} spec.valid\n * @param {(config: Object) => string} spec.message - Default error message\n */\nfunction defineRule(name, { params = [], stringArg = 'string', valid, message }) {\n const fromOptions = (options = {}) => {\n const config = { message: options.message };\n for (const [key, ...aliases] of params) {\n config[key] = [key, ...aliases].map(alias => options[alias]).find(v => v !== undefined);\n }\n return config;\n };\n\n const check = (value, config, formData) => {\n if (valid(value, config, formData || {})) return null;\n return config.message || builtin.message || message(config);\n };\n\n const factory = (...args) => {\n const config = { message: args[params.length] };\n params.forEach(([key], index) => { config[key] = args[index]; });\n\n // Also accepts the (value, options, translator, allValues) arguments of\n // the older direct convention, so it composes with either.\n const rule = (value, formData, _translator, allValues) =>\n check(value, config, allValues ?? formData);\n\n DESCRIPTORS.set(rule, { name, args });\n return rule;\n };\n\n // Direct calls pass a value plus an options object. Anything else is a\n // factory call, including `required(undefined)` (a message that happens to\n // be missing) \u2014 only an explicit options object, or a lone argument that\n // cannot be a message, selects the direct form.\n const isDirectCall = (args) => {\n if (args.length >= 2) {\n return params.length === 0 || isOptionsObject(args[1]);\n }\n if (args.length === 1 && params.length === 0) {\n const [arg] = args;\n return arg === '' || (arg !== undefined && arg !== null && typeof arg !== 'string');\n }\n return false;\n };\n\n function builtin(...args) {\n if (isDirectCall(args)) {\n const [value, options, , allValues] = args;\n return check(value, fromOptions(isOptionsObject(options) ? options : {}), allValues);\n }\n return factory(...args);\n }\n\n Object.defineProperty(builtin, 'name', { value: name });\n builtin[BUILTIN] = {\n name,\n factory,\n argsFromString: (text) => argsFromString(params, stringArg, text)\n };\n return builtin;\n}\n\nconst toNumber = value => (typeof value === 'number' ? value : Number(value));\n\nfunction testRegExp(regex, value) {\n if (!(regex instanceof RegExp)) return true;\n // A /g or /y regex keeps lastIndex between calls; always test from the start.\n regex.lastIndex = 0;\n return regex.test(String(value));\n}\n\nfunction fileMatchesType(file, allowedTypes) {\n const fileType = file.type;\n const fileExt = file.name ? file.name.split('.').pop().toLowerCase() : '';\n\n return allowedTypes.some(type => {\n if (type.startsWith('.')) {\n return fileExt === type.slice(1).toLowerCase();\n }\n if (type.includes('/')) {\n if (type.endsWith('/*')) {\n return fileType.startsWith(type.replace('/*', '/'));\n }\n return fileType === type;\n }\n return fileExt === type.toLowerCase();\n });\n}\n\n/**\n * Built-in validators and helpers. See the module comment for the calling\n * convention.\n */\nexport const validators = {\n required: defineRule('required', {\n valid: value => !isEmpty(value),\n message: () => 'This field is required'\n }),\n\n email: defineRule('email', {\n valid: value => isEmpty(value) || isEmailShaped(value),\n message: () => 'Invalid email address'\n }),\n\n url: defineRule('url', {\n valid: (value) => {\n if (isEmpty(value)) return true;\n try {\n new URL(value);\n return true;\n } catch {\n return false;\n }\n },\n message: () => 'Invalid URL'\n }),\n\n minLength: defineRule('minLength', {\n params: [['min', 'minLength']],\n stringArg: 'number',\n valid: (value, { min }) => !value || !(value.length < (min ?? 0)),\n message: ({ min }) => `Minimum length is ${min}`\n }),\n\n maxLength: defineRule('maxLength', {\n params: [['max', 'maxLength']],\n stringArg: 'number',\n valid: (value, { max }) => !value || !(value.length > (max ?? Infinity)),\n message: ({ max }) => `Maximum length is ${max}`\n }),\n\n // Empty values pass (combine with `required`); a non-numeric value fails.\n min: defineRule('min', {\n params: [['min']],\n stringArg: 'number',\n valid: (value, { min }) => {\n if (isEmpty(value)) return true;\n const number = toNumber(value);\n return !Number.isNaN(number) && !(number < (min ?? -Infinity));\n },\n message: ({ min }) => `Minimum value is ${min}`\n }),\n\n max: defineRule('max', {\n params: [['max']],\n stringArg: 'number',\n valid: (value, { max }) => {\n if (isEmpty(value)) return true;\n const number = toNumber(value);\n return !Number.isNaN(number) && !(number > (max ?? Infinity));\n },\n message: ({ max }) => `Maximum value is ${max}`\n }),\n\n pattern: defineRule('pattern', {\n params: [['pattern', 'regex']],\n stringArg: 'regexp',\n valid: (value, { pattern }) => isEmpty(value) || testRegExp(pattern, value),\n message: () => 'Invalid format'\n }),\n\n /** Equal to another field, even when empty */\n matches: defineRule('matches', {\n params: [['field', 'fieldName']],\n valid: (value, { field }, formData) => value === formData[field],\n message: () => 'Fields do not match'\n }),\n\n /** Equal to another field; an empty value passes */\n match: defineRule('match', {\n params: [['field', 'fieldName']],\n valid: (value, { field }, formData) => !value || value === formData[field],\n message: ({ field }) => `Must match ${field}`\n }),\n\n oneOf: defineRule('oneOf', {\n params: [['options', 'values']],\n stringArg: 'list',\n valid: (value, { options }) => !value || !Array.isArray(options) || options.includes(value),\n message: () => 'Invalid option'\n }),\n\n custom: defineRule('custom', {\n params: [['validator', 'fn']],\n valid: (value, { validator }, formData) =>\n typeof validator !== 'function' || Boolean(validator(value, formData)),\n message: () => 'Validation failed'\n }),\n\n number: defineRule('number', {\n valid: value => isEmpty(value) || !Number.isNaN(Number(value)),\n message: () => 'Must be a valid number'\n }),\n\n integer: defineRule('integer', {\n valid: value => isEmpty(value) || Number.isInteger(Number(value)),\n message: () => 'Must be a whole number'\n }),\n\n phone: defineRule('phone', {\n valid: value => !value || (/^[\\d\\s\\-+()]+$/.test(value) && value.replace(/\\D/g, '').length >= 10),\n message: () => 'Please enter a valid phone number'\n }),\n\n date: defineRule('date', {\n valid: value => !value || !Number.isNaN(new Date(value).getTime()),\n message: () => 'Please enter a valid date'\n }),\n\n alpha: defineRule('alpha', {\n valid: value => !value || /^[a-zA-Z]+$/.test(value),\n message: () => 'Must contain only letters'\n }),\n\n alphanumeric: defineRule('alphanumeric', {\n valid: value => !value || /^[a-zA-Z0-9]+$/.test(value),\n message: () => 'Must contain only letters and numbers'\n }),\n\n uppercase: defineRule('uppercase', {\n valid: value => !value || value === String(value).toUpperCase(),\n message: () => 'Must be uppercase'\n }),\n\n fileType: defineRule('fileType', {\n params: [['accept', 'types']],\n stringArg: 'list',\n valid: (value, { accept }) =>\n !value || value.type === undefined || fileMatchesType(value, accept || []),\n message: ({ accept }) => `File type must be one of: ${(accept || []).join(', ')}`\n }),\n\n fileSize: defineRule('fileSize', {\n params: [['maxSize']],\n stringArg: 'number',\n valid: (value, { maxSize }) =>\n !value || value.size === undefined || !(value.size > (maxSize ?? Infinity)),\n message: ({ maxSize }) => `File size must be less than ${((maxSize ?? Infinity) / (1024 * 1024)).toFixed(2)}MB`\n }),\n\n fileExtension: defineRule('fileExtension', {\n params: [['extensions']],\n stringArg: 'list',\n valid: (value, { extensions }) => {\n if (!value) return true;\n const fileName = value.name || value;\n const ext = `.${String(fileName).split('.').pop().toLowerCase()}`;\n return (extensions || []).some(allowed => ext === allowed.toLowerCase());\n },\n message: ({ extensions }) => `File extension must be one of: ${(extensions || []).join(', ')}`\n }),\n\n /** A registered validator or built-in by name */\n get: (name) => (Object.hasOwn(validators, name) ? validators[name] : undefined),\n\n /** Combine validators into one returning the first error */\n compose: (validatorList) => {\n const rules = validatorList.map(resolveValidator);\n return (value, options, translator, allValues) => {\n for (const rule of rules) {\n const error = rule ? rule(value, options, translator, allValues) : null;\n if (error) {\n return error;\n }\n }\n return null;\n };\n },\n\n /** Debounce an async validator */\n debounce: (validator, delay = 300) => {\n let timeoutId;\n return (value) => {\n return new Promise((resolve) => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(async () => {\n const result = await validator(value);\n resolve(result);\n }, delay);\n });\n };\n },\n\n /** Cancellable async validator */\n cancellable: (validator) => {\n let abortController;\n const wrapped = async (value) => {\n if (abortController) {\n abortController.abort();\n }\n // AbortController is a global browser/Node.js API\n abortController = typeof AbortController !== 'undefined' ? new AbortController() : null;\n try {\n return await validator(value, abortController ? abortController.signal : null);\n } catch (error) {\n if (error.name === 'AbortError') {\n return null;\n }\n throw error;\n }\n };\n wrapped.cancel = () => {\n if (abortController) {\n abortController.abort();\n }\n };\n return wrapped;\n },\n\n /** Run `validator` only when `condition` holds */\n when: (condition, validator) => {\n const rule = resolveValidator(validator);\n return (value, options = {}, translator, allValues = {}) => {\n // Pass options as context if it looks like context (has non-validator properties)\n const context = options.min !== undefined || options.max !== undefined ? allValues : options;\n const shouldValidate = typeof condition === 'function'\n ? condition(value, context)\n : condition;\n\n if (!shouldValidate) {\n return null;\n }\n\n return rule ? rule(value, options, translator, allValues) : null;\n };\n },\n\n /** Validator chain builder */\n chain: (options = {}) => {\n const validatorList = [];\n const stopOnFirstError = options.stopOnFirstError !== false;\n\n const direct = name => (opts) => {\n validatorList.push((v, o, t, a) => validators[name](v, opts || o || {}, t, a));\n return chain;\n };\n\n const chain = {\n required: direct('required'),\n email: direct('email'),\n minLength: direct('minLength'),\n maxLength: direct('maxLength'),\n custom: (fn, message) => {\n validatorList.push((v, o, t, a) => {\n // Custom validator returns null if valid, message if invalid\n const result = fn(v, a);\n return result === null || result === true || result === undefined ? null : (message || result);\n });\n return chain;\n },\n validate: (value, opts, translator, allValues) => {\n if (stopOnFirstError) {\n // Stop on first error - return single error or null\n for (const validator of validatorList) {\n const error = validator(value, opts, translator, allValues);\n if (error) {\n return error;\n }\n }\n return null;\n }\n // Collect all errors - return array or null\n const errors = [];\n for (const validator of validatorList) {\n const error = validator(value, opts, translator, allValues);\n if (error) {\n errors.push(error);\n }\n }\n return errors.length > 0 ? errors : null;\n }\n };\n\n return chain;\n }\n};\n\n/** The registry entry named `name`, if it is a validator (not a helper). */\nfunction lookup(name) {\n const found = Object.hasOwn(validators, name) && !HELPER_NAMES.has(name)\n ? validators[name]\n : null;\n return typeof found === 'function' ? found : null;\n}\n\n/**\n * Resolve a string entry: `'required'`, or `'name:arg1,arg2'` for a built-in\n * with arguments (`'minLength:8'`, `'oneOf:a,b,c'`, `'pattern:^\\\\d+$'`; see\n * argsFromString). A registered validator is named without arguments.\n *\n * @param {string} entry\n * @returns {{ name: string, args: Array, validator: Function }|null} `null`\n * for an unknown name or arguments that do not fit the rule\n */\nfunction resolveString(entry) {\n // An exact registry name wins, so a validator registered under a name that\n // contains a colon still resolves.\n const exact = lookup(entry);\n const colon = exact ? -1 : entry.indexOf(':');\n const name = colon === -1 ? entry : entry.slice(0, colon).trim();\n const found = exact ?? lookup(name);\n if (!found) return null;\n\n const text = colon === -1 ? undefined : entry.slice(colon + 1);\n if (!found[BUILTIN]) {\n return text === undefined ? { name, args: [], validator: found } : null;\n }\n\n const args = found[BUILTIN].argsFromString(text);\n if (!args) return null;\n return { name: found[BUILTIN].name, args, validator: found[BUILTIN].factory(...args) };\n}\n\n/**\n * Turn a schema entry into a validator: a built-in factory listed without\n * calling it (`validators.required`) is called with its defaults, a string\n * names a built-in or registered validator (`'required'`, or a built-in with\n * arguments: `'minLength:8'`), a function is used as is. Anything else \u2014 an\n * unknown name, or arguments that do not fit (`'minLength:abc'`) \u2014 is `null`\n * (skipped).\n *\n * @param {unknown} entry\n * @returns {Function|null}\n */\nexport function resolveValidator(entry) {\n if (typeof entry === 'string') {\n return resolveString(entry)?.validator ?? null;\n }\n if (typeof entry !== 'function') return null;\n return entry[BUILTIN] ? entry[BUILTIN].factory() : entry;\n}\n\n/**\n * Run validators against one value; the first error wins.\n */\nexport function validateField(value, validatorList, formData = {}) {\n const list = Array.isArray(validatorList) ? validatorList : [validatorList];\n for (const entry of list) {\n const validator = resolveValidator(entry);\n const error = validator ? validator(value, formData) : null;\n if (error) {\n return error;\n }\n }\n return null;\n}\n\n/**\n * Validate a whole form: `{ field: [validators] }`. Returns the first error\n * per failing field, or `null` when everything passes.\n */\nexport function validateForm(formData, fieldValidators) {\n const errors = {};\n\n for (const [fieldName, validatorList] of Object.entries(fieldValidators)) {\n const value = formData[fieldName];\n const error = validateField(value, validatorList, formData);\n if (error) {\n errors[fieldName] = error;\n }\n }\n\n return Object.keys(errors).length > 0 ? errors : null;\n}\n\n/**\n * Wrap a check function as a validator: a string result is the error, any\n * other truthy result becomes `message`, a falsy one passes.\n */\nexport function wrapValidator(validatorFn, message) {\n return (value, options, translator, allValues) => {\n const result = validatorFn(value, options, translator, allValues);\n // If validator returns a string, use it as the error message\n if (typeof result === 'string') {\n return result;\n }\n // If validator returns falsy (null, false, undefined), no error\n if (!result) {\n return null;\n }\n // If validator returns truthy (true, object, etc), use provided message\n return message || 'Validation failed';\n };\n}\n\n/**\n * Register a validator under `name`. It is stored as is (not as a factory),\n * so list it as `validators[name]`. Registered validators are described to\n * the client by name, so `hydrateForm` enforces them when the same name is\n * registered in the browser.\n */\nexport function registerValidator(name, validatorFn) {\n validators[name] = validatorFn;\n if (typeof validatorFn === 'function' && !validatorFn[BUILTIN]) {\n DESCRIPTORS.set(validatorFn, { name, args: [] });\n }\n}\n\n/**\n * Compose multiple validators\n */\nexport function composeValidators(...validatorFns) {\n return validators.compose(validatorFns);\n}\n\n// ---------------------------------------------------------------------------\n// Server \u2192 client description (`data-validators`)\n// ---------------------------------------------------------------------------\n\n/** JSON cannot hold a RegExp; tag it so the client can rebuild it. */\nfunction serializeArg(arg) {\n if (arg instanceof RegExp) return { $regexp: [arg.source, arg.flags] };\n return arg;\n}\n\nfunction reviveArg(arg) {\n if (isOptionsObject(arg) && Array.isArray(arg.$regexp)) {\n try {\n return new RegExp(arg.$regexp[0], arg.$regexp[1]);\n } catch {\n return undefined;\n }\n }\n return arg;\n}\n\n/** Whether JSON can carry a factory argument to the client unchanged. */\nfunction isSerializable(arg) {\n if (arg === undefined || arg === null) return true;\n if (arg instanceof RegExp) return true;\n if (Array.isArray(arg)) return arg.every(isSerializable);\n return ['string', 'number', 'boolean'].includes(typeof arg);\n}\n\n/**\n * Describe a schema entry as `{ name, args }` for the client, or `null` when\n * it cannot be rebuilt there (an anonymous function, or a factory argument\n * such as a function that JSON cannot carry). Those still run on the server.\n */\nexport function describeValidator(entry) {\n let descriptor;\n if (typeof entry === 'string') {\n // `'minLength:8'` is described like `validators.minLength(8)`.\n descriptor = resolveString(entry);\n } else if (typeof entry === 'function') {\n descriptor = entry[BUILTIN] ? { name: entry[BUILTIN].name, args: [] } : DESCRIPTORS.get(entry);\n }\n if (!descriptor || !descriptor.args.every(isSerializable)) return null;\n\n // Trailing undefined arguments (an omitted message) are just defaults.\n const args = [...descriptor.args];\n while (args.length > 0 && args[args.length - 1] === undefined) args.pop();\n return { name: descriptor.name, args: args.map(serializeArg) };\n}\n\n/**\n * Serialize a field's validators for its `data-validators` attribute:\n * a JSON array of `{ name, args }`, or `null` when none can be described.\n */\nexport function serializeValidators(validatorList) {\n const specs = (validatorList || []).map(describeValidator).filter(Boolean);\n return specs.length > 0 ? JSON.stringify(specs) : null;\n}\n\n/**\n * Rebuild validators from a `data-validators` attribute. Reads the JSON\n * written by {@link serializeValidators}, and the older comma-separated form\n * (`required,minLength:8`). Unknown names are skipped.\n */\nexport function parseValidators(attribute) {\n if (!attribute) return [];\n const text = String(attribute).trim();\n\n let specs;\n if (text.startsWith('[')) {\n try {\n specs = JSON.parse(text);\n } catch {\n return [];\n }\n if (!Array.isArray(specs)) return [];\n } else {\n specs = text.split(',').map((part) => {\n const [name, ...params] = part.trim().split(':');\n return { name, args: params.map(p => (p !== '' && !Number.isNaN(Number(p)) ? Number(p) : p)) };\n });\n }\n\n return specs.map((spec) => {\n if (!spec || typeof spec.name !== 'string') return null;\n const { name } = spec;\n if (!Object.hasOwn(validators, name) || HELPER_NAMES.has(name)) return null;\n\n const entry = validators[name];\n if (typeof entry !== 'function') return null;\n\n // Built-ins are rebuilt through their factory, never the direct form.\n const args = Array.isArray(spec.args) ? spec.args.map(reviveArg) : [];\n return entry[BUILTIN] ? entry[BUILTIN].factory(...args) : entry;\n }).filter(Boolean);\n}\n", "/**\n * Coherent.js Form Validation\n * \n * Comprehensive validation utilities for forms\n * \n * @module forms/validation\n */\n\nimport { validators, resolveValidator, wrapValidator } from './rules.js';\n\n/**\n * Built-in validators (the same registry `@coherent.js/forms/validators`\n * exports). Each is a factory returning a `(value, formData) => error | null`\n * validator: `validators.minLength(8)`. See rules.js for the full convention.\n */\nexport { validators };\n\n/**\n * Form Validator\n * Manages form validation state\n */\nexport class FormValidator {\n constructor(schema = {}) {\n this.schema = schema;\n this.errors = {};\n this.touched = {};\n }\n\n /**\n * Validate a single field\n */\n validateField(name, value, formData = {}) {\n const fieldValidators = this.schema[name];\n \n if (!fieldValidators) {\n return null;\n }\n\n const validatorArray = Array.isArray(fieldValidators) ? fieldValidators : [fieldValidators];\n\n for (const entry of validatorArray) {\n // Accepts `validators.required` as well as `validators.required()`.\n const validator = resolveValidator(entry);\n const error = validator ? validator(value, formData) : null;\n if (error) {\n return error;\n }\n }\n\n return null;\n }\n\n /**\n * Validate entire form\n */\n validate(formData) {\n const errors = {};\n let isValid = true;\n\n for (const [name, value] of Object.entries(formData)) {\n const error = this.validateField(name, value, formData);\n if (error) {\n errors[name] = error;\n isValid = false;\n }\n }\n\n // Check for required fields not in formData\n for (const name of Object.keys(this.schema)) {\n if (!(name in formData)) {\n const error = this.validateField(name, undefined, formData);\n if (error) {\n errors[name] = error;\n isValid = false;\n }\n }\n }\n\n this.errors = errors;\n return { isValid, errors };\n }\n\n /**\n * Mark field as touched\n */\n touch(name) {\n this.touched[name] = true;\n }\n\n /**\n * Check if field is touched\n */\n isTouched(name) {\n return this.touched[name] || false;\n }\n\n /**\n * Get error for field\n */\n getError(name) {\n return this.errors[name] || null;\n }\n\n /**\n * Check if field has error\n */\n hasError(name) {\n return !!this.errors[name];\n }\n\n /**\n * Clear errors\n */\n clearErrors() {\n this.errors = {};\n }\n\n /**\n * Clear touched state\n */\n clearTouched() {\n this.touched = {};\n }\n\n /**\n * Reset validator\n */\n reset() {\n this.clearErrors();\n this.clearTouched();\n }\n}\n\n/**\n * Create a validator.\n *\n * - `createValidator(schema)` returns a {@link FormValidator} for a\n * `{ field: validator | validator[] }` schema.\n * - `createValidator(fn, message?)` wraps a check function as a validator: a\n * string result is the error, another truthy result becomes `message`, a\n * falsy one passes.\n *\n * Both entry points (`@coherent.js/forms` and `@coherent.js/forms/validators`)\n * export this same function.\n */\nexport function createValidator(schemaOrFn, message) {\n if (typeof schemaOrFn === 'function') {\n return wrapValidator(schemaOrFn, message);\n }\n return new FormValidator(schemaOrFn);\n}\n\n/**\n * Validate form data against schema\n */\nexport function validate(formData, schema) {\n const validator = new FormValidator(schema);\n return validator.validate(formData);\n}\n\nexport default {\n validators,\n FormValidator,\n createValidator,\n validate\n};\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }