@mgdis/mg-components 6.26.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 (106) hide show
  1. package/dist/cjs/loader.cjs.js +1 -1
  2. package/dist/cjs/mg-action-more_36.cjs.entry.js +506 -161
  3. package/dist/cjs/mg-components.cjs.js +1 -1
  4. package/dist/cjs/mg-illustration.cjs.entry.js +1 -1
  5. package/dist/cjs/mg-input-combobox.cjs.entry.js +60 -8
  6. package/dist/cjs/mg-input-file.cjs.entry.js +26 -3
  7. package/dist/cjs/mg-input-rich-text-editor.cjs.entry.js +2304 -1131
  8. package/dist/cjs/mg-loader.cjs.entry.js +1 -1
  9. package/dist/cjs/mg-progress.cjs.entry.js +1 -1
  10. package/dist/collection/assets/illustrations/index.js +1 -1
  11. package/dist/collection/assets/jodit/jodit.css +14 -1
  12. package/dist/collection/components/atoms/mg-button/mg-button.js +86 -13
  13. package/dist/collection/components/atoms/mg-tooltip/mg-tooltip.js +103 -55
  14. package/dist/collection/components/molecules/inputs/mg-input-checkbox/mg-input-checkbox.js +25 -4
  15. package/dist/collection/components/molecules/inputs/mg-input-combobox/mg-input-combobox.js +59 -7
  16. package/dist/collection/components/molecules/inputs/mg-input-date/mg-input-date.js +25 -9
  17. package/dist/collection/components/molecules/inputs/mg-input-file/mg-input-file.js +25 -2
  18. package/dist/collection/components/molecules/inputs/mg-input-numeric/mg-input-numeric.js +24 -6
  19. package/dist/collection/components/molecules/inputs/mg-input-password/mg-input-password.js +24 -5
  20. package/dist/collection/components/molecules/inputs/mg-input-radio/mg-input-radio.js +64 -16
  21. package/dist/collection/components/molecules/inputs/mg-input-select/mg-input-select.js +32 -10
  22. package/dist/collection/components/molecules/inputs/mg-input-text/mg-input-text.js +24 -5
  23. package/dist/collection/components/molecules/inputs/mg-input-textarea/mg-input-textarea.js +24 -6
  24. package/dist/collection/components/molecules/mg-details/mg-details.js +5 -1
  25. package/dist/collection/components/molecules/mg-form/mg-form.js +83 -9
  26. package/dist/collection/components/molecules/mg-illustrated-message/mg-illustrated-message.js +1 -1
  27. package/dist/collection/components/molecules/mg-loader/mg-loader.js +1 -1
  28. package/dist/collection/components/molecules/mg-message/mg-message.js +3 -3
  29. package/dist/collection/components/molecules/mg-modal/mg-modal.js +2 -2
  30. package/dist/collection/components/molecules/mg-panel/mg-panel.js +1 -1
  31. package/dist/collection/components/molecules/mg-popover/mg-popover-content/mg-popover-content.js +1 -1
  32. package/dist/collection/components/molecules/mg-popover/mg-popover.js +1 -1
  33. package/dist/collection/components/molecules/mg-progress/mg-progress.js +1 -1
  34. package/dist/collection/components/molecules/mg-skip-links/mg-skip-links.js +1 -1
  35. package/dist/collection/components/molecules/mg-tabs/mg-tabs.js +1 -1
  36. package/dist/components/mg-button2.js +1 -1
  37. package/dist/components/mg-details.js +1 -1
  38. package/dist/components/mg-form.js +1 -1
  39. package/dist/components/mg-illustrated-message.js +1 -1
  40. package/dist/components/mg-illustration.js +1 -1
  41. package/dist/components/mg-input-checkbox.js +1 -1
  42. package/dist/components/mg-input-combobox.js +1 -1
  43. package/dist/components/mg-input-date.js +1 -1
  44. package/dist/components/mg-input-file.js +1 -1
  45. package/dist/components/mg-input-numeric.js +1 -1
  46. package/dist/components/mg-input-password.js +1 -1
  47. package/dist/components/mg-input-radio.js +1 -1
  48. package/dist/components/mg-input-rich-text-editor.js +285 -285
  49. package/dist/components/mg-input-select2.js +1 -1
  50. package/dist/components/mg-input-text2.js +1 -1
  51. package/dist/components/mg-input-textarea.js +1 -1
  52. package/dist/components/mg-input-toggle.js +1 -1
  53. package/dist/components/mg-input2.js +1 -1
  54. package/dist/components/mg-loader2.js +1 -1
  55. package/dist/components/mg-menu-item2.js +1 -1
  56. package/dist/components/mg-message2.js +1 -1
  57. package/dist/components/mg-modal.js +1 -1
  58. package/dist/components/mg-panel.js +1 -1
  59. package/dist/components/mg-popover-content2.js +1 -1
  60. package/dist/components/mg-popover2.js +1 -1
  61. package/dist/components/mg-progress.js +1 -1
  62. package/dist/components/mg-skip-links.js +1 -1
  63. package/dist/components/mg-tabs2.js +1 -1
  64. package/dist/components/mg-tooltip2.js +1 -1
  65. package/dist/custom-elements.json +12 -2
  66. package/dist/esm/loader.js +1 -1
  67. package/dist/esm/mg-action-more_36.entry.js +507 -162
  68. package/dist/esm/mg-components.js +1 -1
  69. package/dist/esm/mg-illustration.entry.js +1 -1
  70. package/dist/esm/mg-input-combobox.entry.js +60 -8
  71. package/dist/esm/mg-input-file.entry.js +26 -3
  72. package/dist/esm/mg-input-rich-text-editor.entry.js +2304 -1131
  73. package/dist/esm/mg-loader.entry.js +1 -1
  74. package/dist/esm/mg-progress.entry.js +1 -1
  75. package/dist/mg-components/mg-components.esm.js +1 -1
  76. package/dist/mg-components/p-2198f145.entry.js +1 -0
  77. package/dist/mg-components/p-85ebacd6.entry.js +1 -0
  78. package/dist/mg-components/{p-96fc508e.entry.js → p-c2beff2c.entry.js} +1 -1
  79. package/dist/mg-components/p-d4a4eb78.entry.js +1 -0
  80. package/dist/mg-components/p-e8b5e8d8.entry.js +1667 -0
  81. package/dist/mg-components/p-f308e2e4.entry.js +1 -0
  82. package/dist/mg-components/p-fdcce8d5.entry.js +1 -0
  83. package/dist/types/components/atoms/mg-button/mg-button.d.ts +41 -2
  84. package/dist/types/components/atoms/mg-tooltip/mg-tooltip.d.ts +33 -6
  85. package/dist/types/components/molecules/inputs/mg-input/mg-input.conf.d.ts +1 -0
  86. package/dist/types/components/molecules/inputs/mg-input-checkbox/mg-input-checkbox.d.ts +9 -0
  87. package/dist/types/components/molecules/inputs/mg-input-combobox/mg-input-combobox.d.ts +21 -1
  88. package/dist/types/components/molecules/inputs/mg-input-date/mg-input-date.d.ts +9 -0
  89. package/dist/types/components/molecules/inputs/mg-input-file/mg-input-file.d.ts +9 -0
  90. package/dist/types/components/molecules/inputs/mg-input-numeric/mg-input-numeric.d.ts +9 -0
  91. package/dist/types/components/molecules/inputs/mg-input-password/mg-input-password.d.ts +9 -0
  92. package/dist/types/components/molecules/inputs/mg-input-radio/mg-input-radio.d.ts +29 -2
  93. package/dist/types/components/molecules/inputs/mg-input-select/mg-input-select.d.ts +10 -1
  94. package/dist/types/components/molecules/inputs/mg-input-text/mg-input-text.d.ts +9 -0
  95. package/dist/types/components/molecules/inputs/mg-input-textarea/mg-input-textarea.d.ts +9 -0
  96. package/dist/types/components/molecules/mg-form/mg-form.d.ts +22 -0
  97. package/dist/types/components.d.ts +10 -2
  98. package/ide/intellij/web-types.json +4 -4
  99. package/ide/vscode/html-custom-data.json +3 -3
  100. package/package.json +19 -19
  101. package/dist/mg-components/p-206505b5.entry.js +0 -1667
  102. package/dist/mg-components/p-3ad7441c.entry.js +0 -1
  103. package/dist/mg-components/p-5dff879a.entry.js +0 -1
  104. package/dist/mg-components/p-b20f3698.entry.js +0 -1
  105. package/dist/mg-components/p-bf2252c9.entry.js +0 -1
  106. package/dist/mg-components/p-f8e58181.entry.js +0 -1
