@nanoporetech-digital/components 2.12.0 → 2.13.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 (129) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cjs/index.cjs.js +2 -0
  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 +250 -118
  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.js +1 -1
  16. package/dist/collection/components/algolia/algolia-filter.js +2 -2
  17. package/dist/collection/components/algolia/algolia-input.js +5 -5
  18. package/dist/collection/components/algolia/algolia-results.js +1 -1
  19. package/dist/collection/components/algolia/algolia.js +6 -6
  20. package/dist/collection/components/checkbox/checkbox-group.js +2 -2
  21. package/dist/collection/components/checkbox/checkbox.js +3 -3
  22. package/dist/collection/components/datalist/datalist.js +1 -1
  23. package/dist/collection/components/date-input/date-input.js +8 -8
  24. package/dist/collection/components/date-picker/date-picker.js +5 -5
  25. package/dist/collection/components/details/details.js +1 -1
  26. package/dist/collection/components/dialog/dialog.js +1 -1
  27. package/dist/collection/components/dropdown/dropdown.js +1 -1
  28. package/dist/collection/components/field-validator/field-validator-interface.js.map +1 -1
  29. package/dist/collection/components/field-validator/field-validator.js +329 -124
  30. package/dist/collection/components/field-validator/field-validator.js.map +1 -1
  31. package/dist/collection/components/file-upload/file-upload.css +0 -1
  32. package/dist/collection/components/file-upload/file-upload.js +4 -4
  33. package/dist/collection/components/global-nav/global-nav.js +4 -4
  34. package/dist/collection/components/grid/grid-item.js +1 -1
  35. package/dist/collection/components/icon/icon.js +1 -1
  36. package/dist/collection/components/input/input.js +37 -8
  37. package/dist/collection/components/input/input.js.map +1 -1
  38. package/dist/collection/components/nav-item/nav-item.js +4 -4
  39. package/dist/collection/components/range/range.js +4 -4
  40. package/dist/collection/components/resize-observe/resize-observe.js +1 -1
  41. package/dist/collection/components/select/select.js +8 -7
  42. package/dist/collection/components/select/select.js.map +1 -1
  43. package/dist/collection/components/slides/slides.js +7 -7
  44. package/dist/collection/components/tabs/tab-group.js +2 -2
  45. package/dist/collection/index.js +1 -0
  46. package/dist/collection/index.js.map +1 -1
  47. package/dist/components/index.js +1 -0
  48. package/dist/components/index.js.map +1 -1
  49. package/dist/components/input.js +17 -3
  50. package/dist/components/input.js.map +1 -1
  51. package/dist/components/nano-field-validator.js +255 -120
  52. package/dist/components/nano-field-validator.js.map +1 -1
  53. package/dist/components/nano-file-upload.js +1 -1
  54. package/dist/components/nano-file-upload.js.map +1 -1
  55. package/dist/components/select.js +1 -0
  56. package/dist/components/select.js.map +1 -1
  57. package/dist/custom-elements/index.js +270 -124
  58. package/dist/custom-elements/index.js.map +1 -1
  59. package/dist/esm/index.js +1 -0
  60. package/dist/esm/index.js.map +1 -1
  61. package/dist/esm/loader.js +1 -1
  62. package/dist/esm/nano-components.js +1 -1
  63. package/dist/esm/nano-field-validator.entry.js +250 -118
  64. package/dist/esm/nano-field-validator.entry.js.map +1 -1
  65. package/dist/esm/nano-file-upload.entry.js +1 -1
  66. package/dist/esm/nano-file-upload.entry.js.map +1 -1
  67. package/dist/esm/nano-input.entry.js +16 -3
  68. package/dist/esm/nano-input.entry.js.map +1 -1
  69. package/dist/esm/nano-nav-item_2.entry.js +1 -0
  70. package/dist/esm/nano-nav-item_2.entry.js.map +1 -1
  71. package/dist/esm-es5/index.js +2 -2
  72. package/dist/esm-es5/index.js.map +1 -1
  73. package/dist/esm-es5/loader.js +1 -1
  74. package/dist/esm-es5/loader.js.map +1 -1
  75. package/dist/esm-es5/nano-components.js +1 -1
  76. package/dist/esm-es5/nano-components.js.map +1 -1
  77. package/dist/esm-es5/nano-field-validator.entry.js +2 -2
  78. package/dist/esm-es5/nano-field-validator.entry.js.map +1 -1
  79. package/dist/esm-es5/nano-file-upload.entry.js +1 -1
  80. package/dist/esm-es5/nano-file-upload.entry.js.map +1 -1
  81. package/dist/esm-es5/nano-input.entry.js +1 -1
  82. package/dist/esm-es5/nano-input.entry.js.map +1 -1
  83. package/dist/esm-es5/nano-nav-item_2.entry.js +1 -1
  84. package/dist/esm-es5/nano-nav-item_2.entry.js.map +1 -1
  85. package/dist/nano-components/index.esm.js +1 -1
  86. package/dist/nano-components/index.esm.js.map +1 -1
  87. package/dist/nano-components/nano-components.esm.js +1 -1
  88. package/dist/nano-components/nano-components.esm.js.map +1 -1
  89. package/dist/nano-components/p-0d699368.system.js +5 -0
  90. package/dist/nano-components/{p-3258c568.system.js.map → p-0d699368.system.js.map} +1 -1
  91. package/dist/nano-components/p-18863670.system.entry.js +5 -0
  92. package/dist/nano-components/p-18863670.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-7f051c20.entry.js +5 -0
  98. package/dist/nano-components/p-7f051c20.entry.js.map +1 -0
  99. package/dist/nano-components/p-a07cf44c.system.entry.js +5 -0
  100. package/dist/nano-components/{p-4558a9c6.system.entry.js.map → p-a07cf44c.system.entry.js.map} +1 -1
  101. package/dist/nano-components/{p-96d9b8b9.system.entry.js → p-c2bbf0fb.system.entry.js} +2 -2
  102. package/dist/nano-components/p-c2bbf0fb.system.entry.js.map +1 -0
  103. package/dist/nano-components/{p-72893d12.system.entry.js → p-cb512cff.system.entry.js} +2 -2
  104. package/dist/nano-components/p-cb512cff.system.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/themes/nanopore.css +1 -1
  110. package/dist/themes/nanopore.css.map +1 -1
  111. package/dist/types/components/field-validator/field-validator-interface.d.ts +5 -1
  112. package/dist/types/components/field-validator/field-validator.d.ts +61 -12
  113. package/dist/types/components/input/input.d.ts +6 -1
  114. package/dist/types/components.d.ts +25 -3
  115. package/dist/types/index.d.ts +1 -0
  116. package/docs-json.json +65 -3
  117. package/docs-vscode.json +6 -2
  118. package/package.json +2 -2
  119. package/dist/nano-components/p-01667573.entry.js.map +0 -1
  120. package/dist/nano-components/p-055f7d35.entry.js.map +0 -1
  121. package/dist/nano-components/p-2b478ca1.system.entry.js +0 -5
  122. package/dist/nano-components/p-2b478ca1.system.entry.js.map +0 -1
  123. package/dist/nano-components/p-3258c568.system.js +0 -5
  124. package/dist/nano-components/p-4558a9c6.system.entry.js +0 -5
  125. package/dist/nano-components/p-5f4fc2b4.entry.js +0 -5
  126. package/dist/nano-components/p-5f4fc2b4.entry.js.map +0 -1
  127. package/dist/nano-components/p-72893d12.system.entry.js.map +0 -1
  128. package/dist/nano-components/p-91614b43.entry.js +0 -5
  129. 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) => field.name === 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) => {
205
+ this.allFields.forEach(async (field) => {
154
206
  const found = validationState.find((v) => v.name === field.name);
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
247
  name: field.name,
168
- valid: !field.invalid,
169
248
  value: this._store.state[field.name],
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) => f.name === 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)
@@ -199,62 +297,66 @@ export class FieldValidator {
199
297
  }
200
298
  /** Checks for new `nano-...` fields and adds them to our watch array and value store */
201
299
  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);
