@mgdis/mg-components 6.27.0 → 6.28.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 (65) hide show
  1. package/dist/cjs/loader.cjs.js +1 -1
  2. package/dist/cjs/mg-action-more_36.cjs.entry.js +365 -77
  3. package/dist/cjs/mg-components.cjs.js +1 -1
  4. package/dist/cjs/mg-input-combobox.cjs.entry.js +36 -2
  5. package/dist/cjs/mg-input-file.cjs.entry.js +25 -2
  6. package/dist/cjs/mg-input-rich-text-editor.cjs.entry.js +717 -504
  7. package/dist/collection/assets/jodit/jodit.css +1 -1
  8. package/dist/collection/components/atoms/mg-button/mg-button.js +86 -13
  9. package/dist/collection/components/molecules/inputs/mg-input-checkbox/mg-input-checkbox.js +24 -1
  10. package/dist/collection/components/molecules/inputs/mg-input-combobox/mg-input-combobox.js +36 -2
  11. package/dist/collection/components/molecules/inputs/mg-input-date/mg-input-date.js +25 -9
  12. package/dist/collection/components/molecules/inputs/mg-input-file/mg-input-file.js +25 -2
  13. package/dist/collection/components/molecules/inputs/mg-input-numeric/mg-input-numeric.js +24 -6
  14. package/dist/collection/components/molecules/inputs/mg-input-password/mg-input-password.js +24 -5
  15. package/dist/collection/components/molecules/inputs/mg-input-radio/mg-input-radio.js +64 -16
  16. package/dist/collection/components/molecules/inputs/mg-input-select/mg-input-select.js +32 -10
  17. package/dist/collection/components/molecules/inputs/mg-input-text/mg-input-text.js +24 -5
  18. package/dist/collection/components/molecules/inputs/mg-input-textarea/mg-input-textarea.js +24 -6
  19. package/dist/collection/components/molecules/mg-form/mg-form.js +74 -8
  20. package/dist/components/mg-button2.js +1 -1
  21. package/dist/components/mg-form.js +1 -1
  22. package/dist/components/mg-input-checkbox.js +1 -1
  23. package/dist/components/mg-input-combobox.js +1 -1
  24. package/dist/components/mg-input-date.js +1 -1
  25. package/dist/components/mg-input-file.js +1 -1
  26. package/dist/components/mg-input-numeric.js +1 -1
  27. package/dist/components/mg-input-password.js +1 -1
  28. package/dist/components/mg-input-radio.js +1 -1
  29. package/dist/components/mg-input-rich-text-editor.js +262 -262
  30. package/dist/components/mg-input-select2.js +1 -1
  31. package/dist/components/mg-input-text2.js +1 -1
  32. package/dist/components/mg-input-textarea.js +1 -1
  33. package/dist/components/mg-modal.js +1 -1
  34. package/dist/custom-elements.json +12 -2
  35. package/dist/esm/loader.js +1 -1
  36. package/dist/esm/mg-action-more_36.entry.js +366 -78
  37. package/dist/esm/mg-components.js +1 -1
  38. package/dist/esm/mg-input-combobox.entry.js +36 -2
  39. package/dist/esm/mg-input-file.entry.js +25 -2
  40. package/dist/esm/mg-input-rich-text-editor.entry.js +717 -504
  41. package/dist/mg-components/mg-components.esm.js +1 -1
  42. package/dist/mg-components/p-2198f145.entry.js +1 -0
  43. package/dist/mg-components/p-d4a4eb78.entry.js +1 -0
  44. package/dist/mg-components/{p-6f492676.entry.js → p-e8b5e8d8.entry.js} +261 -261
  45. package/dist/mg-components/p-fdcce8d5.entry.js +1 -0
  46. package/dist/types/components/atoms/mg-button/mg-button.d.ts +41 -2
  47. package/dist/types/components/molecules/inputs/mg-input/mg-input.conf.d.ts +1 -0
  48. package/dist/types/components/molecules/inputs/mg-input-checkbox/mg-input-checkbox.d.ts +9 -0
  49. package/dist/types/components/molecules/inputs/mg-input-combobox/mg-input-combobox.d.ts +10 -1
  50. package/dist/types/components/molecules/inputs/mg-input-date/mg-input-date.d.ts +9 -0
  51. package/dist/types/components/molecules/inputs/mg-input-file/mg-input-file.d.ts +9 -0
  52. package/dist/types/components/molecules/inputs/mg-input-numeric/mg-input-numeric.d.ts +9 -0
  53. package/dist/types/components/molecules/inputs/mg-input-password/mg-input-password.d.ts +9 -0
  54. package/dist/types/components/molecules/inputs/mg-input-radio/mg-input-radio.d.ts +29 -2
  55. package/dist/types/components/molecules/inputs/mg-input-select/mg-input-select.d.ts +10 -1
  56. package/dist/types/components/molecules/inputs/mg-input-text/mg-input-text.d.ts +9 -0
  57. package/dist/types/components/molecules/inputs/mg-input-textarea/mg-input-textarea.d.ts +9 -0
  58. package/dist/types/components/molecules/mg-form/mg-form.d.ts +18 -0
  59. package/dist/types/components.d.ts +10 -2
  60. package/ide/intellij/web-types.json +4 -4
  61. package/ide/vscode/html-custom-data.json +3 -3
  62. package/package.json +19 -19
  63. package/dist/mg-components/p-32a55ddb.entry.js +0 -1
  64. package/dist/mg-components/p-813d69c7.entry.js +0 -1
  65. package/dist/mg-components/p-a2445605.entry.js +0 -1
@@ -395,6 +395,88 @@ const MgButton = class {
395
395
  (_b = this.element.querySelector('mg-icon')) === null || _b === void 0 ? void 0 : _b.setAttribute('size', this.size);
396
396
  }
397
397
  };
398
+ /**
399
+ * Submit a native form the way a native submit button does
400
+ * @param form - the form to submit
401
+ */
402
+ this.dispatchSubmit = (form) => {
403
+ form.dispatchEvent(new SubmitEvent('submit', { bubbles: true, cancelable: true }));
404
+ };
405
+ /**
406
+ * Wrap a `mg-form` in the actions its public API offers, so nothing reaches into its shadow DOM
407
+ * @param mgForm - the owning mg-form
408
+ * @returns its submit and reset actions
409
+ */
410
+ this.mgFormActions = (mgForm) => ({
411
+ submit: () => mgForm.requestSubmit(),
412
+ reset: () => mgForm.reset(),
413
+ });
414
+ /**
415
+ * Wrap a native form in the same actions
416
+ * @param form - the owning form
417
+ * @returns its submit and reset actions
418
+ */
419
+ this.nativeFormActions = (form) => ({
420
+ submit: () => this.dispatchSubmit(form),
421
+ reset: () => form.reset(),
422
+ });
423
+ /**
424
+ * Resolve the form the `form` id points to. An id reference does not cross a shadow boundary, so
425
+ * it resolves in the tree the button belongs to — the document, or the shadow tree it sits in —
426
+ * the way a native form owner does. The ancestor wins when it is the one pointed at: two forms
427
+ * can share an identifier, and a tree-wide lookup would retain the first one.
428
+ * @returns the actions of the form holding that id, or null when none does
429
+ */
430
+ this.resolveOwnerById = () => {
431
+ const ancestorMgForm = this.element.closest('mg-form');
432
+ if (ancestorMgForm !== null && ancestorMgForm.identifier === this.form)
433
+ return this.mgFormActions(ancestorMgForm);
434
+ const root = this.element.getRootNode();
435
+ const tree = root instanceof ShadowRoot ? root : this.element.ownerDocument;
436
+ const mgForm = Array.from(tree.querySelectorAll('mg-form')).find((element) => element.identifier === this.form);
437
+ if (mgForm !== undefined)
438
+ return this.mgFormActions(mgForm);
439
+ // Resolved by id, as a native form owner is, rather than through a selector an id could break
440
+ const nativeForm = tree.getElementById(this.form);
441
+ return nativeForm instanceof HTMLFormElement ? this.nativeFormActions(nativeForm) : null;
442
+ };
443
+ /**
444
+ * Resolve the form owning this button: an explicit `form` id overrides the ancestor form, and an
445
+ * id matching nothing logs then falls back on it, so a button never loses an association it had.
446
+ * @returns the owning form actions, or null when the button has no form owner
447
+ */
448
+ this.resolveFormOwner = () => {
449
+ if (index$1.isValidString(this.form)) {
450
+ const ownerById = this.resolveOwnerById();
451
+ if (ownerById !== null)
452
+ return ownerById;
453
+ console.error(`<mg-button> prop "form" matches no form in the button tree. Passed value: ${index$1.toString(this.form)}.`);
454
+ }
455
+ const nativeAncestor = this.element.closest('form');
456
+ if (nativeAncestor !== null)
457
+ return this.nativeFormActions(nativeAncestor);
458
+ const mgAncestor = this.element.closest('mg-form');
459
+ return mgAncestor === null ? null : this.mgFormActions(mgAncestor);
460
+ };
461
+ /**
462
+ * Act on the owning form. The owner is resolved here rather than on load because a native form
463
+ * owner is re-resolved on every submission, never captured once: the button follows a `form`
464
+ * changed after render, and no longer races the target form hydration.
465
+ */
466
+ this.handleFormClick = () => {
467
+ const isSubmit = ['submit', undefined].includes(this.type);
468
+ // A reset only fires from an explicit association: it has never reset a merely ancestor form
469
+ const isReset = this.type === 'reset' && index$1.isValidString(this.form);
470
+ if (!isSubmit && !isReset)
471
+ return;
472
+ const owner = this.resolveFormOwner();
473
+ if (owner === null)
474
+ return;
475
+ if (isSubmit)
476
+ owner.submit();
477
+ else
478
+ owner.reset();
479
+ };
398
480
  }
