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