@undrr/undrr-mangrove 2.1.0 → 2.2.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 (200) hide show
  1. package/README.md +1 -1
  2. package/components/BookCard.js +1 -1
  3. package/components/Breadcrumbs.js +1 -1
  4. package/components/Checkbox.js +1 -1
  5. package/components/Chips.js +1 -1
  6. package/components/ContentCard.js +9 -0
  7. package/components/ContentCard.js.LICENSE.txt +9 -0
  8. package/components/ContentTeaser.js +9 -0
  9. package/components/ContentTeaser.js.LICENSE.txt +9 -0
  10. package/components/CopyButton.js +1 -1
  11. package/components/CtaButton.js +1 -1
  12. package/components/Drawer.js +1 -1
  13. package/components/DrupalContentPatterns.js +8 -0
  14. package/components/EmbedContainer.js +1 -1
  15. package/components/Footer.js +1 -1
  16. package/components/FormAction.js +1 -1
  17. package/components/FormErrorSummary.js +1 -1
  18. package/components/FormGroup.js +1 -1
  19. package/components/FullWidth.js +1 -1
  20. package/components/Gallery.js +1 -1
  21. package/components/Hero.js +1 -1
  22. package/components/HighlightBox.js +1 -1
  23. package/components/HorizontalBookCard.js +1 -1
  24. package/components/HorizontalCard.js +1 -1
  25. package/components/IconCard.js +1 -1
  26. package/components/Legend.js +1 -1
  27. package/components/Loader.js +1 -1
  28. package/components/MegaMenu.js +1 -1
  29. package/components/Notice.js +1 -1
  30. package/components/PageHeader.js +1 -1
  31. package/components/Pager.js +1 -1
  32. package/components/ProgressBar.js +1 -1
  33. package/components/QuoteHighlight.js +1 -1
  34. package/components/Radio.js +1 -1
  35. package/components/Range.js +1 -1
  36. package/components/ScrollContainer.js +1 -1
  37. package/components/SegmentedControl.js +1 -1
  38. package/components/Select.js +1 -1
  39. package/components/ServiceNotice.js +1 -1
  40. package/components/ShareButtons.js +1 -1
  41. package/components/ShowMore.js +1 -1
  42. package/components/SkipLink.js +1 -1
  43. package/components/StatsCard.js +1 -1
  44. package/components/SyndicationSearchWidget.js +2 -2
  45. package/components/Tab.js +1 -1
  46. package/components/TextCta.js +1 -1
  47. package/components/TextInput.js +1 -1
  48. package/components/Textarea.js +1 -1
  49. package/components/Tree.js +1 -1
  50. package/components/UserFeedback.js +1 -1
  51. package/components/VerticalCard.js +1 -1
  52. package/components/hydrate.js +1 -1
  53. package/css/style-all.css +16 -11
  54. package/css/style-all.css.map +1 -1
  55. package/css/style-delta.css +16 -11
  56. package/css/style-delta.css.map +1 -1
  57. package/css/style-gutenberg.css +4 -8
  58. package/css/style-gutenberg.css.map +1 -1
  59. package/css/style-irp.css +16 -11
  60. package/css/style-irp.css.map +1 -1
  61. package/css/style-mcr.css +16 -11
  62. package/css/style-mcr.css.map +1 -1
  63. package/css/style-preventionweb.css +16 -11
  64. package/css/style-preventionweb.css.map +1 -1
  65. package/css/style.css +17 -11
  66. package/css/style.css.map +1 -1
  67. package/docs/EDITORIAL-MANUAL.md +1 -1
  68. package/docs/llms-page-building.txt +5 -3
  69. package/error-pages/401.html +1 -1
  70. package/error-pages/403.html +1 -1
  71. package/error-pages/404.html +1 -1
  72. package/error-pages/429.html +1 -1
  73. package/error-pages/500.html +1 -1
  74. package/error-pages/502.html +1 -1
  75. package/error-pages/503.html +1 -1
  76. package/error-pages/504.html +1 -1
  77. package/error-pages/5xx.html +1 -1
  78. package/error-pages/challenge.html +1 -1
  79. package/error-pages/managed-challenge.html +1 -1
  80. package/js/preview-access.js +180 -30
  81. package/package.json +1 -1
  82. package/scss/Atom/BaseTypography/Blockquote/blockquote.scss +2 -0
  83. package/scss/Atom/BaseTypography/Cite/cite.scss +2 -0
  84. package/scss/Atom/BaseTypography/Code/code.scss +2 -0
  85. package/scss/Atom/BaseTypography/Mark/mark.scss +2 -0
  86. package/scss/Atom/BaseTypography/Small/small.scss +2 -0
  87. package/scss/Atom/Icons/icons.scss +3 -2
  88. package/scss/Atom/Images/ImageCaptionCredit/image-caption-credit.scss +3 -0
  89. package/scss/Atom/Images/ImageCredit/image-credit.scss +2 -0
  90. package/scss/Atom/Layout/Container/container.scss +2 -0
  91. package/scss/Atom/Layout/Grid/grid.scss +2 -0
  92. package/scss/Atom/Logo/logo.scss +2 -0
  93. package/scss/Atom/Navigation/MenuItems/menu-items.scss +2 -0
  94. package/scss/Atom/ReachElement/Details/details.scss +3 -0
  95. package/scss/Atom/ReachElement/Figcaption/figcaption.scss +3 -0
  96. package/scss/Atom/StatusLabel/status-label.scss +2 -0
  97. package/scss/Atom/Table/table.scss +4 -0
  98. package/scss/Atom/Tag/tag.scss +3 -0
  99. package/scss/Components/Boilerplate/boilerplate.scss +3 -0
  100. package/scss/Components/Breadcrumbs/breadcrumbs.scss +2 -0
  101. package/scss/Components/Buttons/Chips/chips.scss +3 -0
  102. package/scss/Components/Buttons/CtaButton/buttons.scss +1 -1
  103. package/scss/Components/Buttons/CtaButton/cta-button.scss +4 -0
  104. package/scss/Components/Buttons/ShareButtons/share-buttons.scss +3 -0
  105. package/scss/Components/Cards/Card/card.scss +156 -3
  106. package/scss/Components/Cards/StatsCard/stats-card.scss +14 -2
  107. package/scss/Components/CiteThis/cite-this.scss +2 -0
  108. package/scss/Components/CookieConsentBanner/cookie-consent-banner.scss +2 -0
  109. package/scss/Components/DataViz/Legend/legend.scss +1 -0
  110. package/scss/Components/ErrorPages/error-pages.scss +2 -0
  111. package/scss/Components/Footer/footer.scss +2 -0
  112. package/scss/Components/Forms/FormAction/form-action.scss +2 -0
  113. package/scss/Components/Forms/FormGroup/form-group.scss +2 -0
  114. package/scss/Components/Forms/Range/range.scss +1 -0
  115. package/scss/Components/Forms/SegmentedControl/segmented-control.scss +2 -0
  116. package/scss/Components/Forms/Select/select.scss +2 -0
  117. package/scss/Components/Forms/_form-base.scss +3 -0
  118. package/scss/Components/Gallery/gallery.scss +4 -0
  119. package/scss/Components/Hero/hero.scss +3 -0
  120. package/scss/Components/HighlightBox/highlight-box.scss +3 -0
  121. package/scss/Components/MegaMenu/mega-menu.scss +3 -0
  122. package/scss/Components/MegaMenu/megamenu.scss +1 -1
  123. package/scss/Components/Navigation/Drawer/drawer.scss +1 -0
  124. package/scss/Components/Navigation/Tree/tree.scss +1 -0
  125. package/scss/Components/OnThisPageNav/on-this-page-nav.scss +3 -0
  126. package/scss/Components/PageHeader/page-header.scss +3 -0
  127. package/scss/Components/Pager/pager.scss +3 -0
  128. package/scss/Components/PreviewAccess/preview-access.scss +3 -0
  129. package/scss/Components/QuoteHighlight/quote-highlight.scss +2 -0
  130. package/scss/Components/ScrollContainer/scroll-container.scss +4 -0
  131. package/scss/Components/Snackbar/snackbar.scss +1 -0
  132. package/scss/Components/SyndicationSearchWidget/syndication-search-widget.scss +58 -1
  133. package/scss/Components/Tab/tab.scss +4 -0
  134. package/scss/Components/TableOfContents/table-of-contents.scss +2 -0
  135. package/scss/Components/TextCta/text-cta.scss +3 -0
  136. package/scss/Components/TextCta/textcta.scss +1 -1
  137. package/scss/Components/UserFeedback/user-feedback.scss +3 -0
  138. package/scss/Molecules/ImageCaption/image-caption.scss +2 -0
  139. package/scss/Molecules/SectionHeader/sectionheader.scss +1 -1
  140. package/scss/Patterns/ContentCard/content-card.scss +79 -0
  141. package/scss/Patterns/ContentTeaser/content-teaser.scss +174 -0
  142. package/scss/Patterns/HubHeader/hub-header.scss +3 -0
  143. package/scss/Patterns/RichContent/rich-content.scss +1 -0
  144. package/scss/Patterns/_shared/pattern-demo.scss +1 -1
  145. package/scss/Utilities/Loader/loader.scss +2 -0
  146. package/scss/Utilities/ShowMore/show-more.scss +2 -0
  147. package/scss/Utilities/SkipLink/skip-link.scss +2 -0
  148. package/scss/assets/fonts/noto-kufi-arabic/noto-kufi-arabic.scss +2 -0
  149. package/scss/assets/fonts/noto-sans-arabic/noto-sans-arabic.scss +2 -0
  150. package/scss/assets/fonts/roboto/roboto.scss +12 -12
  151. package/scss/assets/fonts/roboto/sass/_Black.scss +4 -0
  152. package/scss/assets/fonts/roboto/sass/_BlackItalic.scss +4 -0
  153. package/scss/assets/fonts/roboto/sass/_Bold.scss +4 -0
  154. package/scss/assets/fonts/roboto/sass/_BoldItalic.scss +4 -0
  155. package/scss/assets/fonts/roboto/sass/_Italic.scss +4 -0
  156. package/scss/assets/fonts/roboto/sass/_Light.scss +4 -0
  157. package/scss/assets/fonts/roboto/sass/_LightItalic.scss +4 -0
  158. package/scss/assets/fonts/roboto/sass/_Medium.scss +4 -0
  159. package/scss/assets/fonts/roboto/sass/_MediumItalic.scss +4 -0
  160. package/scss/assets/fonts/roboto/sass/_Regular.scss +4 -0
  161. package/scss/assets/fonts/roboto/sass/_variables.scss +1 -2
  162. package/scss/assets/fonts/roboto/sass/roboto.scss +12 -12
  163. package/scss/assets/fonts/roboto-condensed/roboto-condensed.scss +8 -8
  164. package/scss/assets/fonts/roboto-condensed/sass/_Bold.scss +4 -0
  165. package/scss/assets/fonts/roboto-condensed/sass/_BoldItalic.scss +4 -0
  166. package/scss/assets/fonts/roboto-condensed/sass/_Italic.scss +4 -0
  167. package/scss/assets/fonts/roboto-condensed/sass/_Light.scss +4 -0
  168. package/scss/assets/fonts/roboto-condensed/sass/_LightItalic.scss +4 -0
  169. package/scss/assets/fonts/roboto-condensed/sass/_Regular.scss +4 -0
  170. package/scss/assets/fonts/roboto-condensed/sass/_variables.scss +1 -1
  171. package/scss/assets/scss/_components.scss +79 -77
  172. package/scss/assets/scss/_config.scss +183 -0
  173. package/scss/assets/scss/_font-paths.scss +10 -0
  174. package/scss/assets/scss/_fonts.import.scss +2 -0
  175. package/scss/assets/scss/_fonts.scss +13 -55
  176. package/scss/assets/scss/_foundational.scss +6 -3
  177. package/scss/assets/scss/_legacy-warnings.scss +37 -0
  178. package/scss/assets/scss/_mixins.scss +2 -0
  179. package/scss/assets/scss/_style-all.import.scss +11 -0
  180. package/scss/assets/scss/_style-delta.import.scss +11 -0
  181. package/scss/assets/scss/_style-gutenberg.import.scss +11 -0
  182. package/scss/assets/scss/_style-irp.import.scss +11 -0
  183. package/scss/assets/scss/_style-mcr.import.scss +11 -0
  184. package/scss/assets/scss/_style-preventionweb.import.scss +11 -0
  185. package/scss/assets/scss/_style.import.scss +11 -0
  186. package/scss/assets/scss/_theme-delta.scss +2 -1
  187. package/scss/assets/scss/_theme-irp.scss +2 -1
  188. package/scss/assets/scss/_theme-mcr.scss +2 -1
  189. package/scss/assets/scss/_theme-preventionweb.scss +2 -1
  190. package/scss/assets/scss/_utility.scss +3 -0
  191. package/scss/assets/scss/_variables.import.scss +11 -0
  192. package/scss/assets/scss/_variables.scss +5 -230
  193. package/scss/assets/scss/generated/_tokens-mangrove.scss +87 -0
  194. package/scss/assets/scss/style-all.scss +14 -11
  195. package/scss/assets/scss/style-delta.scss +11 -8
  196. package/scss/assets/scss/style-gutenberg.scss +15 -12
  197. package/scss/assets/scss/style-irp.scss +11 -8
  198. package/scss/assets/scss/style-mcr.scss +11 -8
  199. package/scss/assets/scss/style-preventionweb.scss +11 -8
  200. package/scss/assets/scss/style.scss +10 -7
