@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
@@ -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) => field.name === 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) => {
385
+ this.allFields.forEach(async (field) => {
333
386
  const found = validationState.find((v) => v.name === field.name);
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
427
  name: field.name,
347
- valid: !field.invalid,
348
428
  value: this._store.state[field.name],
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) => f.name === 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)
@@ -378,62 +477,66 @@ let FieldValidator = class {
378
477
  }
379
478
  /** Checks for new `nano-...` fields and adds them to our watch array and value store */
380
479
  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);
480
+ let nanoFields = Array.from(this.host.querySelectorAll(this.nanoFieldSelector));
481
+ let plainFields = Array.from(this.host.querySelectorAll(this.extraFieldSelector)).filter((e) => !e.closest(this.nanoFieldSelector));
482
+ nanoFields = nanoFields.filter((f) => !!f.name && !!f.name.length);
483
+ plainFields = plainFields.filter((f) => !!f.name && !!f.name.length);
389
484
  // do we have any currently un-watched fields?
390
- if (!fields.filter((f) => !this.fields.includes(f)).length)
485
+ if (![...nanoFields, ...plainFields].filter((f) => !this.allFields.includes(f)).length)
391
486
  return;
392
487
  // setup the initial store state / refresh on new fields
393
- this.fields = fields;
394
- this.storeToFields(this.fields);
488
+ this.nanoFields = nanoFields;
489
+ this.plainFields = plainFields;
490
+ this.allFields = [...nanoFields, ...plainFields];
491
+ this.storeToFields(this.allFields);
395
492
  this.validateOnChange();
396
- this.fieldsToStore(this.fields);
493
+ this.fieldsToStore(this.allFields);
397
494
  this.nanoPayloadChange.emit(this._store.state);
398
495
  }
399
496
  storeToFields(fields) {
400
497
  fields.forEach((field) => {
498
+ var _a;
401
499
  const fieldName = field.name;
402
500
  if (!fieldName.length ||
403
501
  typeof this._store.state[fieldName] === 'undefined')
404
502
  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;
503
+ if (field.tagName === 'NANO-CHECKBOX' ||
504
+ ['radio', 'checkbox'].includes(field.type)) {
505
+ let cb = field;
506
+ if (cb.type === 'radio' ||
507
+ cb.type === 'segment' ||
508
+ cb.type === 'segment-pill') {
509
+ // single radio type control
510
+ if (this._store.state[fieldName] === cb.value)
511
+ cb.checked = true;
512
+ else
513
+ cb.checked = false;
514
+ }
515
+ else if (Array.isArray(this._store.state[fieldName])) {
516
+ // multiple checkbox like controls
517
+ if (this._store.state[fieldName].includes(cb.value))
518
+ cb.checked = true;
519
+ else
520
+ cb.checked = false;
521
+ }
522
+ else {
523
+ // single checkbox like control
524
+ if (this._store.state[fieldName] === cb.value)
525
+ cb.checked = true;
526
+ else
527
+ cb.checked = false;
528
+ }
529
+ return;
530
+ }
531
+ if (field.tagName === 'NANO-FILE-UPLOAD') {
532
+ const ff = field;
533
+ // this can only work if the field is empty rn... a one-time deal
534
+ if (!((_a = ff.files) === null || _a === void 0 ? void 0 : _a.length))
535
+ ff.files = this._store.state[fieldName];
536
+ return;
436
537
  }
538
+ // default
539
+ field.value = this._store.state[fieldName];
437
540
  });
438
541
  }
439
542
  /** Loops through all `nano-...` fields and extracts their values into our store */
@@ -442,52 +545,70 @@ let FieldValidator = class {
442
545
  const fieldName = field.name;
443
546
  if (!fieldName.length)
444
547
  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);
548
+ if (field.tagName === 'NANO-CHECKBOX' ||
549
+ ['radio', 'checkbox'].includes(field.type)) {
550
+ let cb = field;
551
+ if (cb.type === 'radio' ||
552
+ cb.type === 'segment' ||
553
+ cb.type === 'segment-pill') {
554
+ // radio type control - only one can be checked
555
+ if (cb.checked)
556
+ this._store.state[fieldName] = cb.value;
557
+ }
558
+ else if (this.allFields.filter((f) => f.name === fieldName &&
559
+ (f.tagName === 'NANO-CHECKBOX' ||
560
+ f.type === 'checkbox')).length > 1) {
561
+ // multiple checkbox type control
562
+ const currentArr = Array.isArray(this._store.state[fieldName])
563
+ ? this._store.state[fieldName]
564
+ : [];
565
+ if (cb.checked) {
566
+ // checked
567
+ if (!this._store.state[fieldName].includes(cb.value)) {
568
+ this._store.state[fieldName] = [...currentArr, cb.value];
471
569
  }
472
570
  }
473
571
  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] = '';
572
+ // unchecked
573
+ this._store.state[fieldName] = currentArr.filter((v) => v !== cb.value);
479
574
  }
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;
575
+ }
576
+ else {
577
+ // single checkbox - on or off
578
+ if (cb.checked)
579
+ this._store.state[fieldName] = cb.value;
580
+ else
581
+ this._store.state[fieldName] = '';
582
+ }
583
+ return;
584
+ }
585
+ if (field.tagName === 'NANO-FILE-UPLOAD') {
586
+ const ff = field;
587
+ if (!this.fileStateEqual(fieldName, ff))
588
+ this._store.state[fieldName] = ff.files;
589
+ return;
487
590
  }
