@undrr/undrr-mangrove 1.4.0 → 1.5.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 (101) hide show
  1. package/README.md +5 -8
  2. package/components/BarChart.js +1 -1
  3. package/components/BookCard.js +2 -2
  4. package/components/Breadcrumbs.js +2 -2
  5. package/components/Checkbox.js +1 -1
  6. package/components/Chips.js +1 -1
  7. package/components/CtaButton.js +2 -2
  8. package/components/EmbedContainer.js +1 -1
  9. package/components/Fetcher.js +1 -1
  10. package/components/Footer.js +1 -1
  11. package/components/FormErrorSummary.js +1 -1
  12. package/components/FormGroup.js +1 -1
  13. package/components/FullWidth.js +1 -1
  14. package/components/Gallery.js +1 -1
  15. package/components/Hero.js +2 -2
  16. package/components/HighlightBox.js +1 -1
  17. package/components/HorizontalBookCard.js +2 -2
  18. package/components/HorizontalCard.js +2 -2
  19. package/components/IconCard.js +2 -2
  20. package/components/Loader.js +1 -1
  21. package/components/MapComponent.js +1 -1
  22. package/components/MegaMenu.js +2 -2
  23. package/components/PageHeader.js +1 -1
  24. package/components/Pager.js +1 -1
  25. package/components/QuoteHighlight.js +1 -1
  26. package/components/Radio.js +1 -1
  27. package/components/ScrollContainer.js +1 -1
  28. package/components/Select.js +1 -1
  29. package/components/ShareButtons.js +2 -2
  30. package/components/ShowMore.js +2 -2
  31. package/components/StatsCard.js +1 -1
  32. package/components/SyndicationSearchWidget.js +2 -2
  33. package/components/Tab.js +2 -2
  34. package/components/TextCta.js +1 -1
  35. package/components/TextInput.js +1 -1
  36. package/components/Textarea.js +1 -1
  37. package/components/VerticalCard.js +2 -2
  38. package/components/hydrate.js +1 -1
  39. package/css/style-delta.css +15 -12
  40. package/css/style-delta.css.map +1 -1
  41. package/css/style-gutenberg.css +6 -3
  42. package/css/style-gutenberg.css.map +1 -1
  43. package/css/style-irp-legacy.css +14 -11
  44. package/css/style-irp-legacy.css.map +1 -1
  45. package/css/style-irp.css +14 -11
  46. package/css/style-irp.css.map +1 -1
  47. package/css/style-legacy.css +14 -11
  48. package/css/style-legacy.css.map +1 -1
  49. package/css/style-mcr-legacy.css +14 -11
  50. package/css/style-mcr-legacy.css.map +1 -1
  51. package/css/style-mcr.css +14 -11
  52. package/css/style-mcr.css.map +1 -1
  53. package/css/style-preventionweb-legacy.css +14 -11
  54. package/css/style-preventionweb-legacy.css.map +1 -1
  55. package/css/style-preventionweb.css +14 -11
  56. package/css/style-preventionweb.css.map +1 -1
  57. package/css/style.css +14 -11
  58. package/css/style.css.map +1 -1
  59. package/error-pages/401.html +1 -1
  60. package/error-pages/403.html +1 -1
  61. package/error-pages/404.html +1 -1
  62. package/error-pages/429.html +1 -1
  63. package/error-pages/500.html +1 -1
  64. package/error-pages/502.html +1 -1
  65. package/error-pages/503.html +1 -1
  66. package/error-pages/504.html +1 -1
  67. package/error-pages/5xx.html +1 -1
  68. package/error-pages/challenge.html +1 -1
  69. package/error-pages/managed-challenge.html +1 -1
  70. package/js/README.md +57 -0
  71. package/js/__tests__/show-more.test.js +76 -0
  72. package/js/__tests__/table-of-contents.test.js +198 -0
  73. package/js/on-this-page-nav.js +571 -0
  74. package/js/show-more.js +24 -3
  75. package/js/table-of-contents.js +168 -0
  76. package/js/tabs.js +18 -2
  77. package/package.json +1 -1
  78. package/scss/Atom/Icons/_icon-definitions.scss +319 -0
  79. package/scss/Atom/Icons/icons.scss +153 -232
  80. package/scss/Atom/Table/table.scss +1 -1
  81. package/scss/Components/Breadcrumbs/breadcrumbs.scss +25 -9
  82. package/scss/Components/Buttons/CtaButton/cta-button.scss +4 -29
  83. package/scss/Components/Buttons/ShareButtons/share-buttons.scss +0 -6
  84. package/scss/Components/Cards/Card/card.scss +7 -7
  85. package/scss/Components/Cards/StatsCard/stats-card.scss +2 -2
  86. package/scss/Components/Gallery/gallery.scss +3 -3
  87. package/scss/Components/Hero/hero.scss +25 -6
  88. package/scss/Components/MegaMenu/mega-menu.scss +26 -13
  89. package/scss/Components/OnThisPageNav/on-this-page-nav.scss +206 -0
  90. package/scss/Components/PageHeader/page-header.scss +1 -1
  91. package/scss/Components/ScrollContainer/scroll-container.scss +1 -1
  92. package/scss/Components/Snackbar/snackbar.scss +1 -1
  93. package/scss/Components/SyndicationSearchWidget/syndication-search-widget.scss +4 -4
  94. package/scss/Components/Tab/tab.scss +2 -2
  95. package/scss/Components/TableOfContents/TableOfContents.scss +3 -0
  96. package/scss/Components/TextCta/text-cta.scss +16 -6
  97. package/scss/Utilities/FullWidth/full-width.scss +1 -1
  98. package/scss/Utilities/ShowMore/show-more.scss +1 -1
  99. package/scss/assets/scss/_components.scss +4 -0
  100. package/scss/assets/scss/_variables-delta.scss +6 -1
  101. package/scss/assets/scss/_variables.scss +35 -2