@@ -4,7 +4,9 @@
4
4
  // The presence of an element with [data-mg-preview-access] in the body
5
5
  // activates the gate. CSS hides the page on parse via :has(); this script
6
6
  // layers a modal over it and asks for a PIN. On success, the gate is marked
7
- // unlocked and the unlock is persisted in sessionStorage.
7
+ // unlocked, an `mg-preview-access:unlock` event is dispatched on it, and the
8
+ // unlock is persisted in sessionStorage (default) or, with
9
+ // data-mg-preview-persist="local", in localStorage.
8
10
  //
9
11
  // Not a security mechanism. The PIN lives in the DOM. Treat this as a
10
12
  // "wet paint" sign: strong, unmissable, but trivial to bypass for anyone
@@ -26,6 +28,10 @@ const DEFAULTS = {
26
28
  };
27
29
 
28
30
  const STORAGE_PREFIX = 'mg-preview-access:';
31
+ const SESSION_VALUE = 'unlocked';
32
+ const UNLOCK_EVENT = 'mg-preview-access:unlock';
33
+ const PERSIST_MODES = ['session', 'local'];
34
+ const DAY_MS = 24 * 60 * 60 * 1000;
29
35
  const TITLE_ID = 'mg-preview-access-title';
30
36
  const BODY_ID = 'mg-preview-access-body';