591
+ // default
592
+ this._store.state[fieldName] = field.value;
488
593
  });
489
594
  }
490
- /** Checks for user defined validations */
595
+ /**
596
+ * Tries to ascertain whether the current model
597
+ * value is the same as the `nano-file-upload` value
598
+ * @param fieldName - the key to access from the data store
599
+ * @param field - the nano-file-upload field to assess against
600
+ * @returns true for equal, false for not equal
601
+ */
602
+ fileStateEqual(fieldName, field) {
603
+ return (JSON.stringify(this._store.state[fieldName]) ===
604
+ JSON.stringify(field.files) ||
605
+ this._store.state[fieldName] == field.files);
606
+ }
607
+ /**
608
+ * Checks for user defined validations
609
+ * @param key - current key of the data model to validate
610
+ * @param newVal - the newly set, incoming value to validate
611
+ */
491
612
  async validate(key, newVal) {
492
613
  if (!this.validation)
493
614
  return;
@@ -500,21 +621,33 @@ let FieldValidator = class {
500
621
  // collection loop into a promise
501
622
  await Promise.all(Object.entries(res).map(async ([key, o]) => {
502
623
  // switch on/off validation messages
503
- const field = this.fields.find((f) => f.name === key);
624
+ const field = this.allFields.find((f) => f.name === key);
504
625
  let validityTarget = field;
505
626
  if (field.tagName === 'NANO-CHECKBOX') {
627
+ // if we have a checkbox-group, set the validation message there
506
628
  const cbg = field.closest('nano-checkbox-group');
507
629
  validityTarget = cbg || field;
508
630
  }
509
- // status is now valid - clear the error
510
- if (validityTarget.validityMessage === o.msg && o.valid)
631
+ if ((validityTarget.validityMessage ||
632
+ validityTarget.validationMessage) === o.msg &&
633
+ o.valid) {
634
+ // status is now valid - clear the error
511
635
  await this.setFieldError(validityTarget, '');
512
- // status is invalid. Set the error
636
+ }
513
637
  else if (!o.valid) {
638
+ // status is invalid. Set the error
514
639
  await this.setFieldError(validityTarget, o.msg);
515
640
  }
516
641
  }));
517
642
  }
643
+ /** Loops through all store entries and checks custom validation */
644
+ async validateAllFields() {
645
+ // This forces our loop to `await` and finish sequentially ... silly async stencil methods
646
+ await Object.entries(this._store.state).reduce(async (memo, [key, value]) => {
647
+ await memo;
648
+ await this.validate(key, value);
649
+ }, undefined);
650
+ }
518
651
  /**
519
652
  * Utility to smooth out setting error messages
520
653
  * (it's a different method on `nano-checkbox` 'cos they don't show errors themselves)
@@ -524,16 +657,10 @@ let FieldValidator = class {
524
657
  async setFieldError(field, msg) {
525
658
  if (field['showError'])
526
659
  await field.showError(msg);
527
- else
660
+ else if (field['setError'])
528
661
  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);
662
+ else
663
+ field.setCustomValidity(msg);
537
664
  }
538
665
  scrollToFirstInvalid() {
539
666
  if (!this.scrollToInvalid)
@@ -562,17 +689,21 @@ let FieldValidator = class {
562
689
  requestAnimationFrame(() => {
563
690
  this.setupFields();
564
691
  this.attachSlotObserver();
565
- this._store.on('set', (key, value) => this.handleStoreChange(key, value));
566
692
  this.host.addEventListener('nanoChange', this.handleFieldChange);
693
+ this.host.addEventListener('input', this.handlePlainFieldChange);
694
+ this.host.addEventListener('change', this.handlePlainFieldChange);
567
695
  this.host.addEventListener('submit', this.handleSubmit);
696
+ this._store.on('set', this.handleStoreChange);
568
697
  });
569
698
  }
570
699
  disconnectedCallback() {
571
700
  if (this.mo)
572
701
  this.mo.disconnect();
573
- this._store.reset();
574
702
  this.host.removeEventListener('nanoChange', this.handleFieldChange);
703
+ this.host.removeEventListener('input', this.handlePlainFieldChange);
704
+ this.host.removeEventListener('change', this.handlePlainFieldChange);
575
705
  this.host.removeEventListener('submit', this.handleSubmit);
706
+ this._store.reset();
576
707
  if (this.activeForm)
577
708
  this.activeForm.removeEventListener('invalid', this.handleFormInvalid, true);
578
709
  }
@@ -582,7 +713,8 @@ let FieldValidator = class {
582
713
  get host() { return getElement(this); }
583
714
  static get watchers() { return {
584
715
  "userForm": ["userFormChange"],
585
- "validateOn": ["validateOnChange"]
716
+ "validateOn": ["validateOnChange"],
717
+ "extraFieldSelector": ["attachSlotObserver"]
586
718
  }; }
587
719
  };
588
720