@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,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.13.22
4
+ * Version: v4.14.2
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,6 +1,6 @@
1
1
  import { h, Host } from "@stencil/core";
2
2
  import { variants, sizes } from "./mg-button.conf";
3
- import { ClassList, isValidString, nextTick, toString } from "@mgdis/core-ui-helpers/dist/utils";
3
+ import { ClassList, isValidString, toString } from "@mgdis/core-ui-helpers/dist/utils";
4
4
  import { normalizeBooleanAttributes } from "@mgdis/core-ui-helpers/dist/stencil";
5
5
  import { Keys } from "../../../utils/events.utils";
6
6
  /**
@@ -95,6 +95,88 @@ export class MgButton {
95
95
  (_b = this.element.querySelector('mg-icon')) === null || _b === void 0 ? void 0 : _b.setAttribute('size', this.size);
96
96
  }
97
97
  };
98
+ /**
99
+ * Submit a native form the way a native submit button does
100
+ * @param form - the form to submit
101
+ */
102
+ this.dispatchSubmit = (form) => {
103
+ form.dispatchEvent(new SubmitEvent('submit', { bubbles: true, cancelable: true }));
104
+ };
105
+ /**
106
+ * Wrap a `mg-form` in the actions its public API offers, so nothing reaches into its shadow DOM
107
+ * @param mgForm - the owning mg-form
108
+ * @returns its submit and reset actions
109
+ */
110
+ this.mgFormActions = (mgForm) => ({
111
+ submit: () => mgForm.requestSubmit(),
112
+ reset: () => mgForm.reset(),
113
+ });
114
+ /**
115
+ * Wrap a native form in the same actions
116
+ * @param form - the owning form
117
+ * @returns its submit and reset actions
118
+ */
119
+ this.nativeFormActions = (form) => ({
120
+ submit: () => this.dispatchSubmit(form),
121
+ reset: () => form.reset(),
122
+ });
123
+ /**
124
+ * Resolve the form the `form` id points to. An id reference does not cross a shadow boundary, so
125
+ * it resolves in the tree the button belongs to — the document, or the shadow tree it sits in —
126
+ * the way a native form owner does. The ancestor wins when it is the one pointed at: two forms
127
+ * can share an identifier, and a tree-wide lookup would retain the first one.
128
+ * @returns the actions of the form holding that id, or null when none does
129
+ */
130
+ this.resolveOwnerById = () => {
131
+ const ancestorMgForm = this.element.closest('mg-form');
132
+ if (ancestorMgForm !== null && ancestorMgForm.identifier === this.form)
133
+ return this.mgFormActions(ancestorMgForm);
134
+ const root = this.element.getRootNode();
135
+ const tree = root instanceof ShadowRoot ? root : this.element.ownerDocument;
136
+ const mgForm = Array.from(tree.querySelectorAll('mg-form')).find((element) => element.identifier === this.form);
137
+ if (mgForm !== undefined)
138
+ return this.mgFormActions(mgForm);
139
+ // Resolved by id, as a native form owner is, rather than through a selector an id could break
140
+ const nativeForm = tree.getElementById(this.form);
141
+ return nativeForm instanceof HTMLFormElement ? this.nativeFormActions(nativeForm) : null;
142
+ };
143
+ /**
144
+ * Resolve the form owning this button: an explicit `form` id overrides the ancestor form, and an
145
+ * id matching nothing logs then falls back on it, so a button never loses an association it had.
146
+ * @returns the owning form actions, or null when the button has no form owner
147
+ */
148
+ this.resolveFormOwner = () => {
149
+ if (isValidString(this.form)) {
150
+ const ownerById = this.resolveOwnerById();
151
+ if (ownerById !== null)
152
+ return ownerById;
153
+ console.error(`<mg-button> prop "form" matches no form in the button tree. Passed value: ${toString(this.form)}.`);
154
+ }
155
+ const nativeAncestor = this.element.closest('form');
156
+ if (nativeAncestor !== null)
157
+ return this.nativeFormActions(nativeAncestor);
158
+ const mgAncestor = this.element.closest('mg-form');
159
+ return mgAncestor === null ? null : this.mgFormActions(mgAncestor);
160
+ };
161
+ /**
162
+ * Act on the owning form. The owner is resolved here rather than on load because a native form
163
+ * owner is re-resolved on every submission, never captured once: the button follows a `form`
164
+ * changed after render, and no longer races the target form hydration.
165
+ */
166
+ this.handleFormClick = () => {
167
+ const isSubmit = ['submit', undefined].includes(this.type);
168
+ // A reset only fires from an explicit association: it has never reset a merely ancestor form
169
+ const isReset = this.type === 'reset' && isValidString(this.form);
170
+ if (!isSubmit && !isReset)
171
+ return;
172
+ const owner = this.resolveFormOwner();
173
+ if (owner === null)
174
+ return;
175
+ if (isSubmit)
176
+ owner.submit();
177
+ else
178
+ owner.reset();
179
+ };
98
180
  }
