@nanoporetech-digital/components 2.12.0 → 2.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/cjs/index.cjs.js +4 -2
  3. package/dist/cjs/index.cjs.js.map +1 -1
  4. package/dist/cjs/loader.cjs.js +1 -1
  5. package/dist/cjs/nano-components.cjs.js +1 -1
  6. package/dist/cjs/nano-field-validator.cjs.entry.js +266 -124
  7. package/dist/cjs/nano-field-validator.cjs.entry.js.map +1 -1
  8. package/dist/cjs/nano-file-upload.cjs.entry.js +1 -1
  9. package/dist/cjs/nano-file-upload.cjs.entry.js.map +1 -1
  10. package/dist/cjs/nano-input.cjs.entry.js +16 -3
  11. package/dist/cjs/nano-input.cjs.entry.js.map +1 -1
  12. package/dist/cjs/nano-nav-item_2.cjs.entry.js +1 -0
  13. package/dist/cjs/nano-nav-item_2.cjs.entry.js.map +1 -1
  14. package/dist/collection/components/accordion/accordion.js +1 -1
  15. package/dist/collection/components/alert/alert.helpers.js +2 -2
  16. package/dist/collection/components/alert/alert.helpers.js.map +1 -1
  17. package/dist/collection/components/alert/alert.js +1 -1
  18. package/dist/collection/components/algolia/algolia-filter.js +2 -2
  19. package/dist/collection/components/algolia/algolia-input.js +5 -5
  20. package/dist/collection/components/algolia/algolia-results.js +1 -1
  21. package/dist/collection/components/algolia/algolia.js +6 -6
  22. package/dist/collection/components/checkbox/checkbox-group.js +2 -2
  23. package/dist/collection/components/checkbox/checkbox.js +3 -3
  24. package/dist/collection/components/datalist/datalist.js +1 -1
  25. package/dist/collection/components/date-input/date-input.js +8 -8
  26. package/dist/collection/components/date-picker/date-picker.js +5 -5
  27. package/dist/collection/components/details/details.js +1 -1
  28. package/dist/collection/components/dialog/dialog.js +1 -1
  29. package/dist/collection/components/dropdown/dropdown.js +1 -1
  30. package/dist/collection/components/field-validator/field-validator-interface.js.map +1 -1
  31. package/dist/collection/components/field-validator/field-validator.js +345 -130
  32. package/dist/collection/components/field-validator/field-validator.js.map +1 -1
  33. package/dist/collection/components/file-upload/file-upload.css +0 -1
  34. package/dist/collection/components/file-upload/file-upload.js +4 -4
  35. package/dist/collection/components/global-nav/global-nav.js +4 -4
  36. package/dist/collection/components/grid/grid-item.js +1 -1
  37. package/dist/collection/components/icon/icon.js +1 -1
  38. package/dist/collection/components/input/input.js +37 -8
  39. package/dist/collection/components/input/input.js.map +1 -1
  40. package/dist/collection/components/nav-item/nav-item.js +4 -4
  41. package/dist/collection/components/range/range.js +4 -4
  42. package/dist/collection/components/resize-observe/resize-observe.js +1 -1
  43. package/dist/collection/components/select/select.js +8 -7
  44. package/dist/collection/components/select/select.js.map +1 -1
  45. package/dist/collection/components/slides/slides.js +7 -7
  46. package/dist/collection/components/tabs/tab-group.js +2 -2
  47. package/dist/collection/index.js +1 -0
  48. package/dist/collection/index.js.map +1 -1
  49. package/dist/components/index.js +3 -2
  50. package/dist/components/index.js.map +1 -1
  51. package/dist/components/input.js +17 -3
  52. package/dist/components/input.js.map +1 -1
  53. package/dist/components/nano-field-validator.js +271 -126
  54. package/dist/components/nano-field-validator.js.map +1 -1
  55. package/dist/components/nano-file-upload.js +1 -1
  56. package/dist/components/nano-file-upload.js.map +1 -1
  57. package/dist/components/select.js +1 -0
  58. package/dist/components/select.js.map +1 -1
  59. package/dist/custom-elements/index.js +288 -132
  60. package/dist/custom-elements/index.js.map +1 -1
  61. package/dist/esm/index.js +3 -2
  62. package/dist/esm/index.js.map +1 -1
  63. package/dist/esm/loader.js +1 -1
  64. package/dist/esm/nano-components.js +1 -1
  65. package/dist/esm/nano-field-validator.entry.js +266 -124
  66. package/dist/esm/nano-field-validator.entry.js.map +1 -1
  67. package/dist/esm/nano-file-upload.entry.js +1 -1
  68. package/dist/esm/nano-file-upload.entry.js.map +1 -1
  69. package/dist/esm/nano-input.entry.js +16 -3
  70. package/dist/esm/nano-input.entry.js.map +1 -1
  71. package/dist/esm/nano-nav-item_2.entry.js +1 -0
  72. package/dist/esm/nano-nav-item_2.entry.js.map +1 -1
  73. package/dist/esm-es5/index.js +2 -2
  74. package/dist/esm-es5/index.js.map +1 -1
  75. package/dist/esm-es5/loader.js +1 -1
  76. package/dist/esm-es5/loader.js.map +1 -1
  77. package/dist/esm-es5/nano-components.js +1 -1
  78. package/dist/esm-es5/nano-components.js.map +1 -1
  79. package/dist/esm-es5/nano-field-validator.entry.js +2 -2
  80. package/dist/esm-es5/nano-field-validator.entry.js.map +1 -1
  81. package/dist/esm-es5/nano-file-upload.entry.js +1 -1
  82. package/dist/esm-es5/nano-file-upload.entry.js.map +1 -1
  83. package/dist/esm-es5/nano-input.entry.js +1 -1
  84. package/dist/esm-es5/nano-input.entry.js.map +1 -1
  85. package/dist/esm-es5/nano-nav-item_2.entry.js +1 -1
  86. package/dist/esm-es5/nano-nav-item_2.entry.js.map +1 -1
  87. package/dist/nano-components/index.esm.js +1 -1
  88. package/dist/nano-components/index.esm.js.map +1 -1
  89. package/dist/nano-components/nano-components.esm.js +1 -1
  90. package/dist/nano-components/nano-components.esm.js.map +1 -1
  91. package/dist/nano-components/p-085e03db.system.entry.js +5 -0
  92. package/dist/nano-components/p-085e03db.system.entry.js.map +1 -0
  93. package/dist/nano-components/p-53957ec6.system.js +1 -1
  94. package/dist/nano-components/p-53957ec6.system.js.map +1 -1
  95. package/dist/nano-components/{p-01667573.entry.js → p-634a58f7.entry.js} +2 -2
  96. package/dist/nano-components/p-634a58f7.entry.js.map +1 -0
  97. package/dist/nano-components/p-a07cf44c.system.entry.js +5 -0
  98. package/dist/nano-components/{p-4558a9c6.system.entry.js.map → p-a07cf44c.system.entry.js.map} +1 -1
  99. package/dist/nano-components/{p-96d9b8b9.system.entry.js → p-c2bbf0fb.system.entry.js} +2 -2
  100. package/dist/nano-components/p-c2bbf0fb.system.entry.js.map +1 -0
  101. package/dist/nano-components/{p-72893d12.system.entry.js → p-cb512cff.system.entry.js} +2 -2
  102. package/dist/nano-components/p-cb512cff.system.entry.js.map +1 -0
  103. package/dist/nano-components/p-e35226a9.entry.js +5 -0
  104. package/dist/nano-components/p-e35226a9.entry.js.map +1 -0
  105. package/dist/nano-components/p-e9fddc1a.entry.js +5 -0
  106. package/dist/nano-components/{p-91614b43.entry.js.map → p-e9fddc1a.entry.js.map} +1 -1
  107. package/dist/nano-components/{p-055f7d35.entry.js → p-ed0bdea9.entry.js} +2 -2
  108. package/dist/nano-components/p-ed0bdea9.entry.js.map +1 -0
  109. package/dist/nano-components/p-f62a40ea.system.js +5 -0
  110. package/dist/nano-components/{p-3258c568.system.js.map → p-f62a40ea.system.js.map} +1 -1
  111. package/dist/themes/nanopore.css +1 -1
  112. package/dist/themes/nanopore.css.map +1 -1
  113. package/dist/types/components/alert/alert.helpers.d.ts +2 -2
  114. package/dist/types/components/field-validator/field-validator-interface.d.ts +5 -1
  115. package/dist/types/components/field-validator/field-validator.d.ts +68 -12
  116. package/dist/types/components/input/input.d.ts +6 -1
  117. package/dist/types/components.d.ts +25 -3
  118. package/dist/types/index.d.ts +1 -0
  119. package/docs-json.json +65 -3
  120. package/docs-vscode.json +6 -2
  121. package/package.json +2 -2
  122. package/dist/nano-components/p-01667573.entry.js.map +0 -1
  123. package/dist/nano-components/p-055f7d35.entry.js.map +0 -1
  124. package/dist/nano-components/p-2b478ca1.system.entry.js +0 -5
  125. package/dist/nano-components/p-2b478ca1.system.entry.js.map +0 -1
  126. package/dist/nano-components/p-3258c568.system.js +0 -5
  127. package/dist/nano-components/p-4558a9c6.system.entry.js +0 -5
  128. package/dist/nano-components/p-5f4fc2b4.entry.js +0 -5
  129. package/dist/nano-components/p-5f4fc2b4.entry.js.map +0 -1
  130. package/dist/nano-components/p-72893d12.system.entry.js.map +0 -1
  131. package/dist/nano-components/p-91614b43.entry.js +0 -5
  132. package/dist/nano-components/p-96d9b8b9.system.entry.js.map +0 -1
