@undrr/undrr-mangrove 1.3.2 → 1.4.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.
- package/README.md +32 -25
- package/components/BarChart.js +2 -2
- package/components/BookCard.js +6 -0
- package/components/BookCard.js.LICENSE.txt +11 -0
- package/components/Breadcrumbs.js +6 -0
- package/components/Breadcrumbs.js.LICENSE.txt +9 -0
- package/components/Checkbox.js +6 -0
- package/components/Checkbox.js.LICENSE.txt +9 -0
- package/components/Chips.js +6 -0
- package/components/Chips.js.LICENSE.txt +9 -0
- package/components/CtaButton.js +6 -0
- package/components/CtaButton.js.LICENSE.txt +9 -0
- package/components/EmbedContainer.js +6 -0
- package/components/EmbedContainer.js.LICENSE.txt +9 -0
- package/components/Fetcher.js +2 -2
- package/components/Footer.js +6 -0
- package/components/Footer.js.LICENSE.txt +9 -0
- package/components/FormErrorSummary.js +6 -0
- package/components/FormErrorSummary.js.LICENSE.txt +9 -0
- package/components/FormGroup.js +6 -0
- package/components/FormGroup.js.LICENSE.txt +9 -0
- package/components/FullWidth.js +6 -0
- package/components/FullWidth.js.LICENSE.txt +9 -0
- package/components/Gallery.js +2 -2
- package/components/Hero.js +6 -0
- package/components/Hero.js.LICENSE.txt +9 -0
- package/components/HighlightBox.js +6 -0
- package/components/HighlightBox.js.LICENSE.txt +9 -0
- package/components/HorizontalBookCard.js +6 -0
- package/components/HorizontalBookCard.js.LICENSE.txt +11 -0
- package/components/HorizontalCard.js +6 -0
- package/components/HorizontalCard.js.LICENSE.txt +11 -0
- package/components/IconCard.js +2 -2
- package/components/IconCard.js.LICENSE.txt +1 -1
- package/components/Loader.js +6 -0
- package/components/Loader.js.LICENSE.txt +9 -0
- package/components/MapComponent.js +2 -2
- package/components/MegaMenu.js +2 -2
- package/components/PageHeader.js +6 -0
- package/components/PageHeader.js.LICENSE.txt +9 -0
- package/components/Pager.js +2 -2
- package/components/QuoteHighlight.js +2 -2
- package/components/Radio.js +6 -0
- package/components/Radio.js.LICENSE.txt +9 -0
- package/components/ScrollContainer.js +2 -2
- package/components/Select.js +6 -0
- package/components/Select.js.LICENSE.txt +9 -0
- package/components/ShareButtons.js +2 -2
- package/components/ShowMore.js +6 -0
- package/components/ShowMore.js.LICENSE.txt +9 -0
- package/components/StatsCard.js +2 -2
- package/components/SyndicationSearchWidget.js +2 -2
- package/components/Tab.js +6 -0
- package/components/Tab.js.LICENSE.txt +9 -0
- package/components/TextCta.js +6 -0
- package/components/TextCta.js.LICENSE.txt +11 -0
- package/components/TextInput.js +6 -0
- package/components/TextInput.js.LICENSE.txt +9 -0
- package/components/Textarea.js +6 -0
- package/components/Textarea.js.LICENSE.txt +9 -0
- package/components/VerticalCard.js +6 -0
- package/components/VerticalCard.js.LICENSE.txt +11 -0
- package/components/hydrate.js +5 -0
- package/css/style-delta.css +464 -0
- package/css/style-delta.css.map +1 -0
- package/css/style-gutenberg.css +19 -18
- package/css/style-gutenberg.css.map +1 -1
- package/css/style-irp-legacy.css +469 -0
- package/css/style-irp-legacy.css.map +1 -0
- package/css/style-irp.css +96 -103
- package/css/style-irp.css.map +1 -1
- package/css/style-legacy.css +462 -0
- package/css/style-legacy.css.map +1 -0
- package/css/style-mcr-legacy.css +469 -0
- package/css/style-mcr-legacy.css.map +1 -0
- package/css/style-mcr.css +96 -103
- package/css/style-mcr.css.map +1 -1
- package/css/style-preventionweb-legacy.css +469 -0
- package/css/style-preventionweb-legacy.css.map +1 -0
- package/css/style-preventionweb.css +96 -103
- package/css/style-preventionweb.css.map +1 -1
- package/css/style.css +96 -103
- package/css/style.css.map +1 -1
- package/error-pages/401.html +10 -11
- package/error-pages/403.html +11 -12
- package/error-pages/404.html +13 -14
- package/error-pages/429.html +12 -13
- package/error-pages/500.html +10 -11
- package/error-pages/502.html +12 -13
- package/error-pages/503.html +12 -13
- package/error-pages/504.html +10 -11
- package/error-pages/5xx.html +10 -11
- package/error-pages/challenge.html +12 -13
- package/error-pages/managed-challenge.html +12 -13
- package/js/tabs.js +427 -88
- package/package.json +1 -1
- package/scss/Atom/BaseTypography/Blockquote/blockquote.scss +1 -8
- package/scss/Atom/BaseTypography/Cite/cite.scss +2 -2
- package/scss/Atom/Images/AuthorImage/author-image.scss +4 -4
- package/scss/Atom/Images/ImageCaptionCredit/image-caption-credit.scss +24 -28
- package/scss/Atom/Images/ImageCredit/image-credit.scss +1 -1
- package/scss/Atom/Layout/Container/container.scss +2 -2
- package/scss/Atom/Layout/Grid/grid.scss +1 -1
- package/scss/Atom/ReachElement/Details/details.scss +6 -6
- package/scss/Atom/ReachElement/Figcaption/figcaption.scss +1 -1
- package/scss/Atom/Table/table.scss +0 -8
- package/scss/Components/Boilerplate/boilerplate.scss +2 -2
- package/scss/Components/Breadcrumbs/breadcrumbs.scss +2 -9
- package/scss/Components/Buttons/Chips/chips.scss +5 -19
- package/scss/Components/Buttons/CtaButton/buttons.scss +3 -124
- package/scss/Components/Buttons/CtaButton/cta-button.scss +124 -0
- package/scss/Components/Buttons/ShareButtons/share-buttons.scss +2 -2
- package/scss/Components/Cards/Card/card.scss +41 -2
- package/scss/Components/ErrorPages/error-pages.scss +12 -12
- package/scss/Components/Footer/footer.scss +72 -4
- package/scss/Components/Forms/Select/select.scss +2 -2
- package/scss/Components/Forms/_form-base.scss +5 -5
- package/scss/Components/Forms/_form-legacy.scss +1 -1
- package/scss/Components/Gallery/gallery.scss +4 -4
- package/scss/Components/Hero/hero.scss +18 -17
- package/scss/Components/HighlightBox/highlight-box.scss +5 -5
- package/scss/Components/MegaMenu/mega-menu.scss +750 -0
- package/scss/Components/MegaMenu/megamenu.scss +3 -659
- package/scss/Components/PageHeader/page-header.scss +4 -4
- package/scss/Components/Pagination/pagination.scss +2 -2
- package/scss/Components/SyndicationSearchWidget/SyndicationSearchWidget.scss +3 -1480
- package/scss/Components/SyndicationSearchWidget/syndication-search-widget.scss +1515 -0
- package/scss/Components/Tab/tab.scss +66 -7
- package/scss/Components/TextCta/text-cta.scss +129 -0
- package/scss/Components/TextCta/textcta.scss +3 -27
- package/scss/Molecules/ImageCaption/image-caption.scss +6 -16
- package/scss/Molecules/SectionHeader/section-header.scss +8 -0
- package/scss/Molecules/SectionHeader/sectionheader.scss +3 -8
- package/scss/Utilities/FullWidth/FullWidth.scss +3 -23
- package/scss/Utilities/FullWidth/full-width.scss +23 -0
- package/scss/Utilities/Loader/loader.scss +1 -1
- package/scss/Utilities/ShowMore/ShowMore.scss +3 -26
- package/scss/Utilities/ShowMore/show-more.scss +26 -0
- package/scss/assets/scss/_components.scss +14 -9
- package/scss/assets/scss/_foundational.scss +13 -7
- package/scss/assets/scss/_mixins.scss +9 -314
- package/scss/assets/scss/_utility.scss +19 -71
- package/scss/assets/scss/_variables-delta.scss +105 -0
- package/scss/assets/scss/_variables-irp.scss +1 -1
- package/scss/assets/scss/_variables-mcr.scss +1 -1
- package/scss/assets/scss/_variables-preventionweb.scss +1 -1
- package/scss/assets/scss/_variables.scss +68 -34
- package/scss/assets/scss/style-delta.scss +8 -0
- package/scss/assets/scss/style-gutenberg.scss +2 -2
- package/scss/assets/scss/style-irp-legacy.scss +20 -0
- package/scss/assets/scss/style-legacy.scss +20 -0
- package/scss/assets/scss/style-mcr-legacy.scss +20 -0
- package/scss/assets/scss/style-preventionweb-legacy.scss +20 -0
- package/scss/Components/BlockquoteComponent/blockquotecomp.scss +0 -31
- package/scss/Components/Buttons/CtaLink/cta-link.scss +0 -61
- /package/scss/Components/TableOfContents/{TableOfContents.scss → table-of-contents.scss} +0 -0
package/js/tabs.js
CHANGED
|
@@ -1,4 +1,71 @@
|
|
|
1
1
|
// mg-tabs
|
|
2
|
+
|
|
3
|
+
// Matches $mg-breakpoint-mobile in _variables.scss
|
|
4
|
+
const BREAKPOINT_MOBILE = 480;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Determine whether a tab container should behave as stacked (disclosure)
|
|
8
|
+
* rather than horizontal tabs.
|
|
9
|
+
* @param {Element} container - the [data-mg-js-tabs] element
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
function isStacked(container) {
|
|
13
|
+
return (
|
|
14
|
+
container.dataset.mgJsTabsVariant === 'stacked' ||
|
|
15
|
+
window.innerWidth < BREAKPOINT_MOBILE
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Set the open/close state of a stacked disclosure panel.
|
|
21
|
+
* @param {Element} trigger - the tab link acting as disclosure trigger
|
|
22
|
+
* @param {Element} panel - the section panel
|
|
23
|
+
* @param {boolean} open - true to open, false to close
|
|
24
|
+
*/
|
|
25
|
+
export function setDisclosureState(trigger, panel, open) {
|
|
26
|
+
if (open) {
|
|
27
|
+
panel.removeAttribute('hidden');
|
|
28
|
+
trigger.setAttribute('aria-expanded', 'true');
|
|
29
|
+
trigger.classList.add('is-active', 'mg-tabs__stacked--open');
|
|
30
|
+
} else {
|
|
31
|
+
panel.setAttribute('hidden', 'until-found');
|
|
32
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
33
|
+
trigger.classList.remove('is-active', 'mg-tabs__stacked--open');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Find the next visible tab index, skipping items hidden by filtering.
|
|
39
|
+
* @param {NodeList} tabs - all tab trigger elements
|
|
40
|
+
* @param {number} fromIndex - current index to start searching from
|
|
41
|
+
* @param {number} step - direction: +1 for forward, -1 for backward
|
|
42
|
+
* @returns {number} index of the next visible tab, or -1 if all hidden
|
|
43
|
+
*/
|
|
44
|
+
function findVisibleTab(tabs, fromIndex, step) {
|
|
45
|
+
const len = tabs.length;
|
|
46
|
+
let idx = fromIndex;
|
|
47
|
+
for (let i = 0; i < len; i++) {
|
|
48
|
+
idx = (idx + step + len) % len;
|
|
49
|
+
if (!tabs[idx].closest('.mg-tabs__item--hidden')) return idx;
|
|
50
|
+
}
|
|
51
|
+
return -1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Normalize text for filter matching: collapse smart quotes, dashes,
|
|
56
|
+
* and other typographic punctuation to their plain ASCII equivalents.
|
|
57
|
+
* @param {string} text
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
function normalizeText(text) {
|
|
61
|
+
return text
|
|
62
|
+
.replace(/[\u2018\u2019\u201A\u2032]/g, "'") // smart single quotes, prime
|
|
63
|
+
.replace(/[\u201C\u201D\u201E\u2033]/g, '"') // smart double quotes, double prime
|
|
64
|
+
.replace(/[\u2013\u2014\u2015]/g, '-') // en dash, em dash, horizontal bar
|
|
65
|
+
.replace(/\u2026/g, '...') // ellipsis
|
|
66
|
+
.replace(/[\u00A0]/g, ' '); // non-breaking space
|
|
67
|
+
}
|
|
68
|
+
|
|
2
69
|
/**
|
|
3
70
|
* Initialize tabs on a page
|
|
4
71
|
* @param {boolean} [activateDeepLinkOnLoad] - if deep linked tabs should be activated on page load, defaults to true
|
|
@@ -19,7 +86,7 @@ export function mgTabs(scope, activateDeepLinkOnLoad = true) {
|
|
|
19
86
|
*/
|
|
20
87
|
export function mgTabsRuntime(scope, activateDeepLinkOnLoad) {
|
|
21
88
|
var scope = scope || document;
|
|
22
|
-
var activateDeepLinkOnLoad = activateDeepLinkOnLoad
|
|
89
|
+
var activateDeepLinkOnLoad = activateDeepLinkOnLoad ?? true;
|
|
23
90
|
|
|
24
91
|
// Get relevant elements and collections
|
|
25
92
|
if (scope.hasAttribute('data-mg-js-tabs')) {
|
|
@@ -29,7 +96,7 @@ export function mgTabsRuntime(scope, activateDeepLinkOnLoad) {
|
|
|
29
96
|
scope.querySelectorAll('[data-mg-js-tabs]') || newTab.closest('.mg-tabs'); // compatibility with v1 tabs
|
|
30
97
|
}
|
|
31
98
|
const tabs = scope.querySelectorAll('.mg-tabs__link');
|
|
32
|
-
var panels = scope.querySelectorAll('[id^="mg-tabs__section"]');
|
|
99
|
+
var panels = scope.querySelectorAll('[id^="mg-tabs__section"]:not(a)');
|
|
33
100
|
// v1 compatibility
|
|
34
101
|
// If panels is empty, try finding them in data-mg-js-tabs-content
|
|
35
102
|
if (!panels.length) {
|
|
@@ -37,17 +104,10 @@ export function mgTabsRuntime(scope, activateDeepLinkOnLoad) {
|
|
|
37
104
|
.closest('.mg-tabs')
|
|
38
105
|
.querySelector('[data-mg-js-tabs-content]');
|
|
39
106
|
if (tabContent) {
|
|
40
|
-
panels = tabContent.querySelectorAll('[id^="mg-tabs__section"]');
|
|
107
|
+
panels = tabContent.querySelectorAll('[id^="mg-tabs__section"]:not(a)');
|
|
41
108
|
}
|
|
42
109
|
}
|
|
43
110
|
|
|
44
|
-
// console.log("debug: All panels");
|
|
45
|
-
// console.log("Tab list: " , tabsList);
|
|
46
|
-
// console.log("Panel List: ", panelsList);
|
|
47
|
-
// console.log("Panels: ", panels);
|
|
48
|
-
// console.log("Tabs: ", tabs);
|
|
49
|
-
// console.log("End Debug: All panels");
|
|
50
|
-
|
|
51
111
|
if (!tabsList || !panels || !tabs) {
|
|
52
112
|
// exit: either tabs or tabbed content not found
|
|
53
113
|
return;
|
|
@@ -57,24 +117,54 @@ export function mgTabsRuntime(scope, activateDeepLinkOnLoad) {
|
|
|
57
117
|
return;
|
|
58
118
|
}
|
|
59
119
|
|
|
120
|
+
// Normalize tabsList to an array so we can iterate uniformly
|
|
121
|
+
// (when scope has [data-mg-js-tabs], tabsList is a single Element)
|
|
122
|
+
const tabsListArray = tabsList.nodeType
|
|
123
|
+
? [tabsList]
|
|
124
|
+
: Array.from(tabsList);
|
|
125
|
+
|
|
60
126
|
// Check if tabs have already been initialized
|
|
61
|
-
if (tabsList.hasAttribute('data-mg-tabs-initialized')) {
|
|
127
|
+
if (tabsList.hasAttribute && tabsList.hasAttribute('data-mg-tabs-initialized')) {
|
|
62
128
|
return;
|
|
63
129
|
}
|
|
64
|
-
tabsList.
|
|
130
|
+
if (tabsList.hasAttribute) {
|
|
131
|
+
tabsList.setAttribute('data-mg-tabs-initialized', 'true');
|
|
132
|
+
} else if (tabsListArray.length > 0) {
|
|
133
|
+
if (tabsListArray[0].hasAttribute('data-mg-tabs-initialized')) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
tabsListArray[0].setAttribute('data-mg-tabs-initialized', 'true');
|
|
137
|
+
}
|
|
65
138
|
|
|
66
|
-
//
|
|
139
|
+
// Determine variant from the container element
|
|
140
|
+
const container = tabsListArray[0];
|
|
141
|
+
const stacked = isStacked(container);
|
|
142
|
+
|
|
143
|
+
// Add semantics and focusability for each tab
|
|
67
144
|
Array.prototype.forEach.call(tabs, (tab, i) => {
|
|
68
|
-
const
|
|
69
|
-
tab.setAttribute('
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
tab.setAttribute('
|
|
73
|
-
|
|
145
|
+
const panelId = tab.href.split('#')[1];
|
|
146
|
+
tab.setAttribute('data-tabs__item', panelId);
|
|
147
|
+
|
|
148
|
+
// Give trigger a distinct ID so it doesn't collide with the panel's ID
|
|
149
|
+
tab.setAttribute('id', panelId + '--trigger');
|
|
150
|
+
|
|
151
|
+
if (stacked) {
|
|
152
|
+
// Disclosure pattern: each trigger is a button that toggles its panel
|
|
153
|
+
tab.setAttribute('role', 'button');
|
|
154
|
+
tab.setAttribute('aria-expanded', 'false');
|
|
155
|
+
tab.setAttribute('aria-controls', panelId);
|
|
156
|
+
tab.parentNode.removeAttribute('role');
|
|
157
|
+
// Stacked triggers must be in the Tab order (disclosure buttons)
|
|
158
|
+
tab.removeAttribute('tabindex');
|
|
159
|
+
} else {
|
|
160
|
+
// Horizontal tabs: standard tablist pattern (roving tabindex)
|
|
161
|
+
tab.setAttribute('role', 'tab');
|
|
162
|
+
tab.parentNode.setAttribute('role', 'presentation');
|
|
163
|
+
tab.setAttribute('tabindex', '-1');
|
|
164
|
+
}
|
|
74
165
|
|
|
75
166
|
// Reset any active tabs from a previous JS call
|
|
76
167
|
tab.removeAttribute('aria-selected');
|
|
77
|
-
tab.setAttribute('tabindex', '-1');
|
|
78
168
|
tab.classList.remove('is-active');
|
|
79
169
|
|
|
80
170
|
// Handle clicking of tabs for mouse users
|
|
@@ -86,47 +176,86 @@ export function mgTabsRuntime(scope, activateDeepLinkOnLoad) {
|
|
|
86
176
|
// Handle keydown events for keyboard users
|
|
87
177
|
tab.addEventListener('keydown', e => {
|
|
88
178
|
// Get the index of the current tab in the tabs node list
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
? 'down'
|
|
99
|
-
: null;
|
|
100
|
-
if (dir !== null) {
|
|
179
|
+
const index = Array.prototype.indexOf.call(tabs, e.currentTarget);
|
|
180
|
+
const parentContainer =
|
|
181
|
+
e.currentTarget.closest('[data-mg-js-tabs]') ||
|
|
182
|
+
e.currentTarget.closest('.mg-tabs');
|
|
183
|
+
const currentlyStacked = isStacked(parentContainer);
|
|
184
|
+
|
|
185
|
+
// Stacked: Space/Enter to toggle, Up/Down to navigate, Home/End for first/last
|
|
186
|
+
// Horizontal: Left/Right to navigate tabs, Down to focus panel
|
|
187
|
+
if (currentlyStacked && (e.key === ' ' || e.key === 'Enter')) {
|
|
101
188
|
e.preventDefault();
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
189
|
+
mgTabsSwitch(e.currentTarget, panels);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const prevKey = currentlyStacked ? 'ArrowUp' : 'ArrowLeft';
|
|
194
|
+
const nextKey = currentlyStacked ? 'ArrowDown' : 'ArrowRight';
|
|
195
|
+
let dir = null;
|
|
196
|
+
|
|
197
|
+
if (e.key === prevKey) {
|
|
198
|
+
dir = findVisibleTab(tabs, index, -1);
|
|
199
|
+
} else if (e.key === nextKey) {
|
|
200
|
+
dir = findVisibleTab(tabs, index, 1);
|
|
201
|
+
} else if (!currentlyStacked && e.key === 'ArrowDown') {
|
|
202
|
+
dir = 'down';
|
|
203
|
+
} else if (e.key === 'Home') {
|
|
204
|
+
dir = findVisibleTab(tabs, -1, 1);
|
|
205
|
+
} else if (e.key === 'End') {
|
|
206
|
+
dir = findVisibleTab(tabs, tabs.length, -1);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (dir !== null && dir !== -1) {
|
|
210
|
+
e.preventDefault();
|
|
211
|
+
if (dir === 'down') {
|
|
212
|
+
panels[i].focus({ preventScroll: true });
|
|
213
|
+
} else if (tabs[dir]) {
|
|
214
|
+
if (currentlyStacked) {
|
|
215
|
+
// In stacked mode, just move focus without switching
|
|
216
|
+
tabs[dir].focus({ preventScroll: true });
|
|
217
|
+
} else {
|
|
218
|
+
mgTabsSwitch(tabs[dir], panels);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
109
221
|
}
|
|
110
222
|
});
|
|
111
223
|
});
|
|
112
224
|
|
|
113
|
-
// Add
|
|
225
|
+
// Add panel semantics and hide them all
|
|
114
226
|
Array.prototype.forEach.call(panels, panel => {
|
|
115
|
-
panel.
|
|
227
|
+
const panelId = panel.id;
|
|
228
|
+
// Find the corresponding tab trigger
|
|
229
|
+
const correspondingTab = scope.querySelector(`[data-tabs__item="${panelId}"]`);
|
|
230
|
+
const labelId = correspondingTab ? correspondingTab.id : panelId;
|
|
231
|
+
|
|
232
|
+
if (stacked) {
|
|
233
|
+
// Disclosure pattern: panels are regions labelled by their trigger
|
|
234
|
+
panel.setAttribute('role', 'region');
|
|
235
|
+
panel.setAttribute('aria-labelledby', labelId);
|
|
236
|
+
panel.setAttribute('hidden', 'until-found');
|
|
237
|
+
} else {
|
|
238
|
+
// Horizontal tabs: standard tabpanel
|
|
239
|
+
panel.setAttribute('role', 'tabpanel');
|
|
240
|
+
panel.setAttribute('aria-labelledby', labelId);
|
|
241
|
+
panel.hidden = true;
|
|
242
|
+
}
|
|
116
243
|
panel.setAttribute('tabindex', '-1');
|
|
117
|
-
// let id = panel.getAttribute("id");
|
|
118
|
-
panel.setAttribute('aria-labelledby', panel.id);
|
|
119
|
-
panel.hidden = true;
|
|
120
244
|
});
|
|
121
245
|
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
246
|
+
// Set up container roles and initial state
|
|
247
|
+
tabsListArray.forEach(tabsListset => {
|
|
248
|
+
if (!stacked) {
|
|
249
|
+
// Apply role="tablist" to the <ul> so role="tab" children are valid
|
|
250
|
+
const tabListEl = tabsListset.querySelector('.mg-tabs__list') || tabsListset;
|
|
251
|
+
tabListEl.setAttribute('role', 'tablist');
|
|
252
|
+
|
|
253
|
+
// All direct <li> children of the tablist need role="presentation"
|
|
254
|
+
// so they don't break the tablist → tab hierarchy
|
|
255
|
+
tabListEl.querySelectorAll(':scope > li').forEach(li => {
|
|
256
|
+
li.setAttribute('role', 'presentation');
|
|
257
|
+
});
|
|
258
|
+
|
|
130
259
|
// Initially activate the first tab
|
|
131
260
|
let firstTab = tabsListset.querySelectorAll('.mg-tabs__link')[0];
|
|
132
261
|
firstTab.removeAttribute('tabindex');
|
|
@@ -149,6 +278,11 @@ export function mgTabsRuntime(scope, activateDeepLinkOnLoad) {
|
|
|
149
278
|
mgTabsDeepLinkOnLoad(tabs, panels);
|
|
150
279
|
}
|
|
151
280
|
|
|
281
|
+
// Initialize filter if the container has the filterable attribute
|
|
282
|
+
if (stacked && container.dataset.mgJsTabsFilterable != null) {
|
|
283
|
+
mgTabsInitFilter(container, tabs, panels);
|
|
284
|
+
}
|
|
285
|
+
|
|
152
286
|
// When using anchor links after load, activate the corresponding tab
|
|
153
287
|
window.addEventListener('hashchange', () => {
|
|
154
288
|
const hash = window.location.hash
|
|
@@ -176,36 +310,56 @@ const mgTabsSwitch = (newTab, panels) => {
|
|
|
176
310
|
// get the parent ul of the clicked tab
|
|
177
311
|
let parentTabContainer =
|
|
178
312
|
newTab.closest('[data-mg-js-tabs]') || newTab.closest('.mg-tabs'); // compatibility with v1 tabs
|
|
313
|
+
const stacked = isStacked(parentTabContainer);
|
|
314
|
+
const targetPanelId = newTab.getAttribute('data-tabs__item');
|
|
179
315
|
let oldTab = parentTabContainer.querySelector('[aria-selected]');
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (
|
|
186
|
-
parentTabContainer.dataset.mgJsTabsVariant == 'stacked' ||
|
|
187
|
-
window.innerWidth <= 600
|
|
188
|
-
) {
|
|
316
|
+
|
|
317
|
+
// if stacked, toggle the clicked panel independently
|
|
318
|
+
if (stacked) {
|
|
319
|
+
const isSingleOpen = parentTabContainer.dataset.mgJsTabsSingleOpen != null;
|
|
320
|
+
|
|
189
321
|
for (let item = 0; item < panels.length; item++) {
|
|
190
322
|
const panel = panels[item];
|
|
191
|
-
if (panel.id ===
|
|
192
|
-
panel.hidden
|
|
323
|
+
if (panel.id === targetPanelId) {
|
|
324
|
+
const wasHidden = panel.hidden || panel.getAttribute('hidden') === 'until-found';
|
|
325
|
+
|
|
326
|
+
// In single-open mode, close all other panels before opening
|
|
327
|
+
if (isSingleOpen && wasHidden) {
|
|
328
|
+
for (let j = 0; j < panels.length; j++) {
|
|
329
|
+
const otherPanel = panels[j];
|
|
330
|
+
if (otherPanel !== panel && !otherPanel.hidden && otherPanel.getAttribute('hidden') !== 'until-found') {
|
|
331
|
+
const otherTrigger = parentTabContainer.querySelector(
|
|
332
|
+
`[data-tabs__item="${otherPanel.id}"]`
|
|
333
|
+
);
|
|
334
|
+
if (otherTrigger) {
|
|
335
|
+
setDisclosureState(otherTrigger, otherPanel, false);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
setDisclosureState(newTab, panel, wasHidden);
|
|
342
|
+
break;
|
|
193
343
|
}
|
|
194
344
|
}
|
|
345
|
+
// In stacked/disclosure mode, we don't deselect other tabs or use aria-selected
|
|
346
|
+
newTab.focus({ preventScroll: true });
|
|
347
|
+
return;
|
|
195
348
|
}
|
|
196
349
|
|
|
350
|
+
// --- Horizontal tab behavior below ---
|
|
351
|
+
|
|
197
352
|
if (oldTab) {
|
|
198
353
|
oldTab.removeAttribute('aria-selected');
|
|
199
354
|
oldTab.setAttribute('tabindex', '-1');
|
|
200
355
|
oldTab.classList.remove('is-active');
|
|
201
356
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
357
|
+
const oldPanelId = oldTab.getAttribute('data-tabs__item');
|
|
358
|
+
for (let item = 0; item < panels.length; item++) {
|
|
359
|
+
const panel = panels[item];
|
|
360
|
+
if (panel.id === oldPanelId) {
|
|
361
|
+
panel.hidden = true;
|
|
362
|
+
break;
|
|
209
363
|
}
|
|
210
364
|
}
|
|
211
365
|
}
|
|
@@ -216,17 +370,194 @@ const mgTabsSwitch = (newTab, panels) => {
|
|
|
216
370
|
// Set the selected state
|
|
217
371
|
newTab.setAttribute('aria-selected', 'true');
|
|
218
372
|
newTab.classList.add('is-active');
|
|
219
|
-
newTab.classList.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
373
|
+
newTab.classList.add('mg-tabs__stacked--open'); // track open state for potential mobile view switch
|
|
374
|
+
|
|
375
|
+
for (let item = 0; item < panels.length; item++) {
|
|
376
|
+
const panel = panels[item];
|
|
377
|
+
if (panel.id === targetPanelId) {
|
|
378
|
+
panel.hidden = false;
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Apply default open/close state for stacked tabs.
|
|
386
|
+
*
|
|
387
|
+
* Priority: per-item `data-mg-js-tabs-default` > container `data-mg-js-tabs-default-open` > open first.
|
|
388
|
+
*
|
|
389
|
+
* @param {Element} container - the [data-mg-js-tabs] element
|
|
390
|
+
* @param {NodeList|Array} tabs - the trigger links
|
|
391
|
+
* @param {NodeList|Array} panels - the section panels
|
|
392
|
+
*/
|
|
393
|
+
export function mgTabsApplyStackedDefaults(container, tabs, panels) {
|
|
394
|
+
const defaultOpen = container.dataset.mgJsTabsDefaultOpen;
|
|
395
|
+
|
|
396
|
+
Array.prototype.forEach.call(tabs, (tab, i) => {
|
|
397
|
+
const tabId = tab.getAttribute('data-tabs__item');
|
|
398
|
+
let matchingPanel = null;
|
|
399
|
+
for (let j = 0; j < panels.length; j++) {
|
|
400
|
+
if (panels[j].id === tabId) {
|
|
401
|
+
matchingPanel = panels[j];
|
|
225
402
|
break;
|
|
226
403
|
}
|
|
227
404
|
}
|
|
405
|
+
if (!matchingPanel) return;
|
|
406
|
+
|
|
407
|
+
const perItemDefault = tab.getAttribute('data-mg-js-tabs-default');
|
|
408
|
+
let shouldOpen;
|
|
409
|
+
|
|
410
|
+
if (perItemDefault === 'true') {
|
|
411
|
+
shouldOpen = true;
|
|
412
|
+
} else if (perItemDefault === 'false') {
|
|
413
|
+
shouldOpen = false;
|
|
414
|
+
} else if (defaultOpen === 'true') {
|
|
415
|
+
shouldOpen = true;
|
|
416
|
+
} else if (defaultOpen === 'false') {
|
|
417
|
+
shouldOpen = false;
|
|
418
|
+
} else {
|
|
419
|
+
// No container default — preserve existing behavior: open first tab
|
|
420
|
+
shouldOpen = (i === 0);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
setDisclosureState(tab, matchingPanel, shouldOpen);
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Initialize filter input for stacked tabs.
|
|
429
|
+
* Injects a search input, sr-only hint, and status region before the tab list.
|
|
430
|
+
* Handles debounced filtering, show/hide via CSS classes, panel expand/collapse,
|
|
431
|
+
* focus rescue, and live region announcements.
|
|
432
|
+
*
|
|
433
|
+
* @param {Element} container - the [data-mg-js-tabs] element
|
|
434
|
+
* @param {NodeList} tabs - the trigger links
|
|
435
|
+
* @param {NodeList} panels - the section panels
|
|
436
|
+
*/
|
|
437
|
+
function mgTabsInitFilter(container, tabs, panels) {
|
|
438
|
+
const placeholder = container.dataset.mgJsTabsFilterPlaceholder || 'Filter sections\u2026';
|
|
439
|
+
const ariaLabel = placeholder.replace(/\u2026$/, '').trim();
|
|
440
|
+
const totalCount = tabs.length;
|
|
441
|
+
|
|
442
|
+
// Build filter DOM
|
|
443
|
+
const filterWrapper = document.createElement('div');
|
|
444
|
+
filterWrapper.className = 'mg-tabs__filter';
|
|
445
|
+
|
|
446
|
+
const input = document.createElement('input');
|
|
447
|
+
input.type = 'search';
|
|
448
|
+
input.className = 'mg-form-input mg-tabs__filter-input';
|
|
449
|
+
input.placeholder = placeholder;
|
|
450
|
+
input.setAttribute('aria-label', ariaLabel);
|
|
451
|
+
|
|
452
|
+
const hintId = 'mg-tabs-filter-hint-' + Math.random().toString(36).slice(2, 8);
|
|
453
|
+
input.setAttribute('aria-describedby', hintId);
|
|
454
|
+
|
|
455
|
+
const hint = document.createElement('span');
|
|
456
|
+
hint.id = hintId;
|
|
457
|
+
hint.className = 'mg-u-sr-only';
|
|
458
|
+
hint.textContent = 'Results will filter as you type';
|
|
459
|
+
|
|
460
|
+
filterWrapper.appendChild(input);
|
|
461
|
+
filterWrapper.appendChild(hint);
|
|
462
|
+
|
|
463
|
+
// Status region for screen reader announcements
|
|
464
|
+
const status = document.createElement('p');
|
|
465
|
+
status.className = 'mg-u-sr-only';
|
|
466
|
+
status.setAttribute('role', 'status');
|
|
467
|
+
|
|
468
|
+
// Insert before the tab list
|
|
469
|
+
const tabList = container.querySelector('.mg-tabs__list');
|
|
470
|
+
container.insertBefore(filterWrapper, tabList);
|
|
471
|
+
|
|
472
|
+
// No-results element (hidden by default, shown when matchCount === 0)
|
|
473
|
+
// No role="status" here — the sr-only status element handles announcements
|
|
474
|
+
const noResults = document.createElement('p');
|
|
475
|
+
noResults.className = 'mg-tabs__no-results mg-tabs__no-results--hidden';
|
|
476
|
+
noResults.textContent = 'No matching sections found.';
|
|
477
|
+
if (tabList.nextSibling) {
|
|
478
|
+
container.insertBefore(noResults, tabList.nextSibling);
|
|
479
|
+
} else {
|
|
480
|
+
container.appendChild(noResults);
|
|
228
481
|
}
|
|
229
|
-
|
|
482
|
+
container.appendChild(status);
|
|
483
|
+
|
|
484
|
+
let hasFiltered = false;
|
|
485
|
+
let debounceTimer = null;
|
|
486
|
+
|
|
487
|
+
function applyFilter() {
|
|
488
|
+
const query = input.value.toLowerCase().trim();
|
|
489
|
+
|
|
490
|
+
if (!query) {
|
|
491
|
+
if (!hasFiltered) return;
|
|
492
|
+
// Restore: show all items, reset to default state
|
|
493
|
+
const items = container.querySelectorAll('.mg-tabs__item');
|
|
494
|
+
items.forEach(item => {
|
|
495
|
+
item.classList.remove('mg-tabs__item--hidden');
|
|
496
|
+
const contentLi = item.nextElementSibling;
|
|
497
|
+
if (contentLi?.classList.contains('mg-tabs-content')) {
|
|
498
|
+
contentLi.classList.remove('mg-tabs-content--hidden');
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
mgTabsApplyStackedDefaults(container, tabs, panels);
|
|
502
|
+
noResults.classList.add('mg-tabs__no-results--hidden');
|
|
503
|
+
status.textContent = '';
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
hasFiltered = true;
|
|
508
|
+
const items = container.querySelectorAll('.mg-tabs__item');
|
|
509
|
+
const words = normalizeText(query).split(/\s+/).filter(Boolean);
|
|
510
|
+
if (words.length === 0) return;
|
|
511
|
+
let matches = 0;
|
|
512
|
+
|
|
513
|
+
items.forEach(item => {
|
|
514
|
+
const trigger = item.querySelector('.mg-tabs__link');
|
|
515
|
+
const contentLi = item.nextElementSibling;
|
|
516
|
+
const panel = contentLi?.querySelector('.mg-tabs__section');
|
|
517
|
+
|
|
518
|
+
const triggerText = trigger?.textContent?.toLowerCase() || '';
|
|
519
|
+
const panelText = panel?.textContent?.toLowerCase() || '';
|
|
520
|
+
const combinedText = normalizeText(triggerText + ' ' + panelText);
|
|
521
|
+
const isMatch = words.every(word => combinedText.includes(word));
|
|
522
|
+
|
|
523
|
+
item.classList.toggle('mg-tabs__item--hidden', !isMatch);
|
|
524
|
+
if (contentLi?.classList.contains('mg-tabs-content')) {
|
|
525
|
+
contentLi.classList.toggle('mg-tabs-content--hidden', !isMatch);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (panel && trigger) {
|
|
529
|
+
setDisclosureState(trigger, panel, isMatch);
|
|
530
|
+
}
|
|
531
|
+
if (isMatch) matches++;
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// Focus rescue: if active element is now hidden, move to filter input
|
|
535
|
+
const active = document.activeElement;
|
|
536
|
+
if (active && active.closest && active.closest('.mg-tabs__item--hidden')) {
|
|
537
|
+
input.focus();
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Update live regions
|
|
541
|
+
if (matches === 0) {
|
|
542
|
+
noResults.classList.remove('mg-tabs__no-results--hidden');
|
|
543
|
+
status.textContent = 'No matching sections found.';
|
|
544
|
+
} else {
|
|
545
|
+
noResults.classList.add('mg-tabs__no-results--hidden');
|
|
546
|
+
status.textContent = matches + ' of ' + totalCount + ' sections match.';
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
input.addEventListener('input', () => {
|
|
551
|
+
clearTimeout(debounceTimer);
|
|
552
|
+
debounceTimer = setTimeout(applyFilter, 150);
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// Also handle the native search clear button (fires 'search' event in some browsers)
|
|
556
|
+
input.addEventListener('search', () => {
|
|
557
|
+
clearTimeout(debounceTimer);
|
|
558
|
+
applyFilter();
|
|
559
|
+
});
|
|
560
|
+
}
|
|
230
561
|
|
|
231
562
|
function mgTabsDeepLinkOnLoad(tabs, panels) {
|
|
232
563
|
var mgTabAnchorFound = false;
|
|
@@ -248,18 +579,26 @@ function mgTabsDeepLinkOnLoad(tabs, panels) {
|
|
|
248
579
|
}
|
|
249
580
|
|
|
250
581
|
if (!mgTabAnchorFound) {
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
mgTabsSwitch(tab, panels);
|
|
256
|
-
defaultTabFound = true;
|
|
257
|
-
}
|
|
258
|
-
});
|
|
582
|
+
// Determine the container to check for stacked mode
|
|
583
|
+
const container = tabs.length > 0
|
|
584
|
+
? (tabs[0].closest('[data-mg-js-tabs]') || tabs[0].closest('.mg-tabs'))
|
|
585
|
+
: null;
|
|
259
586
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
587
|
+
if (container && isStacked(container)) {
|
|
588
|
+
mgTabsApplyStackedDefaults(container, tabs, panels);
|
|
589
|
+
} else {
|
|
590
|
+
// Horizontal tabs: find default or activate first
|
|
591
|
+
let defaultTabFound = false;
|
|
592
|
+
Array.from(tabs).forEach(tab => {
|
|
593
|
+
if (tab.getAttribute('data-mg-js-tabs-default') === 'true') {
|
|
594
|
+
mgTabsSwitch(tab, panels);
|
|
595
|
+
defaultTabFound = true;
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
if (!defaultTabFound && tabs.length > 0) {
|
|
600
|
+
mgTabsSwitch(tabs[0], panels);
|
|
601
|
+
}
|
|
263
602
|
}
|
|
264
603
|
}
|
|
265
604
|
}
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
blockquote {
|
|
1
|
+
.mg-body blockquote {
|
|
2
2
|
font-size: $mg-font-size-600;
|
|
3
3
|
font-weight: 600;
|
|
4
4
|
line-height: 1.16;
|
|
@@ -9,10 +9,3 @@ blockquote {
|
|
|
9
9
|
line-height: 1.15;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
// burmese lang
|
|
14
|
-
:lang(my) {
|
|
15
|
-
blockquote {
|
|
16
|
-
line-height: 1.7;
|
|
17
|
-
}
|
|
18
|
-
}
|