@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
@@ -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.10
4
+ * Version: v4.14.2
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -3841,6 +3841,19 @@ a + .jodit-file-browser-tree__source-title {
3841
3841
  .jodit-color-picker__color-item:active {
3842
3842
  border: 2px solid var(--jd-color-border-selected);
3843
3843
  }
3844
+ .jodit-color-picker__extra {
3845
+ display: flex;
3846
+ align-items: center;
3847
+ justify-content: space-between;
3848
+ }
3849
+ .jodit-color-picker__hex input {
3850
+ width: 80px;
3851
+ padding: 2px 4px;
3852
+ border: 1px solid var(--jd-color-border);
3853
+ border-radius: var(--jd-border-radius-default);
3854
+ font-family: monospace;
3855
+ font-size: var(--jd-font-size-small, 12px);
3856
+ }
3844
3857
  .jodit-color-picker__native svg {
3845
3858
  display: inline-block;
3846
3859
  width: 16px;
@@ -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,
@@ -30,6 +30,13 @@ export class MgTooltip {
30
30
  * Disable tooltip
31
31
  */
32
32
  this.disabled = false;
33
+ /**
34
+ * Toggle the popper scroll and resize listeners, only needed while the tooltip is visible
35
+ * @param enabled - listeners state to apply
36
+ */
37
+ this.setPopperEventListeners = (enabled) => {
38
+ this.popper.setOptions((options) => (Object.assign(Object.assign({}, options), { modifiers: [...options.modifiers, { name: 'eventListeners', enabled }] })));
39
+ };
33
40
  /**
34
41
  * Show tooltip
35
42
  */
@@ -37,7 +44,7 @@ export class MgTooltip {
37
44
  // Make the tooltip visible
38
45
  this.mgTooltipContent.dataset.show = '';
39
46
  // Enable the event listeners
40
- this.popper.setOptions((options) => (Object.assign(Object.assign({}, options), { modifiers: [...options.modifiers, { name: 'eventListeners', enabled: true }] })));
47
+ this.setPopperEventListeners(true);
41
48
  // hide when click outside on nextTick to prevent event to trigger after creation
42
49
  nextTick(() => {
43
50
  this.windows.forEach((localWindow) => {
@@ -53,7 +60,7 @@ export class MgTooltip {
53
60
  // Hide the tooltip
54
61
  this.mgTooltipContent.removeAttribute('data-show');
55
62
  // Disable the event listeners
56
- this.popper.setOptions((options) => (Object.assign(Object.assign({}, options), { modifiers: [...options.modifiers, { name: 'eventListeners', enabled: false }] })));
63
+ this.setPopperEventListeners(false);
57
64
  // Remove event listener
58
65
  this.windows.forEach((localWindow) => {
59
66
  localWindow.removeEventListener('click', this.handleClickOutside, false);
@@ -117,6 +124,47 @@ export class MgTooltip {
117
124
  this.resetGuard = () => {
118
125
  this.guard = undefined;
119
126
  };
127
+ /**
128
+ * Display tooltip when the tooltiped element takes focus
129
+ */
130
+ this.handleTooltipedElementFocus = () => {
131
+ this.guard = Guard.FOCUS;
132
+ this.setDisplay(true);
133
+ };
134
+ /**
135
+ * Hide tooltip when the tooltiped element loses focus
136
+ */
137
+ this.handleTooltipedElementBlur = () => {
138
+ this.resetGuard();
139
+ this.setDisplay(false);
140
+ };
141
+ /**
142
+ * Display tooltip when the tooltiped element is hovered
143
+ */
144
+ this.handleTooltipedElementMouseenter = () => {
145
+ this.tooltipMouseListenerAction(Guard.HOVER_TOOLTIPED_ELEMENT, 'mouseenter', Guard.HOVER_TOOLTIP_ELEMENT);
146
+ };
147
+ /**
148
+ * Hide tooltip when the tooltiped element is not hovered anymore
149
+ */
150
+ this.handleTooltipedElementMouseleave = () => {
151
+ this.tooltipMouseListenerAction(Guard.HOVER_TOOLTIPED_ELEMENT, 'mouseleave', Guard.HOVER_TOOLTIP_ELEMENT);
152
+ };
153
+ /**
154
+ * Manage all tooltiped element listeners
155
+ * @param element - element to bind or unbind
156
+ * @param action - process to apply on element listeners
157
+ */
158
+ this.manageTooltipedElementListeners = (element, action) => {
159
+ element[action]('focus', this.handleTooltipedElementFocus);
160
+ // as `click` event is the composition of `mousedown` and `mouseup` event,
161
+ // and `click` event trigger `focus` event after a success `mousedown` wich set the `guard` to `Guard.FOCUS`
162
+ // we reset the guard on `mouseup` event to unlock tooltip
163
+ element[action]('mouseup', this.resetGuard);
164
+ element[action]('blur', this.handleTooltipedElementBlur);
165
+ element[action]('mouseenter', this.handleTooltipedElementMouseenter);
166
+ element[action]('mouseleave', this.handleTooltipedElementMouseleave);
167
+ };
120
168
  /**
121
169
  * Update slot content when it is a mg-button
122
170
  * @param mgButton - slotted mg-button
@@ -143,10 +191,11 @@ export class MgTooltip {
143
191
  }
144
192
  };
145
193
  /**
146
- * Set popper instance
147
- * @param strategy - popper strategy to apply on instance
194
+ * Set popper instance on the current tooltiped element
148
195
  */
149
- this.setPopper = (strategy) => {
196
+ this.setPopper = () => {
197
+ // mg-popover content is already `fixed`: position a nested tooltip relatively to it
198
+ const strategy = this.element.closest('mg-popover') !== null ? 'absolute' : 'fixed';
150
199
  // Create popperjs tooltip
151
200
  this.popper = createPopper(this.tooltipedElement, this.mgTooltipContent, {
152
201
  placement: this.placement,
@@ -172,29 +221,20 @@ export class MgTooltip {
172
221
  * @param element - tooltiped element
173
222
  */
174
223
  this.setTooltipedElement = (element) => {
224
+ // unbind the previous element so its listeners do not stack up on every `disabled` toggle
225
+ if (this.tooltipedElement !== undefined) {
226
+ this.manageTooltipedElementListeners(this.tooltipedElement, 'removeEventListener');
227
+ }
175
228
  this.tooltipedElement = element;
176
- // Manage tooltipedElement focus events
177
- this.tooltipedElement.addEventListener('focus', () => {
178
- this.guard = Guard.FOCUS;
179
- this.setDisplay(true);
180
- });
181
- // as `click` event is the composition of `mousedown` and `mouseup` event,
182
- // and `click` event trigger `focus` event after a success `mousedown` wich set the `guard` to `Guard.FOCUS`
183
- // we reset the guard on `mouseup` event to unlock tooltip
184
- this.tooltipedElement.addEventListener('mouseup', () => {
185
- this.resetGuard();
186
- });
187
- this.tooltipedElement.addEventListener('blur', () => {
188
- this.resetGuard();
189
- this.setDisplay(false);
190
- });
191
- // manage tooltipElement & tooltipedElement mouseenter/mouseleave events
192
- this.tooltipedElement.addEventListener('mouseenter', () => {
193
- this.tooltipMouseListenerAction(Guard.HOVER_TOOLTIPED_ELEMENT, 'mouseenter', Guard.HOVER_TOOLTIP_ELEMENT);
194
- });
195
- this.tooltipedElement.addEventListener('mouseleave', () => {
196
- this.tooltipMouseListenerAction(Guard.HOVER_TOOLTIPED_ELEMENT, 'mouseleave', Guard.HOVER_TOOLTIP_ELEMENT);
197
- });
229
+ this.manageTooltipedElementListeners(this.tooltipedElement, 'addEventListener');
230
+ // popper freezes its reference element, so re-create it to follow the new one.
231
+ // The initial instance comes from `componentDidLoad`.
232
+ if (this.popper !== undefined) {
233
+ this.popper.destroy();
234
+ this.setPopper();
235
+ // a fresh instance has its listeners enabled, realign them
236
+ this.setPopperEventListeners(this.display);
237
+ }
198
238
  };
199
239
  /**
200
240
  * Set button mutation observer
@@ -222,17 +262,36 @@ export class MgTooltip {
222
262
  }).observe(mgButton, { attributes: true });
223
263
  };
224
264
  /**
225
- * Set tooltiped element aria-describedby
226
- * @param element - element to update
265
+ * Reconcile the tooltiped element a11y: the tooltip is restituted — `aria-describedby` exposed, and a
266
+ * non-interactive element made focusable (`tabindex` + `role`) — unless it is disabled and not displayed.
267
+ * Pre-existing `aria-describedby` ids are preserved.
227
268
  */
228
- this.setAriaDescribedby = (element) => {
229
- // Set aria-describedby
230
- if (!element.hasAttribute('aria-describedby')) {
231
- this.tooltipedElement.setAttribute('aria-describedby', this.identifier);
269
+ this.updateTooltipedElementA11y = () => {
270
+ const restitute = !this.disabled || this.display;
271
+ const currentValue = this.tooltipedElement.getAttribute('aria-describedby');
272
+ const ids = new Set(currentValue !== null ? currentValue.split(' ') : []);
273
+ if (restitute) {
274
+ ids.add(this.identifier);
275
+ }
276
+ else {
277
+ ids.delete(this.identifier);
278
+ }
279
+ if (ids.size > 0) {
280
+ this.tooltipedElement.setAttribute('aria-describedby', [...ids].join(' '));
232
281
  }
233
282
  else {
234
- // We ensure to have uniq ids
235
- element.setAttribute('aria-describedby', `${[...new Set([...element.getAttribute('aria-describedby').split(' '), this.identifier])].join(' ')}`);
283
+ this.tooltipedElement.removeAttribute('aria-describedby');
284
+ }
285
+ if (this.hasCustomTabIndex) {
286
+ if (restitute) {
287
+ this.tooltipedElement.setAttribute('role', 'button');
288
+ if (!this.tooltipedElement.hasAttribute('tabindex'))
289
+ this.tooltipedElement.setAttribute('tabindex', '0');
290
+ }
291
+ else {
292
+ this.tooltipedElement.removeAttribute('role');
293
+ this.tooltipedElement.removeAttribute('tabindex');
294
+ }
236
295
  }
237
296
  };
238
297
  }
@@ -257,14 +316,11 @@ export class MgTooltip {
257
316
  else {
258
317
  this.hide();
259
318
  }
319
+ this.updateTooltipedElementA11y();
260
320
  }
261
- watchDisabled(newValue) {
262
- if (this.hasCustomTabIndex) {
263
- if (newValue)
264
- this.tooltipedElement.removeAttribute('tabindex');
265
- else if (!this.tooltipedElement.hasAttribute('tabindex'))
266
- this.tooltipedElement.setAttribute('tabindex', '0');
267
- }
321
+ watchDisabled() {
322
+ // Only fires on runtime changes; the initial reconcile is done in componentDidLoad via watchDisplay.
323
+ this.updateTooltipedElementA11y();
268
324
  }
269
325
  /**
270
326
  * Render tooltip content element and init listeners
@@ -304,7 +360,6 @@ export class MgTooltip {
304
360
  // Get tooltip element
305
361
  this.renderTooltipContent();
306
362
  //validate properties
307
- this.watchDisabled(this.disabled);
308
363
  this.watchMessage(this.message);
309
364
  this.watchIdentifier(this.identifier);
310
365
  }
@@ -333,20 +388,13 @@ export class MgTooltip {
333
388
  if (this.element.querySelector('mg-button[full-width]') !== null) {
334
389
  this.element.classList.add('mg-c-tooltip--full-width');
335
390
  }
336
- // Add tabindex to slotted element if we can't find any interactive element
337
- if (!this.disabled && [undefined, null].includes(interactiveElement)) {
338
- this.hasCustomTabIndex = true;
339
- slotElement.tabIndex = 0;
340
- // Add role on non-interactive element to work with "aria-describedby" for screen readers
341
- slotElement.setAttribute('role', 'button');
342
- }
343
- // apply a11y aria
344
- this.setAriaDescribedby(slotElement);
391
+ // Non-interactive slotted element (no focusable element found): needs synthesized focus to be reachable.
392
+ this.hasCustomTabIndex = [undefined, null].includes(interactiveElement);
345
393
  // set Tooltip
346
- this.setPopper(this.element.closest('mg-popover') !== null ? 'absolute' : 'fixed');
394
+ this.setPopper();
347
395
  // add document keyboard handler
348
396
  document.addEventListener('keydown', this.handlePressEscape);
349
- // check props
397
+ // check props (also does the initial a11y reconcile)
350
398
  this.watchDisplay(this.display);
351
399
  }
352
400
  /**
@@ -371,7 +419,7 @@ export class MgTooltip {
371
419
  * @returns HTML Element
372
420
  */
373
421
  render() {
374
- return (h(Host, { key: '5cd926147c1b6723065c5334699764f32a8a06c9' }, h("slot", { key: 'a1b208798c79e34a207f3bedb2ad9662e02e83e2' }), h("slot", { key: '4f332961968fd3541caf47c2fd9072720fa2ee71', name: "content" })));
422
+ return (h(Host, { key: '1e9cfc59c335510c21d159253e3f8e1b4256a465' }, h("slot", { key: 'c52d57e3302d4865a2365ac62c931af2948bdf03' }), h("slot", { key: '527b5d8ae562ed1f5faed74356f10be5aca10ac4', name: "content" })));
375
423
  }
376
424
  static get is() { return "mg-tooltip"; }
377
425
  static get encapsulation() { return "shadow"; }
@@ -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
@@ -295,9 +297,7 @@ export class MgInputCheckbox {
295
297
  * Renders the readonly value of the input.
296
298
  * @returns The rendered readonly value.
297
299
  */
298
- this.renderReadonly = (readonlyValue) => {
299
- return this.inputVerticalList ? (h("ul", { class: "mg-c-input__readonly-value" }, readonlyValue.map((value) => (h("li", { key: value }, h("b", null, value)))))) : (h("b", { class: "mg-c-input__readonly-value" }, readonlyValue.join(', ')));
300
- };
300
+ this.renderReadonly = (readonlyValue) => (h("b", { class: "mg-c-input__readonly-value" }, readonlyValue.join(', ')));
301
301
  /**
302
302
  * Renders the MgInputCheckboxList component.
303
303
  * @returns The rendered MgInputCheckboxList component.
@@ -305,6 +305,7 @@ export class MgInputCheckbox {
305
305
  this.renderMgInputCheckboxList = () => (h(MgInputCheckboxList, { checkboxes: this.checkboxItems, id: this.checkboxesId, disabled: this.disabled, name: this.name, invalid: this.invalid }));
306
306
  }
307
307
  validateValue(newValue) {
308
+ this.validityCheckPending = true;
308
309
  if (isCheckboxItems(newValue)) {
309
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) })));
310
311
  this.valueChange.emit(newValue);
@@ -437,6 +438,8 @@ export class MgInputCheckbox {
437
438
  lock: valid ? false : errorMessageLock,
438
439
  message: valid ? undefined : errorMessage,
439
440
  };
441
+ // setError() states the validity explicitly, so drop any pending recomputation
442
+ this.validityCheckPending = false;
440
443
  this.setValidity(valid);
441
444
  this.setErrorMessage(true);
442
445
  this.hasDisplayedError = this.invalid;
@@ -519,8 +522,26 @@ export class MgInputCheckbox {
519
522
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
520
523
  return setTimeout(() => {
521
524
  this.checkValidity();
525
+ // the initial check covers the initial value: nothing is pending yet
526
+ this.validityCheckPending = false;
522
527
  }, 0);
523
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
+ }
524
545
  /**
525
546
  * add listeners and DOM attributed
526
547
  */
@@ -605,7 +626,7 @@ export class MgInputCheckbox {
605
626
  else {
606
627
  inputContent = this.renderCheckboxMulti();
607
628
  }
608
- return (h("mg-input", { key: '8e403fd79ca8a9bb2db5545e946a3763eb156551', 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));
609
630
  }
610
631
  static get is() { return "mg-input-checkbox"; }
611
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
@@ -352,11 +354,29 @@ export class MgInputCombobox {
352
354
  }
353
355
  }
354
356
  };
357
+ /**
358
+ * Check if an option matches the current filter
359
+ * @param option - option to match
360
+ * @returns true if the option matches the filter
361
+ */
362
+ this.matchFilter = (option) => this.filter === '' || option.title.toLocaleLowerCase().includes(this.filter.toLocaleLowerCase());
363
+ /**
364
+ * Get all the options matching the current filter
365
+ * @returns filtered options
366
+ */
367
+ this.getFilteredOptions = () => this.options.items.filter(this.matchFilter);
355
368
  /**
356
369
  * Get first page with filter applied
357
370
  * @returns first page with filter applied
358
371
  */
359
- this.getFirstPage = () => this.options.getPage(0, (option) => this.filter === '' || option.title.toLocaleLowerCase().includes(this.filter.toLocaleLowerCase()));
372
+ this.getFirstPage = () => {
373
+ const page = this.options.getPage(0, this.matchFilter);
374
+ // `Paginate` keeps the unfiltered total: realign it with the filtered options, except in
375
+ // `fetchurl` mode where the total counts the options left to load
376
+ if (this.fetchurl === undefined)
377
+ page.total = this.getFilteredOptions().length;
378
+ return page;
379
+ };
360
380
  /**
361
381
  * Loading wrapper with a given action
362
382
  * @param action - wrapped loading action
@@ -400,9 +420,7 @@ export class MgInputCombobox {
400
420
  return promise()
401
421
  .then(() => {
402
422
  // parse full items list from options
403
- const options = this.filter === ''
404
- ? this.options.items
405
- : this.options.items.filter((option) => option.title.includes(this.filter));
423
+ const options = this.getFilteredOptions();
406
424
  // update visual focus
407
425
  if (action.name !== 'load-data') {
408
426
  index = this.page.getIndexFromCursor(action.cursor, this.loadingAction === 'load-more' && index >= 0 ? options[index] : this.option);
@@ -439,7 +457,9 @@ export class MgInputCombobox {
439
457
  return nextTick(() => {
440
458
  if (typeof this.page.next === 'function') {
441
459
  const nextPage = this.page.next();
442
- this.page = new Page(Object.assign(Object.assign({}, nextPage), { items: [...this.page.items, ...nextPage.items] }));
460
+ this.page = new Page(Object.assign(Object.assign({}, nextPage), { items: [...this.page.items, ...nextPage.items],
461
+ // `Paginate` keeps the unfiltered total, realign it as in `getFirstPage`
462
+ total: this.getFilteredOptions().length }));
443
463
  }
444
464
  });
445
465
  }
@@ -503,10 +523,11 @@ export class MgInputCombobox {
503
523
  };
504
524
  }
505
525
  watchValue(newValue) {
526
+ this.validityCheckPending = true;
506
527
  this.filter = this.getValueTitle();
507
528
  this.valueChange.emit(newValue);
508
529
  }
509
- watchItems(newValue) {
530
+ watchItems(newValue, oldValue) {
510
531
  if (this.fetchurl !== undefined && newValue === undefined) {
511
532
  // skip newValue check
512
533
  return;
@@ -529,6 +550,17 @@ export class MgInputCombobox {
529
550
  else {
530
551
  throw new Error(`<mg-input-combobox> prop "items" values must be the same type, string or Option. Passed value: ${toString(newValue)}.`);
531
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
+ }
532
564
  }
533
565
  watchIdentifier(newValue) {
534
566
  if (isValidString(newValue)) {
@@ -658,6 +690,8 @@ export class MgInputCombobox {
658
690
  lock: valid ? false : errorMessageLock,
659
691
  message: valid ? undefined : errorMessage,
660
692
  };
693
+ // setError() states the validity explicitly, so drop any pending recomputation
694
+ this.validityCheckPending = false;
661
695
  this.setValidity(valid);
662
696
  this.setErrorMessage(true);
663
697
  this.hasDisplayedError = this.invalid;
@@ -747,8 +781,26 @@ export class MgInputCombobox {
747
781
  // https://stenciljs.com/docs/component-lifecycle#componentwillload
748
782
  return setTimeout(() => {
749
783
  this.checkValidity();
784
+ // the initial check covers the initial value: nothing is pending yet
785
+ this.validityCheckPending = false;
750
786
  }, 0);
751
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
+ }
752
804
  /**
753
805
  * Render
754
806
  * @returns HTML Element
@@ -778,7 +830,7 @@ export class MgInputCombobox {
778
830
  else {
779
831
  activeDescendant = (_e = this.value) === null || _e === void 0 ? void 0 : _e.value;
780
832
  }
781
- return (h("mg-input", { key: 'c8cff9468c9bfdcc6dd5c96c1452ad81620e050c', 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: {
782
834
  '--mg-c-input-list-width': ((_f = this.input) === null || _f === void 0 ? void 0 : _f.offsetWidth) > 0 ? `${this.input.offsetWidth}px` : undefined,
783
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) => {
784
836
  if (el !== null)