300
+ let nanoFields = Array.from(this.host.querySelectorAll(this.nanoFieldSelector));
301
+ let plainFields = Array.from(this.host.querySelectorAll(this.extraFieldSelector)).filter((e) => !e.closest(this.nanoFieldSelector));
302
+ nanoFields = nanoFields.filter((f) => !!f.name && !!f.name.length);
303
+ plainFields = plainFields.filter((f) => !!f.name && !!f.name.length);
210
304
  // do we have any currently un-watched fields?
211
- if (!fields.filter((f) => !this.fields.includes(f)).length)
305
+ if (![...nanoFields, ...plainFields].filter((f) => !this.allFields.includes(f)).length)
212
306
  return;
213
307
  // setup the initial store state / refresh on new fields
214
- this.fields = fields;
215
- this.storeToFields(this.fields);
308
+ this.nanoFields = nanoFields;
309
+ this.plainFields = plainFields;
310
+ this.allFields = [...nanoFields, ...plainFields];
311
+ this.storeToFields(this.allFields);
216
312
  this.validateOnChange();
217
- this.fieldsToStore(this.fields);
313
+ this.fieldsToStore(this.allFields);
218
314
  this.nanoPayloadChange.emit(this._store.state);
219
315
  }
220
316
  storeToFields(fields) {
221
317
  fields.forEach((field) => {
318
+ var _a;
222
319
  const fieldName = field.name;
223
320
  if (!fieldName.length ||
224
321
  typeof this._store.state[fieldName] === 'undefined')
225
322
  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;
323
+ if (field.tagName === 'NANO-CHECKBOX' ||
324
+ ['radio', 'checkbox'].includes(field.type)) {
325
+ let cb = field;
326
+ if (cb.type === 'radio' ||
327
+ cb.type === 'segment' ||
328
+ cb.type === 'segment-pill') {
329
+ // single radio type control
330
+ if (this._store.state[fieldName] === cb.value)
331
+ cb.checked = true;
332
+ else
333
+ cb.checked = false;
334
+ }
335
+ else if (Array.isArray(this._store.state[fieldName])) {
336
+ // multiple checkbox like controls
337
+ if (this._store.state[fieldName].includes(cb.value))
338
+ cb.checked = true;
339
+ else
340
+ cb.checked = false;
341
+ }
342
+ else {
343
+ // single checkbox like control
344
+ if (this._store.state[fieldName] === cb.value)
345
+ cb.checked = true;
346
+ else
347
+ cb.checked = false;
348
+ }
349
+ return;
257
350
  }
351
+ if (field.tagName === 'NANO-FILE-UPLOAD') {
352
+ const ff = field;
353
+ // this can only work if the field is empty rn... a one-time deal
354
+ if (!((_a = ff.files) === null || _a === void 0 ? void 0 : _a.length))
355
+ ff.files = this._store.state[fieldName];
356
+ return;
357
+ }
358
+ // default
359
+ field.value = this._store.state[fieldName];
258
360
  });
259
361
  }