@@ -9,6 +9,8 @@ export class MgInputDate {
9
9
  constructor() {
10
10
  // hasDisplayedError (triggered by blur event)
11
11
  this.hasDisplayedError = false;
12
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
13
+ this.validityCheckPending = false;
12
14
  this.customErrorMessage = { lock: false };
13
15
  /**
14
16
  * Input name
@@ -80,10 +82,6 @@ export class MgInputDate {
80
82
  * Handle input event
81
83
  */
82
84
  this.handleInput = () => {
83
- this.checkValidity();
84
- if (this.hasDisplayedError) {
85
- this.setErrorMessage();
86
- }
87
85
  this.value = this.input.value;
88
86
  };
89
87
  /**
@@ -105,11 +103,8 @@ export class MgInputDate {
105
103
  // when we reset the field we need to test a blank value.
106
104
  if (['Delete', 'Backspace'].includes(event.key)) {
107
105
  event.preventDefault();
106
+ // validity is recomputed in componentDidUpdate, once the input actually holds the blank value
108
107
  this.value = null;
109
- this.checkValidity();
110
- if (this.hasDisplayedError) {
111
- this.setErrorMessage();
112
- }
113
108
  }
114
109
  };
115
110
  /**
@@ -196,6 +191,7 @@ export class MgInputDate {
196
191
  };
197
192
  }
198
193
  validateValue(newValue) {
194
+ this.validityCheckPending = true;
199
195
  // When the input is not fully completed or has been cleared, the value becomes an empty string.
200
196
  if (['', undefined].includes(newValue)) {
201
197
  // need to force value update to `null` to prevent extra render
@@ -288,6 +284,8 @@ export class MgInputDate {
288
284
  lock: valid ? false : errorMessageLock,
289
285
  message: valid ? undefined : errorMessage,
290
286
  };
287
+ // setError() states the validity explicitly, so drop any pending recomputation
288
+ this.validityCheckPending = false;
291
289
  this.setValidity(valid);
292
290
  this.setErrorMessage(true);
293
291
  this.hasDisplayedError = this.invalid;
@@ -358,14 +356,32 @@ export class MgInputDate {
358
356
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
359
357
  return setTimeout(() => {
360
358
  this.checkValidity();
359
+ // the initial check covers the initial value: nothing is pending yet
360
+ this.validityCheckPending = false;
361
361
  }, 0);
362
362
  }
363
+ /**
364
+ * Recompute validity once the DOM is in sync with `value`.
365
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
366
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
367
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
368
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
369
+ */
370
+ componentDidUpdate() {
371
+ if (this.validityCheckPending && this.input !== undefined) {
372
+ this.validityCheckPending = false;
373
+ this.checkValidity();
374
+ if (this.hasDisplayedError) {
375
+ this.setErrorMessage();
376
+ }
377
+ }
378
+ }
363
379
  /**
364
380
  * Render
365
381
  * @returns HTML Element
366
382
  */
367
383
  render() {
368
- 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) => {
384
+ 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) => {
369
385
  if (el !== null)
370
386
  this.input = el;
371
387
  } }))));
