@mgdis/mg-components 5.5.0 → 5.6.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 (142) hide show
  1. package/dist/cjs/{index-c3450336.js → index-4dcc1812.js} +11 -1
  2. package/dist/cjs/loader.cjs.js +3 -3
  3. package/dist/cjs/mg-badge_30.cjs.entry.js +742 -244
  4. package/dist/cjs/mg-components.cjs.js +3 -3
  5. package/dist/collection/collection-manifest.json +4 -4
  6. package/dist/collection/components/atoms/mg-badge/mg-badge.conf.js +1 -1
  7. package/dist/collection/components/atoms/mg-badge/mg-badge.css +13 -0
  8. package/dist/collection/components/atoms/mg-badge/mg-badge.js +3 -3
  9. package/dist/collection/components/atoms/mg-button/mg-button.css +2 -1
  10. package/dist/collection/components/atoms/mg-card/mg-card.css +13 -6
  11. package/dist/collection/components/atoms/mg-icon/mg-icon.conf.js +13 -3
  12. package/dist/collection/components/atoms/mg-tag/mg-tag.css +6 -6
  13. package/dist/collection/components/atoms/mg-tag/mg-tag.js +3 -3
  14. package/dist/collection/components/atoms/mg-tooltip/mg-tooltip.css +2 -0
  15. package/dist/collection/components/atoms/mg-tooltip/mg-tooltip.js +3 -3
  16. package/dist/collection/components/molecules/inputs/mg-input-date/mg-input-date.css +3 -3
  17. package/dist/collection/components/molecules/inputs/mg-input-date/mg-input-date.js +4 -1
  18. package/dist/collection/components/molecules/inputs/mg-input-numeric/mg-input-numeric.css +3 -3
  19. package/dist/collection/components/molecules/inputs/mg-input-password/mg-input-password.css +3 -3
  20. package/dist/collection/components/molecules/inputs/mg-input-select/mg-input-select.css +3 -3
  21. package/dist/collection/components/molecules/inputs/mg-input-select/mg-input-select.js +7 -3
  22. package/dist/collection/components/molecules/inputs/mg-input-text/mg-input-text.css +3 -3
  23. package/dist/collection/components/molecules/inputs/mg-input-textarea/mg-input-textarea.css +3 -3
  24. package/dist/collection/components/molecules/menu/mg-menu/doc/mg-menu.stories.js +43 -11
  25. package/dist/collection/components/molecules/menu/mg-menu/mg-menu.conf.js +17 -0
  26. package/dist/collection/components/molecules/menu/mg-menu/mg-menu.css +27 -0
  27. package/dist/collection/components/molecules/menu/mg-menu/mg-menu.js +141 -18
  28. package/dist/collection/components/molecules/menu/mg-menu-item/doc/mg-menu-item.stories.js +5 -10
  29. package/dist/collection/components/molecules/menu/mg-menu-item/mg-menu-item.conf.js +0 -4
  30. package/dist/collection/components/molecules/menu/mg-menu-item/mg-menu-item.css +47 -21
  31. package/dist/collection/components/molecules/menu/mg-menu-item/mg-menu-item.js +118 -70
  32. package/dist/collection/components/molecules/mg-illustrated-message/mg-illustrated-message.js +4 -3
  33. package/dist/collection/components/molecules/mg-message/mg-message.css +7 -0
  34. package/dist/collection/components/molecules/mg-message/mg-message.js +53 -12
  35. package/dist/collection/components/molecules/mg-modal/mg-modal.css +8 -1
  36. package/dist/collection/components/molecules/mg-panel/mg-panel.css +7 -0
  37. package/dist/collection/components/molecules/mg-popover/mg-popover.css +72 -14
  38. package/dist/collection/components/molecules/mg-popover/mg-popover.js +48 -4
  39. package/dist/collection/components/molecules/mg-skip-links/doc/mg-skip-links.stories.js +1 -1
  40. package/dist/collection/components/molecules/mg-skip-links/mg-skip-links.js +32 -1
  41. package/dist/collection/components/molecules/mg-tabs/doc/mg-tabs.stories.js +5 -3
  42. package/dist/collection/components/molecules/mg-tabs/mg-tabs.css +11 -7
  43. package/dist/collection/components/molecules/mg-tabs/mg-tabs.js +101 -54
  44. package/dist/collection/locales/en/messages.json +4 -0
  45. package/dist/collection/locales/fr/messages.json +4 -0
  46. package/dist/collection/locales/index.js +2 -25
  47. package/dist/collection/styles/a11y.scss +32 -0
  48. package/dist/collection/styles/components.scss +3 -0
  49. package/dist/collection/styles/fonts.scss +21 -0
  50. package/dist/collection/styles/form.scss +3 -0
  51. package/dist/collection/styles/global.scss +87 -0
  52. package/dist/collection/styles/layout.scss +29 -0
  53. package/dist/collection/styles/main.scss +23 -0
  54. package/dist/collection/styles/navigation-button.scss +76 -0
  55. package/dist/collection/styles/reset-mg-card-slotted-variables.scss +9 -0
  56. package/dist/collection/styles/typography.scss +75 -0
  57. package/dist/collection/styles/utilities.scss +21 -0
  58. package/dist/collection/utils/behaviors.utils.js +169 -0
  59. package/dist/collection/utils/components.utils.js +50 -0
  60. package/dist/collection/utils/locale.utils.js +22 -0
  61. package/dist/collection/utils/unit.test.utils.js +37 -32
  62. package/dist/collection/variables.css +9 -9
  63. package/dist/components/components.utils.js +51 -1
  64. package/dist/components/index2.js +65 -18
  65. package/dist/components/mg-badge2.js +4 -4
  66. package/dist/components/mg-button2.js +1 -1
  67. package/dist/components/mg-card2.js +1 -1
  68. package/dist/components/mg-icon2.js +13 -3
  69. package/dist/components/mg-illustrated-message.js +4 -3
  70. package/dist/components/mg-input-date.js +6 -4
  71. package/dist/components/mg-input-numeric.js +2 -3
  72. package/dist/components/mg-input-password.js +1 -1
  73. package/dist/components/mg-input-select2.js +8 -4
  74. package/dist/components/mg-input-text2.js +1 -1
  75. package/dist/components/mg-input-textarea.js +1 -1
  76. package/dist/components/mg-menu-item.js +1 -253
  77. package/dist/components/mg-menu-item2.js +517 -0
  78. package/dist/components/mg-menu.js +138 -21
  79. package/dist/components/mg-message.js +54 -13
  80. package/dist/components/mg-modal.js +1 -1
  81. package/dist/components/mg-panel.js +1 -1
  82. package/dist/components/mg-popover.js +1 -190
  83. package/dist/components/mg-popover2.js +212 -0
  84. package/dist/components/mg-skip-links.js +16 -2
  85. package/dist/components/mg-tabs.js +100 -52
  86. package/dist/components/mg-tag.js +4 -4
  87. package/dist/components/mg-tooltip2.js +4 -4
  88. package/dist/esm/{index-78edde1d.js → index-dc9a2348.js} +11 -1
  89. package/dist/esm/loader.js +3 -3
  90. package/dist/esm/mg-badge_30.entry.js +742 -244
  91. package/dist/esm/mg-components.js +3 -3
  92. package/dist/esm/polyfills/css-shim.js +1 -1
  93. package/dist/mg-components/locales/en/messages.json +4 -0
  94. package/dist/mg-components/locales/fr/messages.json +4 -0
  95. package/dist/mg-components/mg-components.css +1 -1
  96. package/dist/mg-components/mg-components.esm.js +1 -1
  97. package/dist/mg-components/{p-125c54ca.js → p-182bd802.js} +2 -2
  98. package/dist/mg-components/p-f7da9dd2.entry.js +1 -0
  99. package/dist/mg-components/styles/a11y.scss +32 -0
  100. package/dist/mg-components/styles/components.scss +3 -0
  101. package/dist/mg-components/styles/fonts.scss +21 -0
  102. package/dist/mg-components/styles/form.scss +3 -0
  103. package/dist/mg-components/styles/global.scss +87 -0
  104. package/dist/mg-components/styles/layout.scss +29 -0
  105. package/dist/mg-components/styles/main.scss +23 -0
  106. package/dist/mg-components/styles/navigation-button.scss +76 -0
  107. package/dist/mg-components/styles/reset-mg-card-slotted-variables.scss +9 -0
  108. package/dist/mg-components/styles/typography.scss +75 -0
  109. package/dist/mg-components/styles/utilities.scss +21 -0
  110. package/dist/mg-components/variables.css +9 -9
  111. package/dist/types/components/atoms/mg-badge/mg-badge.conf.d.ts +2 -2
  112. package/dist/types/components/atoms/mg-button/mg-button.conf.d.ts +2 -2
  113. package/dist/types/components/atoms/mg-icon/mg-icon.conf.d.ts +2 -2
  114. package/dist/types/components/atoms/mg-tag/mg-tag.conf.d.ts +1 -1
  115. package/dist/types/components/molecules/inputs/MgInput.conf.d.ts +2 -2
  116. package/dist/types/components/molecules/inputs/mg-input-checkbox/mg-input-checkbox.conf.d.ts +2 -2
  117. package/dist/types/components/molecules/inputs/mg-input-radio/mg-input-radio.conf.d.ts +1 -1
  118. package/dist/types/components/molecules/inputs/mg-input-select/mg-input-select.conf.d.ts +2 -2
  119. package/dist/types/components/molecules/inputs/mg-input-toggle/mg-input-toggle.conf.d.ts +1 -1
  120. package/dist/types/components/molecules/menu/mg-menu/doc/mg-menu.stories.d.ts +5 -0
  121. package/dist/types/components/molecules/menu/mg-menu/mg-menu.conf.d.ts +37 -0
  122. package/dist/types/components/molecules/menu/mg-menu/mg-menu.d.ts +51 -3
  123. package/dist/types/components/molecules/menu/mg-menu-item/mg-menu-item.conf.d.ts +0 -8
  124. package/dist/types/components/molecules/menu/mg-menu-item/mg-menu-item.d.ts +55 -15
  125. package/dist/types/components/molecules/mg-message/mg-message.d.ts +21 -4
  126. package/dist/types/components/molecules/mg-popover/mg-popover.d.ts +12 -0
  127. package/dist/types/components/molecules/mg-skip-links/mg-skip-links.conf.d.ts +1 -1
  128. package/dist/types/components/molecules/mg-skip-links/mg-skip-links.d.ts +15 -0
  129. package/dist/types/components/molecules/mg-tabs/mg-tabs.conf.d.ts +5 -3
  130. package/dist/types/components/molecules/mg-tabs/mg-tabs.d.ts +22 -12
  131. package/dist/types/components.d.ts +44 -12
  132. package/dist/types/locales/index.d.ts +10 -9
  133. package/dist/types/stencil-public-runtime.d.ts +39 -3
  134. package/dist/types/utils/behaviors.utils.d.ts +83 -0
  135. package/dist/types/utils/components.utils.d.ts +14 -0
  136. package/dist/types/utils/locale.utils.d.ts +13 -0
  137. package/dist/types/utils/unit.test.utils.d.ts +50 -14
  138. package/package.json +26 -26
  139. package/readme.md +22 -13
  140. package/dist/components/locale.utils.js +0 -40
  141. package/dist/components/mg-menu.conf.js +0 -10
  142. package/dist/mg-components/p-4961c96c.entry.js +0 -1