260
362
  /** Loops through all `nano-...` fields and extracts their values into our store */
@@ -263,52 +365,70 @@ export class FieldValidator {
263
365
  const fieldName = field.name;
264
366
  if (!fieldName.length)
265
367
  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);
368
+ if (field.tagName === 'NANO-CHECKBOX' ||
369
+ ['radio', 'checkbox'].includes(field.type)) {
370
+ let cb = field;
371
+ if (cb.type === 'radio' ||
372
+ cb.type === 'segment' ||
373
+ cb.type === 'segment-pill') {
374
+ // radio type control - only one can be checked
375
+ if (cb.checked)
376
+ this._store.state[fieldName] = cb.value;
377
+ }
378
+ else if (this.allFields.filter((f) => f.name === fieldName &&
379
+ (f.tagName === 'NANO-CHECKBOX' ||
380
+ f.type === 'checkbox')).length > 1) {
381
+ // multiple checkbox type control
382
+ const currentArr = Array.isArray(this._store.state[fieldName])
383
+ ? this._store.state[fieldName]
384
+ : [];
385
+ if (cb.checked) {
386
+ // checked
387
+ if (!this._store.state[fieldName].includes(cb.value)) {
388
+ this._store.state[fieldName] = [...currentArr, cb.value];
292
389
  }
293
390
  }
294
391
  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] = '';
392
+ // unchecked
393
+ this._store.state[fieldName] = currentArr.filter((v) => v !== cb.value);
300
394
  }
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;
395
+ }
396
+ else {
397
+ // single checkbox - on or off
398
+ if (cb.checked)
399
+ this._store.state[fieldName] = cb.value;
400
+ else
401
+ this._store.state[fieldName] = '';
402
+ }
403
+ return;
308
404
  }
