elementdrawing 1.0.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.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/dist/elementdrawing.min.js +3 -0
  3. package/dist/elementdrawing.min.js.LICENSE.txt +8 -0
  4. package/dist/elementdrawing.min.js.map +1 -0
  5. package/dist/index.html +1 -0
  6. package/package.json +127 -0
  7. package/src/core/bridge.h +855 -0
  8. package/src/core/diff.c +900 -0
  9. package/src/core/element.c +1078 -0
  10. package/src/core/event.c +813 -0
  11. package/src/core/fiber.c +1027 -0
  12. package/src/core/hooks.c +919 -0
  13. package/src/core/renderer.c +963 -0
  14. package/src/core/scheduler.c +702 -0
  15. package/src/core/state.c +803 -0
  16. package/src/css/animations.css +779 -0
  17. package/src/css/base.css +615 -0
  18. package/src/css/components.css +1311 -0
  19. package/src/css/tailwind.css +370 -0
  20. package/src/css/themes.css +517 -0
  21. package/src/css/utilities.css +475 -0
  22. package/src/index.js +746 -0
  23. package/src/js/animation.js +655 -0
  24. package/src/js/dom.js +665 -0
  25. package/src/js/events.js +585 -0
  26. package/src/js/http.js +446 -0
  27. package/src/js/index.js +26 -0
  28. package/src/js/router.js +483 -0
  29. package/src/js/store.js +539 -0
  30. package/src/js/utils.js +593 -0
  31. package/src/js/validator.js +529 -0
  32. package/src/jsx/components/Accordion.jsx +210 -0
  33. package/src/jsx/components/Alert.jsx +169 -0
  34. package/src/jsx/components/Avatar.jsx +214 -0
  35. package/src/jsx/components/Badge.jsx +136 -0
  36. package/src/jsx/components/Breadcrumb.jsx +200 -0
  37. package/src/jsx/components/Button.jsx +188 -0
  38. package/src/jsx/components/Card.jsx +192 -0
  39. package/src/jsx/components/Carousel.jsx +278 -0
  40. package/src/jsx/components/Checkbox.jsx +215 -0
  41. package/src/jsx/components/Dialog.jsx +242 -0
  42. package/src/jsx/components/Drawer.jsx +190 -0
  43. package/src/jsx/components/Dropdown.jsx +268 -0
  44. package/src/jsx/components/Form.jsx +274 -0
  45. package/src/jsx/components/Input.jsx +285 -0
  46. package/src/jsx/components/Menu.jsx +276 -0
  47. package/src/jsx/components/Modal.jsx +274 -0
  48. package/src/jsx/components/Navbar.jsx +292 -0
  49. package/src/jsx/components/Pagination.jsx +268 -0
  50. package/src/jsx/components/Progress.jsx +252 -0
  51. package/src/jsx/components/Radio.jsx +208 -0
  52. package/src/jsx/components/Select.jsx +397 -0
  53. package/src/jsx/components/Sidebar.jsx +250 -0
  54. package/src/jsx/components/Slider.jsx +310 -0
  55. package/src/jsx/components/Spinner.jsx +198 -0
  56. package/src/jsx/components/Switch.jsx +201 -0
  57. package/src/jsx/components/Table.jsx +332 -0
  58. package/src/jsx/components/Tabs.jsx +227 -0
  59. package/src/jsx/components/Textarea.jsx +212 -0
  60. package/src/jsx/components/Toast.jsx +270 -0
  61. package/src/jsx/components/Tooltip.jsx +178 -0
  62. package/src/jsx/components/Typography.jsx +299 -0
  63. package/src/jsx/components/index.jsx +70 -0
  64. package/src/jsx/core/element.js +3 -0
  65. package/src/jsx/hooks/index.js +356 -0
  66. package/src/jsx/hooks/useCallback.js +472 -0
  67. package/src/jsx/hooks/useContext.js +586 -0
  68. package/src/jsx/hooks/useEffect.js +704 -0
  69. package/src/jsx/hooks/useLayoutEffect.js +508 -0
  70. package/src/jsx/hooks/useMemo.js +689 -0
  71. package/src/jsx/hooks/useReducer.js +729 -0
  72. package/src/jsx/hooks/useRef.js +542 -0
  73. package/src/jsx/hooks/useState.js +854 -0
  74. package/src/jsx/runtime/commit.js +903 -0
  75. package/src/jsx/runtime/createElement.js +860 -0
  76. package/src/jsx/runtime/index.js +356 -0
  77. package/src/jsx/runtime/reconcile.js +687 -0
  78. package/src/jsx/runtime/render.js +914 -0