31
37
  const ERROR_ID = 'mg-preview-access-error';
@@ -40,6 +46,7 @@ const SAFE_URL_SCHEMES = ['http:', 'https:', 'mailto:'];
40
46
  // drop when a gate is detached and unreferenced.
41
47
  const overlays = new WeakMap();
42
48
  const previousFocus = new WeakMap();
49
+ const inertSiblings = new WeakMap();
43
50
 
44
51
  /**
45
52
  * Validate that a contact URL uses an allow-listed scheme. Returns the URL
@@ -56,12 +63,27 @@ function safeContactUrl(value) {
56
63
  return DEFAULTS.contactUrl;
57
64
  }
58
65
 
66
+ /**
67
+ * Parse data-mg-preview-max-age (days). Returns a positive number of days, or
68
+ * null for "no expiry" (missing, zero, negative or non-numeric values).
69
+ */
70
+ function parseMaxAge(value) {
71
+ if (value === null || value === '') return null;
72
+ const days = Number(value);
73
+ return Number.isFinite(days) && days > 0 ? days : null;
74
+ }
75
+
59
76
  function readConfig(el) {
77
+ const persist = (el.getAttribute('data-mg-preview-persist') || '')
78
+ .trim()
79
+ .toLowerCase();
60
80
  return {
61
81
  id:
62
82
  el.getAttribute('data-mg-preview-id') ||
63
83
  (typeof window !== 'undefined' ? window.location.pathname : 'default'),
64
84
  pin: el.getAttribute('data-mg-preview-pin') || DEFAULTS.pin,
85
+ persist: PERSIST_MODES.includes(persist) ? persist : 'session',
86
+ maxAge: parseMaxAge(el.getAttribute('data-mg-preview-max-age')),
65
87
  eyebrow: el.getAttribute('data-mg-preview-eyebrow') || DEFAULTS.eyebrow,
66
88
  title: el.getAttribute('data-mg-preview-title') || DEFAULTS.title,
67
89
  message: el.getAttribute('data-mg-preview-message') || DEFAULTS.message,
@@ -76,26 +98,116 @@ function readConfig(el) {
76
98
  };
77
99
  }
78
100
 
79
- function isUnlocked(id) {
101
+ /**
102
+ * Short, non-cryptographic fingerprint of the PIN (32-bit FNV-1a, hex). Stored
103
+ * with a local unlock so that changing the PIN relocks every browser. The PIN
104
+ * is already in the DOM, so this only avoids writing it to storage verbatim;
105
+ * it is not a security measure.
106
+ */
107
+ function pinFingerprint(pin) {
108
+ let hash = 0x811c9dc5;
109
+ const str = String(pin);
110
+ for (let i = 0; i < str.length; i += 1) {
111
+ hash ^= str.charCodeAt(i);
112
+ hash = Math.imul(hash, 0x01000193);
113
+ }
114
+ return (hash >>> 0).toString(16);
115
+ }
116
+
117
+ /**
118
+ * A local unlock is valid when it was made with the current PIN and, if a
119
+ * max age is set, is not older than that. Anything else (old format, other
120
+ * PIN, expired, unparseable) is treated as locked.
121
+ */
122
+ function isValidLocalEntry(raw, config) {
123
+ let entry;
80
124
  try {
81
- return sessionStorage.getItem(STORAGE_PREFIX + id) === 'unlocked';
125
+ entry = JSON.parse(raw);
82
126
  } catch (e) {
83
127
  return false;
84
128
  }
129
+ if (!entry || entry.pin !== pinFingerprint(config.pin)) return false;
130
+ // Require the format we write, even when expiry is not configured. A future
131
+ // timestamp (including after a clock correction) must not extend an unlock.
132
+ const at = entry.at;
133
+ const age = Date.now() - at;
134
+ if (typeof at !== 'number' || !Number.isFinite(at) || at < 0 || age < 0)
135
+ return false;
136
+ return config.maxAge === null || age < config.maxAge * DAY_MS;
85
137
  }
86
138
 
87
- function persistUnlock(id) {
139
+ function hasLocalUnlock(config) {
140
+ const key = STORAGE_PREFIX + config.id;
88
141
  try {
89
- sessionStorage.setItem(STORAGE_PREFIX + id, 'unlocked');
142
+ const raw = localStorage.getItem(key);
143
+ if (raw === null) return false;
144
+ if (isValidLocalEntry(raw, config)) return true;
145
+ // Expired or made with another PIN: drop it so it cannot linger.
146
+ localStorage.removeItem(key);
147
+ } catch (e) {
148
+ // Storage unavailable (private mode, blocked): treat as locked.
149
+ }
150
+ return false;
151
+ }
152
+
153
+ function hasSessionUnlock(config) {
154
+ try {
155
+ return sessionStorage.getItem(STORAGE_PREFIX + config.id) === SESSION_VALUE;
156
+ } catch (e) {
157
+ return false;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * The session flag is honoured in both modes, so a tab that is already
163
+ * unlocked stays unlocked when a site switches to persist="local".
164
+ */
165
+ function isUnlocked(config) {
166
+ if (config.persist === 'local' && hasLocalUnlock(config)) return true;
167
+ return hasSessionUnlock(config);
168
+ }
169
+
170
+ function persistSession(id) {
171
+ try {
172
+ sessionStorage.setItem(STORAGE_PREFIX + id, SESSION_VALUE);
90
173
  } catch (e) {
91
174
  // Private mode etc. — best effort.
92
175
  }
93
176
  }
94
177
 
178
+ function persistUnlock(config) {
179
+ if (config.persist === 'local') {
180
+ try {
181
+ localStorage.setItem(
182
+ STORAGE_PREFIX + config.id,
183
+ JSON.stringify({ pin: pinFingerprint(config.pin), at: Date.now() })
184
+ );
185
+ return;
186
+ } catch (e) {
187
+ // localStorage blocked or full: fall back to this tab only.
188
+ }
189
+ }
190
+ persistSession(config.id);
191
+ }
192
+
95
193
  function markUnlocked(gate) {
96
194
  gate.classList.add('mg-preview-access--unlocked');
97
195
  }
98
196
 
197
+ /**
198
+ * Tell the host page the gate is open. Bubbles, so a listener on document
199
+ * registered before this script runs also hears the storage-restore case.
200
+ * `source` is "pin" (just entered) or "storage" (restored on init).
201
+ */
202
+ function dispatchUnlock(gate, config, source) {
203
+ gate.dispatchEvent(
204
+ new CustomEvent(UNLOCK_EVENT, {
205
+ bubbles: true,
206
+ detail: { id: config.id, persist: config.persist, source },
207
+ })
208
+ );
209
+ }
210
+
99
211
  function escapeText(value) {
100
212
  return String(value).replace(
101
213
  /[&<>"']/g,
@@ -150,39 +262,76 @@ function buildOverlay(config) {
150
262
  * so the redundant aria-hidden writes are intentionally omitted (ARIA 1.2).
151
263
  */
152
264
  function setSiblingsInert(overlay, on) {
153
- Array.from(document.body.children).forEach(child => {
154
- if (child === overlay) return;
155
- if (on) {
156
- child.setAttribute('inert', '');
157
- } else {
158
- child.removeAttribute('inert');
159
- }
160
- });
265
+ if (on) {
266
+ const siblings = Array.from(document.body.children).filter(
267
+ child => child !== overlay && !child.hasAttribute('inert')
268
+ );
269
+ inertSiblings.set(overlay, siblings);
270
+ siblings.forEach(child => child.setAttribute('inert', ''));
271
+ } else {
272
+ // Only undo this gate's changes; authored inactive content stays inert.
273
+ (inertSiblings.get(overlay) || []).forEach(child =>
274
+ child.removeAttribute('inert')
275
+ );
276
+ inertSiblings.delete(overlay);
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Focus an element that may not be focusable on its own (<main>, a heading,
282
+ * <body>). A temporary tabindex="-1" is added when it has none and removed on
283
+ * blur, so the page's tab order is left as it was.
284
+ */
285
+ function focusElement(el) {
286
+ if (!el || !el.isConnected || typeof el.focus !== 'function') return false;
287
+ if (el.closest('[inert], [hidden]') || el.matches(':disabled')) return false;
288
+ const temporary = !el.hasAttribute('tabindex') && el.tabIndex < 0;
289
+ const cleanup = () => {
290
+ if (el.getAttribute('tabindex') === '-1') el.removeAttribute('tabindex');
291
+ };
292
+ if (temporary) el.setAttribute('tabindex', '-1');
293
+ el.focus({ preventScroll: true });
294
+ if (document.activeElement !== el) {
295
+ if (temporary) cleanup();
296
+ return false;
297
+ }
298
+ if (temporary) el.addEventListener('blur', cleanup, { once: true });
299
+ return true;
300
+ }
301
+
302
+ /**
303
+ * Try previous focus, main landmarks, the skip-link target, headings and body.
304
+ * Connected targets may still be hidden or disabled; only stop once focus
305
+ * actually moves. This also handles CMS pages with an inactive main landmark.
306
+ */
307
+ function focusAfterUnlock(gate) {
308
+ const saved = previousFocus.get(gate);
309
+ const targets = [
310
+ saved !== document.body ? saved : null,
311
+ ...document.querySelectorAll('main, [role="main"]'),
312
+ document.getElementById('main'),
313
+ ...document.querySelectorAll('h1'),
314
+ document.body,
315
+ ];
316
+ targets.some(focusElement);
161
317
  }
162
318
 
163
319
  function unlockGate(gate, overlay, config) {
164
- persistUnlock(config.id);
320
+ persistUnlock(config);
321
+ // Reveal first: the CSS gate hides content with visibility: hidden, and
322
+ // hidden elements cannot take focus.
165
323
  markUnlocked(gate);
166
324
  setSiblingsInert(overlay, false);
167
325
  overlay.remove();
168
326
  overlays.delete(gate);
169
327
 
170
- // Return focus to a known landmark so keyboard and screen reader users are
171
- // not dropped on document.body with no announcement of the state change.
172
- // The previously focused element (often body itself before mount) is the
173
- // safest fallback; otherwise focus the now-revealed <main> or first heading.
174
- const restore =
175
- previousFocus.get(gate) ||
176
- document.querySelector('main, [role="main"]') ||
177
- document.querySelector('h1, h2') ||
178
- document.body;
328
+ // Move focus to the revealed content so keyboard and screen reader users
329
+ // are not dropped on document.body with no sign that the page changed.
330
+ focusAfterUnlock(gate);
179
331
  previousFocus.delete(gate);
180
- if (restore && typeof restore.focus === 'function') {
181
- if (restore === document.body && !restore.hasAttribute('tabindex')) {
182
- restore.setAttribute('tabindex', '-1');
183
- }
184
- restore.focus({ preventScroll: true });
185
- }
332
+
333
+ // Dispatch last, so listeners see the final state (overlay gone, focus set).
334
+ dispatchUnlock(gate, config, 'pin');
186
335
  }
187
336
 
188
337
  function bindHandlers(overlay, gate, config) {
@@ -232,8 +381,9 @@ export function mgPreviewAccess(scope) {
232
381
 
233
382
  const config = readConfig(gate);
234
383
 
235
- if (isUnlocked(config.id)) {
384
+ if (isUnlocked(config)) {
236
385
  markUnlocked(gate);
386
+ dispatchUnlock(gate, config, 'storage');
237
387
  return;
238
388
  }
239
389
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@undrr/undrr-mangrove",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Mangrove design System for UNDRR",
5
5
  "files": [
6
6
  "components/**/*",
@@ -6,6 +6,8 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Atom/BaseTypography/BaseTypography.mdx
8
8
  */
9
+ @use "../../../assets/scss/breakpoints" as *;
10
+
9
11
  .mg-body blockquote,
10
12
  .mg-content blockquote {
11
13
  font-size: var(--mg-font-size-600);
@@ -6,6 +6,8 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Atom/BaseTypography/BaseTypography.mdx
8
8
  */
9
+ @use "../../../assets/scss/config" as *;
10
+
9
11
  .mg-body cite,
10
12
  .mg-content cite {
11
13
  display: block;
@@ -6,6 +6,8 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Atom/BaseTypography/BaseTypography.mdx
8
8
  */
9
+ @use "../../../assets/scss/config" as *;
10
+
9
11
  .mg-code,
10
12
  code {
11
13
  background: rgb(var(--mg-color-interactive) / 0.08);
@@ -6,6 +6,8 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Atom/BaseTypography/BaseTypography.mdx
8
8
  */
9
+ @use "../../../assets/scss/breakpoints" as *;
10
+
9
11
  mark {
10
12
  background: var(--mg-color-yellow-light);
11
13
  font-size: var(--mg-font-size-200);
@@ -6,6 +6,8 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Atom/BaseTypography/BaseTypography.mdx
8
8
  */
9
+ @use "../../../assets/scss/mixins" as *;
10
+
9
11
  small {
10
12
  @extend %paragraph-font-100;
11
13
  }
@@ -16,8 +16,9 @@
16
16
  // See: https://github.com/unisdr/undrr-mangrove/issues/906
17
17
 
18
18
  // Icon definitions (auto-generated by scripts/icon-build.cjs).
19
- // Must be imported before any rules to satisfy CSS @import ordering.
20
- @import "icon-definitions";
19
+ // Module dependencies must precede executable rules.
20
+ @use "icon-definitions" as *;
21
+ @use "../../assets/scss/config" as *;
21
22
 
22
23
  // Font fallback (kept for fa-* selectors — removed in phase 4)
23
24
 
@@ -6,6 +6,9 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Atom/Images/ImageCaptionCredit/ImageCaptionCredit.mdx
8
8
  */
9
+ @use "../../../assets/scss/mixins" as *;
10
+ @use "../../../assets/scss/breakpoints" as *;
11
+
9
12
  // @import '../../../assets/scss/base';
10
13
 
11
14
  /* image with caption start */
@@ -6,6 +6,8 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Atom/Images/ImageCaptionCredit/ImageCaptionCredit.mdx
8
8
  */
9
+ @use "../../../assets/scss/breakpoints" as *;
10
+
9
11
  .mg-credits {
10
12
  font-size: var(--mg-font-size-200);
11
13
  line-height: 1.4;
@@ -6,6 +6,8 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Atom/Layout/Container/Container.mdx
8
8
  */
9
+ @use "../../../assets/scss/config" as *;
10
+
9
11
  // Define your breakpoints if not already defined
10
12
  // $mg-breakpoint-mobile: 480px;
11
13
  // $mg-breakpoint-tablet: 768px;
@@ -6,6 +6,8 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Atom/Layout/Grid/Grid.mdx
8
8
  */
9
+ @use "../../../assets/scss/config" as *;
10
+
9
11
  /* Our custom css grid based on https://stable.visual-framework.dev/components/vf-grid/ */
10
12
  g-grid {
11
13
  display: flex;
@@ -6,6 +6,8 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Atom/Logo/Logo.mdx
8
8
  */
9
+ @use "../../assets/scss/config" as *;
10
+
9
11
  .mg-logo {
10
12
  display: block;
11
13
  max-inline-size: 100%;
@@ -6,6 +6,8 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Atom/Typography/Links/Links.mdx
8
8
  */
9
+ @use "../../../assets/scss/mixins" as *;
10
+
9
11
  // * menu item row start *//
10
12
  .menu li {
11
13
  a {
@@ -6,6 +6,9 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Atom/BaseTypography/BaseTypography.mdx
8
8
  */
9
+ @use "../../../assets/scss/mixins" as *;
10
+ @use "../../../assets/scss/config" as *;
11
+
9
12
  /* details tag start */
10
13
 
11
14
  /* Baseline: usability essentials for all <details> elements */
@@ -6,6 +6,9 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Atom/BaseTypography/BaseTypography.mdx
8
8
  */
9
+ @use "../../../assets/scss/mixins" as *;
10
+ @use "../../../assets/scss/config" as *;
11
+
9
12
  /* figure tag start */
10
13
  figure {
11
14
  margin: 0;
@@ -6,6 +6,8 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Atom/StatusLabel/StatusLabel.mdx
8
8
  */
9
+ @use "../../assets/scss/mixins" as *;
10
+
9
11
  // Status label — a workflow status shown as a coloured indicator plus its name.
10
12
  // -
11
13
  // Sourced from the DELTA design system board "Status Labels for Records and
@@ -6,6 +6,10 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Atom/Table/Table.mdx
8
8
  */
9
+ @use "../../assets/scss/breakpoints" as *;
10
+ @use "../../assets/scss/mixins" as *;
11
+ @use "../../assets/scss/config" as *;
12
+
9
13
  /* table tag start */
10
14
  .mg-table {
11
15
  border-collapse: collapse;
@@ -6,6 +6,9 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Atom/Tag/Tag.mdx
8
8
  */
9
+ @use "../../assets/scss/mixins" as *;
10
+ @use "../../assets/scss/config" as *;
11
+
9
12
  // Compact taxonomy labels. Keep legacy theme overrides and container markup.
10
13
  // :where() gives auto-styled children the same specificity as .mg-tag, so
11
14
  // explicit modifiers win regardless of which markup a Drupal view emits.
@@ -6,6 +6,9 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Components/Boilerplate/Boilerplate.mdx
8
8
  */
9
+ @use "../../assets/scss/mixins" as *;
10
+ @use "../../assets/scss/config" as *;
11
+
9
12
  // Boilerplate component styles
10
13
  // We use the 'mg-' prefix for our CSS classes to namespace them
11
14
  // Sass variables are handled by the import from _components.scss
@@ -6,6 +6,8 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Components/Breadcrumbs/Breadcrumbs.mdx
8
8
  */
9
+ @use "../../assets/scss/mixins" as *;
10
+
9
11
  .mg-breadcrumb {
10
12
  font-family: var(--mg-font-family-ui);
11
13
  font-weight: 400;
@@ -6,6 +6,9 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Components/Buttons/Chips/Chips.mdx
8
8
  */
9
+ @use "../../../assets/scss/mixins" as *;
10
+ @use "../../../assets/scss/config" as *;
11
+
9
12
  .mg-chip {
10
13
  align-items: center;
11
14
  background: rgb(var(--mg-color-neutral-50));
@@ -7,5 +7,5 @@
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Components/Buttons/CtaButton/CtaButton.mdx
8
8
  */
9
9
  // buttons.scss — DEPRECATED, use cta-button.scss
10
- @import "./cta-button";
10
+ @forward "./cta-button";
11
11
  @warn "buttons.scss is deprecated. Import cta-button.scss instead.";
@@ -6,6 +6,10 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Components/Buttons/CtaButton/CtaButton.mdx
8
8
  */
9
+ @use "../../../assets/scss/breakpoints" as *;
10
+ @use "../../../assets/scss/mixins" as *;
11
+ @use "../../../assets/scss/config" as *;
12
+
9
13
  // .mg-buttons {
10
14
  // margin-bottom: 1em;
11
15
  // //Media queries
@@ -6,6 +6,9 @@
6
6
  * Full documentation:
7
7
  * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Components/Buttons/ShareButtons/ShareButtons.mdx
8
8
  */
9
+ @use "../../../assets/scss/mixins" as *;
10
+ @use "../../../assets/scss/config" as *;
11
+
9
12
  .mg-share {
10
13
  max-inline-size: mg-rem(400);
11
14
  min-inline-size: 0;