405
+ if (field.tagName === 'NANO-FILE-UPLOAD') {
406
+ const ff = field;
407
+ if (!this.fileStateEqual(fieldName, ff))
408
+ this._store.state[fieldName] = ff.files;
409
+ return;
410
+ }
411
+ // default
412
+ this._store.state[fieldName] = field.value;
309
413
  });
310
414
  }
311
- /** Checks for user defined validations */
415
+ /**
416
+ * Tries to ascertain whether the current model
417
+ * value is the same as the `nano-file-upload` value
418
+ * @param fieldName - the key to access from the data store
419
+ * @param field - the nano-file-upload field to assess against
420
+ * @returns true for equal, false for not equal
421
+ */
422
+ fileStateEqual(fieldName, field) {
423
+ return (JSON.stringify(this._store.state[fieldName]) ===
424
+ JSON.stringify(field.files) ||
425
+ this._store.state[fieldName] == field.files);
426
+ }
427
+ /**
428
+ * Checks for user defined validations
429
+ * @param key - current key of the data model to validate
430
+ * @param newVal - the newly set, incoming value to validate
431
+ */
312
432
  async validate(key, newVal) {
313
433
  if (!this.validation)
314
434
  return;
@@ -321,21 +441,33 @@ export class FieldValidator {
321
441
  // collection loop into a promise
322
442
  await Promise.all(Object.entries(res).map(async ([key, o]) => {
323
443
  // switch on/off validation messages
324
- const field = this.fields.find((f) => f.name === key);
444
+ const field = this.allFields.find((f) => f.name === key);
325
445
  let validityTarget = field;
326
446
  if (field.tagName === 'NANO-CHECKBOX') {
447
+ // if we have a checkbox-group, set the validation message there
327
448
  const cbg = field.closest('nano-checkbox-group');
328
449
  validityTarget = cbg || field;
329
450
  }
330
- // status is now valid - clear the error
331
- if (validityTarget.validityMessage === o.msg && o.valid)
451
+ if ((validityTarget.validityMessage ||
452
+ validityTarget.validationMessage) === o.msg &&
453
+ o.valid) {
454
+ // status is now valid - clear the error
332
455
  await this.setFieldError(validityTarget, '');
333
- // status is invalid. Set the error
456
+ }
334
457
  else if (!o.valid) {
458
+ // status is invalid. Set the error
335
459
  await this.setFieldError(validityTarget, o.msg);
336
460
  }
337
461
  }));
338
462
  }
463
+ /** Loops through all store entries and checks custom validation */
464
+ async validateAllFields() {
465
+ // This forces our loop to `await` and finish sequentially ... silly async stencil methods
466
+ await Object.entries(this._store.state).reduce(async (memo, [key, value]) => {
467
+ await memo;
468
+ await this.validate(key, value);
469
+ }, undefined);
470
+ }
339
471
  /**
340
472
  * Utility to smooth out setting error messages
341
473
  * (it's a different method on `nano-checkbox` 'cos they don't show errors themselves)
@@ -345,16 +477,10 @@ export class FieldValidator {
345
477
  async setFieldError(field, msg) {
346
478
  if (field['showError'])
347
479
  await field.showError(msg);
348
- else
480
+ else if (field['setError'])
349
481
  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);
482
+ else
483
+ field.setCustomValidity(msg);
358
484
  }
359
485
  scrollToFirstInvalid() {
360
486
  if (!this.scrollToInvalid)
@@ -383,17 +509,21 @@ export class FieldValidator {
383
509
  requestAnimationFrame(() => {
384
510
  this.setupFields();
385
511
  this.attachSlotObserver();
386
- this._store.on('set', (key, value) => this.handleStoreChange(key, value));
387
512
  this.host.addEventListener('nanoChange', this.handleFieldChange);
513
+ this.host.addEventListener('input', this.handlePlainFieldChange);
514
+ this.host.addEventListener('change', this.handlePlainFieldChange);
388
515
  this.host.addEventListener('submit', this.handleSubmit);
516
+ this._store.on('set', this.handleStoreChange);
389
517
  });
390
518
  }
391
519
  disconnectedCallback() {
392
520
  if (this.mo)
393
521
  this.mo.disconnect();
394
- this._store.reset();
395
522
  this.host.removeEventListener('nanoChange', this.handleFieldChange);
523
+ this.host.removeEventListener('input', this.handlePlainFieldChange);
524
+ this.host.removeEventListener('change', this.handlePlainFieldChange);
396
525
  this.host.removeEventListener('submit', this.handleSubmit);
526
+ this._store.reset();
397
527
  if (this.activeForm)
398
528
  this.activeForm.removeEventListener('invalid', this.handleFormInvalid, true);
399
529
  }
@@ -530,7 +660,7 @@ export class FieldValidator {
530
660
  "optional": false,
531
661
  "docs": {
532
662
  "tags": [],
533
- "text": "Returns true if validation errors will be displayed to the user"
663
+ "text": "Returns true if validation errors will be displayed to the user. @readonly"
534
664
  },
535
665
  "getter": true,
536
666
  "setter": false,
@@ -546,7 +676,15 @@ export class FieldValidator {
546
676
  "references": {
547
677
  "ValidationState": {
548
678
  "location": "import",
549
- "path": "/builds/VuwsCczH/0/Digital/nano-components/packages/components/src/interface.d.ts"
679
+ "path": "/builds/f85YXDf1/0/Digital/nano-components/packages/components/src/interface.d.ts"
680
+ },
681
+ "PlainFormEles": {
682
+ "location": "import",
683
+ "path": "/builds/f85YXDf1/0/Digital/nano-components/packages/components/src/interface.d.ts"
684
+ },
685
+ "NanoFormEles": {
686
+ "location": "import",
687
+ "path": "/builds/f85YXDf1/0/Digital/nano-components/packages/components/src/interface.d.ts"
550
688
  }
551
689
  }
552
690
  },
@@ -559,6 +697,26 @@ export class FieldValidator {
559
697
  "getter": true,
560
698
  "setter": false
561
699
  },
700
+ "extraFieldSelector": {
701
+ "type": "string",
702
+ "mutable": false,
703
+ "complexType": {
704
+ "original": "string",
705
+ "resolved": "string",
706
+ "references": {}
707
+ },
708
+ "required": false,
709
+ "optional": false,
710
+ "docs": {
711
+ "tags": [],
712
+ "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."
713
+ },
714
+ "getter": false,
715
+ "setter": false,
716
+ "attribute": "extra-field-selector",
717
+ "reflect": false,
718
+ "defaultValue": "'input, select, textarea'"
719
+ },
562
720
  "validation": {
563
721
  "type": "unknown",
564
722
  "mutable": false,
@@ -568,7 +726,7 @@ export class FieldValidator {
568
726
  "references": {
569
727
  "ValidatorValueStore": {
570
728
  "location": "import",
571
- "path": "/builds/VuwsCczH/0/Digital/nano-components/packages/components/src/interface.d.ts"
729
+ "path": "/builds/f85YXDf1/0/Digital/nano-components/packages/components/src/interface.d.ts"
572
730
  }
573
731
  }
574
732
  },
@@ -595,8 +753,8 @@ export class FieldValidator {
595
753
  }
596
754
  }; }
597
755
  static get states() { return {
598
- "userForm": {},
599
756
  "submitted": {},
757
+ "userForm": {},
600
758
  "_dirty": {},
601
759
  "_valid": {},
602
760
  "_store": {}
@@ -617,7 +775,7 @@ export class FieldValidator {
617
775
  "references": {
618
776
  "ValidatorValueStore": {
619
777
  "location": "import",
620
- "path": "/builds/VuwsCczH/0/Digital/nano-components/packages/components/src/interface.d.ts"
778
+ "path": "/builds/f85YXDf1/0/Digital/nano-components/packages/components/src/interface.d.ts"
621
779
  }
622
780
  }
623
781
  }
@@ -669,7 +827,7 @@ export class FieldValidator {
669
827
  },
670
828
  "ValidatorValueStore": {
671
829
  "location": "import",
672
- "path": "/builds/VuwsCczH/0/Digital/nano-components/packages/components/src/interface.d.ts"
830
+ "path": "/builds/f85YXDf1/0/Digital/nano-components/packages/components/src/interface.d.ts"
673
831
  }
674
832
  },
675
833
  "return": "Promise<void>"
@@ -681,6 +839,50 @@ export class FieldValidator {
681
839
  "text": "state - the state to load in the store"
682
840
  }]
683
841
  }
842
+ },
843
+ "setCustomValidity": {
844
+ "complexType": {
845
+ "signature": "(validity: { [key: string]: string; }) => Promise<void[]>",
846
+ "parameters": [{
847
+ "tags": [{
848
+ "name": "param",
849
+ "text": "validity - a validity object of `{fieldName: errorString}` pairs. Set as an empty string to clear the error."
850
+ }],
851
+ "text": "- a validity object of `{fieldName: errorString}` pairs. Set as an empty string to clear the error."
852
+ }],
853
+ "references": {
854
+ "Promise": {
855
+ "location": "global"
856
+ }
857
+ },
858
+ "return": "Promise<void[]>"
859
+ },
860
+ "docs": {
861
+ "text": "Sets custom validity for all / some form fields.",
862
+ "tags": [{
863
+ "name": "param",
864
+ "text": "validity - a validity object of `{fieldName: errorString}` pairs. Set as an empty string to clear the error."
865
+ }]
866
+ }
867
+ },
868
+ "resetValidity": {
869
+ "complexType": {
870
+ "signature": "() => Promise<void[]>",
871
+ "parameters": [],
872
+ "references": {
873
+ "Promise": {
874
+ "location": "global"
875
+ }
876
+ },
877
+ "return": "Promise<void[]>"
878
+ },
879
+ "docs": {
880
+ "text": "Clear all custom validation.",
881
+ "tags": [{
882
+ "name": "param",
883
+ "text": "validity"
884
+ }]
885
+ }
684
886
  }
685
887
  }; }
686
888
  static get elementRef() { return "host"; }
@@ -690,6 +892,9 @@ export class FieldValidator {
690
892
  }, {
691
893
  "propName": "validateOn",
692
894
  "methodName": "validateOnChange"
895
+ }, {
896
+ "propName": "extraFieldSelector",
897
+ "methodName": "attachSlotObserver"
693
898
  }]; }
694
899
  }
695
900
  //# sourceMappingURL=field-validator.js.map