399
481
  validateVariant(newValue, oldValue) {
400
482
  // validate new value
@@ -493,16 +575,7 @@ const MgButton = class {
493
575
  * Add listners
494
576
  */
495
577
  componentDidLoad() {
496
- index$1.nextTick(() => {
497
- var _a;
498
- const closestForm = this.element.closest('form') || ((_a = this.element.closest('mg-form')) === null || _a === void 0 ? void 0 : _a.shadowRoot.querySelector('form'));
499
- // submit buttons should trigger form submition;
500
- if (closestForm && ['submit', undefined].includes(this.type)) {
501
- this.element.addEventListener('click', () => {
502
- closestForm.dispatchEvent(new SubmitEvent('submit', { bubbles: true, cancelable: true }));
503
- });
504
- }
505
- });
578
+ this.element.addEventListener('click', this.handleFormClick);
506
579
  }
507
580
  /**
508
581
  * Render component
@@ -510,7 +583,7 @@ const MgButton = class {
510
583
  */
511
584
  render() {
512
585
  var _a;
513
- return (index.h(index.Host, { key: '9efa9dbbcdb2965cb8fd301c86988d0b0d97b474', role: "button", tabIndex: this.element.getAttribute('tabindex') || 0, type: this.type, form: this.form, "full-width": this.fullWidth, "aria-label": this.label, "aria-disabled": (_a = this.disabled) === null || _a === void 0 ? void 0 : _a.toString(), onClick: this.handleClick, onKeyup: this.handleKeyup, onKeydown: this.handleKeydown }, index.h("div", { key: '36918b5b208a9e0a9650107f4418cac35ade1ed2', class: this.classCollection.join() }, this.loading && index.h("mg-icon", { key: '8ad3e911d273f69441f2a405f8040cc685a0a486', icon: "loader", spin: true }), index.h("div", { key: 'a6b199c669ef6b15bfc345c98192290ce11e65cb', class: "mg-c-button__content" }, index.h("slot", { key: 'b13d317001dbf66c945cdf9e97c06338ce073c12' })))));
586
+ return (index.h(index.Host, { key: '815bedd3729c652a5faf06c3d451cbc67123ab63', role: "button", tabIndex: this.element.getAttribute('tabindex') || 0, type: this.type, form: this.form, "full-width": this.fullWidth, "aria-label": this.label, "aria-disabled": (_a = this.disabled) === null || _a === void 0 ? void 0 : _a.toString(), onClick: this.handleClick, onKeyup: this.handleKeyup, onKeydown: this.handleKeydown }, index.h("div", { key: '2a1c20c677294870958f01c4042e8f530bf0f89d', class: this.classCollection.join() }, this.loading && index.h("mg-icon", { key: 'db3960249deed458f86bcba16b66bbeb9aac69c1', icon: "loader", spin: true }), index.h("div", { key: '76a3af6c9a105b91b7f6055cc19bbb6f315be1a5', class: "mg-c-button__content" }, index.h("slot", { key: 'ca87921b6c58fbd055a83ea28f5ce58a618f16fe' })))));
514
587
  }
515
588
  get element() { return index.getElement(this); }
516
589
  static get watchers() { return {
@@ -998,6 +1071,38 @@ const MgForm = class {
998
1071
  this.formReset.emit(true);
999
1072
  }
1000
1073
  }
1074
+ /**
1075
+ * Request the form submission: emit `form-submit`, whatever the inputs validity.
1076
+ * This is what a `mg-button` associated with this form goes through, so that reaching the form
1077
+ * needs no access to its shadow DOM. Unlike the native `requestSubmit()`, it runs no constraint
1078
+ * validation: validity is the consumer's call here, as it is on submission from a nested button.
1079
+ * Read `invalid`, or call `displayError()` / `focusFirstInvalidInput()`.
1080
+ * Unlike `reset()`, a readonly form still submits: readonly fields do not block a native
1081
+ * submission either, and a nested button has always submitted such a form.
1082
+ */
1083
+ async requestSubmit() {
1084
+ this.formSubmit.emit();
1085
+ }
1086
+ /**
1087
+ * **Experimental.** The signature may still change in a minor release.
1088
+ *
1089
+ * Display errors, then focus and scroll to the first invalid input. Focusing is what
1090
+ * carries the information for assistive technologies, a scroll announces nothing.
1091
+ * Read the `invalid` property to know whether the form had an invalid input.
1092
+ */
1093
+ async focusFirstInvalidInput() {
1094
+ if (!this.readonly) {
1095
+ await this.displayError();
1096
+ // Inputs defer their displayError() to a requestAnimationFrame
1097
+ await new Promise((resolve) => requestAnimationFrame(resolve));
1098
+ // Read at call time: a MutationObserver rebuilds the list on content change
1099
+ const firstInvalidInput = this.mgInputs.find((input) => input.invalid);
1100
+ if (firstInvalidInput !== undefined) {
1101
+ await firstInvalidInput.setFocus();
1102
+ firstInvalidInput.scrollIntoView();
1103
+ }
1104
+ }
1105
+ }
1001
1106
  /*************
1002
1107
  * Lifecycle *
1003
1108
  *************/
@@ -1016,13 +1121,13 @@ const MgForm = class {
1016
1121
  this.hasActions = this.element.querySelector('[slot="actions"]') !== null;
1017
1122
  // Get slotted mgButtons
1018
1123
  this.mgButtons = Array.from(this.element.querySelectorAll('mg-button'));
1019
- // Set button form identifier
1124
+ // Announce the association: a slotted button then drives this form through its public API and
1125
+ // handles its own submit and reset, which is why none is wired here. An id it already carries
1126
+ // wins, read as property and attribute since frameworks pass the prop as one or the other.
1020
1127
  this.mgButtons.forEach((mgButton) => {
1021
- mgButton.setAttribute('form', this.identifier);
1022
- if (mgButton.type === 'reset') {
1023
- mgButton.addEventListener('click', () => {
1024
- this.reset();
1025
- });
1128
+ var _a;
1129
+ if (!index$1.isValidString((_a = mgButton.form) !== null && _a !== void 0 ? _a : mgButton.getAttribute('form'))) {
1130
+ mgButton.setAttribute('form', this.identifier);
1026
1131
  }
1027
1132
  });
1028
1133
  // Set mgInputs
@@ -1057,7 +1162,7 @@ const MgForm = class {
1057
1162
  * @returns HTML Element
1058
1163
  */
1059
1164
  render() {
1060
- return (index.h("form", { key: 'c851e6f19af32f28cad695daa1f42ddff10796a0', class: this.classCollection.join(), id: this.identifier, name: this.name, onSubmit: this.handleFormSubmit, role: this.ariaRole }, this.requiredMessageText && index.h("p", { key: '7f92b35872c938e87959acac8841937ad503d98f', innerHTML: this.requiredMessageText }), index.h("slot", { key: 'e75e90f1fe3076d93df82a7a426b17d82035e0cf' }), !this.readonly && !this.disabled && this.hasActions && (index.h("div", { key: 'a472cf024a61d6be1665d149749ce7d560a00473', class: "mg-c-form__actions" }, index.h("slot", { key: 'f9098c711cb2d2714057c4f14d6df295b75de774', name: "actions" })))));
1165
+ return (index.h("form", { key: '0c23474cba5ae38a70162129641128cdd17224cb', class: this.classCollection.join(), id: this.identifier, name: this.name, onSubmit: this.handleFormSubmit, role: this.ariaRole }, this.requiredMessageText && index.h("p", { key: '8938192d2b2f4d1278f6e5037ed7f0f839c44861', innerHTML: this.requiredMessageText }), index.h("slot", { key: 'aefbcb13adbe2632b958c55c2c2c0174d5aaf471' }), !this.readonly && !this.disabled && this.hasActions && (index.h("div", { key: '30675225f5a910945f5b31fb044d3501c323acf9', class: "mg-c-form__actions" }, index.h("slot", { key: '6f5b871264d0237fc1eccfac5c454e61135b81c4', name: "actions" })))));
1061
1166
  }
1062
1167
  get element() { return index.getElement(this); }
1063
1168
  static get watchers() { return {
@@ -1936,6 +2041,8 @@ const MgInputCheckbox = class {
1936
2041
  this.inputValid = index.createEvent(this, "input-valid", 7);
1937
2042
  // hasDisplayedError (triggered by blur event)
1938
2043
  this.hasDisplayedError = false;
2044
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
2045
+ this.validityCheckPending = false;
1939
2046
  this.customErrorMessage = { lock: false };
1940
2047
  this.mode = 'custom';
1941
2048
  // style
@@ -2218,6 +2325,7 @@ const MgInputCheckbox = class {
2218
2325
  this.renderMgInputCheckboxList = () => (index.h(MgInputCheckboxList, { checkboxes: this.checkboxItems, id: this.checkboxesId, disabled: this.disabled, name: this.name, invalid: this.invalid }));
2219
2326
  }
2220
2327
  validateValue(newValue) {
2328
+ this.validityCheckPending = true;
2221
2329
  if (isCheckboxItems(newValue)) {
2222
2330
  this.checkboxItems = newValue.map((item, index) => (Object.assign(Object.assign({}, item), { _id: `${this.identifier}_${index}`, _handleInput: this.handleInput.bind(this), _handleBlur: this.handleBlur.bind(this), _handleKeydown: this.handleKeydown.bind(this) })));
2223
2331
  this.valueChange.emit(newValue);
@@ -2350,6 +2458,8 @@ const MgInputCheckbox = class {
2350
2458
  lock: valid ? false : errorMessageLock,
2351
2459
  message: valid ? undefined : errorMessage,
2352
2460
  };
2461
+ // setError() states the validity explicitly, so drop any pending recomputation
2462
+ this.validityCheckPending = false;
2353
2463
  this.setValidity(valid);
2354
2464
  this.setErrorMessage(true);
2355
2465
  this.hasDisplayedError = this.invalid;
@@ -2432,8 +2542,26 @@ const MgInputCheckbox = class {
2432
2542
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
2433
2543
  return setTimeout(() => {
2434
2544
  this.checkValidity();
2545
+ // the initial check covers the initial value: nothing is pending yet
2546
+ this.validityCheckPending = false;
2435
2547
  }, 0);
2436
2548
  }
2549
+ /**
2550
+ * Recompute validity once the DOM is in sync with `value`.
2551
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
2552
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
2553
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
2554
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
2555
+ */
2556
+ componentDidUpdate() {
2557
+ if (this.validityCheckPending && this.checkboxItems.length > 0) {
2558
+ this.validityCheckPending = false;
2559
+ this.checkValidity();
2560
+ if (this.hasDisplayedError) {
2561
+ this.setErrorMessage();
2562
+ }
2563
+ }
2564
+ }
2437
2565
  /**
2438
2566
  * add listeners and DOM attributed
2439
2567
  */
@@ -2518,7 +2646,7 @@ const MgInputCheckbox = class {
2518
2646
  else {
2519
2647
  inputContent = this.renderCheckboxMulti();
2520
2648
  }
2521
- return (index.h("mg-input", { key: 'e243244fb902cf960d112502e4627a787212db31', label: this.label, identifier: this.identifier, class: this.classCollection.join(), labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: !this.readonly ? this.required : undefined, tooltip: this.tooltip, tooltipPosition: this.readonly && readonlyValue.length === 0 ? 'label' : this.tooltipPosition, helpText: !this.readonly ? this.helpText : undefined, errorMessage: !this.readonly ? this.errorMessage : undefined }, inputContent));
2649
+ return (index.h("mg-input", { key: 'e873b2c1a5e96f5f424f21e3531c6c63c1d9ff39', label: this.label, identifier: this.identifier, class: this.classCollection.join(), labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: !this.readonly ? this.required : undefined, tooltip: this.tooltip, tooltipPosition: this.readonly && readonlyValue.length === 0 ? 'label' : this.tooltipPosition, helpText: !this.readonly ? this.helpText : undefined, errorMessage: !this.readonly ? this.errorMessage : undefined }, inputContent));
2522
2650
  }
2523
2651
  get element() { return index.getElement(this); }
2524
2652
  static get watchers() { return {
@@ -2571,6 +2699,8 @@ const MgInputDate = class {
2571
2699
  this.inputValid = index.createEvent(this, "input-valid", 7);
2572
2700
  // hasDisplayedError (triggered by blur event)
2573
2701
  this.hasDisplayedError = false;
2702
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
2703
+ this.validityCheckPending = false;
2574
2704
  this.customErrorMessage = { lock: false };
2575
2705
  /**
2576
2706
  * Input name
@@ -2642,10 +2772,6 @@ const MgInputDate = class {
2642
2772
  * Handle input event
2643
2773
  */
2644
2774
  this.handleInput = () => {
2645
- this.checkValidity();
2646
- if (this.hasDisplayedError) {
2647
- this.setErrorMessage();
2648
- }
2649
2775
  this.value = this.input.value;
2650
2776
  };
2651
2777
  /**
@@ -2667,11 +2793,8 @@ const MgInputDate = class {
2667
2793
  // when we reset the field we need to test a blank value.
2668
2794
  if (['Delete', 'Backspace'].includes(event.key)) {
2669
2795
  event.preventDefault();
2796
+ // validity is recomputed in componentDidUpdate, once the input actually holds the blank value
2670
2797
  this.value = null;
2671
- this.checkValidity();
2672
- if (this.hasDisplayedError) {
2673
- this.setErrorMessage();
2674
- }
2675
2798
  }
2676
2799
  };
2677
2800
  /**
@@ -2758,6 +2881,7 @@ const MgInputDate = class {
2758
2881
  };
2759
2882
  }
2760
2883
  validateValue(newValue) {
2884
+ this.validityCheckPending = true;
2761
2885
  // When the input is not fully completed or has been cleared, the value becomes an empty string.
2762
2886
  if (['', undefined].includes(newValue)) {
2763
2887
  // need to force value update to `null` to prevent extra render
@@ -2850,6 +2974,8 @@ const MgInputDate = class {
2850
2974
  lock: valid ? false : errorMessageLock,
2851
2975
  message: valid ? undefined : errorMessage,
2852
2976
  };
2977
+ // setError() states the validity explicitly, so drop any pending recomputation
2978
+ this.validityCheckPending = false;
2853
2979
  this.setValidity(valid);
2854
2980
  this.setErrorMessage(true);
2855
2981
  this.hasDisplayedError = this.invalid;
@@ -2920,14 +3046,32 @@ const MgInputDate = class {
2920
3046
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
2921
3047
  return setTimeout(() => {
2922
3048
  this.checkValidity();
3049
+ // the initial check covers the initial value: nothing is pending yet
3050
+ this.validityCheckPending = false;
2923
3051
  }, 0);
2924
3052
  }
3053
+ /**
3054
+ * Recompute validity once the DOM is in sync with `value`.
3055
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
3056
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
3057
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
3058
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
3059
+ */
3060
+ componentDidUpdate() {
3061
+ if (this.validityCheckPending && this.input !== undefined) {
3062
+ this.validityCheckPending = false;
3063
+ this.checkValidity();
3064
+ if (this.hasDisplayedError) {
3065
+ this.setErrorMessage();
3066
+ }
3067
+ }
3068
+ }
2925
3069
  /**
2926
3070
  * Render
2927
3071
  * @returns HTML Element
2928
3072
  */
2929
3073
  render() {
2930
- return (index.h("mg-input", { key: 'c13dd54974bd7c8381bcefbe847fe79f2270506c', label: this.label, identifier: this.identifier, class: this.classCollection.join(), labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: this.required, tooltip: this.tooltip, tooltipPosition: this.readonly && this.value === null ? 'label' : this.tooltipPosition, helpText: this.formatHelpText(this.helpText), errorMessage: this.errorMessage }, this.readonly ? (this.value && index.h("b", { class: "mg-c-input__readonly-value" }, index$2.localeDate(this.value, this.locale))) : (index.h("input", { type: "date", class: "mg-c-input__box", min: this.min, max: this.max, value: this.value, id: this.identifier, name: this.name, disabled: this.disabled, required: this.required, "aria-invalid": (this.invalid === true).toString(), onInput: this.handleInput, onBlur: this.handleBlur, onKeyDown: this.handleKeyDown, ref: (el) => {
3074
+ return (index.h("mg-input", { key: '3d3f6910dde9c32bbf3d0c853e0c778380ed40fc', label: this.label, identifier: this.identifier, class: this.classCollection.join(), labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: this.required, tooltip: this.tooltip, tooltipPosition: this.readonly && this.value === null ? 'label' : this.tooltipPosition, helpText: this.formatHelpText(this.helpText), errorMessage: this.errorMessage }, this.readonly ? (this.value && index.h("b", { class: "mg-c-input__readonly-value" }, index$2.localeDate(this.value, this.locale))) : (index.h("input", { type: "date", class: "mg-c-input__box", min: this.min, max: this.max, value: this.value, id: this.identifier, name: this.name, disabled: this.disabled, required: this.required, "aria-invalid": (this.invalid === true).toString(), onInput: this.handleInput, onBlur: this.handleBlur, onKeyDown: this.handleKeyDown, ref: (el) => {
2931
3075
  if (el !== null)
2932
3076
  this.input = el;
2933
3077
  } }))));
@@ -2979,6 +3123,8 @@ const MgInputNumeric = class {
2979
3123
  this.inputValid = index.createEvent(this, "input-valid", 7);
2980
3124
  // hasDisplayedError (triggered by blur event)
2981
3125
  this.hasDisplayedError = false;
3126
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
3127
+ this.validityCheckPending = false;
2982
3128
  this.customErrorMessage = { lock: false };
2983
3129
  /**
2984
3130
  * Input name
@@ -3045,11 +3191,6 @@ const MgInputNumeric = class {
3045
3191
  */
3046
3192
  this.handleInput = () => {
3047
3193
  this.value = this.input.value;
3048
- // Check validity
3049
- this.checkValidity();
3050
- if (this.hasDisplayedError) {
3051
- this.setErrorMessage();
3052
- }
3053
3194
  };
3054
3195
  /**
3055
3196
  * Handle focus event
@@ -3199,6 +3340,7 @@ const MgInputNumeric = class {
3199
3340
  }
3200
3341
  validateValue(newValue) {
3201
3342
  var _a;
3343
+ this.validityCheckPending = true;
3202
3344
  // has input value is always render as string we get a stringified value or a '' for nullish value, so we test string type
3203
3345
  if (typeof newValue === 'string') {
3204
3346
  // Check if integer value starts with '0' (leading zero)
@@ -3319,6 +3461,8 @@ const MgInputNumeric = class {
3319
3461
  lock: valid ? false : errorMessageLock,
3320
3462
  message: valid ? undefined : errorMessage,
3321
3463
  };
3464
+ // setError() states the validity explicitly, so drop any pending recomputation
3465
+ this.validityCheckPending = false;
3322
3466
  this.setValidity(valid);
3323
3467
  this.setErrorMessage(true);
3324
3468
  this.hasDisplayedError = this.invalid;
@@ -3431,14 +3575,32 @@ const MgInputNumeric = class {
3431
3575
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
3432
3576
  return setTimeout(() => {
3433
3577
  this.checkValidity();
3578
+ // the initial check covers the initial value: nothing is pending yet
3579
+ this.validityCheckPending = false;
3434
3580
  }, 0);
3435
3581
  }
3582
+ /**
3583
+ * Recompute validity once the DOM is in sync with `value`.
3584
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
3585
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
3586
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
3587
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
3588
+ */
3589
+ componentDidUpdate() {
3590
+ if (this.validityCheckPending && this.input !== undefined) {
3591
+ this.validityCheckPending = false;
3592
+ this.checkValidity();
3593
+ if (this.hasDisplayedError) {
3594
+ this.setErrorMessage();
3595
+ }
3596
+ }
3597
+ }
3436
3598
  /**
3437
3599
  * Render
3438
3600
  * @returns HTML Element
3439
3601
  */
3440
3602
  render() {
3441
- return (index.h("mg-input", { key: 'dbf435211890922742cbead9ab3a738b5219df44', label: this.label, identifier: this.identifier, class: this.classCollection.join(), labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: this.required, tooltip: this.tooltip, tooltipPosition: this.readonly && this.value === undefined ? 'label' : this.tooltipPosition, helpText: this.formatHelpText(this.helpText), errorMessage: this.errorMessage }, this.readonly
3603
+ return (index.h("mg-input", { key: '8a9a2c269754aa1259e79dd5e68cc0ed6b578fd1', label: this.label, identifier: this.identifier, class: this.classCollection.join(), labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: this.required, tooltip: this.tooltip, tooltipPosition: this.readonly && this.value === undefined ? 'label' : this.tooltipPosition, helpText: this.formatHelpText(this.helpText), errorMessage: this.errorMessage }, this.readonly
3442
3604
  ? this.readonlyValue && this.renderReadonly()
3443
3605
  : [
3444
3606
  index.h("input", { key: "input", type: "text", class: "mg-c-input__box mg-c-input__box--width", value: this.displayValue(), id: this.identifier, name: this.name, placeholder: this.placeholder, title: this.placeholder, disabled: this.disabled, required: this.required, autocomplete: "off", "aria-invalid": (this.invalid === true).toString(), onInput: this.handleInput, onFocus: this.handleFocus, onBlur: this.handleBlur, ref: (el) => {
@@ -3501,6 +3663,8 @@ const MgInputPassword = class {
3501
3663
  // hasDisplayedError (triggered by blur event)
3502
3664
  this.hasDisplayedError = false;
3503
3665
  this.customErrorMessage = { lock: false };
3666
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
3667
+ this.validityCheckPending = false;
3504
3668
  /**
3505
3669
  * Input name
3506
3670
  * If not set the value equals the identifier
@@ -3546,10 +3710,6 @@ const MgInputPassword = class {
3546
3710
  * Handle input event
3547
3711
  */
3548
3712
  this.handleInput = () => {
3549
- this.checkValidity();
3550
- if (this.hasDisplayedError) {
3551
- this.setErrorMessage();
3552
- }
3553
3713
  this.value = this.input.value;
3554
3714
  };
3555
3715
  /**
@@ -3594,6 +3754,7 @@ const MgInputPassword = class {
3594
3754
  };
3595
3755
  }
3596
3756
  handleValue(newValue) {
3757
+ this.validityCheckPending = true;
3597
3758
  this.valueChange.emit(newValue);
3598
3759
  }
3599
3760
  watchReadonly(newValue) {
@@ -3674,6 +3835,8 @@ const MgInputPassword = class {
3674
3835
  lock: valid ? false : errorMessageLock,
3675
3836
  message: valid ? undefined : errorMessage,
3676
3837
  };
3838
+ // setError() states the validity explicitly, so drop any pending recomputation
3839
+ this.validityCheckPending = false;
3677
3840
  this.setValidity(valid);
3678
3841
  this.setErrorMessage(true);
3679
3842
  this.hasDisplayedError = this.invalid;
@@ -3736,15 +3899,33 @@ const MgInputPassword = class {
3736
3899
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
3737
3900
  return setTimeout(() => {
3738
3901
  this.checkValidity();
3902
+ // the initial check covers the initial value: nothing is pending yet
3903
+ this.validityCheckPending = false;
3739
3904
  }, 0);
3740
3905
  }
3906
+ /**
3907
+ * Recompute validity once the DOM is in sync with `value`.
3908
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
3909
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
3910
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
3911
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
3912
+ */
3913
+ componentDidUpdate() {
3914
+ if (this.validityCheckPending && this.input !== undefined) {
3915
+ this.validityCheckPending = false;
3916
+ this.checkValidity();
3917
+ if (this.hasDisplayedError) {
3918
+ this.setErrorMessage();
3919
+ }
3920
+ }
3921
+ }
3741
3922
  /**
3742
3923
  * Render
3743
3924
  * @returns HTML Element
3744
3925
  */
3745
3926
  render() {
3746
3927
  const displayPasswordIcon = this.displayPassword ? 'eye-slash' : 'eye';
3747
- return (index.h("mg-input", { key: '913d0653faad21f56cb930dfdc0947a0747f17ad', label: this.label, identifier: this.identifier, class: this.classCollection.join(), labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: this.required, tooltip: this.tooltip, tooltipPosition: this.readonly && this.value === undefined ? 'label' : this.tooltipPosition, helpText: this.helpText, errorMessage: this.errorMessage }, this.readonly ? (this.value !== undefined && index.h("b", { class: "mg-c-input__readonly-value" }, '•'.repeat(this.value.length))) : (index.h("span", { class: "mg-c-input__input-group" }, index.h("input", { type: this.displayPassword ? 'text' : 'password', class: "mg-c-input__box mg-c-input__box--width", value: this.value, id: this.identifier, name: this.name, placeholder: this.placeholder, title: this.placeholder, disabled: this.disabled, required: this.required, maxlength: this.maxlength, "aria-invalid": (this.invalid === true).toString(), onInput: this.handleInput, onBlur: this.handleBlur, ref: (el) => {
3928
+ return (index.h("mg-input", { key: '2d9498c380536dc32177d1ac16a513ee43944a72', label: this.label, identifier: this.identifier, class: this.classCollection.join(), labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: this.required, tooltip: this.tooltip, tooltipPosition: this.readonly && this.value === undefined ? 'label' : this.tooltipPosition, helpText: this.helpText, errorMessage: this.errorMessage }, this.readonly ? (this.value !== undefined && index.h("b", { class: "mg-c-input__readonly-value" }, '•'.repeat(this.value.length))) : (index.h("span", { class: "mg-c-input__input-group" }, index.h("input", { type: this.displayPassword ? 'text' : 'password', class: "mg-c-input__box mg-c-input__box--width", value: this.value, id: this.identifier, name: this.name, placeholder: this.placeholder, title: this.placeholder, disabled: this.disabled, required: this.required, maxlength: this.maxlength, "aria-invalid": (this.invalid === true).toString(), onInput: this.handleInput, onBlur: this.handleBlur, ref: (el) => {
3748
3929
  if (el !== null)
3749
3930
  this.input = el;
3750
3931
  } }), index.h("mg-button", { label: this.messages.input.password[this.displayPassword ? 'hide' : 'display'], disabled: this.disabled, variant: "flat", class: "mg-c-input__append-button", "is-icon": true, onClick: this.toggleDisplayPassword, "aria-controls": this.identifier }, index.h("mg-icon", { class: "mg-c-input__input-icon", icon: displayPasswordIcon }))))));
@@ -3792,12 +3973,12 @@ const MgInputRadio = class {
3792
3973
  /************
3793
3974
  * Internal *
3794
3975
  ************/
3795
- // HTML selector
3796
- this.inputs = [];
3797
3976
  // classes
3798
3977
  this.classVerticalList = 'mg-c-input--vertical-list';
3799
3978
  // hasDisplayedError (triggered by blur event)
3800
3979
  this.hasDisplayedError = false;
3980
+ // raised when `value` or `items` changed, so componentDidUpdate revalidates once the DOM is in sync
3981
+ this.validityCheckPending = false;
3801
3982
  this.customErrorMessage = { lock: false };
3802
3983
  /**
3803
3984
  * Input name
@@ -3841,7 +4022,6 @@ const MgInputRadio = class {
3841
4022
  * @param event - input event
3842
4023
  */
3843
4024
  this.handleInput = (event) => {
3844
- this.checkValidity();
3845
4025
  this.value = this.options[event.target.value].value;
3846
4026
  };
3847
4027
  /**
@@ -3854,11 +4034,23 @@ const MgInputRadio = class {
3854
4034
  // reset guard
3855
4035
  this.handlerInProgress = undefined;
3856
4036
  };
4037
+ /**
4038
+ * Check if the required constraint is fulfilled
4039
+ * The native group validity cannot answer it: a browser keeps the checked member a group had, even
4040
+ * once that input is removed from the DOM. Options that cannot be selected are ignored.
4041
+ * @returns required constraint is fulfilled
4042
+ */
4043
+ this.validateRequired = () => {
4044
+ const selectableOptions = this.options.filter((option) => !option.disabled);
4045
+ return (!this.required ||
4046
+ selectableOptions.length < 1 ||
4047
+ selectableOptions.some((option) => this.isSelectedValue(option.value)));
4048
+ };
3857
4049
  /**
3858
4050
  * Check if input is valid
3859
4051
  */
3860
4052
  this.checkValidity = () => {
3861
- this.setValidity(this.readonly || this.disabled || this.getInvalidElement() === undefined);
4053
+ this.setValidity(this.readonly || this.disabled || (this.getInvalidElement() === undefined && this.validateRequired()));
3862
4054
  };
3863
4055
  /**
3864
4056
  * Set input error message
@@ -3874,7 +4066,7 @@ const MgInputRadio = class {
3874
4066
  this.errorMessage = this.customErrorMessage.message;
3875
4067
  }
3876
4068
  // required
3877
- else if (invalidElement.validity.valueMissing) {
4069
+ else if (invalidElement === undefined || invalidElement.validity.valueMissing) {
3878
4070
  this.errorMessage = this.messages.errors.required;
3879
4071
  }
3880
4072
  }
@@ -3883,11 +4075,24 @@ const MgInputRadio = class {
3883
4075
  * get invalid element
3884
4076
  * @returns element
3885
4077
  */
3886
- this.getInvalidElement = () => this.inputs.find((input) => !input.disabled && !input.readOnly && !input.checkValidity());
4078
+ this.getInvalidElement = () => this.getInputs().find((input) => !input.disabled && !input.readOnly && !input.checkValidity());
4079
+ /**
4080
+ * Get the rendered radio inputs
4081
+ * Read from the DOM: references kept from a render would outlive the inputs they point to.
4082
+ * @returns rendered radio inputs
4083
+ */
4084
+ this.getInputs = () => Array.from(this.element.shadowRoot.querySelectorAll('input[type="radio"]'));
4085
+ /**
4086
+ * Check if a value is the selected one
4087
+ * @param optionValue - option value to compare to the component value
4088
+ * @returns option value is the selected one
4089
+ */
4090
+ this.isSelectedValue = (optionValue) => JSON.stringify(this.value) === JSON.stringify(optionValue);
3887
4091
  }
3888
4092
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3889
4093
  watchValue(newValue) {
3890
4094
  var _a;
4095
+ this.validityCheckPending = true;
3891
4096
  if (index$1.allItemsAreString(this.items) && typeof newValue === 'string') {
3892
4097
  this.readonlyValue = newValue;
3893
4098
  }
@@ -3899,7 +4104,7 @@ const MgInputRadio = class {
3899
4104
  }
3900
4105
  this.valueChange.emit(newValue);
3901
4106
  }
3902
- validateItems(newValue) {
4107
+ validateItems(newValue, oldValue) {
3903
4108
  // Validate if items have required min length
3904
4109
  if (typeof newValue === 'object' && newValue.length < 2) {
3905
4110
  throw new Error(`<mg-input-radio> prop "items" require at least 2 items. Passed value: ${index$1.toString(newValue)}.`);
@@ -3915,6 +4120,14 @@ const MgInputRadio = class {
3915
4120
  else {
3916
4121
  throw new Error(`<mg-input-radio> prop "items" is required and all items must be the same type, string or RadioOption. Passed value: ${index$1.toString(newValue)}.`);
3917
4122
  }
4123
+ // `oldValue` is undefined on init
4124
+ if (oldValue !== undefined) {
4125
+ // the options cannot display a value they no longer offer, drop it
4126
+ if (this.value !== undefined && !this.options.some((option) => this.isSelectedValue(option.value))) {
4127
+ this.value = undefined;
4128
+ }
4129
+ this.validityCheckPending = true;
4130
+ }
3918
4131
  }
3919
4132
  watchInputVerticalList(newValue) {
3920
4133
  if (newValue)
@@ -3929,7 +4142,7 @@ const MgInputRadio = class {
3929
4142
  this.classCollection.delete(mgInput_conf.classReadonly);
3930
4143
  }
3931
4144
  handleValidityChange(newValue, _oldValue, prop) {
3932
- this.inputs.forEach((input) => {
4145
+ this.getInputs().forEach((input) => {
3933
4146
  input[prop] = newValue;
3934
4147
  });
3935
4148
  this.checkValidity();
@@ -3948,9 +4161,8 @@ const MgInputRadio = class {
3948
4161
  * Set focus on input.
3949
4162
  */
3950
4163
  async setFocus() {
3951
- if (this.inputs.length > 0) {
3952
- this.inputs[0].focus();
3953
- }
4164
+ var _a;
4165
+ (_a = this.element.shadowRoot.querySelector('input')) === null || _a === void 0 ? void 0 : _a.focus();
3954
4166
  }
3955
4167
  /**
3956
4168
  * Display input error if it exists.
@@ -3992,6 +4204,8 @@ const MgInputRadio = class {
3992
4204
  lock: valid ? false : errorMessageLock,
3993
4205
  message: valid ? undefined : errorMessage,
3994
4206
  };
4207
+ // setError() states the validity explicitly, so drop any pending recomputation
4208
+ this.validityCheckPending = false;
3995
4209
  this.setValidity(valid);
3996
4210
  this.setErrorMessage(true);
3997
4211
  this.hasDisplayedError = this.invalid;
@@ -4057,20 +4271,35 @@ const MgInputRadio = class {
4057
4271
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
4058
4272
  return setTimeout(() => {
4059
4273
  this.checkValidity();
4274
+ // the initial check covers the initial value: nothing is pending yet
4275
+ this.validityCheckPending = false;
4060
4276
  }, 0);
4061
4277
  }
4278
+ /**
4279
+ * Recompute validity once the DOM is in sync with `value`.
4280
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
4281
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
4282
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
4283
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
4284
+ */
4285
+ componentDidUpdate() {
4286
+ if (this.validityCheckPending && this.getInputs().length > 0) {
4287
+ this.validityCheckPending = false;
4288
+ this.checkValidity();
4289
+ if (this.hasDisplayedError) {
4290
+ this.setErrorMessage();
4291
+ }
4292
+ }
4293
+ }
4062
4294
  /**
4063
4295
  * Render
4064
4296
  * @returns HTML Element
4065
4297
  */
4066
4298
  render() {
4067
- return (index.h("mg-input", { key: '763e021cdda0735dcbd62700f09cbc32c0de7377', label: this.label, identifier: this.identifier, class: this.classCollection.join(), labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: this.required, tooltip: this.tooltip, tooltipPosition: this.readonly && !this.value ? 'label' : this.tooltipPosition, helpText: this.helpText, errorMessage: this.errorMessage }, this.readonly ? (this.readonlyValue && index.h("b", { class: "mg-c-input__readonly-value" }, this.readonlyValue)) : (index.h("div", { class: "mg-c-input__input-group-container" }, this.options.map((input, index$1) => (index.h("div", { key: input.title, class: {
4299
+ return (index.h("mg-input", { key: '1fd4b600eb95f2ff5c753884c40b982e0552d576', label: this.label, identifier: this.identifier, class: this.classCollection.join(), labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: this.required, tooltip: this.tooltip, tooltipPosition: this.readonly && !this.value ? 'label' : this.tooltipPosition, helpText: this.helpText, errorMessage: this.errorMessage }, this.readonly ? (this.readonlyValue && index.h("b", { class: "mg-c-input__readonly-value" }, this.readonlyValue)) : (index.h("div", { class: "mg-c-input__input-group-container" }, this.options.map((input, index$1) => (index.h("div", { key: input.title, class: {
4068
4300
  'mg-c-input__input-group': true,
4069
4301
  'mg-c-input__input-group--disabled': this.disabled || input.disabled,
4070
- } }, index.h("input", { type: "radio", id: this.identifier + '_' + index$1, name: this.identifier, value: index$1, checked: JSON.stringify(this.value) === JSON.stringify(this.options[index$1].value), disabled: this.disabled || input.disabled, required: this.required, onBlur: this.handleBlur, onInput: this.handleInput, ref: (el) => {
4071
- if (el !== null)
4072
- this.inputs[index$1] = el;
4073
- } }), index.h("label", { htmlFor: this.identifier + '_' + index$1 }, input.title))))))));
4302
+ } }, index.h("input", { type: "radio", id: this.identifier + '_' + index$1, name: this.identifier, value: index$1, checked: this.isSelectedValue(this.options[index$1].value), disabled: this.disabled || input.disabled, required: this.required, onBlur: this.handleBlur, onInput: this.handleInput }), index.h("label", { htmlFor: this.identifier + '_' + index$1 }, input.title))))))));
4074
4303
  }
4075
4304
  get element() { return index.getElement(this); }
4076
4305
  static get watchers() { return {
@@ -4123,8 +4352,9 @@ const isOptGroup = (optgroup) => typeof optgroup === 'object' &&
4123
4352
  typeof optgroup.group === 'string' &&
4124
4353
  allItemsAreOptions(optgroup.options);
4125
4354
  /**
4126
- * Ensure options can be unambiguously differentiated by their title
4355
+ * Report, without enforcing, options that cannot be unambiguously differentiated by their title
4127
4356
  * Two options can share the same title only when they belong to different groups
4357
+ * An option is identified by its render index, so a duplicated title only harms readability: it is reported to the developer and the field still renders
4128
4358
  * @param items - select options to validate
4129
4359
  */
4130
4360
  const validateUniqueTitles = (items) => {
@@ -4136,7 +4366,7 @@ const validateUniqueTitles = (items) => {
4136
4366
  const titles = titlesByGroup.get(group);
4137
4367
  if (titles.has(title)) {
4138
4368
  const groupContext = group === undefined ? 'outside of a group' : `within the group "${group}"`;
4139
- throw new Error(`<mg-input-select> prop "items" must not contain options with the same "title" ${groupContext}. Duplicated title: "${title}".`);
4369
+ console.error(`<mg-input-select> prop "items" must not contain options with the same "title" ${groupContext}. Duplicated title: "${title}".`);
4140
4370
  }
4141
4371
  titles.add(title);
4142
4372
  });
@@ -4172,6 +4402,8 @@ const MgInputSelect = class {
4172
4402
  this.inputValid = index.createEvent(this, "input-valid", 7);
4173
4403
  // hasDisplayedError (triggered by blur event)
4174
4404
  this.hasDisplayedError = false;
4405
+ // raised when `value` or `items` changed, so componentDidUpdate revalidates once the DOM is in sync
4406
+ this.validityCheckPending = false;
4175
4407
  this.customErrorMessage = { lock: false };
4176
4408
  /**
4177
4409
  * Input name
@@ -4319,18 +4551,14 @@ const MgInputSelect = class {
4319
4551
  this.renderOption = (option, index$1) => (index.h("option", { key: index$1, value: `${index$1}`, selected: JSON.stringify(this.value) === JSON.stringify(option.value), disabled: option.disabled }, option.title));
4320
4552
  }
4321
4553
  validateValue(newValue) {
4322
- setTimeout(() => {
4323
- this.checkValidity();
4324
- if (this.hasDisplayedError) {
4325
- this.setErrorMessage();
4326
- }
4327
- }, 0);
4554
+ this.validityCheckPending = true;
4328
4555
  this.valueChange.emit(newValue);
4329
4556
  }
4330
- validateItems(newValue) {
4557
+ validateItems(newValue, oldValue) {
4331
4558
  // Empty options
4332
4559
  if (newValue.length === 0) {
4333
4560
  this.options = [];
4561
+ this.valueExist = false;
4334
4562
  }
4335
4563
  // String array
4336
4564
  else if (index$1.allItemsAreString(newValue)) {
@@ -4356,6 +4584,9 @@ const MgInputSelect = class {
4356
4584
  else {
4357
4585
  throw new Error(`<mg-input-select> prop "items" is required, can be an empty Array or all items must be the same type: string or Option. Passed value: ${index$1.toString(newValue)}.`);
4358
4586
  }
4587
+ // `oldValue` is undefined on init. The value is kept: an unmatched one renders the placeholder.
4588
+ if (oldValue !== undefined)
4589
+ this.validityCheckPending = true;
4359
4590
  }
4360
4591
  watchReadonly(newValue) {
4361
4592
  if (newValue)
@@ -4435,6 +4666,8 @@ const MgInputSelect = class {
4435
4666
  lock: valid ? false : errorMessageLock,
4436
4667
  message: valid ? undefined : errorMessage,
4437
4668
  };
4669
+ // setError() states the validity explicitly, so drop any pending recomputation
4670
+ this.validityCheckPending = false;
4438
4671
  this.setValidity(valid);
4439
4672
  this.setErrorMessage(true);
4440
4673
  this.hasDisplayedError = this.invalid;
@@ -4505,8 +4738,26 @@ const MgInputSelect = class {
4505
4738
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
4506
4739
  return setTimeout(() => {
4507
4740
  this.checkValidity();
4741
+ // the initial check covers the initial value: nothing is pending yet
4742
+ this.validityCheckPending = false;
4508
4743
  }, 0);
4509
4744
  }
4745
+ /**
4746
+ * Recompute validity once the DOM is in sync with `value`.
4747
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
4748
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
4749
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
4750
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
4751
+ */
4752
+ componentDidUpdate() {
4753
+ if (this.validityCheckPending && this.input !== undefined) {
4754
+ this.validityCheckPending = false;
4755
+ this.checkValidity();
4756
+ if (this.hasDisplayedError) {
4757
+ this.setErrorMessage();
4758
+ }
4759
+ }
4760
+ }
4510
4761
  /**
4511
4762
  * Render
4512
4763
  * @returns HTML Element
@@ -4515,7 +4766,7 @@ const MgInputSelect = class {
4515
4766
  const readonlyValue = this.getReadonlyValue();
4516
4767
  // Running index shared across groups, so each option gets a unique value matching its position in getOptions()
4517
4768
  let optionIndex = 0;
4518
- return (index.h("mg-input", { key: 'aab8c17ced01c0afbec9114f280ee2b7fa73cdfc', label: this.label, identifier: this.identifier, class: this.classCollection.join(), labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: this.required, tooltip: this.tooltip, tooltipPosition: this.readonly && readonlyValue === null ? 'label' : this.tooltipPosition, helpText: this.helpText, errorMessage: this.errorMessage }, this.readonly ? (readonlyValue && index.h("b", { class: "mg-c-input__readonly-value" }, readonlyValue)) : (index.h("select", { class: "mg-c-input__box", id: this.identifier, name: this.name, title: this.placeholder, disabled: this.disabled, required: this.required, "aria-invalid": (this.invalid === true).toString(), onInput: this.handleInput, onBlur: this.handleBlur, ref: (el) => {
4769
+ return (index.h("mg-input", { key: 'ca4a3a1b95094505bff8bf062566a633a1b12d1a', label: this.label, identifier: this.identifier, class: this.classCollection.join(), labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: this.required, tooltip: this.tooltip, tooltipPosition: this.readonly && readonlyValue === null ? 'label' : this.tooltipPosition, helpText: this.helpText, errorMessage: this.errorMessage }, this.readonly ? (readonlyValue && index.h("b", { class: "mg-c-input__readonly-value" }, readonlyValue)) : (index.h("select", { class: "mg-c-input__box", id: this.identifier, name: this.name, title: this.placeholder, disabled: this.disabled, required: this.required, "aria-invalid": (this.invalid === true).toString(), onInput: this.handleInput, onBlur: this.handleBlur, ref: (el) => {
4519
4770
  if (el !== null)
4520
4771
  this.input = el;
4521
4772
  } }, (!this.placeholderHide || !this.valueExist) && ( // In case passed value does not match any option we display the placeholder
@@ -4589,6 +4840,8 @@ const MgInputText = class {
4589
4840
  // hasDisplayedError (triggered by blur event)
4590
4841
  this.hasDisplayedError = false;
4591
4842
  this.customErrorMessage = { lock: false };
4843
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
4844
+ this.validityCheckPending = false;
4592
4845
  /**
4593
4846
  * Input name
4594
4847
  * If not set the value equals the identifier
@@ -4653,10 +4906,6 @@ const MgInputText = class {
4653
4906
  * Handle input event
4654
4907
  */
4655
4908
  this.handleInput = () => {
4656
- this.checkValidity();
4657
- if (this.hasDisplayedError) {
4658
- this.setErrorMessage();
4659
- }
4660
4909
  this.value = this.input.value;
4661
4910
  };
4662
4911
  /**
@@ -4737,6 +4986,7 @@ const MgInputText = class {
4737
4986
  }
4738
4987
  handleValue(newValue) {
4739
4988
  var _a, _b;
4989
+ this.validityCheckPending = true;
4740
4990
  if ((_a = this.datalistoptions) === null || _a === void 0 ? void 0 : _a.every(isOption$1)) {
4741
4991
  this.valueChange.emit(((_b = this.datalistoptions.find((option) => option.title === newValue)) === null || _b === void 0 ? void 0 : _b.value) || newValue);
4742
4992
  }
@@ -4856,6 +5106,8 @@ const MgInputText = class {
4856
5106
  lock: valid ? false : errorMessageLock,
4857
5107
  message: valid ? undefined : errorMessage,
4858
5108
  };
5109
+ // setError() states the validity explicitly, so drop any pending recomputation
5110
+ this.validityCheckPending = false;
4859
5111
  this.setValidity(valid);
4860
5112
  this.setErrorMessage(true);
4861
5113
  this.hasDisplayedError = this.invalid;
@@ -4928,14 +5180,32 @@ const MgInputText = class {
4928
5180
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
4929
5181
  return setTimeout(() => {
4930
5182
  this.checkValidity();
5183
+ // the initial check covers the initial value: nothing is pending yet
5184
+ this.validityCheckPending = false;
4931
5185
  }, 0);
4932
5186
  }
5187
+ /**
5188
+ * Recompute validity once the DOM is in sync with `value`.
5189
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
5190
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
5191
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
5192
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
5193
+ */
5194
+ componentDidUpdate() {
5195
+ if (this.validityCheckPending && this.input !== undefined) {
5196
+ this.validityCheckPending = false;
5197
+ this.checkValidity();
5198
+ if (this.hasDisplayedError) {
5199
+ this.setErrorMessage();
5200
+ }
5201
+ }
5202
+ }
4933
5203
  /**
4934
5204
  * Render
4935
5205
  * @returns HTML Element
4936
5206
  */
4937
5207
  render() {
4938
- return (index.h("mg-input", { key: '3454b5f94af7b47a2c94b1416f59f840552ef70a', label: this.label, identifier: this.identifier, class: this.classCollection.join(), ariaDescribedbyIDs: this.characterLeftId, labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: this.required, tooltip: this.tooltip, tooltipPosition: this.readonly && this.value === undefined ? 'label' : this.tooltipPosition, helpText: this.helpText, errorMessage: this.errorMessage }, this.readonly
5208
+ return (index.h("mg-input", { key: '20a89d186c673936740cce348977cc0a8471fafb', label: this.label, identifier: this.identifier, class: this.classCollection.join(), ariaDescribedbyIDs: this.characterLeftId, labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: this.required, tooltip: this.tooltip, tooltipPosition: this.readonly && this.value === undefined ? 'label' : this.tooltipPosition, helpText: this.helpText, errorMessage: this.errorMessage }, this.readonly
4939
5209
  ? this.value && this.renderReadonly()
4940
5210
  : [
4941
5211
  index.h("div", { key: "input", class: "mg-c-input__input-group-container", style: {
@@ -5004,6 +5274,8 @@ const MgInputTextarea = class {
5004
5274
  this.inputValid = index.createEvent(this, "input-valid", 7);
5005
5275
  // hasDisplayedError (triggered by blur event)
5006
5276
  this.hasDisplayedError = false;
5277
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
5278
+ this.validityCheckPending = false;
5007
5279
  this.customErrorMessage = { lock: false };
5008
5280
  /**
5009
5281
  * Input name
@@ -5062,10 +5334,6 @@ const MgInputTextarea = class {
5062
5334
  * Handle input event
5063
5335
  */
5064
5336
  this.handleInput = () => {
5065
- if (this.hasDisplayedError) {
5066
- this.checkValidity();
5067
- this.setErrorMessage();
5068
- }
5069
5337
  this.value = this.input.value;
5070
5338
  };
5071
5339
  /**
@@ -5115,7 +5383,7 @@ const MgInputTextarea = class {
5115
5383
  };
5116
5384
  }
5117
5385
  handleValue(newValue) {
5118
- this.checkValidity();
5386
+ this.validityCheckPending = true;
5119
5387
  this.valueChange.emit(newValue);
5120
5388
  }
5121
5389
  watchReadonly(newValue) {
@@ -5209,6 +5477,8 @@ const MgInputTextarea = class {
5209
5477
  lock: valid ? false : errorMessageLock,
5210
5478
  message: valid ? undefined : errorMessage,
5211
5479
  };
5480
+ // setError() states the validity explicitly, so drop any pending recomputation
5481
+ this.validityCheckPending = false;
5212
5482
  this.setValidity(valid);
5213
5483
  this.setErrorMessage(true);
5214
5484
  this.hasDisplayedError = this.invalid;
@@ -5276,14 +5546,32 @@ const MgInputTextarea = class {
5276
5546
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
5277
5547
  return setTimeout(() => {
5278
5548
  this.checkValidity();
5549
+ // the initial check covers the initial value: nothing is pending yet
5550
+ this.validityCheckPending = false;
5279
5551
  }, 0);
5280
5552
  }
5553
+ /**
5554
+ * Recompute validity once the DOM is in sync with `value`.
5555
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
5556
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
5557
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
5558
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
5559
+ */
5560
+ componentDidUpdate() {
5561
+ if (this.validityCheckPending && this.input !== undefined) {
5562
+ this.validityCheckPending = false;
5563
+ this.checkValidity();
5564
+ if (this.hasDisplayedError) {
5565
+ this.setErrorMessage();
5566
+ }
5567
+ }
5568
+ }
5281
5569
  /**
5282
5570
  * Render
5283
5571
  * @returns HTML Element
5284
5572
  */
5285
5573
  render() {
5286
- return (index.h("mg-input", { key: '36575f026a23722bb6d224aaeacf0e80d3e1bbfd', label: this.label, identifier: this.identifier, class: this.classCollection.join(), ariaDescribedbyIDs: this.characterLeftId, labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: this.required, tooltip: this.tooltip, tooltipPosition: this.readonly && this.value === undefined ? 'label' : this.tooltipPosition, helpText: this.helpText, errorMessage: this.errorMessage }, this.readonly ? (this.value && index.h("b", { class: "mg-c-input__readonly-value" }, this.value)) : (index.h("div", { class: "mg-c-input__input-group-container" }, index.h("textarea", { class: {
5574
+ return (index.h("mg-input", { key: 'e3bf8b4bbddccaf86a78f8517cc529aa41cc61d6', label: this.label, identifier: this.identifier, class: this.classCollection.join(), ariaDescribedbyIDs: this.characterLeftId, labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: this.required, tooltip: this.tooltip, tooltipPosition: this.readonly && this.value === undefined ? 'label' : this.tooltipPosition, helpText: this.helpText, errorMessage: this.errorMessage }, this.readonly ? (this.value && index.h("b", { class: "mg-c-input__readonly-value" }, this.value)) : (index.h("div", { class: "mg-c-input__input-group-container" }, index.h("textarea", { class: {
5287
5575
  'mg-c-input__box': true,
5288
5576
  'mg-c-input__box--width': true,
5289
5577
  'mg-c-input__box--resizable': this.resizable === 'both',
@@ -6562,7 +6850,7 @@ MgMessage.style = mgMessageCss();
6562
6850
  */
6563
6851
  const dialogRoles = ['dialog', 'alertdialog'];
6564
6852
 
6565
- const mgModalCss = () => `:host{--mg-c-card-border-radius:var(--mg-c-modal-border-radius);--mg-c-card-spacing:var(--mg-b-size-16) var(--mg-b-size-24);--mg-c-card-border:none}.mg-c-modal{max-height:100dvh;box-sizing:border-box;border:none;background:none;color:inherit}.mg-c-modal[open]{display:flex;padding:var(--mg-b-size-24) var(--mg-b-size-8)}.mg-c-modal::backdrop{background-color:var(--mg-b-color-dark);opacity:.65}.mg-c-modal mg-card{display:flex;width:calc(75*var(--mg-b-size-8));max-width:100%;max-height:100dvh}.mg-c-modal__dialog{position:relative;display:flex;height:fit-content;max-height:100%;flex-direction:column;align-self:center;row-gap:var(--mg-b-size-16)}.mg-c-modal__title{margin:0;font-family:var(--mg-b-font-family-heading);font-size:var(--mg-c-modal-title-font-size);font-weight:600}.mg-c-modal__close-button{height:var(--mg-c-modal-title-font-size);margin-top:calc((var(--mg-b-size-min-height) - var(--mg-c-modal-title-font-size)*var(--mg-b-line-height))/2*-1);margin-right:calc((var(--mg-b-size-min-height) - var(--mg-b-size-16))/2*-1);margin-left:var(--mg-b-size-16);float:right}.mg-c-modal__content{padding:var(--mg-b-size-16);margin:calc(var(--mg-b-size-16)*-1);overflow-y:auto}.mg-c-modal__footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:var(--mg-b-size-8)}.mg-c-modal ::slotted(*:not(mg-panel,mg-message,mg-modal)){--mg-c-card-border:var(--mg-c-card-border-default);--mg-c-card-spacing:var(--mg-c-card-spacing-default);--mg-c-card-border-radius:var(--mg-c-card-border-radius-default);--mg-c-card-color-background:var(--mg-c-card-color-background-default);--mg-c-card-box-shadow:var(--mg-c-card-box-shadow-default);--mg-c-card-max-width:unset;--mg-c-card-min-width:unset;--mg-c-card-max-height:unset;--mg-c-card-overflow:var(--mg-c-card-overflow-default)}`;
6853
+ const mgModalCss = () => `:host{--mg-c-card-border-radius:var(--mg-c-modal-border-radius);--mg-c-card-spacing:var(--mg-b-size-16) var(--mg-b-size-24);--mg-c-card-border:none}.mg-c-modal{max-height:100dvh;box-sizing:border-box;border:none;background:none;color:inherit}.mg-c-modal[open]{display:flex;padding:var(--mg-b-size-24) var(--mg-b-size-8)}.mg-c-modal::backdrop{background-color:var(--mg-b-color-dark);opacity:.65}.mg-c-modal mg-card{display:flex;width:calc(75*var(--mg-b-size-8));max-width:100%;max-height:100dvh}.mg-c-modal__dialog{position:relative;display:flex;height:fit-content;max-height:100%;flex-direction:column;align-self:center;row-gap:var(--mg-b-size-16)}.mg-c-modal__title{margin:0;font-family:var(--mg-b-font-family-heading);font-size:var(--mg-c-modal-title-font-size);font-weight:600}.mg-c-modal__close-button{height:var(--mg-c-modal-title-font-size);margin-top:calc((var(--mg-b-size-min-height) - var(--mg-c-modal-title-font-size)*var(--mg-b-line-height))/2*-1);margin-right:calc((var(--mg-b-size-min-height) - var(--mg-b-size-16))/2*-1);margin-left:var(--mg-b-size-16);float:right}.mg-c-modal__content{padding:var(--mg-b-size-16);margin:calc(var(--mg-b-size-16)*-1);overflow-y:auto;scroll-padding-block:var(--mg-b-size-16)}.mg-c-modal__footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:var(--mg-b-size-8)}.mg-c-modal ::slotted(*:not(mg-panel,mg-message,mg-modal)){--mg-c-card-border:var(--mg-c-card-border-default);--mg-c-card-spacing:var(--mg-c-card-spacing-default);--mg-c-card-border-radius:var(--mg-c-card-border-radius-default);--mg-c-card-color-background:var(--mg-c-card-color-background-default);--mg-c-card-box-shadow:var(--mg-c-card-box-shadow-default);--mg-c-card-max-width:unset;--mg-c-card-min-width:unset;--mg-c-card-max-height:unset;--mg-c-card-overflow:var(--mg-c-card-overflow-default)}`;
6566
6854
 
6567
6855
  const MgModal = class {
6568
6856
  constructor(hostRef) {