@@ -5,7 +5,6 @@ import { Component, Prop, h, Host, Element, State, Watch, Event, Method, } from
5
5
  import { createStore } from '@stencil/store';
6
6
  /**
7
7
  * A toolbox for `nano-...` form fields and form validation.
8
- *
9
8
  * - Easy to add validation accross field dependencies - e.g. "When Field1 contains '123' Field2 must contain '456'"
10
9
  * - Easy access to whole form and individual field validity states
11
10
  * - Easy access to form data payload
@@ -15,8 +14,15 @@ import { createStore } from '@stencil/store';
15
14
  export class FieldValidator {
16
15
  constructor() {
17
16
  this.submitted = false;
18
- this.fields = [];
19
- // annoyingly, whenever we attempt to checkValidty it fires `invalid` events.
17
+ this.allFields = [];
18
+ this.nanoFieldSelector = `
19
+ nano-input,
20
+ nano-select,
21
+ nano-file-upload,
22
+ nano-date-input,
23
+ nano-checkbox
24
+ `;
25
+ // annoyingly, whenever we attempt to `checkValidty()` it fires `invalid` events.
20
26
  // this is used to prevent infinite loops / multiple calls
21
27
  this.internalValidate = false;
22
28
  // Public API
@@ -25,12 +31,27 @@ export class FieldValidator {
25
31
  /** Tries to scroll to the first invalid field on submit */