@@ -0,0 +1,529 @@
1
+ /**
2
+ * Form/Data Validation
3
+ * ElementDrawing Framework - Validation rules, composition, async validation,
4
+ * form/field validation, error messages, sanitization, schemas, type coercion,
5
+ * conditional and cross-field validation.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ // ─── Validation Rules ─────────────────────────────────────────────────────────
11
+
12
+ const rules = {
13
+ required: function (value) {
14
+ if (value === null || value === undefined) return false;
15
+ if (typeof value === 'string' && value.trim() === '') return false;
16
+ if (Array.isArray(value) && value.length === 0) return false;
17
+ return true;
18
+ },
19
+
20
+ minLength: function (value, min) {
21
+ if (value === null || value === undefined) return false;
22
+ return String(value).length >= min;
23
+ },
24
+
25
+ maxLength: function (value, max) {
26
+ if (value === null || value === undefined) return true;
27
+ return String(value).length <= max;
28
+ },
29
+
30
+ min: function (value, minVal) {
31
+ return Number(value) >= minVal;
32
+ },
33
+
34
+ max: function (value, maxVal) {
35
+ return Number(value) <= maxVal;
36
+ },
37
+
38
+ email: function (value) {
39
+ if (!value) return true;
40
+ return /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(value);
41
+ },
42
+
43
+ url: function (value) {
44
+ if (!value) return true;
45
+ try { new URL(value); return true; } catch (e) { return false; }
46
+ },
47
+
48
+ pattern: function (value, regex) {
49
+ if (!value) return true;
50
+ return regex.test(String(value));
51
+ },
52
+
53
+ numeric: function (value) {
54
+ if (!value && value !== 0) return true;
55
+ return !isNaN(Number(value));
56
+ },
57
+
58
+ integer: function (value) {
59
+ if (!value && value !== 0) return true;
60
+ return Number.isInteger(Number(value));
61
+ },
62
+
63
+ alpha: function (value) {
64
+ if (!value) return true;
65
+ return /^[a-zA-Z]+$/.test(value);
66
+ },
67
+
68
+ alphaNumeric: function (value) {
69
+ if (!value) return true;
70
+ return /^[a-zA-Z0-9]+$/.test(value);
71
+ },
72
+
73
+ date: function (value) {
74
+ if (!value) return true;
75
+ const d = new Date(value);
76
+ return !isNaN(d.getTime());
77
+ },
78
+
79
+ phone: function (value) {
80
+ if (!value) return true;
81
+ return /^[+]?[\d\s()-]{7,20}$/.test(value);
82
+ },
83
+
84
+ creditCard: function (value) {
85
+ if (!value) return true;
86
+ const cleaned = String(value).replace(/\D/g, '');
87
+ if (cleaned.length < 13 || cleaned.length > 19) return false;
88
+ // Luhn algorithm
89
+ let sum = 0, alternate = false;
90
+ for (let i = cleaned.length - 1; i >= 0; i--) {
91
+ let n = parseInt(cleaned[i], 10);
92
+ if (alternate) { n *= 2; if (n > 9) n -= 9; }
93
+ sum += n;
94
+ alternate = !alternate;
95
+ }
96
+ return sum % 10 === 0;
97
+ },
98
+
99
+ custom: function (value, validatorFn) {
100
+ if (typeof validatorFn !== 'function') return true;
101
+ return validatorFn(value);
102
+ },
103
+ };
104
+
105
+ // ─── Default Error Messages ───────────────────────────────────────────────────
106
+
107
+ const defaultMessages = {
108
+ required: 'This field is required',
109
+ minLength: 'Must be at least {min} characters',
110
+ maxLength: 'Must be no more than {max} characters',
111
+ min: 'Must be at least {min}',
112
+ max: 'Must be no more than {max}',
113
+ email: 'Must be a valid email address',
114
+ url: 'Must be a valid URL',
115
+ pattern: 'Invalid format',
116
+ numeric: 'Must be a number',
117
+ integer: 'Must be an integer',
118
+ alpha: 'Must contain only letters',
119
+ alphaNumeric: 'Must contain only letters and numbers',
120
+ date: 'Must be a valid date',
121
+ phone: 'Must be a valid phone number',
122
+ creditCard: 'Must be a valid credit card number',
123
+ custom: 'Validation failed',
124
+ };
125
+
126
+ /**
127
+ * Get an error message for a rule, with parameter interpolation.
128
+ * @param {string} ruleName
129
+ * @param {Object} params
130
+ * @param {Object} [customMessages]
131
+ * @returns {string}
132
+ */
133
+ function getMessage(ruleName, params, customMessages) {
134
+ const template = (customMessages && customMessages[ruleName]) || defaultMessages[ruleName] || 'Validation failed';
135
+ return template.replace(/\{(\w+)\}/g, (_, key) => params[key] !== undefined ? params[key] : '');
136
+ }
137
+
138
+ // ─── Validator Composition ────────────────────────────────────────────────────
139
+
140
+ /**
141
+ * Create a validator that requires all rules to pass (AND).
142
+ * @param {...Function} validators
143
+ * @returns {Function}
144
+ */
145
+ function and() {
146
+ const validators = Array.prototype.slice.call(arguments);
147
+ return function (value) {
148
+ for (const validator of validators) {
149
+ const result = validator(value);
150
+ if (result !== true) return result;
151
+ }
152
+ return true;
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Create a validator that requires at least one rule to pass (OR).
158
+ * @param {...Function} validators
159
+ * @returns {Function}
160
+ */
161
+ function or() {
162
+ const validators = Array.prototype.slice.call(arguments);
163
+ return function (value) {
164
+ const errors = [];
165
+ for (const validator of validators) {
166
+ const result = validator(value);
167
+ if (result === true) return true;
168
+ errors.push(result);
169
+ }
170
+ return errors[0]; // Return first error
171
+ };
172
+ }
173
+
174
+ /**
175
+ * Create a validator that negates another validator.
176
+ * @param {Function} validator
177
+ * @returns {Function}
178
+ */
179
+ function not(validator) {
180
+ return function (value) {
181
+ const result = validator(value);
182
+ return result === true ? 'Value should not match this rule' : true;
183
+ };
184
+ }
185
+
186
+ /**
187
+ * Create a conditional validator that only applies when a condition is met.
188
+ * @param {Function} condition - (value, allValues) => boolean
189
+ * @param {Function} validator
190
+ * @returns {Function}
191
+ */
192
+ function when(condition, validator) {
193
+ return function (value, allValues) {
194
+ if (condition(value, allValues)) {
195
+ return validator(value, allValues);
196
+ }
197
+ return true;
198
+ };
199
+ }
200
+
201
+ // ─── Field Validation ─────────────────────────────────────────────────────────
202
+
203
+ /**
204
+ * Create a field validator from a rule configuration.
205
+ * @param {Object} config
206
+ * @param {Array} config.rules - Array of { rule, params, message }
207
+ * @param {Function} [config.transform] - Transform value before validation
208
+ * @returns {Function} Validator function
209
+ */
210
+ function createFieldValidator(config) {
211
+ const fieldRules = config.rules || [];
212
+ const transform = config.transform;
213
+
214
+ return function validateField(value, allValues) {
215
+ const val = transform ? transform(value) : value;
216
+ const errors = [];
217
+
218
+ for (const ruleConfig of fieldRules) {
219
+ const ruleName = ruleConfig.rule;
220
+ const ruleParams = ruleConfig.params || {};
221
+ const customMessage = ruleConfig.message;
222
+
223
+ const ruleFn = typeof ruleName === 'function' ? ruleName : rules[ruleName];
224
+ if (!ruleFn) continue;
225
+
226
+ const args = ruleName === 'custom' ? [val, ruleParams.validator] : [val];
227
+
228
+ // Add params as additional arguments for rules that need them
229
+ if (ruleName === 'minLength' && ruleParams.min !== undefined) args.push(ruleParams.min);
230
+ if (ruleName === 'maxLength' && ruleParams.max !== undefined) args.push(ruleParams.max);
231
+ if (ruleName === 'min' && ruleParams.min !== undefined) args.push(ruleParams.min);
232
+ if (ruleName === 'max' && ruleParams.max !== undefined) args.push(ruleParams.max);
233
+ if (ruleName === 'pattern' && ruleParams.regex) args.push(ruleParams.regex);
234
+
235
+ const result = ruleFn.apply(null, args);
236
+
237
+ if (result !== true) {
238
+ const message = customMessage || getMessage(ruleName, ruleParams);
239
+ errors.push(message);
240
+ }
241
+ }
242
+
243
+ return errors.length === 0 ? true : errors;
244
+ };
245
+ }
246
+
247
+ // ─── Async Validation ─────────────────────────────────────────────────────────
248
+
249
+ /**
250
+ * Create an async validator.
251
+ * @param {Function} asyncFn - (value, allValues) => Promise<boolean|string>
252
+ * @returns {Function} Async validator function
253
+ */
254
+ function asyncValidator(asyncFn) {
255
+ return function (value, allValues) {
256
+ return Promise.resolve(asyncFn(value, allValues)).then((result) => {
257
+ if (result === true) return true;
258
+ if (typeof result === 'string') return [result];
259
+ return ['Async validation failed'];
260
+ }).catch((error) => {
261
+ return [error.message || 'Async validation error'];
262
+ });
263
+ };
264
+ }
265
+
266
+ // ─── Form Validation ──────────────────────────────────────────────────────────
267
+
268
+ /**
269
+ * Create a form validator from a schema.
270
+ * @param {Object} schema - Field name to validator mapping
271
+ * @returns {Object} Form validator with validate method
272
+ */
273
+ function createFormValidator(schema) {
274
+ const fieldValidators = {};
275
+ const crossFieldValidators = [];
276
+
277
+ Object.keys(schema).forEach((fieldName) => {
278
+ const fieldConfig = schema[fieldName];
279
+ if (typeof fieldConfig === 'function') {
280
+ fieldValidators[fieldName] = fieldConfig;
281
+ } else if (fieldConfig.rules) {
282
+ fieldValidators[fieldName] = createFieldValidator(fieldConfig);
283
+ } else if (fieldConfig.crossField) {
284
+ crossFieldValidators.push({
285
+ fields: fieldConfig.fields,
286
+ validator: fieldConfig.validator,
287
+ message: fieldConfig.message,
288
+ });
289
+ } else {
290
+ fieldValidators[fieldName] = createFieldValidator({ rules: Array.isArray(fieldConfig) ? fieldConfig : [fieldConfig] });
291
+ }
292
+ });
293
+
294
+ return {
295
+ /**
296
+ * Validate a single field.
297
+ * @param {string} fieldName
298
+ * @param {*} value
299
+ * @param {Object} [allValues]
300
+ * @returns {true|Array<string>}
301
+ */
302
+ validateField: function (fieldName, value, allValues) {
303
+ const validator = fieldValidators[fieldName];
304
+ if (!validator) return true;
305
+ return validator(value, allValues || {});
306
+ },
307
+
308
+ /**
309
+ * Validate all fields in the form.
310
+ * @param {Object} values - Field name to value mapping
311
+ * @returns {Object} { valid: boolean, errors: { [fieldName]: string[] } }
312
+ */
313
+ validate: function (values) {
314
+ const errors = {};
315
+ let valid = true;
316
+
317
+ // Validate each field
318
+ Object.keys(fieldValidators).forEach((fieldName) => {
319
+ const result = fieldValidators[fieldName](values[fieldName], values);
320
+ if (result !== true) {
321
+ errors[fieldName] = Array.isArray(result) ? result : [result];
322
+ valid = false;
323
+ }
324
+ });
325
+
326
+ // Run cross-field validators
327
+ crossFieldValidators.forEach((crossVal) => {
328
+ const result = crossVal.validator(values);
329
+ if (result !== true) {
330
+ const message = crossVal.message || result;
331
+ crossVal.fields.forEach((f) => {
332
+ if (!errors[f]) errors[f] = [];
333
+ errors[f].push(typeof message === 'string' ? message : 'Cross-field validation failed');
334
+ });
335
+ valid = false;
336
+ }
337
+ });
338
+
339
+ return { valid, errors };
340
+ },
341
+
342
+ /**
343
+ * Async validation for all fields.
344
+ * @param {Object} values
345
+ * @returns {Promise<Object>}
346
+ */
347
+ validateAsync: async function (values) {
348
+ const errors = {};
349
+ let valid = true;
350
+
351
+ const promises = Object.keys(fieldValidators).map(async (fieldName) => {
352
+ const result = await Promise.resolve(fieldValidators[fieldName](values[fieldName], values));
353
+ if (result !== true) {
354
+ errors[fieldName] = Array.isArray(result) ? result : [result];
355
+ valid = false;
356
+ }
357
+ });
358
+
359
+ await Promise.all(promises);
360
+ return { valid, errors };
361
+ },
362
+
363
+ /**
364
+ * Validate and return only the first error for each field.
365
+ * @param {Object} values
366
+ * @returns {Object} { valid, errors: { [fieldName]: string } }
367
+ */
368
+ validateFirst: function (values) {
369
+ const result = this.validate(values);
370
+ const firstErrors = {};
371
+ Object.keys(result.errors).forEach((k) => {
372
+ firstErrors[k] = result.errors[k][0];
373
+ });
374
+ return { valid: result.valid, errors: firstErrors };
375
+ },
376
+ };
377
+ }
378
+
379
+ // ─── Schema Definition ────────────────────────────────────────────────────────
380
+
381
+ /**
382
+ * Define a validation schema fluently.
383
+ * @returns {Object} Schema builder
384
+ */
385
+ function schema() {
386
+ const fields = {};
387
+
388
+ return {
389
+ field: function (name) {
390
+ const fieldRules = [];
391
+ const builder = {
392
+ required: function (msg) { fieldRules.push({ rule: 'required', message: msg }); return builder; },
393
+ minLength: function (min, msg) { fieldRules.push({ rule: 'minLength', params: { min }, message: msg }); return builder; },
394
+ maxLength: function (max, msg) { fieldRules.push({ rule: 'maxLength', params: { max }, message: msg }); return builder; },
395
+ min: function (minVal, msg) { fieldRules.push({ rule: 'min', params: { min: minVal }, message: msg }); return builder; },
396
+ max: function (maxVal, msg) { fieldRules.push({ rule: 'max', params: { max: maxVal }, message: msg }); return builder; },
397
+ email: function (msg) { fieldRules.push({ rule: 'email', message: msg }); return builder; },
398
+ url: function (msg) { fieldRules.push({ rule: 'url', message: msg }); return builder; },
399
+ pattern: function (regex, msg) { fieldRules.push({ rule: 'pattern', params: { regex }, message: msg }); return builder; },
400
+ numeric: function (msg) { fieldRules.push({ rule: 'numeric', message: msg }); return builder; },
401
+ integer: function (msg) { fieldRules.push({ rule: 'integer', message: msg }); return builder; },
402
+ custom: function (fn, msg) { fieldRules.push({ rule: 'custom', params: { validator: fn }, message: msg }); return builder; },
403
+ end: function () { return schema(); },
404
+ };
405
+
406
+ fields[name] = fieldRules;
407
+ return builder;
408
+ },
409
+
410
+ build: function () {
411
+ const formSchema = {};
412
+ Object.keys(fields).forEach((name) => {
413
+ formSchema[name] = { rules: fields[name] };
414
+ });
415
+ return createFormValidator(formSchema);
416
+ },
417
+ };
418
+ }
419
+
420
+ // ─── Sanitization ─────────────────────────────────────────────────────────────
421
+
422
+ const sanitizers = {
423
+ trim: function (value) { return typeof value === 'string' ? value.trim() : value; },
424
+ escape: function (value) {
425
+ if (typeof value !== 'string') return value;
426
+ const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
427
+ return value.replace(/[&<>"']/g, (c) => map[c]);
428
+ },
429
+ normalize: function (value) { return typeof value === 'string' ? value.normalize() : value; },
430
+ stripHtml: function (value) { return typeof value === 'string' ? value.replace(/<[^>]*>/g, '') : value; },
431
+ toNumber: function (value) { const n = Number(value); return isNaN(n) ? value : n; },
432
+ toDate: function (value) { const d = new Date(value); return isNaN(d.getTime()) ? value : d; },
433
+ toLowerCase: function (value) { return typeof value === 'string' ? value.toLowerCase() : value; },
434
+ toUpperCase: function (value) { return typeof value === 'string' ? value.toUpperCase() : value; },
435
+ toBoolean: function (value) {
436
+ if (typeof value === 'string') {
437
+ if (value.toLowerCase() === 'true' || value === '1') return true;
438
+ if (value.toLowerCase() === 'false' || value === '0') return false;
439
+ }
440
+ return Boolean(value);
441
+ },
442
+ truncate: function (value, maxLen) {
443
+ if (typeof value !== 'string') return value;
444
+ return value.length > maxLen ? value.slice(0, maxLen) : value;
445
+ },
446
+ };
447
+
448
+ // ─── Type Coercion ────────────────────────────────────────────────────────────
449
+
450
+ /**
451
+ * Coerce a value to a specific type.
452
+ * @param {*} value
453
+ * @param {string} type - 'string' | 'number' | 'boolean' | 'array' | 'object' | 'date'
454
+ * @returns {*}
455
+ */
456
+ function coerce(value, type) {
457
+ switch (type) {
458
+ case 'string': return String(value);
459
+ case 'number': { const n = Number(value); return isNaN(n) ? value : n; }
460
+ case 'boolean': return sanitizers.toBoolean(value);
461
+ case 'array': return Array.isArray(value) ? value : [value];
462
+ case 'object': return typeof value === 'object' && value !== null ? value : { value };
463
+ case 'date': { const d = new Date(value); return isNaN(d.getTime()) ? value : d; }
464
+ default: return value;
465
+ }
466
+ }
467
+
468
+ // ─── Cross-Field Validation ───────────────────────────────────────────────────
469
+
470
+ /**
471
+ * Create a cross-field validator.
472
+ * @param {string[]} fields - Field names involved
473
+ * @param {Function} validator - (values) => boolean | string
474
+ * @param {string} [message]
475
+ * @returns {Object}
476
+ */
477
+ function crossField(fields, validator, message) {
478
+ return { fields, validator, message, crossField: true };
479
+ }
480
+
481
+ /**
482
+ * Pre-built cross-field validators.
483
+ */
484
+ const crossFieldRules = {
485
+ match: function (field1, field2, message) {
486
+ return crossField([field1, field2], (values) => {
487
+ return values[field1] === values[field2];
488
+ }, message || field1 + ' and ' + field2 + ' must match');
489
+ },
490
+
491
+ different: function (field1, field2, message) {
492
+ return crossField([field1, field2], (values) => {
493
+ return values[field1] !== values[field2];
494
+ }, message || field1 + ' and ' + field2 + ' must be different');
495
+ },
496
+
497
+ requiredIf: function (field, conditionField, message) {
498
+ return crossField([field, conditionField], (values) => {
499
+ if (values[conditionField]) return rules.required(values[field]);
500
+ return true;
501
+ }, message || field + ' is required when ' + conditionField + ' is set');
502
+ },
503
+
504
+ greaterThan: function (field1, field2, message) {
505
+ return crossField([field1, field2], (values) => {
506
+ return Number(values[field1]) > Number(values[field2]);
507
+ }, message || field1 + ' must be greater than ' + field2);
508
+ },
509
+ };
510
+
511
+ // ─── Exports ──────────────────────────────────────────────────────────────────
512
+
513
+ module.exports = {
514
+ rules,
515
+ defaultMessages,
516
+ getMessage,
517
+ and,
518
+ or,
519
+ not,
520
+ when,
521
+ createFieldValidator,
522
+ asyncValidator,
523
+ createFormValidator,
524
+ schema,
525
+ sanitizers,
526
+ coerce,
527
+ crossField,
528
+ crossFieldRules,
529
+ };