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