@@ -0,0 +1,198 @@
1
+ import { mgTableOfContents, mgTableOfContentsInit } from '../table-of-contents';
2
+
3
+ afterEach(() => {
4
+ document.body.innerHTML = '';
5
+ });
6
+
7
+ function setupPage({ contentSelector, title, showTitle, skipAutoInit } = {}) {
8
+ document.body.innerHTML = `
9
+ <article class="mg-content">
10
+ <section
11
+ data-mg-table-of-contents
12
+ ${contentSelector ? `data-mg-table-of-contents-content="${contentSelector}"` : ''}
13
+ ${title ? `data-mg-table-of-contents-title="${title}"` : ''}
14
+ ${showTitle === false ? 'data-mg-table-of-contents-show-title="false"' : ''}
15
+ ${skipAutoInit ? 'data-mg-table-of-contents-skip-auto-init' : ''}
16
+ class="mg-table-of-contents"
17
+ ></section>
18
+ <h2 id="section-a">Section A</h2>
19
+ <p>Content A</p>
20
+ <h2>Section B</h2>
21
+ <p>Content B</p>
22
+ <h2 class="mg-table-of-contents--exclude">Excluded</h2>
23
+ <p>Should not appear</p>
24
+ <h2 class="mg-u-sr-only">Screen reader only</h2>
25
+ <p>Should not appear</p>
26
+ <h2>Section C</h2>
27
+ <p>Content C</p>
28
+ </article>
29
+ `;
30
+ return {
31
+ toc: document.querySelector('[data-mg-table-of-contents]'),
32
+ content: document.querySelector('.mg-content'),
33
+ };
34
+ }
35
+
36
+ describe('mgTableOfContents', () => {
37
+ it('generates TOC links from h2 headings', () => {
38
+ const { toc, content } = setupPage();
39
+ mgTableOfContents(content, toc);
40
+
41
+ const links = toc.querySelectorAll('li a');
42
+ expect(links).toHaveLength(3);
43
+ expect(links[0].textContent).toBe('Section A');
44
+ expect(links[1].textContent).toBe('Section B');
45
+ expect(links[2].textContent).toBe('Section C');
46
+ });
47
+
48
+ it('uses existing heading IDs', () => {
49
+ const { toc, content } = setupPage();
50
+ mgTableOfContents(content, toc);
51
+
52
+ const links = toc.querySelectorAll('li a');
53
+ expect(links[0].getAttribute('href')).toBe('#section-a');
54
+ });
55
+
56
+ it('generates IDs for headings without them', () => {
57
+ const { toc, content } = setupPage();
58
+ mgTableOfContents(content, toc);
59
+
60
+ // Index 2 because mgTableOfContents prepends an h2 title to the TOC
61
+ // element which is inside content: [0]=title, [1]=Section A, [2]=Section B
62
+ const sectionB = content.querySelectorAll('h2')[2];
63
+ expect(sectionB.id).toBe('section-b');
64
+
65
+ const links = toc.querySelectorAll('li a');
66
+ expect(links[1].getAttribute('href')).toBe('#section-b');
67
+ });
68
+
69
+ it('excludes headings with mg-table-of-contents--exclude', () => {
70
+ const { toc, content } = setupPage();
71
+ mgTableOfContents(content, toc);
72
+
73
+ const linkTexts = Array.from(toc.querySelectorAll('li a')).map(
74
+ a => a.textContent
75
+ );
76
+ expect(linkTexts).not.toContain('Excluded');
77
+ });
78
+
79
+ it('excludes headings with mg-u-sr-only', () => {
80
+ const { toc, content } = setupPage();
81
+ mgTableOfContents(content, toc);
82
+
83
+ const linkTexts = Array.from(toc.querySelectorAll('li a')).map(
84
+ a => a.textContent
85
+ );
86
+ expect(linkTexts).not.toContain('Screen reader only');
87
+ });
88
+
89
+ it('adds default title "On this page"', () => {
90
+ const { toc, content } = setupPage();
91
+ mgTableOfContents(content, toc);
92
+
93
+ const header = toc.querySelector('h2#on-this-page');
94
+ expect(header).not.toBeNull();
95
+ expect(header.textContent).toBe('On this page');
96
+ });
97
+
98
+ it('uses custom title from data attribute', () => {
99
+ const { toc, content } = setupPage({ title: 'Contents' });
100
+ mgTableOfContents(content, toc);
101
+
102
+ const header = toc.querySelector('h2#on-this-page');
103
+ expect(header.textContent).toBe('Contents');
104
+ });
105
+
106
+ it('hides title when show-title is false', () => {
107
+ const { toc, content } = setupPage({ showTitle: false });
108
+ mgTableOfContents(content, toc);
109
+
110
+ const header = toc.querySelector('h2#on-this-page');
111
+ expect(header).toBeNull();
112
+ });
113
+
114
+ it('adds mg-u-sr-only class when title is "hidden"', () => {
115
+ const { toc, content } = setupPage({ title: 'hidden' });
116
+ mgTableOfContents(content, toc);
117
+
118
+ const header = toc.querySelector('h2#on-this-page');
119
+ expect(header.classList.contains('mg-u-sr-only')).toBe(true);
120
+ });
121
+
122
+ it('renders ordered list when showNumbers is true', () => {
123
+ const { toc, content } = setupPage();
124
+ mgTableOfContents(content, toc, true);
125
+
126
+ expect(toc.querySelector('ol')).not.toBeNull();
127
+ expect(toc.querySelector('ul')).toBeNull();
128
+ });
129
+
130
+ it('logs error when content element is missing', () => {
131
+ const error = jest.spyOn(console, 'error').mockImplementation(() => {});
132
+ const { toc } = setupPage();
133
+ mgTableOfContents(null, toc);
134
+ expect(error).toHaveBeenCalled();
135
+ error.mockRestore();
136
+ });
137
+ });
138
+
139
+ describe('mgTableOfContentsInit', () => {
140
+ it('auto-initializes elements with data-mg-table-of-contents', () => {
141
+ const { toc } = setupPage({ contentSelector: '.mg-content' });
142
+ mgTableOfContentsInit();
143
+
144
+ expect(toc.dataset.mgTableOfContentsInitialized).toBe('true');
145
+ expect(toc.querySelectorAll('li a').length).toBeGreaterThan(0);
146
+ });
147
+
148
+ it('skips elements with skip-auto-init during auto-init', () => {
149
+ const { toc } = setupPage({ skipAutoInit: true });
150
+ mgTableOfContentsInit(); // no scope = auto-init path
151
+ expect(toc.dataset.mgTableOfContentsInitialized).toBeUndefined();
152
+ });
153
+
154
+ it('processes skip-auto-init elements when scope is passed explicitly', () => {
155
+ const { toc } = setupPage({
156
+ skipAutoInit: true,
157
+ contentSelector: '.mg-content',
158
+ });
159
+ mgTableOfContentsInit([toc]);
160
+ expect(toc.dataset.mgTableOfContentsInitialized).toBe('true');
161
+ });
162
+
163
+ it('does not double-initialize', () => {
164
+ const { toc } = setupPage({ contentSelector: '.mg-content' });
165
+ mgTableOfContentsInit();
166
+ const linkCountFirst = toc.querySelectorAll('li a').length;
167
+
168
+ mgTableOfContentsInit();
169
+ const linkCountSecond = toc.querySelectorAll('li a').length;
170
+ expect(linkCountSecond).toBe(linkCountFirst);
171
+ });
172
+
173
+ it('accepts a single HTMLElement as scope', () => {
174
+ const { toc } = setupPage({ contentSelector: '.mg-content' });
175
+ expect(() => mgTableOfContentsInit(toc)).not.toThrow();
176
+ expect(toc.dataset.mgTableOfContentsInitialized).toBe('true');
177
+ });
178
+
179
+ it('defaults to document.body when no content selector is set', () => {
180
+ const { toc } = setupPage(); // no contentSelector
181
+ mgTableOfContentsInit();
182
+
183
+ // Should still find headings since they're children of document.body
184
+ expect(toc.querySelectorAll('li a').length).toBeGreaterThan(0);
185
+ });
186
+
187
+ it('warns when content selector points to nonexistent element', () => {
188
+ const { toc } = setupPage({ contentSelector: '.nonexistent' });
189
+ const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
190
+ mgTableOfContentsInit();
191
+ expect(warn).toHaveBeenCalledWith(
192
+ expect.stringContaining('.nonexistent')
193
+ );
194
+ // Should still mark as initialized (no retry without clearing flag)
195
+ expect(toc.dataset.mgTableOfContentsInitialized).toBe('true');
196
+ warn.mockRestore();
197
+ });
198
+ });