@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
@@ -1,5 +1,5 @@
1
1
  import { r as registerInstance, h, H as Host, a as getElement, c as createEvent } from './index-D4bO_UCn.js';
2
- import { h as createID, t as toString, C as ClassList, i as isValidString, n as nextTick, j as isValideID, k as isTagName, f as formatID, l as cleanString, m as dateRegExp, o as dateToString, b as allItemsAreString, p as getWindows, q as focusableElements } from './index-91BOpzAB.js';
2
+ import { h as createID, t as toString, C as ClassList, i as isValidString, j as isValideID, k as isTagName, f as formatID, l as cleanString, m as dateRegExp, o as dateToString, b as allItemsAreString, n as nextTick, p as getWindows, q as focusableElements } from './index-91BOpzAB.js';
3
3
  import { i as initLocales, a as localeDate, b as localeDatePattern, c as localeNumber, d as localeUnit, e as localePercent, f as localeCurrency } from './index-BfblyCaJ.js';
4
4
  import { n as normalizeBooleanAttributes, i as isMgInputFile, l as labelHeadings, t as tooltipPositions, b as classFieldset, c as classReadonly, a as classDisabled, d as classVerticalList, w as widths, e as classDisplayCharacterLeft } from './mg-input.conf-DOi9lJXy.js';
5
5
  import { K as Keys } from './events.utils-yMGqY8YG.js';
@@ -393,6 +393,88 @@ const MgButton = class {
393
393
  (_b = this.element.querySelector('mg-icon')) === null || _b === void 0 ? void 0 : _b.setAttribute('size', this.size);
394
394
  }
395
395
  };
396
+ /**
397
+ * Submit a native form the way a native submit button does
398
+ * @param form - the form to submit
399
+ */
400
+ this.dispatchSubmit = (form) => {
401
+ form.dispatchEvent(new SubmitEvent('submit', { bubbles: true, cancelable: true }));
402
+ };
403
+ /**
404
+ * Wrap a `mg-form` in the actions its public API offers, so nothing reaches into its shadow DOM
405
+ * @param mgForm - the owning mg-form
406
+ * @returns its submit and reset actions
407
+ */
408
+ this.mgFormActions = (mgForm) => ({
409
+ submit: () => mgForm.requestSubmit(),
410
+ reset: () => mgForm.reset(),
411
+ });
412
+ /**
413
+ * Wrap a native form in the same actions
414
+ * @param form - the owning form
415
+ * @returns its submit and reset actions
416
+ */
417
+ this.nativeFormActions = (form) => ({
418
+ submit: () => this.dispatchSubmit(form),
419
+ reset: () => form.reset(),
420
+ });
421
+ /**
422
+ * Resolve the form the `form` id points to. An id reference does not cross a shadow boundary, so
423
+ * it resolves in the tree the button belongs to — the document, or the shadow tree it sits in —
424
+ * the way a native form owner does. The ancestor wins when it is the one pointed at: two forms
425
+ * can share an identifier, and a tree-wide lookup would retain the first one.
426
+ * @returns the actions of the form holding that id, or null when none does
427
+ */
428
+ this.resolveOwnerById = () => {
429
+ const ancestorMgForm = this.element.closest('mg-form');
430
+ if (ancestorMgForm !== null && ancestorMgForm.identifier === this.form)
431
+ return this.mgFormActions(ancestorMgForm);
432
+ const root = this.element.getRootNode();
433
+ const tree = root instanceof ShadowRoot ? root : this.element.ownerDocument;
434
+ const mgForm = Array.from(tree.querySelectorAll('mg-form')).find((element) => element.identifier === this.form);
435
+ if (mgForm !== undefined)
436
+ return this.mgFormActions(mgForm);
437
+ // Resolved by id, as a native form owner is, rather than through a selector an id could break
438
+ const nativeForm = tree.getElementById(this.form);
439
+ return nativeForm instanceof HTMLFormElement ? this.nativeFormActions(nativeForm) : null;
440
+ };
441
+ /**
442
+ * Resolve the form owning this button: an explicit `form` id overrides the ancestor form, and an
443
+ * id matching nothing logs then falls back on it, so a button never loses an association it had.
444
+ * @returns the owning form actions, or null when the button has no form owner
445
+ */
446
+ this.resolveFormOwner = () => {
447
+ if (isValidString(this.form)) {
448
+ const ownerById = this.resolveOwnerById();
449
+ if (ownerById !== null)
450
+ return ownerById;
451
+ console.error(`<mg-button> prop "form" matches no form in the button tree. Passed value: ${toString(this.form)}.`);
452
+ }
453
+ const nativeAncestor = this.element.closest('form');
454
+ if (nativeAncestor !== null)
455
+ return this.nativeFormActions(nativeAncestor);
456
+ const mgAncestor = this.element.closest('mg-form');
457
+ return mgAncestor === null ? null : this.mgFormActions(mgAncestor);
458
+ };
459
+ /**
460
+ * Act on the owning form. The owner is resolved here rather than on load because a native form
461
+ * owner is re-resolved on every submission, never captured once: the button follows a `form`
462
+ * changed after render, and no longer races the target form hydration.
463
+ */
464
+ this.handleFormClick = () => {
465
+ const isSubmit = ['submit', undefined].includes(this.type);
466
+ // A reset only fires from an explicit association: it has never reset a merely ancestor form
467
+ const isReset = this.type === 'reset' && isValidString(this.form);
468
+ if (!isSubmit && !isReset)
469
+ return;
470
+ const owner = this.resolveFormOwner();
471
+ if (owner === null)
472
+ return;
473
+ if (isSubmit)
474
+ owner.submit();
475
+ else
476
+ owner.reset();
477
+ };
396
478
  }