26
32
  this.scrollToInvalid = true;
27
33
  this._dirty = false;
34
+ /** By default, `nano-field-validator` will also track all native form field elements.
35
+ * You can add extra web-component form fields to listen to
36
+ * (as long as they match the standard form field spec) by using the `fieldSelector` prop.
37
+ */
38
+ this.extraFieldSelector = 'input, select, textarea';
28
39
  // Event handlers
29
- /** Fired whenever store values change and potentially checks validity */
40
+ /**
41
+ * Fired whenever store values change and potentially checks validity
42
+ * @param key - the key of the store that's just changed
43
+ * @param newVal - the incoming, new value
44
+ */
30
45
  this.handleStoreChange = async (key, newVal) => {
31
- const found = this.fields.find((field) => field.name === key);
32
- if (found && found.value !== newVal)
46
+ const found = this.allFields.find((field) => this.getName(field) === key);
47
+ // field update has come programmatically (not from ui),
48
+ // so let's update the underlying ui field
49
+ if ((found &&
50
+ found.tagName === 'NANO-FILE-UPLOAD' &&
51
+ !this.fileStateEqual(key, found)) ||
52
+ (found.tagName !== 'NANO-FILE-UPLOAD' && found.value !== newVal)) {
33
53
  this.storeToFields([found]);
54
+ }
34
55
  if (this.validateOn === 'dirty' && this.dirty) {
35
56
  this.internalValidate = true;
36
57
  await this.validateAllFields();
@@ -39,17 +60,40 @@ export class FieldValidator {
39
60
  }
40
61
  this.nanoPayloadChange.emit(this._store.state);
41
62
  };
42
- /** Handles field value changes and passes to store */
63
+ /**
64
+ * Handles nano field value changes and passes to store
65
+ * @param ev - the incoming change event
66
+ */
43
67
  this.handleFieldChange = (ev) => {
68
+ if (!this.nanoFields.includes(ev.target))
69
+ return;
44
70
  this._dirty = true;
45
71
  this.fieldsToStore([ev.target]);
46
72
  };
47
- /** Handles default field validation events */
73
+ /**
74
+ * Handles non-nano field value changes and passes to store
75
+ * @param ev - the incoming change event
76
+ */
77
+ this.handlePlainFieldChange = (ev) => {
78
+ if (!this.plainFields.includes(ev.target))
79
+ return;
80
+ this.fieldsToStore([ev.target]);
81
+ };
82
+ /**
83
+ * Handles default field validation events
84
+ * @param ev - the invalid event
85
+ */
48
86
  this.handleFormInvalid = async (ev) => {
49
- ev.preventDefault();
87
+ // if it's a non-nano field, we'll let default html5 validation do it's thing
88
+ if (!this.plainFields.includes(ev.target)) {
89
+ ev.preventDefault();
90
+ }
50
91
  this._valid = false;
92
+ // whenever `checkValidity` is called, this handler is in-turn called.
93
+ // this flag is used to stop infinite loops
51
94
  if (this.internalValidate)
52
95
  return;
96
+ // a submit must have happened to if 'submitThenDirty' turn on 'dirty' checking now
53
97
  if (this.validateOn === 'submitThenDirty')
54
98
  this.validateOn = 'dirty';
55
99
  this.submitted = true;
@@ -69,7 +113,10 @@ export class FieldValidator {
69
113
  this.scrollToFirstInvalid();
70
114
  this.nanoInvalid.emit();
71
115
  };
72
- /** stops default form submission, checks if valid, then submits manually */
116
+ /**
117
+ * stops default form submission, checks if valid, then submits manually
118
+ * @param e - a submit event from the nested form element
119
+ */
73
120
  this.handleSubmit = async (e) => {
74
121
  e.preventDefault();
75
122
  if (this.validateOn === 'submitThenDirty')
@@ -94,6 +141,7 @@ export class FieldValidator {
94
141
  return this._activeForm;
95
142
  }
96
143
  set activeForm(form) {
144
+ // manages event listners on whatever form is used (slotted on created here)
97
145
  if (!form)
98
146
  return;
99
147
  if (this._activeForm) {
@@ -104,7 +152,7 @@ export class FieldValidator {
104
152
  }
105
153
  /** Sync up validateOn with all fields */
106
154
  validateOnChange() {
107
- this.fields.forEach((field) => {
155
+ this.nanoFields.forEach((field) => {
108
156
  if (field.tagName === 'NANO-CHECKBOX') {
109
157
  const cbg = field.closest('nano-checkbox-group');
110
158
  if (cbg)
@@ -132,7 +180,7 @@ export class FieldValidator {
132
180
  get payload() {
133
181
  return this._store.state;
134
182
  }
135
- /** Returns true if validation errors will be displayed to the user */
183
+ /** Returns true if validation errors will be displayed to the user. @readonly */
136
184
  get showValidation() {
137
185
  return (this.validateOn === 'dirty' && this.dirty) || this.submitted;
138
186
  }
@@ -149,26 +197,58 @@ export class FieldValidator {
149
197
  ```
150
198
  */
151
199
  get validationState() {
200
+ // TODO - migrate nano-fields away from using proprietary methods in a bid to be closer to the spec
201
+ // this is big and ugly.
202
+ // why? Cos' it must unify checking validity state for both
203
+ // `nano-...` and plain form fields.
152
204
  const validationState = [];
153
- this.fields.forEach(async (field) => {
154
- const found = validationState.find((v) => v.name === field.name);
205
+ this.allFields.forEach(async (field) => {
206
+ const found = validationState.find((v) => v.name === this.getName(field));
207
+ let pf;
208
+ let nf;
155
209
  if (found) {
156
- found.validityMessage = field.validityMessage.length
157
- ? field.validityMessage
158
- : found.validityMessage;
210
+ if (field.validationMessage) {
211
+ pf = field;
212
+ found.validityMessage = pf.validationMessage.length
213
+ ? pf.validationMessage
214
+ : found.validityMessage;
215
+ this.internalValidate = true;
216
+ if (found.valid && !pf.checkValidity())
217
+ found.valid = false;
218
+ this.internalValidate = false;
219
+ }
220
+ else if (field.validityMessage) {
221
+ nf = field;
222
+ found.validityMessage = nf.validityMessage.length
223
+ ? nf.validityMessage
224
+ : nf.validityMessage;
225
+ if (found.valid && nf.invalid)
226
+ found.valid = false;
227
+ }
159
228
  if (!found.fields.find((f) => f === field))
160
229
  found.fields.push(field);
161
- if (found.valid && field.invalid)
162
- found.valid = false;
163
- return;
230
+ }
231
+ let valid;
232
+ let validityMessage;
233
+ if (field.checkValidity) {
234
+ pf = field;
235
+ this.internalValidate = true;
236
+ valid = pf.checkValidity();
237
+ this.internalValidate = false;
238
+ validityMessage = pf.validationMessage;
239
+ }
240
+ else {
241
+ nf = field;
242
+ valid = !nf.invalid;
243
+ validityMessage = nf.validityMessage;
164
244
  }
165
245
  validationState.push({
166
246
  fields: [field],
167
- name: field.name,
168
- valid: !field.invalid,
169
- value: this._store.state[field.name],
247
+ name: this.getName(field),
248
+ value: this._store.state[this.getName(field)],
170
249
  dirty: false,
171
- validityMessage: field.validityMessage,
250
+ valid,
251
+ validityMessage,
172
252
  });
173
253
  });
174
254
  return validationState;
@@ -180,6 +260,24 @@ export class FieldValidator {
180
260
  async setStore(state) {
181
261
  Object.entries(state).forEach(([key, val]) => (this.store.state[key] = val));
182
262
  }
263
+ /**
264
+ * Sets custom validity for all / some form fields.
265
+ * @param validity - a validity object of `{fieldName: errorString}` pairs. Set as an empty string to clear the error.
266
+ */
267
+ async setCustomValidity(validity) {
268
+ return await Promise.all(Object.entries(validity).map(async ([key, err]) => {
269
+ const field = this.allFields.find((f) => this.getName(f) === key);
270
+ if (!!field)
271
+ await this.setFieldError(field, err);
272
+ }));
273
+ }
274
+ /**
275
+ * Clear all custom validation.
276
+ * @param validity
277
+ */
278
+ async resetValidity() {
279
+ return await Promise.all(this.allFields.map(async (field) => await this.setFieldError(field, '')));
280
+ }
183
281
  // private methods
184
282
  attachSlotObserver() {
185
283
  if (!!this.mo)
@@ -197,118 +295,149 @@ export class FieldValidator {
197
295
  subtree: true,
198
296
  });
199
297
  }
298
+ /**
299
+ * During spec tests, mockelement props aren't set - only attributes.
300
+ * This irons out that kink
301
+ * @param field
302
+ * @returns
303
+ */
304
+ getName(field) {
305
+ return field.name || field.getAttribute('name');
306
+ }
200
307
  /** Checks for new `nano-...` fields and adds them to our watch array and value store */
201
308
  setupFields() {
202
- let fields = Array.from(this.host.querySelectorAll(`
203
- nano-input,
204
- nano-select,
205
- nano-file-upload,
206
- nano-date-input,
207
- nano-checkbox
208
- `));
209
- fields = fields.filter((f) => !!f.name && !!f.name.length);
309
+ let nanoFields = Array.from(this.host.querySelectorAll(this.nanoFieldSelector));
310
+ let plainFields = Array.from(this.host.querySelectorAll(this.extraFieldSelector)).filter((e) => !e.closest(this.nanoFieldSelector));
311
+ nanoFields = nanoFields.filter((f) => !!this.getName(f) && !!this.getName(f).length);
312
+ plainFields = plainFields.filter((f) => !!this.getName(f) && !!this.getName(f).length);
210
313
  // do we have any currently un-watched fields?
211
- if (!fields.filter((f) => !this.fields.includes(f)).length)
314
+ if (![...nanoFields, ...plainFields].filter((f) => !this.allFields.includes(f)).length)
212
315
  return;
213
316
  // setup the initial store state / refresh on new fields
214
- this.fields = fields;
215
- this.storeToFields(this.fields);
317
+ this.nanoFields = nanoFields;
318
+ this.plainFields = plainFields;
319
+ this.allFields = [...nanoFields, ...plainFields];
320
+ this.storeToFields(this.allFields);
216
321
  this.validateOnChange();
217
- this.fieldsToStore(this.fields);
322
+ this.fieldsToStore(this.allFields);
218
323
  this.nanoPayloadChange.emit(this._store.state);
219
324
  }
220
325
  storeToFields(fields) {
221
326
  fields.forEach((field) => {
222
- const fieldName = field.name;
327
+ var _a;
328
+ const fieldName = this.getName(field);
223
329
  if (!fieldName.length ||
224
330
  typeof this._store.state[fieldName] === 'undefined')
225
331
  return;
226
- switch (field.tagName) {
227
- case 'NANO-CHECKBOX':
228
- let cb = field;
229
- if (cb.type === 'radio' ||
230
- cb.type === 'segment' ||
231
- cb.type === 'segment-pill') {
232
- if (this._store.state[fieldName] === cb.value)
233
- cb.checked = true;
234
- else
235
- cb.checked = false;
236
- }
237
- else if (Array.isArray(this._store.state[fieldName])) {
238
- if (this._store.state[fieldName].includes(cb.value))
239
- cb.checked = true;
240
- else
241
- cb.checked = false;
242
- }
243
- else {
244
- if (this._store.state[fieldName] === cb.value)
245
- cb.checked = true;
246
- else
247
- cb.checked = false;
248
- }
249
- break;
250
- case 'NANO-FILE-UPLOAD':
251
- field.files =
252
- this._store.state[fieldName];
253
- break;
254
- default:
255
- field.value = this._store.state[fieldName];
256
- break;
332
+ if (field.tagName === 'NANO-CHECKBOX' ||
333
+ ['radio', 'checkbox'].includes(field.type)) {
334
+ let cb = field;
335
+ if (cb.type === 'radio' ||
336
+ cb.type === 'segment' ||
337
+ cb.type === 'segment-pill') {
338
+ // single radio type control
339
+ if (this._store.state[fieldName] === cb.value)
340
+ cb.checked = true;
341
+ else
342
+ cb.checked = false;
343
+ }
344
+ else if (Array.isArray(this._store.state[fieldName])) {
345
+ // multiple checkbox like controls
346
+ if (this._store.state[fieldName].includes(cb.value))
347
+ cb.checked = true;
348
+ else
349
+ cb.checked = false;
350
+ }
351
+ else {
352
+ // single checkbox like control
353
+ if (this._store.state[fieldName] === cb.value)
354
+ cb.checked = true;
355
+ else
356
+ cb.checked = false;
357
+ }
358
+ return;
359
+ }
360
+ if (field.tagName === 'NANO-FILE-UPLOAD') {
361
+ const ff = field;
362
+ // this can only work if the field is empty rn... a one-time deal
363
+ if (!((_a = ff.files) === null || _a === void 0 ? void 0 : _a.length))
364
+ ff.files = this._store.state[fieldName];
365
+ return;
257
366
  }
367
+ // default
368
+ field.value = this._store.state[fieldName];
258
369
  });
259
370
  }
260
371
  /** Loops through all `nano-...` fields and extracts their values into our store */
261
372
  fieldsToStore(fields) {
262
373
  fields.forEach((field) => {
263
- const fieldName = field.name;
374
+ const fieldName = this.getName(field);
264
375
  if (!fieldName.length)
265
376
  return;
266
- switch (field.tagName) {
267
- case 'NANO-CHECKBOX':
268
- let cb = field;
269
- if (cb.type === 'radio' ||
270
- cb.type === 'segment' ||
271
- cb.type === 'segment-pill') {
272
- // radio type control - only one can be checked
273
- if (cb.checked)
274
- this._store.state[fieldName] = cb.value;
275
- else if (!cb.checked &&
276
- (cb.value === this._store.state[fieldName] ||
277
- !this._store.state[fieldName]))
278
- this._store.state[fieldName] = '';
279
- }
280
- else if (this.fields.filter((f) => f.name === fieldName && f.tagName === 'NANO-CHECKBOX').length > 1) {
281
- // multiple checkbox type control
282
- const currentArr = Array.isArray(this._store.state[fieldName])
283
- ? this._store.state[fieldName]
284
- : [];
285
- if (cb.checked) {
286
- if (!this._store.state[fieldName].includes(cb.value)) {
287
- this._store.state[fieldName] = [...currentArr, cb.value];
288
- }
289
- }
290
- else {
291
- this._store.state[fieldName] = currentArr.filter((v) => v !== cb.value);
377
+ if (field.tagName === 'NANO-CHECKBOX' ||
378
+ ['radio', 'checkbox'].includes(field.type)) {
379
+ let cb = field;
380
+ if (cb.type === 'radio' ||
381
+ cb.type === 'segment' ||
382
+ cb.type === 'segment-pill') {
383
+ // radio type control - only one can be checked
384
+ if (cb.checked)
385
+ this._store.state[fieldName] = cb.value;
386
+ }
387
+ else if (this.allFields.filter((f) => !!this.getName(field) &&
388
+ (f.tagName === 'NANO-CHECKBOX' ||
389
+ f.type === 'checkbox')).length > 1) {
390
+ // multiple checkbox type control
391
+ const currentArr = Array.isArray(this._store.state[fieldName])
392
+ ? this._store.state[fieldName]
393
+ : [];
394
+ if (cb.checked) {
395
+ // checked
396
+ if (!this._store.state[fieldName].includes(cb.value)) {
397
+ this._store.state[fieldName] = [...currentArr, cb.value];
292
398
  }
293
399
  }
294
400
  else {
295
- // single checkbox - on or off
296
- if (cb.checked)
297
- this._store.state[fieldName] = cb.value;
298
- else
299
- this._store.state[fieldName] = '';
401
+ // unchecked
402
+ this._store.state[fieldName] = currentArr.filter((v) => v !== cb.value);
300
403
  }
301
- break;
302
- case 'NANO-FILE-UPLOAD':
303
- this._store.state[fieldName] = field.files;
304
- break;
305
- default:
306
- this._store.state[fieldName] = field.value;
307
- break;
404
+ }
405
+ else {
406
+ // single checkbox - on or off
407
+ if (cb.checked)
408
+ this._store.state[fieldName] = cb.value;
409
+ else
410
+ this._store.state[fieldName] = '';
411
+ }
412
+ return;
308
413
  }
414
+ if (field.tagName === 'NANO-FILE-UPLOAD') {
415
+ const ff = field;
416
+ if (!this.fileStateEqual(fieldName, ff))
417
+ this._store.state[fieldName] = ff.files;
418
+ return;
419
+ }
420
+ // default
421
+ this._store.state[fieldName] = field.value;
309
422
  });
310
423
  }
311
- /** Checks for user defined validations */
424
+ /**
425
+ * Tries to ascertain whether the current model
426
+ * value is the same as the `nano-file-upload` value
427
+ * @param fieldName - the key to access from the data store
428
+ * @param field - the nano-file-upload field to assess against
429
+ * @returns true for equal, false for not equal
430
+ */
431
+ fileStateEqual(fieldName, field) {
432
+ return (JSON.stringify(this._store.state[fieldName]) ===
433
+ JSON.stringify(field.files) ||
434
+ this._store.state[fieldName] == field.files);
435
+ }
436
+ /**
437
+ * Checks for user defined validations
438
+ * @param key - current key of the data model to validate
439
+ * @param newVal - the newly set, incoming value to validate
440
+ */
312
441
  async validate(key, newVal) {
313
442
  if (!this.validation)
314
443
  return;
@@ -321,21 +450,33 @@ export class FieldValidator {
321
450
  // collection loop into a promise
322
451
  await Promise.all(Object.entries(res).map(async ([key, o]) => {
323
452
  // switch on/off validation messages
324
- const field = this.fields.find((f) => f.name === key);
453
+ const field = this.allFields.find((f) => this.getName(f) === key);
325
454
  let validityTarget = field;
326
455
  if (field.tagName === 'NANO-CHECKBOX') {
456
+ // if we have a checkbox-group, set the validation message there
327
457
  const cbg = field.closest('nano-checkbox-group');
328
458
  validityTarget = cbg || field;
329
459
  }
330
- // status is now valid - clear the error
331
- if (validityTarget.validityMessage === o.msg && o.valid)
460
+ if ((validityTarget.validityMessage ||
461
+ validityTarget.validationMessage) === o.msg &&
462
+ o.valid) {
463
+ // status is now valid - clear the error
332
464
  await this.setFieldError(validityTarget, '');
333
- // status is invalid. Set the error
465
+ }
334
466
  else if (!o.valid) {
467
+ // status is invalid. Set the error
335
468
  await this.setFieldError(validityTarget, o.msg);
336
469
  }
337
470
  }));
338
471
  }
472
+ /** Loops through all store entries and checks custom validation */
473
+ async validateAllFields() {
474
+ // This forces our loop to `await` and finish sequentially ... silly async stencil methods
475
+ await Object.entries(this._store.state).reduce(async (memo, [key, value]) => {
476
+ await memo;
477
+ await this.validate(key, value);
478
+ }, undefined);
479
+ }
339
480
  /**
340
481
  * Utility to smooth out setting error messages
341
482
  * (it's a different method on `nano-checkbox` 'cos they don't show errors themselves)
@@ -343,18 +484,13 @@ export class FieldValidator {
343
484
  * @param msg
344
485
  */
345
486
  async setFieldError(field, msg) {
346
- if (field['showError'])
487
+ if (field['showError']) {
347
488
  await field.showError(msg);
348
- else
489
+ }
490
+ else if (field['setError'])
349
491
  await field.setError(msg);
350
- }
351
- /** Loops through all store entries and checks field validity */
352
- async validateAllFields() {
353
- // This forces our loop to `await` and finish sequentially ... silly async stencil methods
354
- await Object.entries(this._store.state).reduce(async (memo, [key, value]) => {
355
- await memo;
356
- await this.validate(key, value);
357
- }, undefined);
492
+ else
493
+ field.setCustomValidity(msg);
358
494
  }
359
495
  scrollToFirstInvalid() {
360
496
  if (!this.scrollToInvalid)
@@ -383,17 +519,21 @@ export class FieldValidator {
383
519
  requestAnimationFrame(() => {
384
520
  this.setupFields();
385
521
  this.attachSlotObserver();
386
- this._store.on('set', (key, value) => this.handleStoreChange(key, value));
387
522
  this.host.addEventListener('nanoChange', this.handleFieldChange);
523
+ this.host.addEventListener('input', this.handlePlainFieldChange);
524
+ this.host.addEventListener('change', this.handlePlainFieldChange);
388
525
  this.host.addEventListener('submit', this.handleSubmit);
526
+ this._store.on('set', this.handleStoreChange);
389
527
  });
390
528
  }
391
529
  disconnectedCallback() {
392
530
  if (this.mo)
393
531
  this.mo.disconnect();
394
- this._store.reset();
395
532
  this.host.removeEventListener('nanoChange', this.handleFieldChange);
533
+ this.host.removeEventListener('input', this.handlePlainFieldChange);
534
+ this.host.removeEventListener('change', this.handlePlainFieldChange);
396
535
  this.host.removeEventListener('submit', this.handleSubmit);
536
+ this._store.reset();
397
537
  if (this.activeForm)
398
538
  this.activeForm.removeEventListener('invalid', this.handleFormInvalid, true);
399
539
  }
@@ -530,7 +670,7 @@ export class FieldValidator {
530
670
  "optional": false,
531
671
  "docs": {
532
672
  "tags": [],
533
- "text": "Returns true if validation errors will be displayed to the user"
673
+ "text": "Returns true if validation errors will be displayed to the user. @readonly"
534
674
  },
535
675
  "getter": true,
536
676
  "setter": false,
@@ -546,7 +686,15 @@ export class FieldValidator {
546
686
  "references": {
547
687
  "ValidationState": {
548
688
  "location": "import",
549
- "path": "/builds/VuwsCczH/0/Digital/nano-components/packages/components/src/interface.d.ts"
689
+ "path": "/builds/YUQZGnoL/0/Digital/nano-components/packages/components/src/interface.d.ts"
690
+ },
691
+ "PlainFormEles": {
692
+ "location": "import",
693
+ "path": "/builds/YUQZGnoL/0/Digital/nano-components/packages/components/src/interface.d.ts"
694
+ },
695
+ "NanoFormEles": {
696
+ "location": "import",
697
+ "path": "/builds/YUQZGnoL/0/Digital/nano-components/packages/components/src/interface.d.ts"
550
698
  }
551
699
  }
552
700
  },
@@ -559,6 +707,26 @@ export class FieldValidator {
559
707
  "getter": true,
560
708
  "setter": false
561
709
  },
710
+ "extraFieldSelector": {
711
+ "type": "string",
712
+ "mutable": false,
713
+ "complexType": {
714
+ "original": "string",
715
+ "resolved": "string",
716
+ "references": {}
717
+ },
718
+ "required": false,
719
+ "optional": false,
720
+ "docs": {
721
+ "tags": [],
722
+ "text": "By default, `nano-field-validator` will also track all native form field elements.\nYou can add extra web-component form fields to listen to\n(as long as they match the standard form field spec) by using the `fieldSelector` prop."
723
+ },
724
+ "getter": false,
725
+ "setter": false,
726
+ "attribute": "extra-field-selector",
727
+ "reflect": false,
728
+ "defaultValue": "'input, select, textarea'"
729
+ },
562
730
  "validation": {
563
731
  "type": "unknown",
564
732
  "mutable": false,
@@ -568,7 +736,7 @@ export class FieldValidator {
568
736
  "references": {
569
737
  "ValidatorValueStore": {
570
738
  "location": "import",
571
- "path": "/builds/VuwsCczH/0/Digital/nano-components/packages/components/src/interface.d.ts"
739
+ "path": "/builds/YUQZGnoL/0/Digital/nano-components/packages/components/src/interface.d.ts"
572
740
  }
573
741
  }
574
742
  },
@@ -595,8 +763,8 @@ export class FieldValidator {
595
763
  }
596
764
  }; }
597
765
  static get states() { return {
598
- "userForm": {},
599
766
  "submitted": {},
767
+ "userForm": {},
600
768
  "_dirty": {},
601
769
  "_valid": {},
602
770
  "_store": {}
@@ -617,7 +785,7 @@ export class FieldValidator {
617
785
  "references": {
618
786
  "ValidatorValueStore": {
619
787
  "location": "import",
620
- "path": "/builds/VuwsCczH/0/Digital/nano-components/packages/components/src/interface.d.ts"
788
+ "path": "/builds/YUQZGnoL/0/Digital/nano-components/packages/components/src/interface.d.ts"
621
789
  }
622
790
  }
623
791
  }
@@ -669,7 +837,7 @@ export class FieldValidator {
669
837
  },
670
838
  "ValidatorValueStore": {
671
839
  "location": "import",
672
- "path": "/builds/VuwsCczH/0/Digital/nano-components/packages/components/src/interface.d.ts"
840
+ "path": "/builds/YUQZGnoL/0/Digital/nano-components/packages/components/src/interface.d.ts"
673
841
  }
674
842
  },
675
843
  "return": "Promise<void>"
@@ -681,6 +849,50 @@ export class FieldValidator {
681
849
  "text": "state - the state to load in the store"
682
850
  }]
683
851
  }
852
+ },
853
+ "setCustomValidity": {
854
+ "complexType": {
855
+ "signature": "(validity: { [key: string]: string; }) => Promise<void[]>",
856
+ "parameters": [{
857
+ "tags": [{
858
+ "name": "param",
859
+ "text": "validity - a validity object of `{fieldName: errorString}` pairs. Set as an empty string to clear the error."
860
+ }],
861
+ "text": "- a validity object of `{fieldName: errorString}` pairs. Set as an empty string to clear the error."
862
+ }],
863
+ "references": {
864
+ "Promise": {
865
+ "location": "global"
866
+ }
867
+ },
868
+ "return": "Promise<void[]>"
869
+ },
870
+ "docs": {
871
+ "text": "Sets custom validity for all / some form fields.",
872
+ "tags": [{
873
+ "name": "param",
874
+ "text": "validity - a validity object of `{fieldName: errorString}` pairs. Set as an empty string to clear the error."
875
+ }]
876
+ }
877
+ },
878
+ "resetValidity": {
879
+ "complexType": {
880
+ "signature": "() => Promise<void[]>",
881
+ "parameters": [],
882
+ "references": {
883
+ "Promise": {
884
+ "location": "global"
885
+ }
886
+ },
887
+ "return": "Promise<void[]>"
888
+ },
889
+ "docs": {
890
+ "text": "Clear all custom validation.",
891
+ "tags": [{
892
+ "name": "param",
893
+ "text": "validity"
894
+ }]
895
+ }
684
896
  }
685
897
  }; }
686
898
  static get elementRef() { return "host"; }
@@ -690,6 +902,9 @@ export class FieldValidator {
690
902
  }, {
691
903
  "propName": "validateOn",
692
904
  "methodName": "validateOnChange"
905
+ }, {
906
+ "propName": "extraFieldSelector",
907
+ "methodName": "attachSlotObserver"
693
908
  }]; }
694
909
  }
695
910
  //# sourceMappingURL=field-validator.js.map