@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
@@ -195,8 +195,15 @@ let FieldValidator = class extends HTMLElement {
195
195
  this.nanoSubmit = createEvent(this, "nanoSubmit", 7);
196
196
  this.nanoInvalid = createEvent(this, "nanoInvalid", 7);
197
197
  this.submitted = false;
198
- this.fields = [];
199
- // annoyingly, whenever we attempt to checkValidty it fires `invalid` events.
198
+ this.allFields = [];
199
+ this.nanoFieldSelector = `
200
+ nano-input,
201
+ nano-select,
202
+ nano-file-upload,
203
+ nano-date-input,
204
+ nano-checkbox
205
+ `;
206
+ // annoyingly, whenever we attempt to `checkValidty()` it fires `invalid` events.
200
207
  // this is used to prevent infinite loops / multiple calls
201
208
  this.internalValidate = false;
202
209
  // Public API
@@ -205,12 +212,27 @@ let FieldValidator = class extends HTMLElement {
205
212
  /** Tries to scroll to the first invalid field on submit */
206
213
  this.scrollToInvalid = true;
207
214
  this._dirty = false;
215
+ /** By default, `nano-field-validator` will also track all native form field elements.
216
+ * You can add extra web-component form fields to listen to
217
+ * (as long as they match the standard form field spec) by using the `fieldSelector` prop.
218
+ */
219
+ this.extraFieldSelector = 'input, select, textarea';
208
220
  // Event handlers
209
- /** Fired whenever store values change and potentially checks validity */
221
+ /**
222
+ * Fired whenever store values change and potentially checks validity
223
+ * @param key - the key of the store that's just changed
224
+ * @param newVal - the incoming, new value
225
+ */
210
226
  this.handleStoreChange = async (key, newVal) => {
211
- const found = this.fields.find((field) => field.name === key);
212
- if (found && found.value !== newVal)
227
+ const found = this.allFields.find((field) => this.getName(field) === key);
228
+ // field update has come programmatically (not from ui),
229
+ // so let's update the underlying ui field
230
+ if ((found &&
231
+ found.tagName === 'NANO-FILE-UPLOAD' &&
232
+ !this.fileStateEqual(key, found)) ||
233
+ (found.tagName !== 'NANO-FILE-UPLOAD' && found.value !== newVal)) {
213
234
  this.storeToFields([found]);
235
+ }
214
236
  if (this.validateOn === 'dirty' && this.dirty) {
215
237
  this.internalValidate = true;
216
238
  await this.validateAllFields();
@@ -219,17 +241,40 @@ let FieldValidator = class extends HTMLElement {
219
241
  }
220
242
  this.nanoPayloadChange.emit(this._store.state);
221
243
  };
222
- /** Handles field value changes and passes to store */
244
+ /**
245
+ * Handles nano field value changes and passes to store
246
+ * @param ev - the incoming change event
247
+ */
223
248
  this.handleFieldChange = (ev) => {
249
+ if (!this.nanoFields.includes(ev.target))
250
+ return;
224
251
  this._dirty = true;
225
252
  this.fieldsToStore([ev.target]);
226
253
  };
227
- /** Handles default field validation events */
254
+ /**
255
+ * Handles non-nano field value changes and passes to store
256
+ * @param ev - the incoming change event
257
+ */
258
+ this.handlePlainFieldChange = (ev) => {
259
+ if (!this.plainFields.includes(ev.target))
260
+ return;
261
+ this.fieldsToStore([ev.target]);
262
+ };
263
+ /**
264
+ * Handles default field validation events
265
+ * @param ev - the invalid event
266
+ */
228
267
  this.handleFormInvalid = async (ev) => {
229
- ev.preventDefault();
268
+ // if it's a non-nano field, we'll let default html5 validation do it's thing
269
+ if (!this.plainFields.includes(ev.target)) {
270
+ ev.preventDefault();
271
+ }
230
272
  this._valid = false;
273
+ // whenever `checkValidity` is called, this handler is in-turn called.
274
+ // this flag is used to stop infinite loops
231
275
  if (this.internalValidate)
232
276
  return;
277
+ // a submit must have happened to if 'submitThenDirty' turn on 'dirty' checking now
233
278
  if (this.validateOn === 'submitThenDirty')
234
279
  this.validateOn = 'dirty';
235
280
  this.submitted = true;
@@ -249,7 +294,10 @@ let FieldValidator = class extends HTMLElement {
249
294
  this.scrollToFirstInvalid();
250
295
  this.nanoInvalid.emit();
251
296
  };
252
- /** stops default form submission, checks if valid, then submits manually */
297
+ /**
298
+ * stops default form submission, checks if valid, then submits manually
299
+ * @param e - a submit event from the nested form element
300
+ */
253
301
  this.handleSubmit = async (e) => {
254
302
  e.preventDefault();
255
303
  if (this.validateOn === 'submitThenDirty')
@@ -274,6 +322,7 @@ let FieldValidator = class extends HTMLElement {
274
322
  return this._activeForm;
275
323
  }
276
324
  set activeForm(form) {
325
+ // manages event listners on whatever form is used (slotted on created here)
277
326
  if (!form)
278
327
  return;
279
328
  if (this._activeForm) {
@@ -284,7 +333,7 @@ let FieldValidator = class extends HTMLElement {
284
333
  }
285
334
  /** Sync up validateOn with all fields */
286
335
  validateOnChange() {
287
- this.fields.forEach((field) => {
336
+ this.nanoFields.forEach((field) => {
288
337
  if (field.tagName === 'NANO-CHECKBOX') {
289
338
  const cbg = field.closest('nano-checkbox-group');
290
339
  if (cbg)
@@ -312,7 +361,7 @@ let FieldValidator = class extends HTMLElement {
312
361
  get payload() {
313
362
  return this._store.state;
314
363
  }
315
- /** Returns true if validation errors will be displayed to the user */
364
+ /** Returns true if validation errors will be displayed to the user. @readonly */
316
365
  get showValidation() {
317
366
  return (this.validateOn === 'dirty' && this.dirty) || this.submitted;
318
367
  }
@@ -329,26 +378,58 @@ let FieldValidator = class extends HTMLElement {
329
378
  ```
330
379
  */
331
380
  get validationState() {
381
+ // TODO - migrate nano-fields away from using proprietary methods in a bid to be closer to the spec
382
+ // this is big and ugly.
383
+ // why? Cos' it must unify checking validity state for both
384
+ // `nano-...` and plain form fields.
332
385
  const validationState = [];
333
- this.fields.forEach(async (field) => {
334
- const found = validationState.find((v) => v.name === field.name);
386
+ this.allFields.forEach(async (field) => {
387
+ const found = validationState.find((v) => v.name === this.getName(field));
388
+ let pf;
389
+ let nf;
335
390
  if (found) {
336
- found.validityMessage = field.validityMessage.length
337
- ? field.validityMessage
338
- : found.validityMessage;
391
+ if (field.validationMessage) {
392
+ pf = field;
393
+ found.validityMessage = pf.validationMessage.length
394
+ ? pf.validationMessage
395
+ : found.validityMessage;
396
+ this.internalValidate = true;
397
+ if (found.valid && !pf.checkValidity())
398
+ found.valid = false;
399
+ this.internalValidate = false;
400
+ }
401
+ else if (field.validityMessage) {
402
+ nf = field;
403
+ found.validityMessage = nf.validityMessage.length
404
+ ? nf.validityMessage
405
+ : nf.validityMessage;
406
+ if (found.valid && nf.invalid)
407
+ found.valid = false;
408
+ }
339
409
  if (!found.fields.find((f) => f === field))
340
410
  found.fields.push(field);
341
- if (found.valid && field.invalid)
342
- found.valid = false;
343
- return;
411
+ }
412
+ let valid;
413
+ let validityMessage;
414
+ if (field.checkValidity) {
415
+ pf = field;
416
+ this.internalValidate = true;
417
+ valid = pf.checkValidity();
418
+ this.internalValidate = false;
419
+ validityMessage = pf.validationMessage;
420
+ }
421
+ else {
422
+ nf = field;
423
+ valid = !nf.invalid;
424
+ validityMessage = nf.validityMessage;
344
425
  }
345
426
  validationState.push({
346
427
  fields: [field],
347
- name: field.name,
348
- valid: !field.invalid,
349
- value: this._store.state[field.name],
428
+ name: this.getName(field),
429
+ value: this._store.state[this.getName(field)],
350
430
  dirty: false,
351
- validityMessage: field.validityMessage,
431
+ valid,
432
+ validityMessage,
352
433
  });
353
434
  });
354
435
  return validationState;
@@ -360,6 +441,24 @@ let FieldValidator = class extends HTMLElement {
360
441
  async setStore(state) {
361
442
  Object.entries(state).forEach(([key, val]) => (this.store.state[key] = val));
362
443
  }
444
+ /**
445
+ * Sets custom validity for all / some form fields.
446
+ * @param validity - a validity object of `{fieldName: errorString}` pairs. Set as an empty string to clear the error.
447
+ */
448
+ async setCustomValidity(validity) {
449
+ return await Promise.all(Object.entries(validity).map(async ([key, err]) => {
450
+ const field = this.allFields.find((f) => this.getName(f) === key);
451
+ if (!!field)
452
+ await this.setFieldError(field, err);
453
+ }));
454
+ }
455
+ /**
456
+ * Clear all custom validation.
457
+ * @param validity
458
+ */
459
+ async resetValidity() {
460
+ return await Promise.all(this.allFields.map(async (field) => await this.setFieldError(field, '')));
461
+ }
363
462
  // private methods
364
463
  attachSlotObserver() {
365
464
  if (!!this.mo)
@@ -377,118 +476,149 @@ let FieldValidator = class extends HTMLElement {
377
476
  subtree: true,
378
477
  });
379
478
  }
479
+ /**
480
+ * During spec tests, mockelement props aren't set - only attributes.
481
+ * This irons out that kink
482
+ * @param field
483
+ * @returns
484
+ */
485
+ getName(field) {
486
+ return field.name || field.getAttribute('name');
487
+ }
380
488
  /** Checks for new `nano-...` fields and adds them to our watch array and value store */
381
489
  setupFields() {
382
- let fields = Array.from(this.host.querySelectorAll(`
383
- nano-input,
384
- nano-select,
385
- nano-file-upload,
386
- nano-date-input,
387
- nano-checkbox
388
- `));
389
- fields = fields.filter((f) => !!f.name && !!f.name.length);
490
+ let nanoFields = Array.from(this.host.querySelectorAll(this.nanoFieldSelector));
491
+ let plainFields = Array.from(this.host.querySelectorAll(this.extraFieldSelector)).filter((e) => !e.closest(this.nanoFieldSelector));
492
+ nanoFields = nanoFields.filter((f) => !!this.getName(f) && !!this.getName(f).length);
493
+ plainFields = plainFields.filter((f) => !!this.getName(f) && !!this.getName(f).length);
390
494
  // do we have any currently un-watched fields?
391
- if (!fields.filter((f) => !this.fields.includes(f)).length)
495
+ if (![...nanoFields, ...plainFields].filter((f) => !this.allFields.includes(f)).length)
392
496
  return;
393
497
  // setup the initial store state / refresh on new fields
394
- this.fields = fields;
395
- this.storeToFields(this.fields);
498
+ this.nanoFields = nanoFields;
499
+ this.plainFields = plainFields;
500
+ this.allFields = [...nanoFields, ...plainFields];
501
+ this.storeToFields(this.allFields);
396
502
  this.validateOnChange();
397
- this.fieldsToStore(this.fields);
503
+ this.fieldsToStore(this.allFields);
398
504
  this.nanoPayloadChange.emit(this._store.state);
399
505
  }
400
506
  storeToFields(fields) {
401
507
  fields.forEach((field) => {
402
- const fieldName = field.name;
508
+ var _a;
509
+ const fieldName = this.getName(field);
403
510
  if (!fieldName.length ||
404
511
  typeof this._store.state[fieldName] === 'undefined')
405
512
  return;
406
- switch (field.tagName) {
407
- case 'NANO-CHECKBOX':
408
- let cb = field;
409
- if (cb.type === 'radio' ||
410
- cb.type === 'segment' ||
411
- cb.type === 'segment-pill') {
412
- if (this._store.state[fieldName] === cb.value)
413
- cb.checked = true;
414
- else
415
- cb.checked = false;
416
- }
417
- else if (Array.isArray(this._store.state[fieldName])) {
418
- if (this._store.state[fieldName].includes(cb.value))
419
- cb.checked = true;
420
- else
421
- cb.checked = false;
422
- }
423
- else {
424
- if (this._store.state[fieldName] === cb.value)
425
- cb.checked = true;
426
- else
427
- cb.checked = false;
428
- }
429
- break;
430
- case 'NANO-FILE-UPLOAD':
431
- field.files =
432
- this._store.state[fieldName];
433
- break;
434
- default:
435
- field.value = this._store.state[fieldName];
436
- break;
513
+ if (field.tagName === 'NANO-CHECKBOX' ||
514
+ ['radio', 'checkbox'].includes(field.type)) {
515
+ let cb = field;
516
+ if (cb.type === 'radio' ||
517
+ cb.type === 'segment' ||
518
+ cb.type === 'segment-pill') {
519
+ // single radio type control
520
+ if (this._store.state[fieldName] === cb.value)
521
+ cb.checked = true;
522
+ else
523
+ cb.checked = false;
524
+ }
525
+ else if (Array.isArray(this._store.state[fieldName])) {
526
+ // multiple checkbox like controls
527
+ if (this._store.state[fieldName].includes(cb.value))
528
+ cb.checked = true;
529
+ else
530
+ cb.checked = false;
531
+ }
532
+ else {
533
+ // single checkbox like control
534
+ if (this._store.state[fieldName] === cb.value)
535
+ cb.checked = true;
536
+ else
537
+ cb.checked = false;
538
+ }
539
+ return;
540
+ }
541
+ if (field.tagName === 'NANO-FILE-UPLOAD') {
542
+ const ff = field;
543
+ // this can only work if the field is empty rn... a one-time deal
544
+ if (!((_a = ff.files) === null || _a === void 0 ? void 0 : _a.length))
545
+ ff.files = this._store.state[fieldName];
546
+ return;
437
547
  }
548
+ // default
549
+ field.value = this._store.state[fieldName];
438
550
  });
439
551
  }
440
552
  /** Loops through all `nano-...` fields and extracts their values into our store */
441
553
  fieldsToStore(fields) {
442
554
  fields.forEach((field) => {
443
- const fieldName = field.name;
555
+ const fieldName = this.getName(field);
444
556
  if (!fieldName.length)
445
557
  return;
446
- switch (field.tagName) {
447
- case 'NANO-CHECKBOX':
448
- let cb = field;
449
- if (cb.type === 'radio' ||
450
- cb.type === 'segment' ||
451
- cb.type === 'segment-pill') {
452
- // radio type control - only one can be checked
453
- if (cb.checked)
454
- this._store.state[fieldName] = cb.value;
455
- else if (!cb.checked &&
456
- (cb.value === this._store.state[fieldName] ||
457
- !this._store.state[fieldName]))
458
- this._store.state[fieldName] = '';
459
- }
460
- else if (this.fields.filter((f) => f.name === fieldName && f.tagName === 'NANO-CHECKBOX').length > 1) {
461
- // multiple checkbox type control
462
- const currentArr = Array.isArray(this._store.state[fieldName])
463
- ? this._store.state[fieldName]
464
- : [];
465
- if (cb.checked) {
466
- if (!this._store.state[fieldName].includes(cb.value)) {
467
- this._store.state[fieldName] = [...currentArr, cb.value];
468
- }
469
- }
470
- else {
471
- this._store.state[fieldName] = currentArr.filter((v) => v !== cb.value);
558
+ if (field.tagName === 'NANO-CHECKBOX' ||
559
+ ['radio', 'checkbox'].includes(field.type)) {
560
+ let cb = field;
561
+ if (cb.type === 'radio' ||
562
+ cb.type === 'segment' ||
563
+ cb.type === 'segment-pill') {
564
+ // radio type control - only one can be checked
565
+ if (cb.checked)
566
+ this._store.state[fieldName] = cb.value;
567
+ }
568
+ else if (this.allFields.filter((f) => !!this.getName(field) &&
569
+ (f.tagName === 'NANO-CHECKBOX' ||
570
+ f.type === 'checkbox')).length > 1) {
571
+ // multiple checkbox type control
572
+ const currentArr = Array.isArray(this._store.state[fieldName])
573
+ ? this._store.state[fieldName]
574
+ : [];
575
+ if (cb.checked) {
576
+ // checked
577
+ if (!this._store.state[fieldName].includes(cb.value)) {
578
+ this._store.state[fieldName] = [...currentArr, cb.value];
472
579
  }
473
580
  }
474
581
  else {
475
- // single checkbox - on or off
476
- if (cb.checked)
477
- this._store.state[fieldName] = cb.value;
478
- else
479
- this._store.state[fieldName] = '';
582
+ // unchecked
583
+ this._store.state[fieldName] = currentArr.filter((v) => v !== cb.value);
480
584
  }
481
- break;
482
- case 'NANO-FILE-UPLOAD':
483
- this._store.state[fieldName] = field.files;
484
- break;
485
- default:
486
- this._store.state[fieldName] = field.value;
487
- break;
585
+ }
586
+ else {
587
+ // single checkbox - on or off
588
+ if (cb.checked)
589
+ this._store.state[fieldName] = cb.value;
590
+ else
591
+ this._store.state[fieldName] = '';
592
+ }
593
+ return;
594
+ }
595
+ if (field.tagName === 'NANO-FILE-UPLOAD') {
596
+ const ff = field;
597
+ if (!this.fileStateEqual(fieldName, ff))
598
+ this._store.state[fieldName] = ff.files;
599
+ return;
488
600
  }
601
+ // default
602
+ this._store.state[fieldName] = field.value;
489
603
  });
490
604
  }
491
- /** Checks for user defined validations */
605
+ /**
606
+ * Tries to ascertain whether the current model
607
+ * value is the same as the `nano-file-upload` value
608
+ * @param fieldName - the key to access from the data store
609
+ * @param field - the nano-file-upload field to assess against
610
+ * @returns true for equal, false for not equal
611
+ */
612
+ fileStateEqual(fieldName, field) {
613
+ return (JSON.stringify(this._store.state[fieldName]) ===
614
+ JSON.stringify(field.files) ||
615
+ this._store.state[fieldName] == field.files);
616
+ }
617
+ /**
618
+ * Checks for user defined validations
619
+ * @param key - current key of the data model to validate
620
+ * @param newVal - the newly set, incoming value to validate
621
+ */
492
622
  async validate(key, newVal) {
493
623
  if (!this.validation)
494
624
  return;
@@ -501,21 +631,33 @@ let FieldValidator = class extends HTMLElement {
501
631
  // collection loop into a promise
502
632
  await Promise.all(Object.entries(res).map(async ([key, o]) => {
503
633
  // switch on/off validation messages
504
- const field = this.fields.find((f) => f.name === key);
634
+ const field = this.allFields.find((f) => this.getName(f) === key);
505
635
  let validityTarget = field;
506
636
  if (field.tagName === 'NANO-CHECKBOX') {
637
+ // if we have a checkbox-group, set the validation message there
507
638
  const cbg = field.closest('nano-checkbox-group');
508
639
  validityTarget = cbg || field;
509
640
  }
510
- // status is now valid - clear the error
511
- if (validityTarget.validityMessage === o.msg && o.valid)
641
+ if ((validityTarget.validityMessage ||
642
+ validityTarget.validationMessage) === o.msg &&
643
+ o.valid) {
644
+ // status is now valid - clear the error
512
645
  await this.setFieldError(validityTarget, '');
513
- // status is invalid. Set the error
646
+ }
514
647
  else if (!o.valid) {
648
+ // status is invalid. Set the error
515
649
  await this.setFieldError(validityTarget, o.msg);
516
650
  }
517
651
  }));
518
652
  }
653
+ /** Loops through all store entries and checks custom validation */
654
+ async validateAllFields() {
655
+ // This forces our loop to `await` and finish sequentially ... silly async stencil methods
656
+ await Object.entries(this._store.state).reduce(async (memo, [key, value]) => {
657
+ await memo;
658
+ await this.validate(key, value);
659
+ }, undefined);
660
+ }
519
661
  /**
520
662
  * Utility to smooth out setting error messages
521
663
  * (it's a different method on `nano-checkbox` 'cos they don't show errors themselves)
@@ -523,18 +665,13 @@ let FieldValidator = class extends HTMLElement {
523
665
  * @param msg
524
666
  */
525
667
  async setFieldError(field, msg) {
526
- if (field['showError'])
668
+ if (field['showError']) {
527
669
  await field.showError(msg);
528
- else
670
+ }
671
+ else if (field['setError'])
529
672
  await field.setError(msg);
530
- }
531
- /** Loops through all store entries and checks field validity */
532
- async validateAllFields() {
533
- // This forces our loop to `await` and finish sequentially ... silly async stencil methods
534
- await Object.entries(this._store.state).reduce(async (memo, [key, value]) => {
535
- await memo;
536
- await this.validate(key, value);
537
- }, undefined);
673
+ else
674
+ field.setCustomValidity(msg);
538
675
  }
539
676
  scrollToFirstInvalid() {
540
677
  if (!this.scrollToInvalid)
@@ -563,17 +700,21 @@ let FieldValidator = class extends HTMLElement {
563
700
  requestAnimationFrame(() => {
564
701
  this.setupFields();
565
702
  this.attachSlotObserver();
566
- this._store.on('set', (key, value) => this.handleStoreChange(key, value));
567
703
  this.host.addEventListener('nanoChange', this.handleFieldChange);
704
+ this.host.addEventListener('input', this.handlePlainFieldChange);
705
+ this.host.addEventListener('change', this.handlePlainFieldChange);
568
706
  this.host.addEventListener('submit', this.handleSubmit);
707
+ this._store.on('set', this.handleStoreChange);
569
708
  });
570
709
  }
571
710
  disconnectedCallback() {
572
711
  if (this.mo)
573
712
  this.mo.disconnect();
574
- this._store.reset();
575
713
  this.host.removeEventListener('nanoChange', this.handleFieldChange);
714
+ this.host.removeEventListener('input', this.handlePlainFieldChange);
715
+ this.host.removeEventListener('change', this.handlePlainFieldChange);
576
716
  this.host.removeEventListener('submit', this.handleSubmit);
717
+ this._store.reset();
577
718
  if (this.activeForm)
578
719
  this.activeForm.removeEventListener('invalid', this.handleFormInvalid, true);
579
720
  }
@@ -583,7 +724,8 @@ let FieldValidator = class extends HTMLElement {
583
724
  get host() { return this; }
584
725
  static get watchers() { return {
585
726
  "userForm": ["userFormChange"],
586
- "validateOn": ["validateOnChange"]
727
+ "validateOn": ["validateOnChange"],
728
+ "extraFieldSelector": ["attachSlotObserver"]
587
729
  }; }
588
730
  };
589
731
  FieldValidator = /*@__PURE__*/ proxyCustomElement(FieldValidator, [4, "nano-field-validator", {
@@ -595,13 +737,16 @@ FieldValidator = /*@__PURE__*/ proxyCustomElement(FieldValidator, [4, "nano-fiel
595
737
  "payload": [2064],
596
738
  "showValidation": [2052, "show-validation"],
597
739
  "validationState": [2064],
740
+ "extraFieldSelector": [1, "extra-field-selector"],
598
741
  "validation": [16],
599
- "userForm": [32],
600
742
  "submitted": [32],
743
+ "userForm": [32],
601
744
  "_dirty": [32],
602
745
  "_valid": [32],
603
746
  "_store": [32],
604
- "setStore": [64]
747
+ "setStore": [64],
748
+ "setCustomValidity": [64],
749
+ "resetValidity": [64]
605
750
  }]);
606
751
  function defineCustomElement$1() {
607
752
  if (typeof customElements === "undefined") {