397
479
  validateVariant(newValue, oldValue) {
398
480
  // validate new value
@@ -491,16 +573,7 @@ const MgButton = class {
491
573
  * Add listners
492
574
  */
493
575
  componentDidLoad() {
494
- nextTick(() => {
495
- var _a;
496
- const closestForm = this.element.closest('form') || ((_a = this.element.closest('mg-form')) === null || _a === void 0 ? void 0 : _a.shadowRoot.querySelector('form'));
497
- // submit buttons should trigger form submition;
498
- if (closestForm && ['submit', undefined].includes(this.type)) {
499
- this.element.addEventListener('click', () => {
500
- closestForm.dispatchEvent(new SubmitEvent('submit', { bubbles: true, cancelable: true }));
501
- });
502
- }
503
- });
576
+ this.element.addEventListener('click', this.handleFormClick);
504
577
  }
505
578
  /**
506
579
  * Render component
@@ -508,7 +581,7 @@ const MgButton = class {
508
581
  */
509
582
  render() {
510
583
  var _a;
511
- return (h(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 }, h("div", { key: '36918b5b208a9e0a9650107f4418cac35ade1ed2', class: this.classCollection.join() }, this.loading && h("mg-icon", { key: '8ad3e911d273f69441f2a405f8040cc685a0a486', icon: "loader", spin: true }), h("div", { key: 'a6b199c669ef6b15bfc345c98192290ce11e65cb', class: "mg-c-button__content" }, h("slot", { key: 'b13d317001dbf66c945cdf9e97c06338ce073c12' })))));
584
+ return (h(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 }, h("div", { key: '2a1c20c677294870958f01c4042e8f530bf0f89d', class: this.classCollection.join() }, this.loading && h("mg-icon", { key: 'db3960249deed458f86bcba16b66bbeb9aac69c1', icon: "loader", spin: true }), h("div", { key: '76a3af6c9a105b91b7f6055cc19bbb6f315be1a5', class: "mg-c-button__content" }, h("slot", { key: 'ca87921b6c58fbd055a83ea28f5ce58a618f16fe' })))));
512
585
  }
513
586
  get element() { return getElement(this); }
