@undrr/undrr-mangrove 1.3.3 → 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 +1 -1
- 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/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 +1 -1
- package/components/Radio.js +6 -0
- package/components/Radio.js.LICENSE.txt +9 -0
- package/components/ScrollContainer.js +1 -1
- package/components/Select.js +6 -0
- package/components/Select.js.LICENSE.txt +9 -0
- package/components/ShareButtons.js +1 -1
- 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 +1 -1
- package/css/style-delta.css +464 -0
- package/css/style-delta.css.map +1 -0
- package/css/style-gutenberg.css +18 -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 -81
- 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 +39 -0
- 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 -733
- 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,7 +104,7 @@ 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
|
|
|
@@ -50,24 +117,54 @@ export function mgTabsRuntime(scope, activateDeepLinkOnLoad) {
|
|
|
50
117
|
return;
|
|
51
118
|
}
|
|
52
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
|
+
|
|
53
126
|
// Check if tabs have already been initialized
|
|
54
|
-
if (tabsList.hasAttribute('data-mg-tabs-initialized')) {
|
|
127
|
+
if (tabsList.hasAttribute && tabsList.hasAttribute('data-mg-tabs-initialized')) {
|
|
55
128
|
return;
|
|
56
129
|
}
|
|
57
|
-
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
|
+
}
|
|
138
|
+
|
|
139
|
+
// Determine variant from the container element
|
|
140
|
+
const container = tabsListArray[0];
|
|
141
|
+
const stacked = isStacked(container);
|
|
58
142
|
|
|
59
|
-
// Add semantics
|
|
143
|
+
// Add semantics and focusability for each tab
|
|
60
144
|
Array.prototype.forEach.call(tabs, (tab, i) => {
|
|
61
|
-
const
|
|
62
|
-
tab.setAttribute('
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
tab.setAttribute('
|
|
66
|
-
|
|
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
|
+
}
|
|
67
165
|
|
|
68
166
|
// Reset any active tabs from a previous JS call
|
|
69
167
|
tab.removeAttribute('aria-selected');
|
|
70
|
-
tab.setAttribute('tabindex', '-1');
|
|
71
168
|
tab.classList.remove('is-active');
|
|
72
169
|
|
|
73
170
|
// Handle clicking of tabs for mouse users
|
|
@@ -79,47 +176,86 @@ export function mgTabsRuntime(scope, activateDeepLinkOnLoad) {
|
|
|
79
176
|
// Handle keydown events for keyboard users
|
|
80
177
|
tab.addEventListener('keydown', e => {
|
|
81
178
|
// Get the index of the current tab in the tabs node list
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
? 'down'
|
|
92
|
-
: null;
|
|
93
|
-
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')) {
|
|
94
188
|
e.preventDefault();
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
+
}
|
|
102
221
|
}
|
|
103
222
|
});
|
|
104
223
|
});
|
|
105
224
|
|
|
106
|
-
// Add
|
|
225
|
+
// Add panel semantics and hide them all
|
|
107
226
|
Array.prototype.forEach.call(panels, panel => {
|
|
108
|
-
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
|
+
}
|
|
109
243
|
panel.setAttribute('tabindex', '-1');
|
|
110
|
-
// let id = panel.getAttribute("id");
|
|
111
|
-
panel.setAttribute('aria-labelledby', panel.id);
|
|
112
|
-
panel.hidden = true;
|
|
113
244
|
});
|
|
114
245
|
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
+
|
|
123
259
|
// Initially activate the first tab
|
|
124
260
|
let firstTab = tabsListset.querySelectorAll('.mg-tabs__link')[0];
|
|
125
261
|
firstTab.removeAttribute('tabindex');
|
|
@@ -142,6 +278,11 @@ export function mgTabsRuntime(scope, activateDeepLinkOnLoad) {
|
|
|
142
278
|
mgTabsDeepLinkOnLoad(tabs, panels);
|
|
143
279
|
}
|
|
144
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
|
+
|
|
145
286
|
// When using anchor links after load, activate the corresponding tab
|
|
146
287
|
window.addEventListener('hashchange', () => {
|
|
147
288
|
const hash = window.location.hash
|
|
@@ -169,36 +310,56 @@ const mgTabsSwitch = (newTab, panels) => {
|
|
|
169
310
|
// get the parent ul of the clicked tab
|
|
170
311
|
let parentTabContainer =
|
|
171
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');
|
|
172
315
|
let oldTab = parentTabContainer.querySelector('[aria-selected]');
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
parentTabContainer.dataset.mgJsTabsVariant == 'stacked' ||
|
|
180
|
-
window.innerWidth <= 600
|
|
181
|
-
) {
|
|
316
|
+
|
|
317
|
+
// if stacked, toggle the clicked panel independently
|
|
318
|
+
if (stacked) {
|
|
319
|
+
const isSingleOpen = parentTabContainer.dataset.mgJsTabsSingleOpen != null;
|
|
320
|
+
|
|
182
321
|
for (let item = 0; item < panels.length; item++) {
|
|
183
322
|
const panel = panels[item];
|
|
184
|
-
if (panel.id ===
|
|
185
|
-
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;
|
|
186
343
|
}
|
|
187
344
|
}
|
|
345
|
+
// In stacked/disclosure mode, we don't deselect other tabs or use aria-selected
|
|
346
|
+
newTab.focus({ preventScroll: true });
|
|
347
|
+
return;
|
|
188
348
|
}
|
|
189
349
|
|
|
350
|
+
// --- Horizontal tab behavior below ---
|
|
351
|
+
|
|
190
352
|
if (oldTab) {
|
|
191
353
|
oldTab.removeAttribute('aria-selected');
|
|
192
354
|
oldTab.setAttribute('tabindex', '-1');
|
|
193
355
|
oldTab.classList.remove('is-active');
|
|
194
356
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
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;
|
|
202
363
|
}
|
|
203
364
|
}
|
|
204
365
|
}
|
|
@@ -209,17 +370,194 @@ const mgTabsSwitch = (newTab, panels) => {
|
|
|
209
370
|
// Set the selected state
|
|
210
371
|
newTab.setAttribute('aria-selected', 'true');
|
|
211
372
|
newTab.classList.add('is-active');
|
|
212
|
-
newTab.classList.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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];
|
|
218
402
|
break;
|
|
219
403
|
}
|
|
220
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);
|
|
221
481
|
}
|
|
222
|
-
|
|
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
|
+
}
|
|
223
561
|
|
|
224
562
|
function mgTabsDeepLinkOnLoad(tabs, panels) {
|
|
225
563
|
var mgTabAnchorFound = false;
|
|
@@ -241,18 +579,26 @@ function mgTabsDeepLinkOnLoad(tabs, panels) {
|
|
|
241
579
|
}
|
|
242
580
|
|
|
243
581
|
if (!mgTabAnchorFound) {
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
mgTabsSwitch(tab, panels);
|
|
249
|
-
defaultTabFound = true;
|
|
250
|
-
}
|
|
251
|
-
});
|
|
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;
|
|
252
586
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
+
}
|
|
256
602
|
}
|
|
257
603
|
}
|
|
258
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
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* author image */
|
|
2
2
|
|
|
3
|
-
.
|
|
3
|
+
.mg-author-image {
|
|
4
4
|
border-radius: $author-image-radius;
|
|
5
5
|
height: 80px;
|
|
6
6
|
min-width: 80px;
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
@each $name, $color in $colors {
|
|
35
|
-
|
|
35
|
+
&.mg-author-image--#{$name}::before {
|
|
36
36
|
@include background-image(45deg, $mg-color-yellow, transparent 53%);
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
@extend %img-cover;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
&.large {
|
|
45
|
+
&.mg-author-image--large {
|
|
46
46
|
height: 180px;
|
|
47
47
|
width: 180px;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
[dir="rtl"] {
|
|
52
|
-
.
|
|
52
|
+
.mg-author-image {
|
|
53
53
|
&::before {
|
|
54
54
|
@include transform(scaleX(-1));
|
|
55
55
|
}
|