99
181
  validateVariant(newValue, oldValue) {
100
182
  // validate new value
@@ -193,16 +275,7 @@ export class MgButton {
193
275
  * Add listners
194
276
  */
195
277
  componentDidLoad() {
196
- nextTick(() => {
197
- var _a;
198
- const closestForm = this.element.closest('form') || ((_a = this.element.closest('mg-form')) === null || _a === void 0 ? void 0 : _a.shadowRoot.querySelector('form'));
199
- // submit buttons should trigger form submition;
200
- if (closestForm && ['submit', undefined].includes(this.type)) {
201
- this.element.addEventListener('click', () => {
202
- closestForm.dispatchEvent(new SubmitEvent('submit', { bubbles: true, cancelable: true }));
203
- });
204
- }
205
- });
278
+ this.element.addEventListener('click', this.handleFormClick);
206
279
  }
207
280
  /**
208
281
  * Render component
@@ -210,7 +283,7 @@ export class MgButton {
210
283
  */
211
284
  render() {
212
285
  var _a;
213
- 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' })))));
286
+ 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' })))));
214
287
  }
215
288
  static get is() { return "mg-button"; }
216
289
  static get encapsulation() { return "shadow"; }
@@ -357,7 +430,7 @@ export class MgButton {
357
430
  "optional": true,
358
431
  "docs": {
359
432
  "tags": [],
360
- "text": "Define form id to attach button with.\nIf this attribute is not set, the `<button>` is associated with its ancestor `<form>` element."
433
+ "text": "Id of the form to associate the button with, following the native `form` attribute semantics:\nit must match the `identifier` of a `mg-form`, or the `id` of a `<form>`, in the same document,\nand it overrides the ancestor form.\nIf this attribute is not set, the button is associated with its ancestor form."
361
434
  },
362
435
  "getter": false,
363
436
  "setter": false,
@@ -21,6 +21,8 @@ export class MgInputCheckbox {
21
21
  constructor() {
22
22
  // hasDisplayedError (triggered by blur event)
23
23
  this.hasDisplayedError = false;
24
+ // raised when `value` changed, so componentDidUpdate revalidates once the DOM is in sync
25
+ this.validityCheckPending = false;
24
26
  this.customErrorMessage = { lock: false };
25
27
  this.mode = 'custom';
26
28
  // style
@@ -303,6 +305,7 @@ export class MgInputCheckbox {
303
305
  this.renderMgInputCheckboxList = () => (h(MgInputCheckboxList, { checkboxes: this.checkboxItems, id: this.checkboxesId, disabled: this.disabled, name: this.name, invalid: this.invalid }));
304
306
  }
305
307
  validateValue(newValue) {
308
+ this.validityCheckPending = true;
306
309
  if (isCheckboxItems(newValue)) {
307
310
  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) })));
308
311
  this.valueChange.emit(newValue);
@@ -435,6 +438,8 @@ export class MgInputCheckbox {
435
438
  lock: valid ? false : errorMessageLock,
436
439
  message: valid ? undefined : errorMessage,
437
440
  };
441
+ // setError() states the validity explicitly, so drop any pending recomputation
442
+ this.validityCheckPending = false;
438
443
  this.setValidity(valid);
439
444
  this.setErrorMessage(true);
440
445
  this.hasDisplayedError = this.invalid;
@@ -517,8 +522,26 @@ export class MgInputCheckbox {
517
522
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
518
523
  return setTimeout(() => {
519
524
  this.checkValidity();
525
+ // the initial check covers the initial value: nothing is pending yet
526
+ this.validityCheckPending = false;
520
527
  }, 0);
521
528
  }
529
+ /**
530
+ * Recompute validity once the DOM is in sync with `value`.
531
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
532
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
533
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
534
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
535
+ */
536
+ componentDidUpdate() {
537
+ if (this.validityCheckPending && this.checkboxItems.length > 0) {
538
+ this.validityCheckPending = false;
539
+ this.checkValidity();
540
+ if (this.hasDisplayedError) {
541
+ this.setErrorMessage();
542
+ }
543
+ }
544
+ }
522
545
  /**
523
546
  * add listeners and DOM attributed
524
547
  */
@@ -603,7 +626,7 @@ export class MgInputCheckbox {
603
626
  else {
604
627
  inputContent = this.renderCheckboxMulti();
605
628
  }
606
- 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));
629
+ 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));
607
630
  }
608
631
  static get is() { return "mg-input-checkbox"; }
609
632
  static get encapsulation() { return "shadow"; }
