@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.
@@ -8,6 +8,396 @@ function isEmailShaped(value) {
8
8
  return typeof value === "string" && value.length <= EMAIL_MAX_LENGTH && EMAIL_PATTERN.test(value);
9
9
  }
10
10
 
11
+ // src/rules.js
12
+ var BUILTIN = /* @__PURE__ */ Symbol("coherent.forms.builtin");
13
+ var DESCRIPTORS = /* @__PURE__ */ new WeakMap();
14
+ var HELPER_NAMES = /* @__PURE__ */ new Set(["get", "compose", "debounce", "cancellable", "when", "chain"]);
15
+ var isEmpty = (value) => value === null || value === void 0 || value === "";
16
+ var isOptionsObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof RegExp);
17
+ function argsFromString(params, kind, text) {
18
+ if (text === void 0 || text.trim() === "") return [];
19
+ if (params.length === 0) return [text.trim()];
20
+ if (kind === "list") {
21
+ return [text.split(",").map((part) => part.trim()).filter(Boolean)];
22
+ }
23
+ if (kind === "regexp") {
24
+ try {
25
+ return [new RegExp(text)];
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+ const comma = text.indexOf(",");
31
+ const raw = (comma === -1 ? text : text.slice(0, comma)).trim();
32
+ const message = comma === -1 ? "" : text.slice(comma + 1).trim();
33
+ let arg = raw;
34
+ if (kind === "number") {
35
+ arg = Number(raw);
36
+ if (raw === "" || Number.isNaN(arg)) return null;
37
+ }
38
+ return message ? [arg, message] : [arg];
39
+ }
40
+ function defineRule(name, { params = [], stringArg = "string", valid, message }) {
41
+ const fromOptions = (options = {}) => {
42
+ const config = { message: options.message };
43
+ for (const [key, ...aliases] of params) {
44
+ config[key] = [key, ...aliases].map((alias) => options[alias]).find((v) => v !== void 0);
45
+ }
46
+ return config;
47
+ };
48
+ const check = (value, config, formData) => {
49
+ if (valid(value, config, formData || {})) return null;
50
+ return config.message || builtin.message || message(config);
51
+ };
52
+ const factory = (...args) => {
53
+ const config = { message: args[params.length] };
54
+ params.forEach(([key], index) => {
55
+ config[key] = args[index];
56
+ });
57
+ const rule = (value, formData, _translator, allValues) => check(value, config, allValues ?? formData);
58
+ DESCRIPTORS.set(rule, { name, args });
59
+ return rule;
60
+ };
61
+ const isDirectCall = (args) => {
62
+ if (args.length >= 2) {
63
+ return params.length === 0 || isOptionsObject(args[1]);
64
+ }
65
+ if (args.length === 1 && params.length === 0) {
66
+ const [arg] = args;
67
+ return arg === "" || arg !== void 0 && arg !== null && typeof arg !== "string";
68
+ }
69
+ return false;
70
+ };
71
+ function builtin(...args) {
72
+ if (isDirectCall(args)) {
73
+ const [value, options, , allValues] = args;
74
+ return check(value, fromOptions(isOptionsObject(options) ? options : {}), allValues);
75
+ }
76
+ return factory(...args);
77
+ }
78
+ Object.defineProperty(builtin, "name", { value: name });
79
+ builtin[BUILTIN] = {
80
+ name,
81
+ factory,
82
+ argsFromString: (text) => argsFromString(params, stringArg, text)
83
+ };
84
+ return builtin;
85
+ }
86
+ var toNumber = (value) => typeof value === "number" ? value : Number(value);
87
+ function testRegExp(regex, value) {
88
+ if (!(regex instanceof RegExp)) return true;
89
+ regex.lastIndex = 0;
90
+ return regex.test(String(value));
91
+ }
92
+ function fileMatchesType(file, allowedTypes) {
93
+ const fileType = file.type;
94
+ const fileExt = file.name ? file.name.split(".").pop().toLowerCase() : "";
95
+ return allowedTypes.some((type) => {
96
+ if (type.startsWith(".")) {
97
+ return fileExt === type.slice(1).toLowerCase();
98
+ }
99
+ if (type.includes("/")) {
100
+ if (type.endsWith("/*")) {
101
+ return fileType.startsWith(type.replace("/*", "/"));
102
+ }
103
+ return fileType === type;
104
+ }
105
+ return fileExt === type.toLowerCase();
106
+ });
107
+ }
108
+ var validators = {
109
+ required: defineRule("required", {
110
+ valid: (value) => !isEmpty(value),
111
+ message: () => "This field is required"
112
+ }),
113
+ email: defineRule("email", {
114
+ valid: (value) => isEmpty(value) || isEmailShaped(value),
115
+ message: () => "Invalid email address"
116
+ }),
117
+ url: defineRule("url", {
118
+ valid: (value) => {
119
+ if (isEmpty(value)) return true;
120
+ try {
121
+ new URL(value);
122
+ return true;
123
+ } catch {
124
+ return false;
125
+ }
126
+ },
127
+ message: () => "Invalid URL"
128
+ }),
129
+ minLength: defineRule("minLength", {
130
+ params: [["min", "minLength"]],
131
+ stringArg: "number",
132
+ valid: (value, { min }) => !value || !(value.length < (min ?? 0)),
133
+ message: ({ min }) => `Minimum length is ${min}`
134
+ }),
135
+ maxLength: defineRule("maxLength", {
136
+ params: [["max", "maxLength"]],
137
+ stringArg: "number",
138
+ valid: (value, { max }) => !value || !(value.length > (max ?? Infinity)),
139
+ message: ({ max }) => `Maximum length is ${max}`
140
+ }),
141
+ // Empty values pass (combine with `required`); a non-numeric value fails.
142
+ min: defineRule("min", {
143
+ params: [["min"]],
144
+ stringArg: "number",
145
+ valid: (value, { min }) => {
146
+ if (isEmpty(value)) return true;
147
+ const number = toNumber(value);
148
+ return !Number.isNaN(number) && !(number < (min ?? -Infinity));
149
+ },
150
+ message: ({ min }) => `Minimum value is ${min}`
151
+ }),
152
+ max: defineRule("max", {
153
+ params: [["max"]],
154
+ stringArg: "number",
155
+ valid: (value, { max }) => {
156
+ if (isEmpty(value)) return true;
157
+ const number = toNumber(value);
158
+ return !Number.isNaN(number) && !(number > (max ?? Infinity));
159
+ },
160
+ message: ({ max }) => `Maximum value is ${max}`
161
+ }),
162
+ pattern: defineRule("pattern", {
163
+ params: [["pattern", "regex"]],
164
+ stringArg: "regexp",
165
+ valid: (value, { pattern }) => isEmpty(value) || testRegExp(pattern, value),
166
+ message: () => "Invalid format"
167
+ }),
168
+ /** Equal to another field, even when empty */
169
+ matches: defineRule("matches", {
170
+ params: [["field", "fieldName"]],
171
+ valid: (value, { field }, formData) => value === formData[field],
172
+ message: () => "Fields do not match"
173
+ }),
174
+ /** Equal to another field; an empty value passes */
175
+ match: defineRule("match", {
176
+ params: [["field", "fieldName"]],
177
+ valid: (value, { field }, formData) => !value || value === formData[field],
178
+ message: ({ field }) => `Must match ${field}`
179
+ }),
180
+ oneOf: defineRule("oneOf", {
181
+ params: [["options", "values"]],
182
+ stringArg: "list",
183
+ valid: (value, { options }) => !value || !Array.isArray(options) || options.includes(value),
184
+ message: () => "Invalid option"
185
+ }),
186
+ custom: defineRule("custom", {
187
+ params: [["validator", "fn"]],
188
+ valid: (value, { validator }, formData) => typeof validator !== "function" || Boolean(validator(value, formData)),
189
+ message: () => "Validation failed"
190
+ }),
191
+ number: defineRule("number", {
192
+ valid: (value) => isEmpty(value) || !Number.isNaN(Number(value)),
193
+ message: () => "Must be a valid number"
194
+ }),
195
+ integer: defineRule("integer", {
196
+ valid: (value) => isEmpty(value) || Number.isInteger(Number(value)),
197
+ message: () => "Must be a whole number"
198
+ }),
199
+ phone: defineRule("phone", {
200
+ valid: (value) => !value || /^[\d\s\-+()]+$/.test(value) && value.replace(/\D/g, "").length >= 10,
201
+ message: () => "Please enter a valid phone number"
202
+ }),
203
+ date: defineRule("date", {
204
+ valid: (value) => !value || !Number.isNaN(new Date(value).getTime()),
205
+ message: () => "Please enter a valid date"
206
+ }),
207
+ alpha: defineRule("alpha", {
208
+ valid: (value) => !value || /^[a-zA-Z]+$/.test(value),
209
+ message: () => "Must contain only letters"
210
+ }),
211
+ alphanumeric: defineRule("alphanumeric", {
212
+ valid: (value) => !value || /^[a-zA-Z0-9]+$/.test(value),
213
+ message: () => "Must contain only letters and numbers"
214
+ }),
215
+ uppercase: defineRule("uppercase", {
216
+ valid: (value) => !value || value === String(value).toUpperCase(),
217
+ message: () => "Must be uppercase"
218
+ }),
219
+ fileType: defineRule("fileType", {
220
+ params: [["accept", "types"]],
221
+ stringArg: "list",
222
+ valid: (value, { accept }) => !value || value.type === void 0 || fileMatchesType(value, accept || []),
223
+ message: ({ accept }) => `File type must be one of: ${(accept || []).join(", ")}`
224
+ }),
225
+ fileSize: defineRule("fileSize", {
226
+ params: [["maxSize"]],
227
+ stringArg: "number",
228
+ valid: (value, { maxSize }) => !value || value.size === void 0 || !(value.size > (maxSize ?? Infinity)),
229
+ message: ({ maxSize }) => `File size must be less than ${((maxSize ?? Infinity) / (1024 * 1024)).toFixed(2)}MB`
230
+ }),
231
+ fileExtension: defineRule("fileExtension", {
232
+ params: [["extensions"]],
233
+ stringArg: "list",
234
+ valid: (value, { extensions }) => {
235
+ if (!value) return true;
236
+ const fileName = value.name || value;
237
+ const ext = `.${String(fileName).split(".").pop().toLowerCase()}`;
238
+ return (extensions || []).some((allowed) => ext === allowed.toLowerCase());
239
+ },
240
+ message: ({ extensions }) => `File extension must be one of: ${(extensions || []).join(", ")}`
241
+ }),
242
+ /** A registered validator or built-in by name */
243
+ get: (name) => Object.hasOwn(validators, name) ? validators[name] : void 0,
244
+ /** Combine validators into one returning the first error */
245
+ compose: (validatorList) => {
246
+ const rules = validatorList.map(resolveValidator);
247
+ return (value, options, translator, allValues) => {
248
+ for (const rule of rules) {
249
+ const error = rule ? rule(value, options, translator, allValues) : null;
250
+ if (error) {
251
+ return error;
252
+ }
253
+ }
254
+ return null;
255
+ };
256
+ },
257
+ /** Debounce an async validator */
258
+ debounce: (validator, delay = 300) => {
259
+ let timeoutId;
260
+ return (value) => {
261
+ return new Promise((resolve) => {
262
+ clearTimeout(timeoutId);
263
+ timeoutId = setTimeout(async () => {
264
+ const result = await validator(value);
265
+ resolve(result);
266
+ }, delay);
267
+ });
268
+ };
269
+ },
270
+ /** Cancellable async validator */
271
+ cancellable: (validator) => {
272
+ let abortController;
273
+ const wrapped = async (value) => {
274
+ if (abortController) {
275
+ abortController.abort();
276
+ }
277
+ abortController = typeof AbortController !== "undefined" ? new AbortController() : null;
278
+ try {
279
+ return await validator(value, abortController ? abortController.signal : null);
280
+ } catch (error) {
281
+ if (error.name === "AbortError") {
282
+ return null;
283
+ }
284
+ throw error;
285
+ }
286
+ };
287
+ wrapped.cancel = () => {
288
+ if (abortController) {
289
+ abortController.abort();
290
+ }
291
+ };
292
+ return wrapped;
293
+ },
294
+ /** Run `validator` only when `condition` holds */
295
+ when: (condition, validator) => {
296
+ const rule = resolveValidator(validator);
297
+ return (value, options = {}, translator, allValues = {}) => {
298
+ const context = options.min !== void 0 || options.max !== void 0 ? allValues : options;
299
+ const shouldValidate = typeof condition === "function" ? condition(value, context) : condition;
300
+ if (!shouldValidate) {
301
+ return null;
302
+ }
303
+ return rule ? rule(value, options, translator, allValues) : null;
304
+ };
305
+ },
306
+ /** Validator chain builder */
307
+ chain: (options = {}) => {
308
+ const validatorList = [];
309
+ const stopOnFirstError = options.stopOnFirstError !== false;
310
+ const direct = (name) => (opts) => {
311
+ validatorList.push((v, o, t, a) => validators[name](v, opts || o || {}, t, a));
312
+ return chain;
313
+ };
314
+ const chain = {
315
+ required: direct("required"),
316
+ email: direct("email"),
317
+ minLength: direct("minLength"),
318
+ maxLength: direct("maxLength"),
319
+ custom: (fn, message) => {
320
+ validatorList.push((v, o, t, a) => {
321
+ const result = fn(v, a);
322
+ return result === null || result === true || result === void 0 ? null : message || result;
323
+ });
324
+ return chain;
325
+ },
326
+ validate: (value, opts, translator, allValues) => {
327
+ if (stopOnFirstError) {
328
+ for (const validator of validatorList) {
329
+ const error = validator(value, opts, translator, allValues);
330
+ if (error) {
331
+ return error;
332
+ }
333
+ }
334
+ return null;
335
+ }
336
+ const errors = [];
337
+ for (const validator of validatorList) {
338
+ const error = validator(value, opts, translator, allValues);
339
+ if (error) {
340
+ errors.push(error);
341
+ }
342
+ }
343
+ return errors.length > 0 ? errors : null;
344
+ }
345
+ };
346
+ return chain;
347
+ }
348
+ };
349
+ function lookup(name) {
350
+ const found = Object.hasOwn(validators, name) && !HELPER_NAMES.has(name) ? validators[name] : null;
351
+ return typeof found === "function" ? found : null;
352
+ }
353
+ function resolveString(entry) {
354
+ const exact = lookup(entry);
355
+ const colon = exact ? -1 : entry.indexOf(":");
356
+ const name = colon === -1 ? entry : entry.slice(0, colon).trim();
357
+ const found = exact ?? lookup(name);
358
+ if (!found) return null;
359
+ const text = colon === -1 ? void 0 : entry.slice(colon + 1);
360
+ if (!found[BUILTIN]) {
361
+ return text === void 0 ? { name, args: [], validator: found } : null;
362
+ }
363
+ const args = found[BUILTIN].argsFromString(text);
364
+ if (!args) return null;
365
+ return { name: found[BUILTIN].name, args, validator: found[BUILTIN].factory(...args) };
366
+ }
367
+ function resolveValidator(entry) {
368
+ if (typeof entry === "string") {
369
+ return resolveString(entry)?.validator ?? null;
370
+ }
371
+ if (typeof entry !== "function") return null;
372
+ return entry[BUILTIN] ? entry[BUILTIN].factory() : entry;
373
+ }
374
+ function serializeArg(arg) {
375
+ if (arg instanceof RegExp) return { $regexp: [arg.source, arg.flags] };
376
+ return arg;
377
+ }
378
+ function isSerializable(arg) {
379
+ if (arg === void 0 || arg === null) return true;
380
+ if (arg instanceof RegExp) return true;
381
+ if (Array.isArray(arg)) return arg.every(isSerializable);
382
+ return ["string", "number", "boolean"].includes(typeof arg);
383
+ }
384
+ function describeValidator(entry) {
385
+ let descriptor;
386
+ if (typeof entry === "string") {
387
+ descriptor = resolveString(entry);
388
+ } else if (typeof entry === "function") {
389
+ descriptor = entry[BUILTIN] ? { name: entry[BUILTIN].name, args: [] } : DESCRIPTORS.get(entry);
390
+ }
391
+ if (!descriptor || !descriptor.args.every(isSerializable)) return null;
392
+ const args = [...descriptor.args];
393
+ while (args.length > 0 && args[args.length - 1] === void 0) args.pop();
394
+ return { name: descriptor.name, args: args.map(serializeArg) };
395
+ }
396
+ function serializeValidators(validatorList) {
397
+ const specs = (validatorList || []).map(describeValidator).filter(Boolean);
398
+ return specs.length > 0 ? JSON.stringify(specs) : null;
399
+ }
400
+
11
401
  // src/form-builder.js
12
402
  var DEFAULT_CLASS_NAMES = {
13
403
  /** Wrapper around label, control and error */
@@ -41,10 +431,11 @@ function safeAttributes(attributes) {
41
431
  }
42
432
  return safe;
43
433
  }
434
+ var DEFAULT_CSRF_FIELD_NAME = "_csrf";
44
435
  function joinClasses(...names) {
45
436
  return names.filter(Boolean).join(" ");
46
437
  }
47
- var FormBuilder = class {
438
+ var FormBuilder = class _FormBuilder {
48
439
  constructor(options = {}) {
49
440
  this.options = {
50
441
  validateOnChange: true,
@@ -255,8 +646,9 @@ var FormBuilder = class {
255
646
  return error;
256
647
  }
257
648
  }
258
- for (const validator of field.validators || []) {
259
- const error = validator(value, this.values);
649
+ for (const entry of field.validators || []) {
650
+ const validator = resolveValidator(entry);
651
+ const error = validator ? validator(value, this.values) : null;
260
652
  if (error) {
261
653
  this.errors[name] = error;
262
654
  return error;
@@ -336,20 +728,59 @@ var FormBuilder = class {
336
728
  touch(name) {
337
729
  this.touched[name] = true;
338
730
  }
731
+ /**
732
+ * A copy of this form's definition — fields, groups, options and handlers —
733
+ * with fresh values, errors and touched state.
734
+ *
735
+ * A FormBuilder holds the state of one submission. On a server, keep the
736
+ * shared definition at module scope and fork it per request, so one user's
737
+ * submitted values and errors never render into another user's page.
738
+ */
739
+ fork() {
740
+ const copy = new _FormBuilder({ ...this.options });
741
+ for (const [name, config] of this.fields) {
742
+ copy.fields.set(name, config);
743
+ }
744
+ for (const [name, group] of this.groups) {
745
+ copy.groups.set(name, group);
746
+ }
747
+ copy.values = { ...this.initialValues };
748
+ copy.initialValues = { ...this.initialValues };
749
+ copy.submitHandler = this.submitHandler;
750
+ copy.errorHandler = this.errorHandler;
751
+ return copy;
752
+ }
753
+ /**
754
+ * The values, errors and touched state a render uses.
755
+ *
756
+ * Passing any of `values`, `errors` or `touched` to buildForm renders from
757
+ * those alone — the instance's own state is neither read nor changed — so a
758
+ * shared builder can render per-request state. Values are merged over the
759
+ * fields' default values, and fields with an error count as touched unless
760
+ * `touched` is given.
761
+ */
762
+ resolveRenderState(options = {}) {
763
+ const { values, errors, touched } = options;
764
+ if (values === void 0 && errors === void 0 && touched === void 0) {
765
+ return { values: this.values, errors: this.errors, touched: this.touched };
766
+ }
767
+ const renderErrors = errors || {};
768
+ return {
769
+ values: { ...this.initialValues, ...values },
770
+ errors: renderErrors,
771
+ touched: touched || Object.fromEntries(Object.keys(renderErrors).map((name) => [name, true]))
772
+ };
773
+ }
339
774
  /**
340
775
  * Build input component with validation metadata for hydration
341
776
  */
342
- buildInput(name, classNames = this.resolveClassNames()) {
777
+ buildInput(name, classNames = this.resolveClassNames(), state = this.resolveRenderState()) {
343
778
  const field = this.fields.get(name);
344
779
  if (!field) return null;
345
- const value = this.values[name] || "";
346
- const error = this.errors[name];
347
- const isTouched = this.touched[name];
348
- const validatorNames = field.validators.map((v) => {
349
- if (typeof v === "function") return v.name || "custom";
350
- if (typeof v === "string") return v;
351
- return null;
352
- }).filter(Boolean).join(",");
780
+ const value = state.values[name] || "";
781
+ const error = state.errors[name];
782
+ const isTouched = state.touched[name];
783
+ const validatorSpec = serializeValidators(field.validators);
353
784
  const controlClass = joinClasses(
354
785
  classNames.control,
355
786
  field.className,
@@ -374,8 +805,8 @@ var FormBuilder = class {
374
805
  inputProps.required = true;
375
806
  inputProps["data-required"] = "true";
376
807
  }
377
- if (validatorNames) {
378
- inputProps["data-validators"] = validatorNames;
808
+ if (validatorSpec) {
809
+ inputProps["data-validators"] = validatorSpec;
379
810
  }
380
811
  if (field.type === "textarea" || field.type === "select") {
381
812
  const { type: _type, value: _value, ...rest } = inputProps;
@@ -417,9 +848,9 @@ var FormBuilder = class {
417
848
  /**
418
849
  * Build error component
419
850
  */
420
- buildError(name, classNames = this.resolveClassNames()) {
421
- const error = this.errors[name];
422
- const isTouched = this.touched[name];
851
+ buildError(name, classNames = this.resolveClassNames(), state = this.resolveRenderState()) {
852
+ const error = state.errors[name];
853
+ const isTouched = state.touched[name];
423
854
  if (!error || !isTouched) return null;
424
855
  const props = { id: `${name}-error`, role: "alert", text: error };
425
856
  if (classNames.error) props.className = classNames.error;
@@ -434,14 +865,14 @@ var FormBuilder = class {
434
865
  /**
435
866
  * Build complete field component
436
867
  */
437
- buildField(name, classNames = this.resolveClassNames()) {
868
+ buildField(name, classNames = this.resolveClassNames(), state = this.resolveRenderState()) {
438
869
  const field = this.fields.get(name);
439
870
  if (!field) return null;
440
871
  const children = [
441
872
  this.buildLabel(name, classNames),
442
- this.buildInput(name, classNames)
873
+ this.buildInput(name, classNames, state)
443
874
  ];
444
- const error = this.buildError(name, classNames);
875
+ const error = this.buildError(name, classNames, state);
445
876
  if (error) {
446
877
  children.push(error);
447
878
  }
@@ -451,14 +882,31 @@ var FormBuilder = class {
451
882
  }
452
883
  /**
453
884
  * Build entire form
885
+ *
886
+ * `options.values`, `options.errors` and `options.touched` render
887
+ * per-request state without touching the builder's own (see
888
+ * resolveRenderState). `options.csrfToken` adds a hidden `_csrf` input (the
889
+ * name is `options.csrfFieldName`); create the token with
890
+ * `@coherent.js/forms/csrf`.
454
891
  */
455
892
  buildForm(options = {}) {
456
- const settings = { ...this.options, ...options };
893
+ const { values: _values, errors: _errors, touched: _touched, ...formOptions } = options;
894
+ const settings = { ...this.options, ...formOptions };
457
895
  const classNames = this.resolveClassNames(options.classNames);
896
+ const state = this.resolveRenderState(options);
458
897
  const fields = [];
898
+ if (settings.csrfToken !== void 0 && settings.csrfToken !== null && settings.csrfToken !== "") {
899
+ fields.push({
900
+ input: {
901
+ type: "hidden",
902
+ name: settings.csrfFieldName || DEFAULT_CSRF_FIELD_NAME,
903
+ value: String(settings.csrfToken)
904
+ }
905
+ });
906
+ }
459
907
  for (const [name] of this.fields) {
460
- if (!this.isFieldVisible(name)) continue;
461
- fields.push(this.buildField(name, classNames));
908
+ if (!this.isFieldVisible(name, state.values)) continue;
909
+ fields.push(this.buildField(name, classNames, state));
462
910
  }
463
911
  if (settings.submitButton !== false) {
464
912
  const button = { type: "submit", text: settings.submitText || "Submit" };
@@ -520,13 +968,13 @@ var FormBuilder = class {
520
968
  /**
521
969
  * Check if a field is visible
522
970
  */
523
- isFieldVisible(name) {
971
+ isFieldVisible(name, values = this.values) {
524
972
  const field = this.fields.get(name);
525
973
  if (!field) return false;
526
974
  if (field.visible === false) return false;
527
975
  const showCondition = field.showWhen || field.showIf;
528
976
  if (showCondition) {
529
- return showCondition(this.values);
977
+ return showCondition(values);
530
978
  }
531
979
  return true;
532
980
  }