@qaflo/forms-client 1.1.1 → 1.3.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.
package/README.md CHANGED
@@ -106,6 +106,15 @@ const { formProps, fieldProps, errors, composed } = forms.useContactForm({
106
106
  email: { required: true, email: true, max: 254 },
107
107
  phone: { required: true, min: 5, max: 32 }, // optional to the service
108
108
  message: { required: true, min: 10, max: 5000 },
109
+ // A shape the service has no opinion on. Checked only when the field has
110
+ // something in it, so an optional field with a pattern stays optional.
111
+ licence: { pattern: /^[0-9]{6}$/ },
112
+ // And when no set of rules says it, write the function. It receives the
113
+ // whole form, and its message wins over the rules above.
114
+ area: {
115
+ check: (value, all) =>
116
+ all.enquiry === 'delivery' && !value ? 'We need an area to deliver to.' : undefined,
117
+ },
109
118
  },
110
119
 
111
120
  // The rules are the service's. The voice is yours.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qaflo/forms-client",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "description": "Client half of the shared qaflo contact-form service: transport, Turnstile lifecycle and visitor-facing status messages.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/validate.d.ts CHANGED
@@ -3,6 +3,14 @@ export interface FieldRule {
3
3
  min?: number
4
4
  max?: number
5
5
  email?: boolean
6
+ /** A site-specific shape. Checked only when the field is non-empty. */
7
+ pattern?: RegExp
8
+ /**
9
+ * Anything the rules above cannot express. Returns the message, or
10
+ * undefined to pass. Runs only when the field is non-empty, and its message
11
+ * wins over every other rule.
12
+ */
13
+ check?: (value: string, values: Record<string, string>) => string | undefined
6
14
  /** Used in the default messages; defaults to the field's own name. */
7
15
  label?: string
8
16
  }
@@ -14,6 +22,7 @@ export type FieldMessage =
14
22
  export interface FieldMessages {
15
23
  required?: FieldMessage
16
24
  email?: FieldMessage
25
+ pattern?: FieldMessage
17
26
  min?: FieldMessage
18
27
  max?: FieldMessage
19
28
  }
package/validate.js CHANGED
@@ -35,6 +35,7 @@ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
35
35
  const DEFAULTS = {
36
36
  required: (label) => `Please enter your ${label}.`,
37
37
  email: () => 'That does not look like a valid email address.',
38
+ pattern: (label) => `That ${label} does not look right.`,
38
39
  min: (label, n) => `That looks too short — please enter at least ${n} characters.`,
39
40
  max: (label, n) => `That is too long — ${n} characters at most.`,
40
41
  }
@@ -79,8 +80,25 @@ export function validateFields(values, fields = DEFAULT_FIELDS, messages) {
79
80
  // is how a form ends up scolding someone for leaving Company blank.
80
81
  continue
81
82
  }
83
+ // An escape hatch for a rule this cannot express declaratively — a phone
84
+ // check with a different sentence for letters, for too short and for too
85
+ // long. It runs after the empty check, so an optional field stays optional,
86
+ // and it owns the field's message outright when it returns one.
87
+ if (rule.check) {
88
+ const own = rule.check(value, values)
89
+ if (own) {
90
+ errors[field] = own
91
+ continue
92
+ }
93
+ }
94
+
82
95
  if (rule.email && !EMAIL_RE.test(value)) {
83
96
  errors[field] = message(messages, field, 'email', label, 0, value)
97
+ } else if (rule.pattern && !rule.pattern.test(value)) {
98
+ // A site-specific shape the service cannot know about — a UAE mobile
99
+ // number, a trade licence, a postcode. Checked only when the field has
100
+ // something in it, so an optional field with a pattern stays optional.
101
+ errors[field] = message(messages, field, 'pattern', label, 0, value)
84
102
  } else if (rule.min && value.length < rule.min) {
85
103
  errors[field] = message(messages, field, 'min', label, rule.min, value)
86
104
  } else if (rule.max && value.length > rule.max) {