@@ -12,6 +12,8 @@ export class MgInputFile {
12
12
  constructor() {
13
13
  // hasDisplayedError (triggered by blur event)
14
14
  this.hasDisplayedError = false;
15
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
16
+ this.validityCheckPending = false;
15
17
  this.customErrorMessage = { lock: false };
16
18
  /**
17
19
  * Input name
@@ -172,6 +174,7 @@ export class MgInputFile {
172
174
  };
173
175
  }
174
176
  watchValue(newValue) {
177
+ this.validityCheckPending = true;
175
178
  if (allItemsAreString(newValue)) {
176
179
  this.files = this.value.map((fileName) => new File([], fileName));
177
180
  }
@@ -277,6 +280,8 @@ export class MgInputFile {
277
280
  lock: valid ? false : errorMessageLock,
278
281
  message: valid ? undefined : errorMessage,
279
282
  };
283
+ // setError() states the validity explicitly, so drop any pending recomputation
284
+ this.validityCheckPending = false;
280
285
  this.setValidity(valid);
281
286
  this.setErrorMessage(true);
282
287
  this.hasDisplayedError = this.invalid;
@@ -328,16 +333,34 @@ export class MgInputFile {
328
333
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
329
334
  return setTimeout(() => {
330
335
  this.checkValidity();
336
+ // the initial check covers the initial value: nothing is pending yet
337
+ this.validityCheckPending = false;
331
338
  }, 0);
332
339
  }
340
+ /**
341
+ * Recompute validity once the DOM is in sync with `value`.
342
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
343
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
344
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
345
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
346
+ */
347
+ componentDidUpdate() {
348
+ if (this.validityCheckPending && this.inputElement !== undefined) {
349
+ this.validityCheckPending = false;
350
+ this.checkValidity();
351
+ if (this.hasDisplayedError) {
352
+ this.setErrorMessage();
353
+ }
354
+ }
355
+ }
333
356
  /**
334
357
  * Render
335
358
  * @returns HTML Element
336
359
  */
337
360
  render() {
338
- return (h("mg-input", { key: '3c4c0ebdd1661e381f0002b2ae68dcf454097913', label: this.label, identifier: this.identifier, class: this.classCollection.join(), labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: this.required, tooltip: this.tooltip, tooltipPosition: this.tooltipPosition, helpText: this.helpText, errorMessage: this.errorMessage }, h("div", { key: 'bd7665fe8c6e01ec4e04fbbbf9cc21ccc4128b17', class: "mg-c-input__file-container" }, h("input", { key: 'f27d10160115ec1ff5163a1c55aa2658779c6fd7', name: this.name, id: this.identifier, class: "mg-u-visually-hidden", type: "file", multiple: this.multiple === true, capture: this.capture, accept: this.accept, disabled: this.disabled, required: this.files.length === 0 && this.required, onFocus: this.handleInputFocus, onBlur: this.handleInputBlur, onChange: this.handleInputChange, "aria-invalid": (this.invalid === true).toString(), ref: (el) => {
361
+ return (h("mg-input", { key: 'f1613e1105c1eea0c08624ffc270a74889d85ce0', label: this.label, identifier: this.identifier, class: this.classCollection.join(), labelOnTop: this.labelOnTop, labelHide: this.labelHide, required: this.required, tooltip: this.tooltip, tooltipPosition: this.tooltipPosition, helpText: this.helpText, errorMessage: this.errorMessage }, h("div", { key: '23897ae896de9569ea68bf88e26f1349eb5bc909', class: "mg-c-input__file-container" }, h("input", { key: '0601a8af1c3f17331cb671184353b8b63a5f4fa8', name: this.name, id: this.identifier, class: "mg-u-visually-hidden", type: "file", multiple: this.multiple === true, capture: this.capture, accept: this.accept, disabled: this.disabled, required: this.files.length === 0 && this.required, onFocus: this.handleInputFocus, onBlur: this.handleInputBlur, onChange: this.handleInputChange, "aria-invalid": (this.invalid === true).toString(), ref: (el) => {
339
362
  this.inputElement = el;
340
- } }), h("mg-button", { key: 'd16c4cbd95d676bcddb895659aab37f0f9c396eb', variant: "secondary", class: "mg-c-input__file-button", type: "button", disabled: this.disabled, onClick: this.handleButtonClick, onBlur: this.handleButtonBlur, tabindex: "-1", ref: (el) => {
363
+ } }), h("mg-button", { key: '5ac1076a443cc1a0bb2d7168b1d248d31faeedfd', variant: "secondary", class: "mg-c-input__file-button", type: "button", disabled: this.disabled, onClick: this.handleButtonClick, onBlur: this.handleButtonBlur, tabindex: "-1", ref: (el) => {
341
364
  this.fileButtonElement = el;
342
365
  } }, this.messages.input.file.browse), this.renderFiles())));
343
366
  }
@@ -12,6 +12,8 @@ export class MgInputNumeric {
12
12
  constructor() {
13
13
  // hasDisplayedError (triggered by blur event)
14
14
  this.hasDisplayedError = false;
15
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
16
+ this.validityCheckPending = false;
15
17
  this.customErrorMessage = { lock: false };
16
18
  /**
17
19
  * Input name
@@ -78,11 +80,6 @@ export class MgInputNumeric {
78
80
  */
79
81
  this.handleInput = () => {
80
82
  this.value = this.input.value;
81
- // Check validity
82
- this.checkValidity();
83
- if (this.hasDisplayedError) {
84
- this.setErrorMessage();
85
- }
86
83
  };
87
84
  /**
88
85
  * Handle focus event
@@ -232,6 +229,7 @@ export class MgInputNumeric {
232
229
  }
233
230
  validateValue(newValue) {
234
231
  var _a;
232
+ this.validityCheckPending = true;
235
233
  // has input value is always render as string we get a stringified value or a '' for nullish value, so we test string type
236
234
  if (typeof newValue === 'string') {
237
235
  // Check if integer value starts with '0' (leading zero)
@@ -352,6 +350,8 @@ export class MgInputNumeric {
352
350
  lock: valid ? false : errorMessageLock,
353
351
  message: valid ? undefined : errorMessage,
354
352
  };
353
+ // setError() states the validity explicitly, so drop any pending recomputation
354
+ this.validityCheckPending = false;
355
355
  this.setValidity(valid);
356
356
  this.setErrorMessage(true);
357
357
  this.hasDisplayedError = this.invalid;
@@ -464,14 +464,32 @@ export class MgInputNumeric {
464
464
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
465
465
  return setTimeout(() => {
466
466
  this.checkValidity();
467
+ // the initial check covers the initial value: nothing is pending yet
468
+ this.validityCheckPending = false;
467
469
  }, 0);
468
470
  }
471
+ /**
472
+ * Recompute validity once the DOM is in sync with `value`.
473
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
474
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
475
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
476
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
477
+ */
478
+ componentDidUpdate() {
479
+ if (this.validityCheckPending && this.input !== undefined) {
480
+ this.validityCheckPending = false;
481
+ this.checkValidity();
482
+ if (this.hasDisplayedError) {
483
+ this.setErrorMessage();
484
+ }
485
+ }
486
+ }
469
487
  /**
470
488
  * Render
471
489
  * @returns HTML Element
472
490
  */
473
491
  render() {
474
- 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
492
+ 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
475
493
  ? this.readonlyValue && this.renderReadonly()
476
494
  : [
477
495
  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) => {
@@ -8,6 +8,8 @@ export class MgInputPassword {
8
8
  // hasDisplayedError (triggered by blur event)
9
9
  this.hasDisplayedError = false;
10
10
  this.customErrorMessage = { lock: false };
11
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
12
+ this.validityCheckPending = false;
11
13
  /**
12
14
  * Input name
13
15
  * If not set the value equals the identifier
@@ -53,10 +55,6 @@ export class MgInputPassword {
53
55
  * Handle input event
54
56
  */
55
57
  this.handleInput = () => {
56
- this.checkValidity();
57
- if (this.hasDisplayedError) {
58
- this.setErrorMessage();
59
- }
60
58
  this.value = this.input.value;
61
59
  };
62
60
  /**
@@ -101,6 +99,7 @@ export class MgInputPassword {
101
99
  };
102
100
  }
103
101
  handleValue(newValue) {
102
+ this.validityCheckPending = true;
104
103
  this.valueChange.emit(newValue);
105
104
  }
106
105
  watchReadonly(newValue) {
@@ -181,6 +180,8 @@ export class MgInputPassword {
181
180
  lock: valid ? false : errorMessageLock,
182
181
  message: valid ? undefined : errorMessage,
183
182
  };
183
+ // setError() states the validity explicitly, so drop any pending recomputation
184
+ this.validityCheckPending = false;
184
185
  this.setValidity(valid);
185
186
  this.setErrorMessage(true);
186
187
  this.hasDisplayedError = this.invalid;
@@ -243,15 +244,33 @@ export class MgInputPassword {
243
244
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
244
245
  return setTimeout(() => {
245
246
  this.checkValidity();
247
+ // the initial check covers the initial value: nothing is pending yet
248
+ this.validityCheckPending = false;
246
249
  }, 0);
247
250
  }
251
+ /**
252
+ * Recompute validity once the DOM is in sync with `value`.
253
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
254
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
255
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
256
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
257
+ */
258
+ componentDidUpdate() {
259
+ if (this.validityCheckPending && this.input !== undefined) {
260
+ this.validityCheckPending = false;
261
+ this.checkValidity();
262
+ if (this.hasDisplayedError) {
263
+ this.setErrorMessage();
264
+ }
265
+ }
266
+ }
248
267
  /**
249
268
  * Render
250
269
  * @returns HTML Element
251
270
  */
252
271
  render() {
253
272
  const displayPasswordIcon = this.displayPassword ? 'eye-slash' : 'eye';
254
- 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) => {
273
+ 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) => {
255
274
  if (el !== null)
256
275
  this.input = el;
257
276
  } }), 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 }))))));
@@ -16,12 +16,12 @@ export class MgInputRadio {
16
16
  /************
17
17
  * Internal *
18
18
  ************/
19
- // HTML selector
20
- this.inputs = [];
21
19
  // classes
22
20
  this.classVerticalList = 'mg-c-input--vertical-list';
23
21
  // hasDisplayedError (triggered by blur event)
24
22
  this.hasDisplayedError = false;
23
+ // raised when `value` or `items` changed, so componentDidUpdate revalidates once the DOM is in sync
24
+ this.validityCheckPending = false;
25
25
  this.customErrorMessage = { lock: false };
26
26
  /**
27
27
  * Input name
@@ -65,7 +65,6 @@ export class MgInputRadio {
65
65
  * @param event - input event
66
66
  */
67
67
  this.handleInput = (event) => {
68
- this.checkValidity();
69
68
  this.value = this.options[event.target.value].value;
70
69
  };
71
70
  /**
@@ -78,11 +77,23 @@ export class MgInputRadio {
78
77
  // reset guard
79
78
  this.handlerInProgress = undefined;
80
79
  };
80
+ /**
81
+ * Check if the required constraint is fulfilled
82
+ * The native group validity cannot answer it: a browser keeps the checked member a group had, even
83
+ * once that input is removed from the DOM. Options that cannot be selected are ignored.
84
+ * @returns required constraint is fulfilled
85
+ */
86
+ this.validateRequired = () => {
87
+ const selectableOptions = this.options.filter((option) => !option.disabled);
88
+ return (!this.required ||
89
+ selectableOptions.length < 1 ||
90
+ selectableOptions.some((option) => this.isSelectedValue(option.value)));
91
+ };
81
92
  /**
82
93
  * Check if input is valid
83
94
  */
84
95
  this.checkValidity = () => {
85
- this.setValidity(this.readonly || this.disabled || this.getInvalidElement() === undefined);
96
+ this.setValidity(this.readonly || this.disabled || (this.getInvalidElement() === undefined && this.validateRequired()));
86
97
  };
87
98
  /**
88
99
  * Set input error message
@@ -98,7 +109,7 @@ export class MgInputRadio {
98
109
  this.errorMessage = this.customErrorMessage.message;
99
110
  }
100
111
  // required
101
- else if (invalidElement.validity.valueMissing) {
112
+ else if (invalidElement === undefined || invalidElement.validity.valueMissing) {
102
113
  this.errorMessage = this.messages.errors.required;
103
114
  }
104
115
  }
@@ -107,11 +118,24 @@ export class MgInputRadio {
107
118
  * get invalid element
108
119
  * @returns element
109
120
  */
110
- this.getInvalidElement = () => this.inputs.find((input) => !input.disabled && !input.readOnly && !input.checkValidity());
121
+ this.getInvalidElement = () => this.getInputs().find((input) => !input.disabled && !input.readOnly && !input.checkValidity());
122
+ /**
123
+ * Get the rendered radio inputs
124
+ * Read from the DOM: references kept from a render would outlive the inputs they point to.
125
+ * @returns rendered radio inputs
126
+ */
127
+ this.getInputs = () => Array.from(this.element.shadowRoot.querySelectorAll('input[type="radio"]'));
128
+ /**
129
+ * Check if a value is the selected one
130
+ * @param optionValue - option value to compare to the component value
131
+ * @returns option value is the selected one
132
+ */
133
+ this.isSelectedValue = (optionValue) => JSON.stringify(this.value) === JSON.stringify(optionValue);
111
134
  }
112
135
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
136
  watchValue(newValue) {
114
137
  var _a;
138
+ this.validityCheckPending = true;
115
139
  if (allItemsAreString(this.items) && typeof newValue === 'string') {
116
140
  this.readonlyValue = newValue;
117
141
  }
@@ -123,7 +147,7 @@ export class MgInputRadio {
123
147
  }
124
148
  this.valueChange.emit(newValue);
125
149
  }
126
- validateItems(newValue) {
150
+ validateItems(newValue, oldValue) {
127
151
  // Validate if items have required min length
128
152
  if (typeof newValue === 'object' && newValue.length < 2) {
129
153
  throw new Error(`<mg-input-radio> prop "items" require at least 2 items. Passed value: ${toString(newValue)}.`);
@@ -139,6 +163,14 @@ export class MgInputRadio {
139
163
  else {
140
164
  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)}.`);
141
165
  }
166
+ // `oldValue` is undefined on init
167
+ if (oldValue !== undefined) {
168
+ // the options cannot display a value they no longer offer, drop it
169
+ if (this.value !== undefined && !this.options.some((option) => this.isSelectedValue(option.value))) {
170
+ this.value = undefined;
171
+ }
172
+ this.validityCheckPending = true;
173
+ }
142
174
  }
143
175
  watchInputVerticalList(newValue) {
144
176
  if (newValue)
@@ -153,7 +185,7 @@ export class MgInputRadio {
153
185
  this.classCollection.delete(classReadonly);
154
186
  }
155
187
  handleValidityChange(newValue, _oldValue, prop) {
156
- this.inputs.forEach((input) => {
188
+ this.getInputs().forEach((input) => {
157
189
  input[prop] = newValue;
158
190
  });
159
191
  this.checkValidity();
@@ -172,9 +204,8 @@ export class MgInputRadio {
172
204
  * Set focus on input.
173
205
  */
174
206
  async setFocus() {
175
- if (this.inputs.length > 0) {
176
- this.inputs[0].focus();
177
- }
207
+ var _a;
208
+ (_a = this.element.shadowRoot.querySelector('input')) === null || _a === void 0 ? void 0 : _a.focus();
178
209
  }
179
210
  /**
180
211
  * Display input error if it exists.
@@ -216,6 +247,8 @@ export class MgInputRadio {
216
247
  lock: valid ? false : errorMessageLock,
217
248
  message: valid ? undefined : errorMessage,
218
249
  };
250
+ // setError() states the validity explicitly, so drop any pending recomputation
251
+ this.validityCheckPending = false;
219
252
  this.setValidity(valid);
220
253
  this.setErrorMessage(true);
221
254
  this.hasDisplayedError = this.invalid;
@@ -281,20 +314,35 @@ export class MgInputRadio {
281
314
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
282
315
  return setTimeout(() => {
283
316
  this.checkValidity();
317
+ // the initial check covers the initial value: nothing is pending yet
318
+ this.validityCheckPending = false;
284
319
  }, 0);
285
320
  }
321
+ /**
322
+ * Recompute validity once the DOM is in sync with `value`.
323
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
324
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
325
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
326
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
327
+ */
328
+ componentDidUpdate() {
329
+ if (this.validityCheckPending && this.getInputs().length > 0) {
330
+ this.validityCheckPending = false;
331
+ this.checkValidity();
332
+ if (this.hasDisplayedError) {
333
+ this.setErrorMessage();
334
+ }
335
+ }
336
+ }
286
337
  /**
287
338
  * Render
288
339
  * @returns HTML Element
289
340
  */
290
341
  render() {
291
- 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: {
342
+ 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: {
292
343
  'mg-c-input__input-group': true,
293
344
  'mg-c-input__input-group--disabled': this.disabled || input.disabled,
294
- } }, 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) => {
295
- if (el !== null)
296
- this.inputs[index] = el;
297
- } }), h("label", { htmlFor: this.identifier + '_' + index }, input.title))))))));
345
+ } }, 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))))))));
298
346
  }
299
347
  static get is() { return "mg-input-radio"; }
300
348
  static get encapsulation() { return "shadow"; }
@@ -24,8 +24,9 @@ const isOptGroup = (optgroup) => typeof optgroup === 'object' &&
24
24
  typeof optgroup.group === 'string' &&
25
25
  allItemsAreOptions(optgroup.options);
26
26
  /**
27
- * Ensure options can be unambiguously differentiated by their title
27
+ * Report, without enforcing, options that cannot be unambiguously differentiated by their title
28
28
  * Two options can share the same title only when they belong to different groups
29
+ * 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
29
30
  * @param items - select options to validate
30
31
  */
31
32
  const validateUniqueTitles = (items) => {
@@ -37,7 +38,7 @@ const validateUniqueTitles = (items) => {
37
38
  const titles = titlesByGroup.get(group);
38
39
  if (titles.has(title)) {
39
40
  const groupContext = group === undefined ? 'outside of a group' : `within the group "${group}"`;
40
- throw new Error(`<mg-input-select> prop "items" must not contain options with the same "title" ${groupContext}. Duplicated title: "${title}".`);
41
+ console.error(`<mg-input-select> prop "items" must not contain options with the same "title" ${groupContext}. Duplicated title: "${title}".`);
41
42
  }
42
43
  titles.add(title);
43
44
  });
@@ -70,6 +71,8 @@ export class MgInputSelect {
70
71
  constructor() {
71
72
  // hasDisplayedError (triggered by blur event)
72
73
  this.hasDisplayedError = false;
74
+ // raised when `value` or `items` changed, so componentDidUpdate revalidates once the DOM is in sync
75
+ this.validityCheckPending = false;
73
76
  this.customErrorMessage = { lock: false };
74
77
  /**
75
78
  * Input name
@@ -217,18 +220,14 @@ export class MgInputSelect {
217
220
  this.renderOption = (option, index) => (h("option", { key: index, value: `${index}`, selected: JSON.stringify(this.value) === JSON.stringify(option.value), disabled: option.disabled }, option.title));
218
221
  }
219
222
  validateValue(newValue) {
220
- setTimeout(() => {
221
- this.checkValidity();
222
- if (this.hasDisplayedError) {
223
- this.setErrorMessage();
224
- }
225
- }, 0);
223
+ this.validityCheckPending = true;
226
224
  this.valueChange.emit(newValue);
227
225
  }
228
- validateItems(newValue) {
226
+ validateItems(newValue, oldValue) {
229
227
  // Empty options
230
228
  if (newValue.length === 0) {
231
229
  this.options = [];
230
+ this.valueExist = false;
232
231
  }
233
232
  // String array
234
233
  else if (allItemsAreString(newValue)) {
@@ -254,6 +253,9 @@ export class MgInputSelect {
254
253
  else {
255
254
  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)}.`);
256
255
  }
256
+ // `oldValue` is undefined on init. The value is kept: an unmatched one renders the placeholder.
257
+ if (oldValue !== undefined)
258
+ this.validityCheckPending = true;
257
259
  }
258
260
  watchReadonly(newValue) {
259
261
  if (newValue)
@@ -333,6 +335,8 @@ export class MgInputSelect {
333
335
  lock: valid ? false : errorMessageLock,
334
336
  message: valid ? undefined : errorMessage,
335
337
  };
338
+ // setError() states the validity explicitly, so drop any pending recomputation
339
+ this.validityCheckPending = false;
336
340
  this.setValidity(valid);
337
341
  this.setErrorMessage(true);
338
342
  this.hasDisplayedError = this.invalid;
@@ -403,8 +407,26 @@ export class MgInputSelect {
403
407
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
404
408
  return setTimeout(() => {
405
409
  this.checkValidity();
410
+ // the initial check covers the initial value: nothing is pending yet
411
+ this.validityCheckPending = false;
406
412
  }, 0);
407
413
  }
414
+ /**
415
+ * Recompute validity once the DOM is in sync with `value`.
416
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
417
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
418
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
419
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
420
+ */
421
+ componentDidUpdate() {
422
+ if (this.validityCheckPending && this.input !== undefined) {
423
+ this.validityCheckPending = false;
424
+ this.checkValidity();
425
+ if (this.hasDisplayedError) {
426
+ this.setErrorMessage();
427
+ }
428
+ }
429
+ }
408
430
  /**
409
431
  * Render
410
432
  * @returns HTML Element
@@ -413,7 +435,7 @@ export class MgInputSelect {
413
435
  const readonlyValue = this.getReadonlyValue();
414
436
  // Running index shared across groups, so each option gets a unique value matching its position in getOptions()
415
437
  let optionIndex = 0;
416
- 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) => {
438
+ 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) => {
417
439
  if (el !== null)
418
440
  this.input = el;
419
441
  } }, (!this.placeholderHide || !this.valueExist) && ( // In case passed value does not match any option we display the placeholder
@@ -34,6 +34,8 @@ export class MgInputText {
34
34
  // hasDisplayedError (triggered by blur event)
35
35
  this.hasDisplayedError = false;
36
36
  this.customErrorMessage = { lock: false };
37
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
38
+ this.validityCheckPending = false;
37
39
  /**
38
40
  * Input name
39
41
  * If not set the value equals the identifier
@@ -98,10 +100,6 @@ export class MgInputText {
98
100
  * Handle input event
99
101
  */
100
102
  this.handleInput = () => {
101
- this.checkValidity();
102
- if (this.hasDisplayedError) {
103
- this.setErrorMessage();
104
- }
105
103
  this.value = this.input.value;
106
104
  };
107
105
  /**
@@ -182,6 +180,7 @@ export class MgInputText {
182
180
  }
183
181
  handleValue(newValue) {
184
182
  var _a, _b;
183
+ this.validityCheckPending = true;
185
184
  if ((_a = this.datalistoptions) === null || _a === void 0 ? void 0 : _a.every(isOption)) {
186
185
  this.valueChange.emit(((_b = this.datalistoptions.find((option) => option.title === newValue)) === null || _b === void 0 ? void 0 : _b.value) || newValue);
187
186
  }
@@ -301,6 +300,8 @@ export class MgInputText {
301
300
  lock: valid ? false : errorMessageLock,
302
301
  message: valid ? undefined : errorMessage,
303
302
  };
303
+ // setError() states the validity explicitly, so drop any pending recomputation
304
+ this.validityCheckPending = false;
304
305
  this.setValidity(valid);
305
306
  this.setErrorMessage(true);
306
307
  this.hasDisplayedError = this.invalid;
@@ -373,14 +374,32 @@ export class MgInputText {
373
374
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
374
375
  return setTimeout(() => {
375
376
  this.checkValidity();
377
+ // the initial check covers the initial value: nothing is pending yet
378
+ this.validityCheckPending = false;
376
379
  }, 0);
377
380
  }
381
+ /**
382
+ * Recompute validity once the DOM is in sync with `value`.
383
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
384
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
385
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
386
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
387
+ */
388
+ componentDidUpdate() {
389
+ if (this.validityCheckPending && this.input !== undefined) {
390
+ this.validityCheckPending = false;
391
+ this.checkValidity();
392
+ if (this.hasDisplayedError) {
393
+ this.setErrorMessage();
394
+ }
395
+ }
396
+ }
378
397
  /**
379
398
  * Render
380
399
  * @returns HTML Element
381
400
  */
382
401
  render() {
383
- 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
402
+ 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
384
403
  ? this.value && this.renderReadonly()
385
404
  : [
386
405
  h("div", { key: "input", class: "mg-c-input__input-group-container", style: {