514
587
  static get watchers() { return {
@@ -996,6 +1069,38 @@ const MgForm = class {
996
1069
  this.formReset.emit(true);
997
1070
  }
998
1071
  }
1072
+ /**
1073
+ * Request the form submission: emit `form-submit`, whatever the inputs validity.
1074
+ * This is what a `mg-button` associated with this form goes through, so that reaching the form
1075
+ * needs no access to its shadow DOM. Unlike the native `requestSubmit()`, it runs no constraint
1076
+ * validation: validity is the consumer's call here, as it is on submission from a nested button.
1077
+ * Read `invalid`, or call `displayError()` / `focusFirstInvalidInput()`.
1078
+ * Unlike `reset()`, a readonly form still submits: readonly fields do not block a native
1079
+ * submission either, and a nested button has always submitted such a form.
1080
+ */
1081
+ async requestSubmit() {
1082
+ this.formSubmit.emit();
1083
+ }
1084
+ /**
1085
+ * **Experimental.** The signature may still change in a minor release.
1086
+ *
1087
+ * Display errors, then focus and scroll to the first invalid input. Focusing is what
1088
+ * carries the information for assistive technologies, a scroll announces nothing.
1089
+ * Read the `invalid` property to know whether the form had an invalid input.
1090
+ */
1091
+ async focusFirstInvalidInput() {
1092
+ if (!this.readonly) {
1093
+ await this.displayError();
1094
+ // Inputs defer their displayError() to a requestAnimationFrame
1095
+ await new Promise((resolve) => requestAnimationFrame(resolve));
1096
+ // Read at call time: a MutationObserver rebuilds the list on content change
1097
+ const firstInvalidInput = this.mgInputs.find((input) => input.invalid);
1098
+ if (firstInvalidInput !== undefined) {
1099
+ await firstInvalidInput.setFocus();
1100
+ firstInvalidInput.scrollIntoView();
1101
+ }
1102
+ }
1103
+ }
999
1104
  /*************
1000
1105
  * Lifecycle *
1001
1106
  *************/
@@ -1014,13 +1119,13 @@ const MgForm = class {
1014
1119
  this.hasActions = this.element.querySelector('[slot="actions"]') !== null;
1015
1120
  // Get slotted mgButtons
1016
1121
  this.mgButtons = Array.from(this.element.querySelectorAll('mg-button'));
1017
- // Set button form identifier
1122
+ // Announce the association: a slotted button then drives this form through its public API and
1123
+ // handles its own submit and reset, which is why none is wired here. An id it already carries
1124
+ // wins, read as property and attribute since frameworks pass the prop as one or the other.
1018
1125
  this.mgButtons.forEach((mgButton) => {
1019
- mgButton.setAttribute('form', this.identifier);
1020
- if (mgButton.type === 'reset') {
1021
- mgButton.addEventListener('click', () => {
1022
- this.reset();
1023
- });
1126
+ var _a;
1127
+ if (!isValidString((_a = mgButton.form) !== null && _a !== void 0 ? _a : mgButton.getAttribute('form'))) {
1128
+ mgButton.setAttribute('form', this.identifier);
1024
1129
  }
1025
1130
  });
1026
1131
  // Set mgInputs
@@ -1055,7 +1160,7 @@ const MgForm = class {
1055
1160
  * @returns HTML Element
1056
1161
  */
1057
1162
  render() {
1058
- return (h("form", { key: 'c851e6f19af32f28cad695daa1f42ddff10796a0', class: this.classCollection.join(), id: this.identifier, name: this.name, onSubmit: this.handleFormSubmit, role: this.ariaRole }, this.requiredMessageText && h("p", { key: '7f92b35872c938e87959acac8841937ad503d98f', innerHTML: this.requiredMessageText }), h("slot", { key: 'e75e90f1fe3076d93df82a7a426b17d82035e0cf' }), !this.readonly && !this.disabled && this.hasActions && (h("div", { key: 'a472cf024a61d6be1665d149749ce7d560a00473', class: "mg-c-form__actions" }, h("slot", { key: 'f9098c711cb2d2714057c4f14d6df295b75de774', name: "actions" })))));
1163
+ return (h("form", { key: '0c23474cba5ae38a70162129641128cdd17224cb', class: this.classCollection.join(), id: this.identifier, name: this.name, onSubmit: this.handleFormSubmit, role: this.ariaRole }, this.requiredMessageText && h("p", { key: '8938192d2b2f4d1278f6e5037ed7f0f839c44861', innerHTML: this.requiredMessageText }), h("slot", { key: 'aefbcb13adbe2632b958c55c2c2c0174d5aaf471' }), !this.readonly && !this.disabled && this.hasActions && (h("div", { key: '30675225f5a910945f5b31fb044d3501c323acf9', class: "mg-c-form__actions" }, h("slot", { key: '6f5b871264d0237fc1eccfac5c454e61135b81c4', name: "actions" })))));
1059
1164
  }
1060
1165
  get element() { return getElement(this); }
1061
1166
  static get watchers() { return {
@@ -1934,6 +2039,8 @@ const MgInputCheckbox = class {
1934
2039
  this.inputValid = createEvent(this, "input-valid", 7);
1935
2040
  // hasDisplayedError (triggered by blur event)
1936
2041
  this.hasDisplayedError = false;
2042
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
2043
+ this.validityCheckPending = false;
1937
2044
  this.customErrorMessage = { lock: false };
1938
2045
  this.mode = 'custom';
1939
2046
  // style
@@ -2216,6 +2323,7 @@ const MgInputCheckbox = class {
2216
2323
  this.renderMgInputCheckboxList = () => (h(MgInputCheckboxList, { checkboxes: this.checkboxItems, id: this.checkboxesId, disabled: this.disabled, name: this.name, invalid: this.invalid }));
2217
2324
  }
2218
2325
  validateValue(newValue) {
2326
+ this.validityCheckPending = true;
2219
2327
  if (isCheckboxItems(newValue)) {
2220
2328
  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) })));
2221
2329
  this.valueChange.emit(newValue);
@@ -2348,6 +2456,8 @@ const MgInputCheckbox = class {
2348
2456
  lock: valid ? false : errorMessageLock,
2349
2457
  message: valid ? undefined : errorMessage,
2350
2458
  };
2459
+ // setError() states the validity explicitly, so drop any pending recomputation
2460
+ this.validityCheckPending = false;
2351
2461
  this.setValidity(valid);
2352
2462
  this.setErrorMessage(true);
2353
2463
  this.hasDisplayedError = this.invalid;
@@ -2430,8 +2540,26 @@ const MgInputCheckbox = class {
2430
2540
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
2431
2541
  return setTimeout(() => {
2432
2542
  this.checkValidity();
2543
+ // the initial check covers the initial value: nothing is pending yet
2544
+ this.validityCheckPending = false;
2433
2545
  }, 0);
2434
2546
  }
2547
+ /**
2548
+ * Recompute validity once the DOM is in sync with `value`.
2549
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
2550
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
2551
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
2552
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
2553
+ */
2554
+ componentDidUpdate() {
2555
+ if (this.validityCheckPending && this.checkboxItems.length > 0) {
2556
+ this.validityCheckPending = false;
2557
+ this.checkValidity();
2558
+ if (this.hasDisplayedError) {
2559
+ this.setErrorMessage();
2560
+ }
2561
+ }
2562
+ }
2435
2563
  /**
2436
2564
  * add listeners and DOM attributed
2437
2565
  */
@@ -2516,7 +2644,7 @@ const MgInputCheckbox = class {
2516
2644
  else {
2517
2645
  inputContent = this.renderCheckboxMulti();
2518
2646
  }
2519
- return (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));
2647
+ return (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));
2520
2648
  }
2521
2649
  get element() { return getElement(this); }
2522
2650
  static get watchers() { return {
@@ -2569,6 +2697,8 @@ const MgInputDate = class {
2569
2697
  this.inputValid = createEvent(this, "input-valid", 7);
2570
2698
  // hasDisplayedError (triggered by blur event)
2571
2699
  this.hasDisplayedError = false;
2700
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
2701
+ this.validityCheckPending = false;
2572
2702
  this.customErrorMessage = { lock: false };
2573
2703
  /**
2574
2704
  * Input name
@@ -2640,10 +2770,6 @@ const MgInputDate = class {
2640
2770
  * Handle input event
2641
2771
  */
2642
2772
  this.handleInput = () => {
2643
- this.checkValidity();
2644
- if (this.hasDisplayedError) {
2645
- this.setErrorMessage();
2646
- }
2647
2773
  this.value = this.input.value;
2648
2774
  };
2649
2775
  /**
@@ -2665,11 +2791,8 @@ const MgInputDate = class {
2665
2791
  // when we reset the field we need to test a blank value.
2666
2792
  if (['Delete', 'Backspace'].includes(event.key)) {
2667
2793
  event.preventDefault();
2794
+ // validity is recomputed in componentDidUpdate, once the input actually holds the blank value
2668
2795
  this.value = null;
2669
- this.checkValidity();
2670
- if (this.hasDisplayedError) {
2671
- this.setErrorMessage();
2672
- }
2673
2796
  }
2674
2797
  };
2675
2798
  /**
@@ -2756,6 +2879,7 @@ const MgInputDate = class {
2756
2879
  };
2757
2880
  }
2758
2881
  validateValue(newValue) {
2882
+ this.validityCheckPending = true;
2759
2883
  // When the input is not fully completed or has been cleared, the value becomes an empty string.
2760
2884
  if (['', undefined].includes(newValue)) {
2761
2885
  // need to force value update to `null` to prevent extra render
@@ -2848,6 +2972,8 @@ const MgInputDate = class {
2848
2972
  lock: valid ? false : errorMessageLock,
2849
2973
  message: valid ? undefined : errorMessage,
2850
2974
  };
2975
+ // setError() states the validity explicitly, so drop any pending recomputation
2976
+ this.validityCheckPending = false;
2851
2977
  this.setValidity(valid);
2852
2978
  this.setErrorMessage(true);
2853
2979
  this.hasDisplayedError = this.invalid;
@@ -2918,14 +3044,32 @@ const MgInputDate = class {
2918
3044
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
2919
3045
  return setTimeout(() => {
2920
3046
  this.checkValidity();
3047
+ // the initial check covers the initial value: nothing is pending yet
3048
+ this.validityCheckPending = false;
2921
3049
  }, 0);
2922
3050
  }
3051
+ /**
3052
+ * Recompute validity once the DOM is in sync with `value`.
3053
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
3054
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
3055
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
3056
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
3057
+ */
3058
+ componentDidUpdate() {
3059
+ if (this.validityCheckPending && this.input !== undefined) {
3060
+ this.validityCheckPending = false;
3061
+ this.checkValidity();
3062
+ if (this.hasDisplayedError) {
3063
+ this.setErrorMessage();
3064
+ }
3065
+ }
3066
+ }
2923
3067
  /**
2924
3068
  * Render
2925
3069
  * @returns HTML Element
2926
3070
  */
2927
3071
  render() {
2928
- return (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 && h("b", { class: "mg-c-input__readonly-value" }, localeDate(this.value, this.locale))) : (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) => {
3072
+ return (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 && h("b", { class: "mg-c-input__readonly-value" }, localeDate(this.value, this.locale))) : (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) => {
2929
3073
  if (el !== null)
2930
3074
  this.input = el;
2931
3075
  } }))));
@@ -2977,6 +3121,8 @@ const MgInputNumeric = class {
2977
3121
  this.inputValid = createEvent(this, "input-valid", 7);
2978
3122
  // hasDisplayedError (triggered by blur event)
2979
3123
  this.hasDisplayedError = false;
3124
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
3125
+ this.validityCheckPending = false;
2980
3126
  this.customErrorMessage = { lock: false };
2981
3127
  /**
2982
3128
  * Input name
@@ -3043,11 +3189,6 @@ const MgInputNumeric = class {
3043
3189
  */
3044
3190
  this.handleInput = () => {
3045
3191
  this.value = this.input.value;
3046
- // Check validity
3047
- this.checkValidity();
3048
- if (this.hasDisplayedError) {
3049
- this.setErrorMessage();
3050
- }
3051
3192
  };
3052
3193
  /**
3053
3194
  * Handle focus event
@@ -3197,6 +3338,7 @@ const MgInputNumeric = class {
3197
3338
  }
3198
3339
  validateValue(newValue) {
3199
3340
  var _a;
3341
+ this.validityCheckPending = true;
3200
3342
  // has input value is always render as string we get a stringified value or a '' for nullish value, so we test string type
3201
3343
  if (typeof newValue === 'string') {
3202
3344
  // Check if integer value starts with '0' (leading zero)
@@ -3317,6 +3459,8 @@ const MgInputNumeric = class {
3317
3459
  lock: valid ? false : errorMessageLock,
3318
3460
  message: valid ? undefined : errorMessage,
3319
3461
  };
3462
+ // setError() states the validity explicitly, so drop any pending recomputation
3463
+ this.validityCheckPending = false;
3320
3464
  this.setValidity(valid);
3321
3465
  this.setErrorMessage(true);
3322
3466
  this.hasDisplayedError = this.invalid;
@@ -3429,14 +3573,32 @@ const MgInputNumeric = class {
3429
3573
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
3430
3574
  return setTimeout(() => {
3431
3575
  this.checkValidity();
3576
+ // the initial check covers the initial value: nothing is pending yet
3577
+ this.validityCheckPending = false;
3432
3578
  }, 0);
3433
3579
  }
3580
+ /**
3581
+ * Recompute validity once the DOM is in sync with `value`.
3582
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
3583
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
3584
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
3585
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
3586
+ */
3587
+ componentDidUpdate() {
3588
+ if (this.validityCheckPending && this.input !== undefined) {
3589
+ this.validityCheckPending = false;
3590
+ this.checkValidity();
3591
+ if (this.hasDisplayedError) {
3592
+ this.setErrorMessage();
3593
+ }
3594
+ }
3595
+ }
3434
3596
  /**
3435
3597
  * Render
3436
3598
  * @returns HTML Element
3437
3599
  */
3438
3600
  render() {
3439
- return (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
3601
+ return (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
3440
3602
  ? this.readonlyValue && this.renderReadonly()
3441
3603
  : [
3442
3604
  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) => {
@@ -3499,6 +3661,8 @@ const MgInputPassword = class {
3499
3661
  // hasDisplayedError (triggered by blur event)
3500
3662
  this.hasDisplayedError = false;
3501
3663
  this.customErrorMessage = { lock: false };
3664
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
3665
+ this.validityCheckPending = false;
3502
3666
  /**
3503
3667
  * Input name
3504
3668
  * If not set the value equals the identifier
@@ -3544,10 +3708,6 @@ const MgInputPassword = class {
3544
3708
  * Handle input event
3545
3709
  */
3546
3710
  this.handleInput = () => {
3547
- this.checkValidity();
3548
- if (this.hasDisplayedError) {
3549
- this.setErrorMessage();
3550
- }
3551
3711
  this.value = this.input.value;
3552
3712
  };
3553
3713
  /**
@@ -3592,6 +3752,7 @@ const MgInputPassword = class {
3592
3752
  };
3593
3753
  }
3594
3754
  handleValue(newValue) {
3755
+ this.validityCheckPending = true;
3595
3756
  this.valueChange.emit(newValue);
3596
3757
  }
3597
3758
  watchReadonly(newValue) {
@@ -3672,6 +3833,8 @@ const MgInputPassword = class {
3672
3833
  lock: valid ? false : errorMessageLock,
3673
3834
  message: valid ? undefined : errorMessage,
3674
3835
  };
3836
+ // setError() states the validity explicitly, so drop any pending recomputation
3837
+ this.validityCheckPending = false;
3675
3838
  this.setValidity(valid);
3676
3839
  this.setErrorMessage(true);
3677
3840
  this.hasDisplayedError = this.invalid;
@@ -3734,15 +3897,33 @@ const MgInputPassword = class {
3734
3897
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
3735
3898
  return setTimeout(() => {
3736
3899
  this.checkValidity();
3900
+ // the initial check covers the initial value: nothing is pending yet
3901
+ this.validityCheckPending = false;
3737
3902
  }, 0);
3738
3903
  }
3904
+ /**
3905
+ * Recompute validity once the DOM is in sync with `value`.
3906
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
3907
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
3908
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
3909
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
3910
+ */
3911
+ componentDidUpdate() {
3912
+ if (this.validityCheckPending && this.input !== undefined) {
3913
+ this.validityCheckPending = false;
3914
+ this.checkValidity();
3915
+ if (this.hasDisplayedError) {
3916
+ this.setErrorMessage();
3917
+ }
3918
+ }
3919
+ }
3739
3920
  /**
3740
3921
  * Render
3741
3922
  * @returns HTML Element
3742
3923
  */
3743
3924
  render() {
3744
3925
  const displayPasswordIcon = this.displayPassword ? 'eye-slash' : 'eye';
3745
- return (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 && h("b", { class: "mg-c-input__readonly-value" }, '•'.repeat(this.value.length))) : (h("span", { class: "mg-c-input__input-group" }, 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) => {
3926
+ return (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 && h("b", { class: "mg-c-input__readonly-value" }, '•'.repeat(this.value.length))) : (h("span", { class: "mg-c-input__input-group" }, 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) => {
3746
3927
  if (el !== null)
3747
3928
  this.input = el;
3748
3929
  } }), 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 }, h("mg-icon", { class: "mg-c-input__input-icon", icon: displayPasswordIcon }))))));
@@ -3790,12 +3971,12 @@ const MgInputRadio = class {
3790
3971
  /************
3791
3972
  * Internal *
3792
3973
  ************/
3793
- // HTML selector
3794
- this.inputs = [];
3795
3974
  // classes
3796
3975
  this.classVerticalList = 'mg-c-input--vertical-list';
3797
3976
  // hasDisplayedError (triggered by blur event)
3798
3977
  this.hasDisplayedError = false;
3978
+ // raised when `value` or `items` changed, so componentDidUpdate revalidates once the DOM is in sync
3979
+ this.validityCheckPending = false;
3799
3980
  this.customErrorMessage = { lock: false };
3800
3981
  /**
3801
3982
  * Input name
@@ -3839,7 +4020,6 @@ const MgInputRadio = class {
3839
4020
  * @param event - input event
3840
4021
  */
3841
4022
  this.handleInput = (event) => {
3842
- this.checkValidity();
3843
4023
  this.value = this.options[event.target.value].value;
3844
4024
  };
3845
4025
  /**
@@ -3852,11 +4032,23 @@ const MgInputRadio = class {
3852
4032
  // reset guard
3853
4033
  this.handlerInProgress = undefined;
3854
4034
  };
4035
+ /**
4036
+ * Check if the required constraint is fulfilled
4037
+ * The native group validity cannot answer it: a browser keeps the checked member a group had, even
4038
+ * once that input is removed from the DOM. Options that cannot be selected are ignored.
4039
+ * @returns required constraint is fulfilled
4040
+ */
4041
+ this.validateRequired = () => {
4042
+ const selectableOptions = this.options.filter((option) => !option.disabled);
4043
+ return (!this.required ||
4044
+ selectableOptions.length < 1 ||
4045
+ selectableOptions.some((option) => this.isSelectedValue(option.value)));
4046
+ };
3855
4047
  /**
3856
4048
  * Check if input is valid
3857
4049
  */
3858
4050
  this.checkValidity = () => {
3859
- this.setValidity(this.readonly || this.disabled || this.getInvalidElement() === undefined);
4051
+ this.setValidity(this.readonly || this.disabled || (this.getInvalidElement() === undefined && this.validateRequired()));
3860
4052
  };
3861
4053
  /**
3862
4054
  * Set input error message
@@ -3872,7 +4064,7 @@ const MgInputRadio = class {
3872
4064
  this.errorMessage = this.customErrorMessage.message;
3873
4065
  }
3874
4066
  // required
3875
- else if (invalidElement.validity.valueMissing) {
4067
+ else if (invalidElement === undefined || invalidElement.validity.valueMissing) {
3876
4068
  this.errorMessage = this.messages.errors.required;
3877
4069
  }
3878
4070
  }
@@ -3881,11 +4073,24 @@ const MgInputRadio = class {
3881
4073
  * get invalid element
3882
4074
  * @returns element
3883
4075
  */
3884
- this.getInvalidElement = () => this.inputs.find((input) => !input.disabled && !input.readOnly && !input.checkValidity());
4076
+ this.getInvalidElement = () => this.getInputs().find((input) => !input.disabled && !input.readOnly && !input.checkValidity());
4077
+ /**
4078
+ * Get the rendered radio inputs
4079
+ * Read from the DOM: references kept from a render would outlive the inputs they point to.
4080
+ * @returns rendered radio inputs
4081
+ */
4082
+ this.getInputs = () => Array.from(this.element.shadowRoot.querySelectorAll('input[type="radio"]'));
4083
+ /**
4084
+ * Check if a value is the selected one
4085
+ * @param optionValue - option value to compare to the component value
4086
+ * @returns option value is the selected one
4087
+ */
4088
+ this.isSelectedValue = (optionValue) => JSON.stringify(this.value) === JSON.stringify(optionValue);
3885
4089
  }
3886
4090
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3887
4091
  watchValue(newValue) {
3888
4092
  var _a;
4093
+ this.validityCheckPending = true;
3889
4094
  if (allItemsAreString(this.items) && typeof newValue === 'string') {
3890
4095
  this.readonlyValue = newValue;
3891
4096
  }
@@ -3897,7 +4102,7 @@ const MgInputRadio = class {
3897
4102
  }
3898
4103
  this.valueChange.emit(newValue);
3899
4104
  }
3900
- validateItems(newValue) {
4105
+ validateItems(newValue, oldValue) {
3901
4106
  // Validate if items have required min length
3902
4107
  if (typeof newValue === 'object' && newValue.length < 2) {
3903
4108
  throw new Error(`<mg-input-radio> prop "items" require at least 2 items. Passed value: ${toString(newValue)}.`);
@@ -3913,6 +4118,14 @@ const MgInputRadio = class {
3913
4118
  else {
3914
4119
  throw new Error(`<mg-input-radio> prop "items" is required and all items must be the same type, string or RadioOption. Passed value: ${toString(newValue)}.`);
3915
4120
  }
4121
+ // `oldValue` is undefined on init
4122
+ if (oldValue !== undefined) {
4123
+ // the options cannot display a value they no longer offer, drop it
4124
+ if (this.value !== undefined && !this.options.some((option) => this.isSelectedValue(option.value))) {
4125
+ this.value = undefined;
4126
+ }
4127
+ this.validityCheckPending = true;
4128
+ }
3916
4129
  }
3917
4130
  watchInputVerticalList(newValue) {
3918
4131
  if (newValue)
@@ -3927,7 +4140,7 @@ const MgInputRadio = class {
3927
4140
  this.classCollection.delete(classReadonly);
3928
4141
  }
3929
4142
  handleValidityChange(newValue, _oldValue, prop) {
3930
- this.inputs.forEach((input) => {
4143
+ this.getInputs().forEach((input) => {
3931
4144
  input[prop] = newValue;
3932
4145
  });
3933
4146
  this.checkValidity();
@@ -3946,9 +4159,8 @@ const MgInputRadio = class {
3946
4159
  * Set focus on input.
3947
4160
  */
3948
4161
  async setFocus() {
3949
- if (this.inputs.length > 0) {
3950
- this.inputs[0].focus();
3951
- }
4162
+ var _a;
4163
+ (_a = this.element.shadowRoot.querySelector('input')) === null || _a === void 0 ? void 0 : _a.focus();
3952
4164
  }
3953
4165
  /**
3954
4166
  * Display input error if it exists.
@@ -3990,6 +4202,8 @@ const MgInputRadio = class {
3990
4202
  lock: valid ? false : errorMessageLock,
3991
4203
  message: valid ? undefined : errorMessage,
3992
4204
  };
4205
+ // setError() states the validity explicitly, so drop any pending recomputation
4206
+ this.validityCheckPending = false;
3993
4207
  this.setValidity(valid);
3994
4208
  this.setErrorMessage(true);
3995
4209
  this.hasDisplayedError = this.invalid;
@@ -4055,20 +4269,35 @@ const MgInputRadio = class {
4055
4269
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
4056
4270
  return setTimeout(() => {
4057
4271
  this.checkValidity();
4272
+ // the initial check covers the initial value: nothing is pending yet
4273
+ this.validityCheckPending = false;
4058
4274
  }, 0);
4059
4275
  }
4276
+ /**
4277
+ * Recompute validity once the DOM is in sync with `value`.
4278
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
4279
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
4280
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
4281
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
4282
+ */
4283
+ componentDidUpdate() {
4284
+ if (this.validityCheckPending && this.getInputs().length > 0) {
4285
+ this.validityCheckPending = false;
4286
+ this.checkValidity();
4287
+ if (this.hasDisplayedError) {
4288
+ this.setErrorMessage();
4289
+ }
4290
+ }
4291
+ }
4060
4292
  /**
4061
4293
  * Render
4062
4294
  * @returns HTML Element
4063
4295
  */
4064
4296
  render() {
4065
- return (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 && h("b", { class: "mg-c-input__readonly-value" }, this.readonlyValue)) : (h("div", { class: "mg-c-input__input-group-container" }, this.options.map((input, index) => (h("div", { key: input.title, class: {
4297
+ return (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 && h("b", { class: "mg-c-input__readonly-value" }, this.readonlyValue)) : (h("div", { class: "mg-c-input__input-group-container" }, this.options.map((input, index) => (h("div", { key: input.title, class: {
4066
4298
  'mg-c-input__input-group': true,
4067
4299
  'mg-c-input__input-group--disabled': this.disabled || input.disabled,
4068
- } }, h("input", { type: "radio", id: this.identifier + '_' + index, name: this.identifier, value: index, checked: JSON.stringify(this.value) === JSON.stringify(this.options[index].value), disabled: this.disabled || input.disabled, required: this.required, onBlur: this.handleBlur, onInput: this.handleInput, ref: (el) => {
4069
- if (el !== null)
4070
- this.inputs[index] = el;
4071
- } }), h("label", { htmlFor: this.identifier + '_' + index }, input.title))))))));
4300
+ } }, h("input", { type: "radio", id: this.identifier + '_' + index, name: this.identifier, value: index, checked: this.isSelectedValue(this.options[index].value), disabled: this.disabled || input.disabled, required: this.required, onBlur: this.handleBlur, onInput: this.handleInput }), h("label", { htmlFor: this.identifier + '_' + index }, input.title))))))));
4072
4301
  }
4073
4302
  get element() { return getElement(this); }
4074
4303
  static get watchers() { return {
@@ -4121,8 +4350,9 @@ const isOptGroup = (optgroup) => typeof optgroup === 'object' &&
4121
4350
  typeof optgroup.group === 'string' &&
4122
4351
  allItemsAreOptions(optgroup.options);
4123
4352
  /**
4124
- * Ensure options can be unambiguously differentiated by their title
4353
+ * Report, without enforcing, options that cannot be unambiguously differentiated by their title
4125
4354
  * Two options can share the same title only when they belong to different groups
4355
+ * 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
4126
4356
  * @param items - select options to validate
4127
4357
  */
4128
4358
  const validateUniqueTitles = (items) => {
@@ -4134,7 +4364,7 @@ const validateUniqueTitles = (items) => {
4134
4364
  const titles = titlesByGroup.get(group);
4135
4365
  if (titles.has(title)) {
4136
4366
  const groupContext = group === undefined ? 'outside of a group' : `within the group "${group}"`;
4137
- throw new Error(`<mg-input-select> prop "items" must not contain options with the same "title" ${groupContext}. Duplicated title: "${title}".`);
4367
+ console.error(`<mg-input-select> prop "items" must not contain options with the same "title" ${groupContext}. Duplicated title: "${title}".`);
4138
4368
  }
4139
4369
  titles.add(title);
4140
4370
  });
@@ -4170,6 +4400,8 @@ const MgInputSelect = class {
4170
4400
  this.inputValid = createEvent(this, "input-valid", 7);
4171
4401
  // hasDisplayedError (triggered by blur event)
4172
4402
  this.hasDisplayedError = false;
4403
+ // raised when `value` or `items` changed, so componentDidUpdate revalidates once the DOM is in sync
4404
+ this.validityCheckPending = false;
4173
4405
  this.customErrorMessage = { lock: false };
4174
4406
  /**
4175
4407
  * Input name
@@ -4317,18 +4549,14 @@ const MgInputSelect = class {
4317
4549
  this.renderOption = (option, index) => (h("option", { key: index, value: `${index}`, selected: JSON.stringify(this.value) === JSON.stringify(option.value), disabled: option.disabled }, option.title));
4318
4550
  }
4319
4551
  validateValue(newValue) {
4320
- setTimeout(() => {
4321
- this.checkValidity();
4322
- if (this.hasDisplayedError) {
4323
- this.setErrorMessage();
4324
- }
4325
- }, 0);
4552
+ this.validityCheckPending = true;
4326
4553
  this.valueChange.emit(newValue);
4327
4554
  }
4328
- validateItems(newValue) {
4555
+ validateItems(newValue, oldValue) {
4329
4556
  // Empty options
4330
4557
  if (newValue.length === 0) {
4331
4558
  this.options = [];
4559
+ this.valueExist = false;
4332
4560
  }
4333
4561
  // String array
4334
4562
  else if (allItemsAreString(newValue)) {
@@ -4354,6 +4582,9 @@ const MgInputSelect = class {
4354
4582
  else {
4355
4583
  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: ${toString(newValue)}.`);
4356
4584
  }
4585
+ // `oldValue` is undefined on init. The value is kept: an unmatched one renders the placeholder.
4586
+ if (oldValue !== undefined)
4587
+ this.validityCheckPending = true;
4357
4588
  }
4358
4589
  watchReadonly(newValue) {
4359
4590
  if (newValue)
@@ -4433,6 +4664,8 @@ const MgInputSelect = class {
4433
4664
  lock: valid ? false : errorMessageLock,
4434
4665
  message: valid ? undefined : errorMessage,
4435
4666
  };
4667
+ // setError() states the validity explicitly, so drop any pending recomputation
4668
+ this.validityCheckPending = false;
4436
4669
  this.setValidity(valid);
4437
4670
  this.setErrorMessage(true);
4438
4671
  this.hasDisplayedError = this.invalid;
@@ -4503,8 +4736,26 @@ const MgInputSelect = class {
4503
4736
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
4504
4737
  return setTimeout(() => {
4505
4738
  this.checkValidity();
4739
+ // the initial check covers the initial value: nothing is pending yet
4740
+ this.validityCheckPending = false;
4506
4741
  }, 0);
4507
4742
  }
4743
+ /**
4744
+ * Recompute validity once the DOM is in sync with `value`.
4745
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
4746
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
4747
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
4748
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
4749
+ */
4750
+ componentDidUpdate() {
4751
+ if (this.validityCheckPending && this.input !== undefined) {
4752
+ this.validityCheckPending = false;
4753
+ this.checkValidity();
4754
+ if (this.hasDisplayedError) {
4755
+ this.setErrorMessage();
4756
+ }
4757
+ }
4758
+ }
4508
4759
  /**
4509
4760
  * Render
4510
4761
  * @returns HTML Element
@@ -4513,7 +4764,7 @@ const MgInputSelect = class {
4513
4764
  const readonlyValue = this.getReadonlyValue();
4514
4765
  // Running index shared across groups, so each option gets a unique value matching its position in getOptions()
4515
4766
  let optionIndex = 0;
4516
- return (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 && h("b", { class: "mg-c-input__readonly-value" }, readonlyValue)) : (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) => {
4767
+ return (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 && h("b", { class: "mg-c-input__readonly-value" }, readonlyValue)) : (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) => {
4517
4768
  if (el !== null)
4518
4769
  this.input = el;
4519
4770
  } }, (!this.placeholderHide || !this.valueExist) && ( // In case passed value does not match any option we display the placeholder
@@ -4587,6 +4838,8 @@ const MgInputText = class {
4587
4838
  // hasDisplayedError (triggered by blur event)
4588
4839
  this.hasDisplayedError = false;
4589
4840
  this.customErrorMessage = { lock: false };
4841
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
4842
+ this.validityCheckPending = false;
4590
4843
  /**
4591
4844
  * Input name
4592
4845
  * If not set the value equals the identifier
@@ -4651,10 +4904,6 @@ const MgInputText = class {
4651
4904
  * Handle input event
4652
4905
  */
4653
4906
  this.handleInput = () => {
4654
- this.checkValidity();
4655
- if (this.hasDisplayedError) {
4656
- this.setErrorMessage();
4657
- }
4658
4907
  this.value = this.input.value;
4659
4908
  };
4660
4909
  /**
@@ -4735,6 +4984,7 @@ const MgInputText = class {
4735
4984
  }
4736
4985
  handleValue(newValue) {
4737
4986
  var _a, _b;
4987
+ this.validityCheckPending = true;
4738
4988
  if ((_a = this.datalistoptions) === null || _a === void 0 ? void 0 : _a.every(isOption$1)) {
4739
4989
  this.valueChange.emit(((_b = this.datalistoptions.find((option) => option.title === newValue)) === null || _b === void 0 ? void 0 : _b.value) || newValue);
4740
4990
  }
@@ -4854,6 +5104,8 @@ const MgInputText = class {
4854
5104
  lock: valid ? false : errorMessageLock,
4855
5105
  message: valid ? undefined : errorMessage,
4856
5106
  };
5107
+ // setError() states the validity explicitly, so drop any pending recomputation
5108
+ this.validityCheckPending = false;
4857
5109
  this.setValidity(valid);
4858
5110
  this.setErrorMessage(true);
4859
5111
  this.hasDisplayedError = this.invalid;
@@ -4926,14 +5178,32 @@ const MgInputText = class {
4926
5178
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
4927
5179
  return setTimeout(() => {
4928
5180
  this.checkValidity();
5181
+ // the initial check covers the initial value: nothing is pending yet
5182
+ this.validityCheckPending = false;
4929
5183
  }, 0);
4930
5184
  }
5185
+ /**
5186
+ * Recompute validity once the DOM is in sync with `value`.
5187
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
5188
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
5189
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
5190
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
5191
+ */
5192
+ componentDidUpdate() {
5193
+ if (this.validityCheckPending && this.input !== undefined) {
5194
+ this.validityCheckPending = false;
5195
+ this.checkValidity();
5196
+ if (this.hasDisplayedError) {
5197
+ this.setErrorMessage();
5198
+ }
5199
+ }
5200
+ }
4931
5201
  /**
4932
5202
  * Render
4933
5203
  * @returns HTML Element
4934
5204
  */
4935
5205
  render() {
4936
- return (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
5206
+ return (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
4937
5207
  ? this.value && this.renderReadonly()
4938
5208
  : [
4939
5209
  h("div", { key: "input", class: "mg-c-input__input-group-container", style: {
@@ -5002,6 +5272,8 @@ const MgInputTextarea = class {
5002
5272
  this.inputValid = createEvent(this, "input-valid", 7);
5003
5273
  // hasDisplayedError (triggered by blur event)
5004
5274
  this.hasDisplayedError = false;
5275
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
5276
+ this.validityCheckPending = false;
5005
5277
  this.customErrorMessage = { lock: false };
5006
5278
  /**
5007
5279
  * Input name
@@ -5060,10 +5332,6 @@ const MgInputTextarea = class {
5060
5332
  * Handle input event
5061
5333
  */
5062
5334
  this.handleInput = () => {
5063
- if (this.hasDisplayedError) {
5064
- this.checkValidity();
5065
- this.setErrorMessage();
5066
- }
5067
5335
  this.value = this.input.value;
5068
5336
  };
5069
5337
  /**
@@ -5113,7 +5381,7 @@ const MgInputTextarea = class {
5113
5381
  };
5114
5382
  }
5115
5383
  handleValue(newValue) {
5116
- this.checkValidity();
5384
+ this.validityCheckPending = true;
5117
5385
  this.valueChange.emit(newValue);
5118
5386
  }
5119
5387
  watchReadonly(newValue) {
@@ -5207,6 +5475,8 @@ const MgInputTextarea = class {
5207
5475
  lock: valid ? false : errorMessageLock,
5208
5476
  message: valid ? undefined : errorMessage,
5209
5477
  };
5478
+ // setError() states the validity explicitly, so drop any pending recomputation
5479
+ this.validityCheckPending = false;
5210
5480
  this.setValidity(valid);
5211
5481
  this.setErrorMessage(true);
5212
5482
  this.hasDisplayedError = this.invalid;
@@ -5274,14 +5544,32 @@ const MgInputTextarea = class {
5274
5544
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
5275
5545
  return setTimeout(() => {
5276
5546
  this.checkValidity();
5547
+ // the initial check covers the initial value: nothing is pending yet
5548
+ this.validityCheckPending = false;
5277
5549
  }, 0);
5278
5550
  }
5551
+ /**
5552
+ * Recompute validity once the DOM is in sync with `value`.
5553
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
5554
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
5555
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
5556
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
5557
+ */
5558
+ componentDidUpdate() {
5559
+ if (this.validityCheckPending && this.input !== undefined) {
5560
+ this.validityCheckPending = false;
5561
+ this.checkValidity();
5562
+ if (this.hasDisplayedError) {
5563
+ this.setErrorMessage();
5564
+ }
5565
+ }
5566
+ }
5279
5567
  /**
5280
5568
  * Render
5281
5569
  * @returns HTML Element
5282
5570
  */
5283
5571
  render() {
5284
- return (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 && h("b", { class: "mg-c-input__readonly-value" }, this.value)) : (h("div", { class: "mg-c-input__input-group-container" }, h("textarea", { class: {
5572
+ return (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 && h("b", { class: "mg-c-input__readonly-value" }, this.value)) : (h("div", { class: "mg-c-input__input-group-container" }, h("textarea", { class: {
5285
5573
  'mg-c-input__box': true,
5286
5574
  'mg-c-input__box--width': true,
5287
5575
  'mg-c-input__box--resizable': this.resizable === 'both',
@@ -6560,7 +6848,7 @@ MgMessage.style = mgMessageCss();
6560
6848
  */
6561
6849
  const dialogRoles = ['dialog', 'alertdialog'];
6562
6850
 
6563
- 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)}`;
6851
+ 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)}`;
6564
6852
 
6565
6853
  const MgModal = class {
6566
6854
  constructor(hostRef) {