@@ -1,10 +1,13 @@
1
1
  import { Host, h } from '@stencil/core';
2
2
  import { createID, isTagName } from '../../../utils/components.utils';
3
3
  import { createPopper } from '@popperjs/core';
4
+ import { getWindows, ClassList } from '../../../utils/components.utils';
4
5
  import { initLocales } from '../../../locales';
5
6
  export class MgPopover {
6
7
  constructor() {
7
8
  this.closeButtonId = '';
9
+ // Classes
10
+ this.classArrowHide = `mg-popover--arrow-hide`;
8
11
  /**
9
12
  * Check if clicked outside of component
10
13
  *
@@ -31,7 +34,9 @@ export class MgPopover {
31
34
  // hide when click outside
32
35
  // setTimeout is used to prevent event to trigger after creation
33
36
  setTimeout(() => {
34
- document.addEventListener('click', this.clickOutside, false);
37
+ this.windows.forEach((localWindow) => {
38
+ localWindow.addEventListener('click', this.clickOutside, false);
39
+ });
35
40
  }, 0);
36
41
  };
37
42
  /**
@@ -45,7 +50,9 @@ export class MgPopover {
45
50
  // Disable the event listeners
46
51
  this.popper.setOptions(options => (Object.assign(Object.assign({}, options), { modifiers: [...options.modifiers, { name: 'eventListeners', enabled: false }] })));
47
52
  // Remove event listener
48
- document.removeEventListener('click', this.clickOutside, false);
53
+ this.windows.forEach((localWindow) => {
54
+ localWindow.removeEventListener('click', this.clickOutside, false);
55
+ });
49
56
  };
50
57
  /**
51
58
  * Handle action for close button
@@ -57,9 +64,17 @@ export class MgPopover {
57
64
  };
58
65
  this.identifier = createID('mg-popover');
59
66
  this.placement = 'bottom';
67
+ this.arrowHide = false;
60
68
  this.closeButton = false;
61
69
  this.display = false;
62
70
  this.disabled = false;
71
+ this.classList = new ClassList(['mg-popover']);
72
+ }
73
+ validateArrowHide(newValue) {
74
+ if (newValue)
75
+ this.classList.add(this.classArrowHide);
76
+ else
77
+ this.classList.delete(this.classArrowHide);
63
78
  }
64
79
  handleDisplay(newValue) {
65
80
  if (newValue) {
@@ -79,8 +94,11 @@ export class MgPopover {
79
94
  * @returns {void} timeout
80
95
  */
81
96
  componentWillLoad() {
97
+ // Get windows to attach events
98
+ this.windows = getWindows(window);
82
99
  // Get locales
83
100
  this.messages = initLocales(this.element).messages;
101
+ this.validateArrowHide(this.arrowHide);
84
102
  }
85
103
  /**
86
104
  * Check if component props are well configured on init
@@ -112,7 +130,7 @@ export class MgPopover {
112
130
  {
113
131
  name: 'offset',
114
132
  options: {
115
- offset: [0, 10],
133
+ offset: [0, 0],
116
134
  },
117
135
  },
118
136
  ],
@@ -135,7 +153,7 @@ export class MgPopover {
135
153
  * @returns {HTMLElement} HTML Element
136
154
  */
137
155
  render() {
138
- return (h(Host, null, h("slot", null), h("div", { id: this.identifier, class: "mg-popover" }, h("mg-card", null, !this.disabled && this.closeButton && (h("mg-button", { identifier: this.closeButtonId, "is-icon": true, variant: "flat", label: this.messages.general.close, onClick: this.handleCloseButton }, h("mg-icon", { icon: "cross" }))), h("div", { class: "mg-popover__title" }, h("slot", { name: "title" })), h("slot", { name: "content" })), h("div", { class: "mg-popover__arrow", "data-popper-arrow": true }))));
156
+ return (h(Host, null, h("slot", null), h("div", { id: this.identifier, class: this.classList.join() }, h("mg-card", null, this.closeButton && (h("mg-button", { identifier: this.closeButtonId, "is-icon": true, variant: "flat", label: this.messages.general.close, onClick: this.handleCloseButton }, h("mg-icon", { icon: "cross" }))), h("div", { class: "mg-popover__title" }, h("slot", { name: "title" })), h("slot", { name: "content" })), !this.arrowHide && h("div", { class: "mg-popover__arrow", "data-popper-arrow": true }))));
139
157
  }
140
158
  static get is() { return "mg-popover"; }
141
159
  static get encapsulation() { return "shadow"; }
@@ -192,6 +210,24 @@ export class MgPopover {
192
210
  "reflect": false,
193
211
  "defaultValue": "'bottom'"
194
212
  },
213
+ "arrowHide": {
214
+ "type": "boolean",
215
+ "mutable": false,
216
+ "complexType": {
217
+ "original": "boolean",
218
+ "resolved": "boolean",
219
+ "references": {}
220
+ },
221
+ "required": false,
222
+ "optional": false,
223
+ "docs": {
224
+ "tags": [],
225
+ "text": "Hide popover arrow"
226
+ },
227
+ "attribute": "arrow-hide",
228
+ "reflect": false,
229
+ "defaultValue": "false"
230
+ },
195
231
  "closeButton": {
196
232
  "type": "boolean",
197
233
  "mutable": false,
@@ -248,6 +284,11 @@ export class MgPopover {
248
284
  }
249
285
  };
250
286
  }
287
+ static get states() {
288
+ return {
289
+ "classList": {}
290
+ };
291
+ }
251
292
  static get events() {
252
293
  return [{
253
294
  "method": "displayChange",
@@ -269,6 +310,9 @@ export class MgPopover {
269
310
  static get elementRef() { return "element"; }
270
311
  static get watchers() {
271
312
  return [{
313
+ "propName": "arrowHide",
314
+ "methodName": "validateArrowHide"
315
+ }, {
272
316
  "propName": "display",
273
317
  "methodName": "handleDisplay"
274
318
  }];
@@ -3,7 +3,7 @@ import { filterArgs } from '../../../../../.storybook/utils';
3
3
  export default {
4
4
  component: 'mg-skip-links',
5
5
  title: 'Molecules/mg-skip-links',
6
- parameters: { actions: { handles: ['expanded-change'] } },
6
+ parameters: { actions: { handles: ['go-to-anchor'] } },
7
7
  };
8
8
  /**
9
9
  * Template
@@ -2,6 +2,19 @@ import { h } from '@stencil/core';
2
2
  import { initLocales } from '../../../locales';
3
3
  export class MgSkipLinks {
4
4
  constructor() {
5
+ /***********
6
+ * Methods *
7
+ **********/
8
+ /**
9
+ * Handle link click and emit go-to-anchor event
10
+ *
11
+ * @param {MouseEvent} event mouse event
12
+ * @returns {void}
13
+ */
14
+ this.handleLinkCLick = (event) => {
15
+ event.currentTarget.blur();
16
+ this.goToAnchor.emit(event.currentTarget.getAttribute('href'));
17
+ };
5
18
  this.links = undefined;
6
19
  }
7
20
  validateLinks(links) {
@@ -29,7 +42,7 @@ export class MgSkipLinks {
29
42
  * @returns {HTMLElement} HTML Element
30
43
  */
31
44
  render() {
32
- return (h("nav", { class: "mg-skip-links", role: "navigation", "aria-label": this.messages.skipLinks.navLabel }, h("ul", { class: "mg-skip-links__list" }, this.links.map(link => (h("li", null, h("a", { class: "mg-skip-links__link", href: link.href }, link.label)))))));
45
+ return (h("nav", { class: "mg-skip-links", role: "navigation", "aria-label": this.messages.skipLinks.navLabel }, h("ul", { class: "mg-skip-links__list" }, this.links.map(link => (h("li", null, h("a", { class: "mg-skip-links__link", href: link.href, onClick: this.handleLinkCLick }, link.label)))))));
33
46
  }
34
47
  static get is() { return "mg-skip-links"; }
35
48
  static get encapsulation() { return "shadow"; }
@@ -67,6 +80,24 @@ export class MgSkipLinks {
67
80
  }
68
81
  };
69
82
  }
83
+ static get events() {
84
+ return [{
85
+ "method": "goToAnchor",
86
+ "name": "go-to-anchor",
87
+ "bubbles": true,
88
+ "cancelable": true,
89
+ "composed": true,
90
+ "docs": {
91
+ "tags": [],
92
+ "text": "Emited event when link is clicked"
93
+ },
94
+ "complexType": {
95
+ "original": "string",
96
+ "resolved": "string",
97
+ "references": {}
98
+ }
99
+ }];
100
+ }
70
101
  static get elementRef() { return "element"; }
71
102
  static get watchers() {
72
103
  return [{
@@ -24,11 +24,11 @@ export const MgTabs = Template.bind({});
24
24
  MgTabs.args = {
25
25
  items: ['Tab 1', 'Tab 2', 'Tab 3', 'Tab 4'],
26
26
  label: 'Short tabs description. Needed for accessibility',
27
- activeTab: undefined,
27
+ activeTab: 2,
28
28
  size: sizes[0], // regular
29
29
  };
30
30
  export const MgTabsItems = Template.bind({});
31
- MgTabsItems.args = Object.assign(Object.assign({}, MgTabs.args), { activeTab: 3, items: [
31
+ MgTabsItems.args = Object.assign(Object.assign({}, MgTabs.args), { activeTab: undefined, items: [
32
32
  {
33
33
  label: 'Tab 1',
34
34
  icon: 'check',
@@ -36,12 +36,14 @@ MgTabsItems.args = Object.assign(Object.assign({}, MgTabs.args), { activeTab: 3,
36
36
  },
37
37
  {
38
38
  label: 'Tab 2',
39
- badge: { value: 5, label: 'messages' },
39
+ badge: { value: 5, label: 'messages', role: 'information' },
40
40
  status: Status.DISABLED,
41
41
  },
42
42
  {
43
43
  label: 'Tab 3',
44
44
  icon: 'cross',
45
+ badge: { value: '9+', label: 'messages', role: 'notification' },
46
+ status: Status.ACTIVE,
45
47
  },
46
48
  {
47
49
  label: 'Tab 4',
@@ -1,6 +1,7 @@
1
1
  .mg-tabs.mg-tabs--size-regular .mg-tabs__navigation-button,
2
2
  .mg-tabs__navigation-button.mg-tabs__navigation-button--size-regular {
3
3
  padding: 1rem 2rem;
4
+ height: calc();
4
5
  }
5
6
  .mg-tabs.mg-tabs--size-regular .mg-tabs__navigation-button,
6
7
  .mg-tabs.mg-tabs--size-regular .mg-tabs__navigation-button ::slotted([slot=label]),
@@ -8,7 +9,7 @@
8
9
  .mg-tabs__navigation-button.mg-tabs__navigation-button--size-regular,
9
10
  .mg-tabs__navigation-button.mg-tabs__navigation-button--size-regular ::slotted([slot=label]),
10
11
  .mg-tabs__navigation-button.mg-tabs__navigation-button--size-regular ::slotted([slot=metadata]) {
11
- font-size: 1.3rem;
12
+ font-size: var(--font-size);
12
13
  }
13
14
  .mg-tabs.mg-tabs--size-regular .mg-tabs__navigation-button.mg-tabs__navigation-button--horizontal,
14
15
  .mg-tabs__navigation-button.mg-tabs__navigation-button--size-regular.mg-tabs__navigation-button--horizontal {
@@ -22,6 +23,7 @@
22
23
  .mg-tabs.mg-tabs--size-large .mg-tabs__navigation-button,
23
24
  .mg-tabs__navigation-button.mg-tabs__navigation-button--size-large {
24
25
  padding: 1.5rem 3rem;
26
+ height: calc();
25
27
  }
26
28
  .mg-tabs.mg-tabs--size-large .mg-tabs__navigation-button,
27
29
  .mg-tabs.mg-tabs--size-large .mg-tabs__navigation-button ::slotted([slot=label]),
@@ -29,7 +31,7 @@
29
31
  .mg-tabs__navigation-button.mg-tabs__navigation-button--size-large,
30
32
  .mg-tabs__navigation-button.mg-tabs__navigation-button--size-large ::slotted([slot=label]),
31
33
  .mg-tabs__navigation-button.mg-tabs__navigation-button--size-large ::slotted([slot=metadata]) {
32
- font-size: 1.5rem;
34
+ font-size: calc(var(--font-size) + 0.2rem);
33
35
  }
34
36
  .mg-tabs.mg-tabs--size-large .mg-tabs__navigation-button.mg-tabs__navigation-button--horizontal,
35
37
  .mg-tabs__navigation-button.mg-tabs__navigation-button--size-large.mg-tabs__navigation-button--horizontal {
@@ -43,28 +45,30 @@
43
45
  .mg-tabs__navigation-button {
44
46
  display: flex;
45
47
  align-items: center;
46
- column-gap: 1rem;
48
+ column-gap: var(--mg-tabs-navigation-button-column-gap, 1rem);
47
49
  background-color: unset;
48
50
  border: none;
49
51
  font-family: var(--font-family);
50
52
  color: hsl(var(--mg-tabs-color-hsl, var(--color-dark)));
51
53
  line-height: var(--line-height);
52
54
  text-decoration: none;
55
+ white-space: nowrap;
56
+ box-sizing: border-box;
53
57
  cursor: pointer;
54
58
  }
55
59
  .mg-tabs__navigation-button:disabled, .mg-tabs__navigation-button--disabled {
56
60
  cursor: default;
57
- opacity: 30%;
61
+ opacity: 0.3;
58
62
  }
59
63
  .mg-tabs__navigation-button:focus-visible {
60
64
  z-index: 10;
61
65
  }
62
- .mg-tabs__navigation-button:hover:not(.mg-tabs__navigation-button--disabled), .mg-tabs__navigation-button:focus:not(.mg-tabs__navigation-button--disabled) {
63
- background-color: hsla(var(--mg-tabs-focused-background-color-hsl), 10%);
66
+ .mg-tabs__navigation-button:hover:not(.mg-tabs__navigation-button--disabled), .mg-tabs__navigation-button:focus-visible:not(.mg-tabs__navigation-button--disabled) {
67
+ background-color: hsl(var(--mg-tabs-focused-background-color-hsl), 0.1);
64
68
  }
65
69
  .mg-tabs__navigation-button.mg-tabs__navigation-button--active {
66
70
  color: hsl(var(--mg-tabs-color-active-hsl));
67
- font-weight: 700;
71
+ font-weight: 600;
68
72
  }
69
73
  .mg-tabs__navigation-button.mg-tabs__navigation-button--active.mg-tabs__navigation-button--horizontal {
70
74
  border-bottom-color: hsl(var(--mg-tabs-border-color-active-hsl));
@@ -16,25 +16,7 @@ export class MgTabs {
16
16
  // classes
17
17
  this.tabPanel = 'panel';
18
18
  this.buttonTabBaseClass = 'mg-tabs__navigation-button';
19
- // variables
20
19
  this.startIndex = 1;
21
- /**
22
- * Method to set active tab
23
- *
24
- * @param {number} tabKey item tab key to set to ACTIVE status
25
- * @returns {void}
26
- */
27
- this.setActiveTab = (tabKey) => {
28
- // reset active tabs
29
- this.tabs.forEach(tab => {
30
- if (this.tabHasGivenStatus(tab, Status.ACTIVE))
31
- tab.status = Status.VISIBLE;
32
- });
33
- // set active tab from given tab key
34
- this.tabs[tabKey - this.startIndex].status = Status.ACTIVE;
35
- // emit change active tab key event
36
- this.activeTabChange.emit(tabKey);
37
- };
38
20
  /**
39
21
  * Method to know if given tab has the given status
40
22
  *
@@ -42,7 +24,7 @@ export class MgTabs {
42
24
  * @param {Status} status status to valide
43
25
  * @returns {boolean} status comparaison
44
26
  */
45
- this.tabHasGivenStatus = (tab, status) => tab.status === status;
27
+ this.tabHasStatus = (tab, status) => tab.status === status;
46
28
  /**
47
29
  * Method to get element id from index
48
30
  *
@@ -50,14 +32,20 @@ export class MgTabs {
50
32
  * @param {number} index to generate id
51
33
  * @returns {string} generated element id
52
34
  */
53
- this.getElementId = (element, index) => `${element}-${this.getTabIndex(index)}`;
35
+ this.getElementId = (element, index) => `${element}-${this.getTabItemIndex(index)}`;
54
36
  /**
55
- * Method to get tab index
37
+ * Method to get tab item index
56
38
  *
57
39
  * @param {number} index to get
58
40
  * @returns {number} index
59
41
  */
60
- this.getTabIndex = (index) => index + this.startIndex;
42
+ this.getTabItemIndex = (index) => index + this.startIndex;
43
+ /**
44
+ * Method to get the active-tab value
45
+ *
46
+ * @returns {number} of the active-tab
47
+ */
48
+ this.getActiveTab = () => this.activeTab || this.getTabItemIndex(this.tabs.map((tab, index) => (Object.assign(Object.assign({}, tab), { index }))).find(tab => this.tabHasStatus(tab, Status.ACTIVE)).index);
61
49
  /**
62
50
  * Handle click events on tabs
63
51
  *
@@ -65,16 +53,22 @@ export class MgTabs {
65
53
  */
66
54
  this.handleClick = (event) => {
67
55
  const tabId = event.currentTarget.getAttribute('data-index');
68
- this.activeTab = Number(tabId);
56
+ const tab = this.tabs[Number(tabId) - this.startIndex];
57
+ if (this.tabHasStatus(tab, Status.HIDDEN) || this.tabHasStatus(tab, Status.DISABLED)) {
58
+ event.preventDefault();
59
+ }
60
+ else {
61
+ this.activeTab = Number(tabId);
62
+ this.tabFocus = undefined;
63
+ }
69
64
  };
70
65
  /**
71
66
  * get navigation button class from given status
72
67
  *
73
68
  * @param {Status} status button tab status
74
- * @param {boolean} isSelector set if we need a CSS selector with the dot '.' at the begin. default value = false.
75
69
  * @returns {string} button class/selector variant
76
70
  */
77
- this.getNavigationButtonClass = (status, isSelector = false) => `${isSelector ? '.' : ''}${this.buttonTabBaseClass}--${status}`;
71
+ this.getNavigationButtonClass = (status) => `${this.buttonTabBaseClass}--${status}`;
78
72
  /**
79
73
  * Handle keyboard event on tabs
80
74
  *
@@ -82,37 +76,65 @@ export class MgTabs {
82
76
  * @returns {void}
83
77
  */
84
78
  this.handleKeydown = (event) => {
85
- let tabId = Number(event.target.getAttribute('data-index'));
86
79
  const parent = event.target.parentElement;
87
- // change selected id from key code
88
- if (event.key === 'ArrowRight') {
89
- tabId++;
90
- }
91
- else if (event.key === 'ArrowLeft') {
92
- tabId--;
80
+ if (['ArrowRight', 'ArrowLeft'].includes(event.key)) {
81
+ this.tabFocus = Number(event.target.getAttribute('data-index'));
82
+ parent.querySelector(`[data-index="${this.tabFocus}"]`).setAttribute('tabindex', '-1');
83
+ // Move right
84
+ if (event.key === 'ArrowRight') {
85
+ this.tabFocus++;
86
+ // If we're at the end, go to the start
87
+ if (this.tabFocus > this.tabs.length)
88
+ this.tabFocus = this.startIndex;
89
+ // Move left
90
+ }
91
+ else if (event.key === 'ArrowLeft') {
92
+ this.tabFocus--;
93
+ // If we're at the start, move to the end
94
+ if (this.tabFocus < this.startIndex)
95
+ this.tabFocus = this.tabs.length;
96
+ }
97
+ // if focus item is ['hidden'] we recursively repeat action
98
+ if (this.tabHasStatus(this.tabs[this.tabFocus - 1], Status.HIDDEN)) {
99
+ parent.querySelector(`[data-index="${this.tabFocus}"]`).dispatchEvent(new KeyboardEvent('keydown', { key: event.key, bubbles: true }));
100
+ // else run focus methods on tabFocus element
101
+ }
102
+ else {
103
+ parent.querySelector(`[data-index="${this.tabFocus}"]`).setAttribute('tabindex', '0');
104
+ parent.querySelector(`[data-index="${this.tabFocus}"]`).focus();
105
+ }
93
106
  }
94
- // get selected tab if NOT hidden, disabled
95
- const selectedTab = parent.querySelector(`[data-index="${tabId}"]:not(${[this.getNavigationButtonClass(Status.HIDDEN, true), this.getNavigationButtonClass(Status.DISABLED, true)].join()})`);
96
- // apply selected tab if exist
97
- if (selectedTab !== null) {
98
- selectedTab.focus();
99
- this.activeTab = tabId;
107
+ else if (event.key === 'Tab') {
108
+ this.resetFocus();
100
109
  }
101
110
  };
111
+ /**
112
+ * Method to reset focus behavior
113
+ *
114
+ * @returns {void}
115
+ */
116
+ this.resetFocus = () => {
117
+ // update asynchronously tabindex to prevent get focus on new tabindex at then end of event process
118
+ setTimeout(() => {
119
+ this.tabFocus = undefined;
120
+ Array.from(this.element.shadowRoot.querySelectorAll('[data-index]')).forEach((tab, index) => {
121
+ tab.setAttribute('tabindex', this.getActiveTab() - this.startIndex !== index ? '-1' : '0');
122
+ });
123
+ }, 0);
124
+ };
102
125
  /**
103
126
  * Validate slots setting
104
127
  */
105
128
  this.validateSlots = () => {
106
129
  const slots = Array.from(this.element.children).filter(slot => { var _a; return (_a = slot.getAttribute('slot')) === null || _a === void 0 ? void 0 : _a.includes('tab_content-'); });
107
- if (slots.length !== this.tabs.length) {
130
+ if (slots.length !== this.tabs.length)
108
131
  throw new Error('<mg-tabs> Must have slots counts equal to tabs count.');
109
- }
110
132
  };
111
133
  this.identifier = createID('mg-tabs');
112
134
  this.label = undefined;
113
135
  this.size = 'regular';
114
136
  this.items = undefined;
115
- this.activeTab = 1;
137
+ this.activeTab = undefined;
116
138
  this.tabs = [];
117
139
  this.classList = new ClassList(['mg-tabs']);
118
140
  }
@@ -130,22 +152,37 @@ export class MgTabs {
130
152
  validateItems(newValue) {
131
153
  // String array
132
154
  if (allItemsAreString(newValue)) {
133
- this.tabs = newValue.map(item => ({ label: item, status: Status.VISIBLE }));
155
+ this.tabs = newValue.map((item, index) => ({ label: item, status: index === 0 ? Status.ACTIVE : Status.VISIBLE }));
134
156
  }
135
157
  // Object array
136
- else if (newValue && newValue.every(item => isTabItem(item))) {
158
+ else if (newValue && newValue.length > 0 && newValue.every(item => isTabItem(item))) {
137
159
  this.tabs = newValue;
160
+ // init active tabs if not set. Default: index 0.
161
+ if (this.tabs.find(tab => this.tabHasStatus(tab, Status.ACTIVE)) === undefined)
162
+ this.tabs[0].status = Status.ACTIVE;
138
163
  }
139
164
  else {
140
165
  throw new Error('<mg-tabs> prop "items" is required and all items must be the same type: TabItem.');
141
166
  }
142
167
  }
143
168
  validateActiveTab(newValue) {
144
- if (newValue < 1 || newValue > this.tabs.length) {
145
- throw new Error('<mg-tabs> prop "activeTab" must be between 1 and tabs length.');
169
+ if (typeof newValue === 'number' && newValue >= 1 && newValue <= this.tabs.length) {
170
+ this.tabs.forEach((tab, index) => {
171
+ const isNewActiveTab = index === newValue - this.startIndex;
172
+ // reset active tabs
173
+ if (this.tabHasStatus(tab, Status.ACTIVE) && !isNewActiveTab)
174
+ tab.status = Status.VISIBLE;
175
+ // set active tab from given tab key
176
+ else if ((tab.status === undefined || this.tabHasStatus(tab, Status.VISIBLE)) && isNewActiveTab)
177
+ tab.status = Status.ACTIVE;
178
+ });
179
+ // emit change active tab key event
180
+ if (this.tabs.find(tab => this.tabHasStatus(tab, Status.ACTIVE)) !== undefined) {
181
+ this.activeTabChange.emit(newValue);
182
+ }
146
183
  }
147
- else {
148
- this.setActiveTab(newValue);
184
+ else if (newValue !== undefined) {
185
+ throw new Error('<mg-tabs> prop "activeTab" must be between 1 and tabs length.');
149
186
  }
150
187
  }
151
188
  /*************
@@ -164,6 +201,17 @@ export class MgTabs {
164
201
  this.validateSize(this.size);
165
202
  this.validateSlots();
166
203
  }
204
+ /**
205
+ * add listners
206
+ *
207
+ * @returns {void}
208
+ */
209
+ componentDidLoad() {
210
+ document.addEventListener('click', (event) => {
211
+ if (event.target.closest('mg-tabs') !== this.element)
212
+ this.resetFocus();
213
+ }, false);
214
+ }
167
215
  /**
168
216
  * Render
169
217
  *
@@ -173,10 +221,10 @@ export class MgTabs {
173
221
  return (h("div", { class: this.classList.join() }, h("header", { role: "tablist", "aria-label": this.label, class: "mg-tabs__header" }, this.tabs.map((tab, index) => (h("button", { role: "tab", id: this.getElementId(this.identifier, index), class: {
174
222
  [`${this.buttonTabBaseClass}`]: true,
175
223
  [`${this.buttonTabBaseClass}--horizontal`]: true,
176
- [`${this.getNavigationButtonClass(Status.ACTIVE)}`]: this.tabHasGivenStatus(tab, Status.ACTIVE),
177
- [`${this.getNavigationButtonClass(Status.DISABLED)}`]: this.tabHasGivenStatus(tab, Status.DISABLED),
178
- [`${this.getNavigationButtonClass(Status.HIDDEN)}`]: this.tabHasGivenStatus(tab, Status.HIDDEN),
179
- }, tabindex: this.tabHasGivenStatus(tab, Status.ACTIVE) ? 0 : -1, "aria-selected": this.tabHasGivenStatus(tab, Status.ACTIVE).toString(), "aria-controls": this.getElementId(this.tabPanel, index), onClick: this.handleClick, onKeyDown: this.handleKeydown, "data-index": this.getTabIndex(index), disabled: this.tabHasGivenStatus(tab, Status.DISABLED) }, tab.icon !== undefined && h("mg-icon", { icon: tab.icon }), tab.label, tab.badge !== undefined && h("mg-badge", { variant: tab.status !== Status.ACTIVE ? 'primary' : 'info', value: tab.badge.value, label: tab.badge.label }))))), this.tabs.map((tab, index) => (h("article", { role: "tabpanel", id: this.getElementId(this.tabPanel, index), hidden: !this.tabHasGivenStatus(tab, Status.ACTIVE), "aria-labelledby": this.getElementId(this.identifier, index), tabindex: this.tabHasGivenStatus(tab, Status.ACTIVE) ? 0 : -1, class: "mg-tabs__content-container" }, h("slot", { name: this.getElementId('tab_content', index) }))))));
224
+ [`${this.getNavigationButtonClass(Status.ACTIVE)}`]: this.tabHasStatus(tab, Status.ACTIVE),
225
+ [`${this.getNavigationButtonClass(Status.DISABLED)}`]: this.tabHasStatus(tab, Status.DISABLED),
226
+ [`${this.getNavigationButtonClass(Status.HIDDEN)}`]: this.tabHasStatus(tab, Status.HIDDEN),
227
+ }, tabindex: this.tabHasStatus(tab, Status.ACTIVE) ? 0 : -1, "aria-selected": this.tabHasStatus(tab, Status.ACTIVE).toString(), "aria-controls": this.getElementId(this.tabPanel, index), "aria-disabled": this.tabHasStatus(tab, Status.DISABLED), onClick: this.handleClick, onKeyDown: this.handleKeydown, "data-index": this.getTabItemIndex(index) }, tab.icon !== undefined && h("mg-icon", { icon: tab.icon }), tab.label, tab.badge !== undefined && h("mg-badge", { variant: "text-color", value: tab.badge.value, label: tab.badge.label, outline: tab.badge.role === 'information' }))))), this.tabs.map((tab, index) => (h("article", { role: "tabpanel", id: this.getElementId(this.tabPanel, index), hidden: !this.tabHasStatus(tab, Status.ACTIVE), "aria-labelledby": this.getElementId(this.identifier, index), tabindex: this.tabHasStatus(tab, Status.ACTIVE) ? 0 : -1, class: "mg-tabs__content-container" }, h("slot", { name: this.getElementId('tab_content', index) }))))));
180
228
  }
181
229
  static get is() { return "mg-tabs"; }
182
230
  static get encapsulation() { return "shadow"; }
@@ -282,11 +330,10 @@ export class MgTabs {
282
330
  "optional": false,
283
331
  "docs": {
284
332
  "tags": [],
285
- "text": "Active tab number\ndefault: first is 1"
333
+ "text": "Active tab number"
286
334
  },
287
335
  "attribute": "active-tab",
288
- "reflect": true,
289
- "defaultValue": "1"
336
+ "reflect": true
290
337
  }
291
338
  };
292
339
  }
@@ -49,5 +49,9 @@
49
49
  "close": "Close",
50
50
  "next": "next",
51
51
  "previous": "previous"
52
+ },
53
+ "plusMenu": {
54
+ "moreLabel": "Additional menu",
55
+ "badgeLabel": "Include notification(s)"
52
56
  }
53
57
  }
@@ -49,5 +49,9 @@
49
49
  "close": "Fermer",
50
50
  "next": "suivant",
51
51
  "previous": "précédent"
52
+ },
53
+ "plusMenu": {
54
+ "moreLabel": "Menu complémentaire",
55
+ "badgeLabel": "Notification(s) incluse(s)"
52
56
  }
53
57
  }
@@ -1,35 +1,12 @@
1
1
  import en from './en/messages.json';
2
2
  import fr from './fr/messages.json';
3
+ import { getLocaleMessages } from '../utils/locale.utils';
3
4
  const defaultLocale = 'en';
4
5
  const messages = { en, fr };
5
- /**
6
- * Get locale
7
- *
8
- * @param {HTMLElement} element element that needs to know the locale to use
9
- * @returns {string} locale
10
- */
11
- const getLocale = (element) => {
12
- var _a;
13
- // validate locale
14
- const closestLang = Intl.NumberFormat.supportedLocalesOf((_a = element.closest('[lang]')) === null || _a === void 0 ? void 0 : _a.lang);
15
- return closestLang.length > 0 ? closestLang[0] : navigator.language || defaultLocale;
16
- };
17
6
  /**
18
7
  * Get Intl object
19
- * We load the defined locale but for now we only support the first subtag for messages
20
8
  *
21
9
  * @param {HTMLElement} element element we need to get the language
22
10
  * @returns {{ locale: string; messages: unknown }} messages object
23
11
  */
24
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
- export const initLocales = (element) => {
26
- // Get local
27
- const locale = getLocale(element);
28
- // Only keep first subtag
29
- const localeSubtag = locale.split('-').shift();
30
- // Return
31
- return {
32
- locale,
33
- messages: messages[localeSubtag] || messages[defaultLocale],
34
- };
35
- };
12
+ export const initLocales = (element) => getLocaleMessages(element, messages, defaultLocale);
@@ -0,0 +1,32 @@
1
+ /**
2
+ * A11Y
3
+ */
4
+
5
+ // Make content available only for screen reader
6
+ // https://gist.github.com/ffoodd/000b59f431e3e64e4ce1a24d5bb36034
7
+ .sr-only {
8
+ border: 0 !important;
9
+ clip: rect(1px, 1px, 1px, 1px) !important;
10
+ -webkit-clip-path: inset(50%) !important;
11
+ clip-path: inset(50%) !important;
12
+ height: 1px !important;
13
+ margin: -1px !important;
14
+ overflow: hidden !important;
15
+ padding: 0 !important;
16
+ position: absolute !important;
17
+ width: 1px !important;
18
+ white-space: nowrap !important;
19
+ }
20
+
21
+ // Remove outline for non-keyboard :focus
22
+ // https://www.tpgi.com/focus-visible-and-backwards-compatibility/
23
+ *:focus:not(:focus-visible) {
24
+ outline: none;
25
+ }
26
+
27
+ @media (prefers-reduced-motion) {
28
+ .mg-a11y-animation {
29
+ animation: none !important;
30
+ transition: none !important;
31
+ }
32
+ }
@@ -0,0 +1,3 @@
1
+ [hide] {
2
+ display: none;
3
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Open Sans
3
+ * https://fonts.google.com/specimen/Open+Sans
4
+ */
5
+
6
+ @font-face {
7
+ font-family: 'Open Sans';
8
+ src: url('fonts/OpenSans-Regular.ttf') format('truetype');
9
+ }
10
+
11
+ @font-face {
12
+ font-family: 'Open Sans';
13
+ font-weight: 600;
14
+ src: url('fonts/OpenSans-SemiBold.ttf') format('truetype');
15
+ }
16
+
17
+ @font-face {
18
+ font-family: 'Open Sans';
19
+ font-weight: 700;
20
+ src: url('fonts/OpenSans-Bold.ttf') format('truetype');
21
+ }