@@ -48,6 +48,8 @@ export class MgInputCombobox {
48
48
  constructor() {
49
49
  // hasDisplayedError (triggered by blur event)
50
50
  this.hasDisplayedError = false;
51
+ // raised when `value` or `items` changed, so componentDidUpdate revalidates once the DOM is in sync
52
+ this.validityCheckPending = false;
51
53
  this.customErrorMessage = { lock: false };
52
54
  /**
53
55
  * Input name
@@ -521,10 +523,11 @@ export class MgInputCombobox {
521
523
  };
522
524
  }
523
525
  watchValue(newValue) {
526
+ this.validityCheckPending = true;
524
527
  this.filter = this.getValueTitle();
525
528
  this.valueChange.emit(newValue);
526
529
  }
527
- watchItems(newValue) {
530
+ watchItems(newValue, oldValue) {
528
531
  if (this.fetchurl !== undefined && newValue === undefined) {
529
532
  // skip newValue check
530
533
  return;
@@ -547,6 +550,17 @@ export class MgInputCombobox {
547
550
  else {
548
551
  throw new Error(`<mg-input-combobox> prop "items" values must be the same type, string or Option. Passed value: ${toString(newValue)}.`);
549
552
  }
553
+ // `oldValue` is undefined on init
554
+ if (oldValue !== undefined) {
555
+ // the input displays the value's own title, so an unoffered value cannot stay. An empty list is a
556
+ // reload in progress rather than an answer: the value is kept, as `mg-input-select` keeps its own.
557
+ if (this.value !== undefined &&
558
+ this.options.items.length > 0 &&
559
+ !this.options.items.some((option) => this.isCurrentOption(option))) {
560
+ this.value = undefined;
561
+ }
562
+ this.validityCheckPending = true;
563
+ }
550
564
  }
551
565
  watchIdentifier(newValue) {
552
566
  if (isValidString(newValue)) {
@@ -676,6 +690,8 @@ export class MgInputCombobox {
676
690
  lock: valid ? false : errorMessageLock,
677
691
  message: valid ? undefined : errorMessage,
678
692
  };
693
+ // setError() states the validity explicitly, so drop any pending recomputation
694
+ this.validityCheckPending = false;
679
695
  this.setValidity(valid);
680
696
  this.setErrorMessage(true);
681
697
  this.hasDisplayedError = this.invalid;
@@ -765,8 +781,26 @@ export class MgInputCombobox {
765
781
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
766
782
  return setTimeout(() => {
767
783
  this.checkValidity();
784
+ // the initial check covers the initial value: nothing is pending yet
785
+ this.validityCheckPending = false;
768
786
  }, 0);
769
787
  }
788
+ /**
789
+ * Recompute validity once the DOM is in sync with `value`.
790
+ * `@Watch('value')` fires before Stencil re-renders, so the rendered control still holds the
791
+ * previous value at that point (visible with a programmatic value change, e.g. a `v-model`
792
+ * default value). The watcher therefore only raises a flag, and the check runs here, after the
793
+ * render. Renders unrelated to `value` leave the flag down and recompute nothing.
794
+ */
795
+ componentDidUpdate() {
796
+ if (this.validityCheckPending && this.input !== undefined) {
797
+ this.validityCheckPending = false;
798
+ this.checkValidity();
799
+ if (this.hasDisplayedError) {
800
+ this.setErrorMessage();
801
+ }
802
+ }
803
+ }
770
804
  /**
771
805
  * Render
772
806
  * @returns HTML Element
@@ -796,7 +830,7 @@ export class MgInputCombobox {
796
830
  else {
797
831
  activeDescendant = (_e = this.value) === null || _e === void 0 ? void 0 : _e.value;
798
832
  }
799
- return (h("mg-input", { key: '047c14310f70117e1aec89161d2261f175487be7', label: this.label, identifier: this.identifier, class: this.classCollection.classes.join(' '), ariaDescribedbyIDs: popoverContent && popoverContent !== 'list' ? popoverContent : undefined, 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 ? (readonlyValue && h("b", { class: "mg-c-input__readonly-value" }, readonlyValue)) : (h("mg-popover", { identifier: this.popoverId, placement: this.popoverPlacement, display: this.popoverDisplay, style: {
833
+ return (h("mg-input", { key: '20006f3066f661cd9cf8ec25b4090221d3925b04', label: this.label, identifier: this.identifier, class: this.classCollection.classes.join(' '), ariaDescribedbyIDs: popoverContent && popoverContent !== 'list' ? popoverContent : undefined, 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 ? (readonlyValue && h("b", { class: "mg-c-input__readonly-value" }, readonlyValue)) : (h("mg-popover", { identifier: this.popoverId, placement: this.popoverPlacement, display: this.popoverDisplay, style: {
800
834
  '--mg-c-input-list-width': ((_f = this.input) === null || _f === void 0 ? void 0 : _f.offsetWidth) > 0 ? `${this.input.offsetWidth}px` : undefined,
801
835
  }, "data-fallback-placement": "bottom-end", "onDisplay-change": this.handlePopoverDisplay }, h("div", { class: "mg-c-input__input-group-container" }, h("mg-icon", { icon: "magnifying-glass" }), h("input", { type: "text", role: "combobox", class: "mg-c-input__box mg-c-input__box--width", value: this.filter, id: this.identifier, name: this.name, placeholder: this.placeholder, title: this.placeholder, disabled: this.disabled, required: this.required, autocomplete: "off", "aria-autocomplete": "list", "aria-expanded": this.popoverDisplay.toString(), "aria-controls": listId, "aria-invalid": (this.invalid === true).toString(), "aria-activedescendant": formatID(activeDescendant), onInput: this.handleFilterInput, onBlur: this.handleFilterBlur, onFocus: this.handleFilterFocus, onKeyDown: this.handleFilterKeydown, ref: (el) => {
802
836
  if (el !== null)
@@ -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 }))))));