@undrr/undrr-mangrove 2.3.0 → 2.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 +1 -1
- package/components/BookCard.js +5 -2
- package/components/Breadcrumbs.js +2 -2
- package/components/Checkbox.js +3 -2
- package/components/Chips.js +2 -2
- package/components/ContentCard.js +8 -2
- package/components/ContentTeaser.js +8 -2
- package/components/CopyButton.js +3 -2
- package/components/CtaButton.js +3 -2
- package/components/Drawer.js +2 -2
- package/components/DrupalContentPatterns.js +1 -1
- package/components/EmbedContainer.js +3 -2
- package/components/Footer.js +2 -2
- package/components/FormAction.js +3 -2
- package/components/FormErrorSummary.js +2 -2
- package/components/FormGroup.js +3 -2
- package/components/FullWidth.js +3 -2
- package/components/Gallery.js +3 -2
- package/components/Hero.js +6 -2
- package/components/HighlightBox.js +3 -2
- package/components/HorizontalBookCard.js +6 -2
- package/components/HorizontalCard.js +6 -2
- package/components/IconCard.js +4 -2
- package/components/Legend.js +3 -2
- package/components/Loader.js +2 -2
- package/components/MegaMenu.js +7 -2
- package/components/Notice.js +3 -2
- package/components/PageHeader.js +4 -2
- package/components/Pager.js +7 -2
- package/components/ProgressBar.js +3 -2
- package/components/QuoteHighlight.js +3 -2
- package/components/Radio.js +3 -2
- package/components/Range.js +3 -2
- package/components/ScrollContainer.js +2 -2
- package/components/SegmentedControl.js +6 -2
- package/components/Select.js +3 -2
- package/components/ServiceNotice.js +4 -2
- package/components/ShareButtons.js +4 -2
- package/components/ShowMore.js +3 -2
- package/components/SkipLink.js +3 -2
- package/components/StatsCard.js +5 -2
- package/components/SyndicationSearchWidget.js +31 -2
- package/components/Tab.js +7 -2
- package/components/TextCta.js +4 -2
- package/components/TextInput.js +3 -2
- package/components/Textarea.js +3 -2
- package/components/Tree.js +7 -2
- package/components/UserFeedback.js +2 -2
- package/components/VerticalCard.js +6 -2
- package/components/hydrate.js +2 -2
- package/css/style-all.css +14 -8
- package/css/style-all.css.map +1 -1
- package/css/style-delta.css +14 -8
- package/css/style-delta.css.map +1 -1
- package/css/style-gutenberg.css +1 -1
- package/css/style-irp.css +14 -8
- package/css/style-irp.css.map +1 -1
- package/css/style-mcr.css +14 -8
- package/css/style-mcr.css.map +1 -1
- package/css/style-preventionweb.css +14 -8
- package/css/style-preventionweb.css.map +1 -1
- package/css/style.css +14 -8
- package/css/style.css.map +1 -1
- package/docs/EDITORIAL-MANUAL.md +2 -2
- package/docs/llms-editorial-manual.txt +1 -1
- package/docs/llms-page-building.txt +4 -2
- package/docs/llms-search-widget.txt +3 -1
- package/error-pages/401.html +1 -1
- package/error-pages/403.html +1 -1
- package/error-pages/404.html +1 -1
- package/error-pages/429.html +1 -1
- package/error-pages/500.html +1 -1
- package/error-pages/502.html +1 -1
- package/error-pages/503.html +1 -1
- package/error-pages/504.html +1 -1
- package/error-pages/5xx.html +1 -1
- package/error-pages/challenge.html +1 -1
- package/error-pages/managed-challenge.html +1 -1
- package/js/dialog.js +205 -0
- package/package.json +1 -1
- package/scss/Atom/BaseTypography/Code/code.scss +0 -1
- package/scss/Atom/Table/table.scss +0 -1
- package/scss/Atom/Tag/tag.scss +0 -1
- package/scss/Components/Buttons/ShareButtons/share-buttons.scss +18 -0
- package/scss/Components/Cards/StatsCard/stats-card.scss +0 -1
- package/scss/Components/Dialog/dialog.scss +87 -0
- package/scss/Components/MegaMenu/mega-menu.scss +403 -0
- package/scss/Components/SyndicationSearchWidget/syndication-search-widget.scss +70 -2
- package/scss/Patterns/Content/Publication/publication-record.scss +110 -0
- package/scss/Patterns/LandingPages/publication-example.scss +28 -0
- package/scss/assets/scss/_components.scss +1 -0
- package/scss/assets/scss/_variables.scss +1 -1
package/js/dialog.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/** Native modal enhancement for authored disclosures. See Dialog.mdx. */
|
|
2
|
+
const KEY = Symbol.for('@undrr/mangrove/dialog');
|
|
3
|
+
const LOCK = Symbol.for('@undrr/mangrove/dialog-scroll');
|
|
4
|
+
const SELECTOR = '[data-mg-dialog]';
|
|
5
|
+
|
|
6
|
+
function elements(scope) {
|
|
7
|
+
return [
|
|
8
|
+
...(scope.matches?.(SELECTOR) ? [scope] : []),
|
|
9
|
+
...scope.querySelectorAll(SELECTOR),
|
|
10
|
+
];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function lock(doc) {
|
|
14
|
+
const root = doc.documentElement;
|
|
15
|
+
const state = (doc[LOCK] ||= {
|
|
16
|
+
count: 0,
|
|
17
|
+
// A shorthand can serialize to an empty string when axes have different
|
|
18
|
+
// priorities. Save the actual declarations, without touching other styles.
|
|
19
|
+
properties: Array.from(root.style)
|
|
20
|
+
.filter(name => ['overflow', 'overflow-x', 'overflow-y'].includes(name))
|
|
21
|
+
.map(name => [
|
|
22
|
+
name,
|
|
23
|
+
root.style.getPropertyValue(name),
|
|
24
|
+
root.style.getPropertyPriority(name),
|
|
25
|
+
]),
|
|
26
|
+
});
|
|
27
|
+
state.count += 1;
|
|
28
|
+
root.style.setProperty('overflow', 'hidden', 'important');
|
|
29
|
+
return () => {
|
|
30
|
+
if (--state.count) return;
|
|
31
|
+
for (const name of ['overflow', 'overflow-x', 'overflow-y'])
|
|
32
|
+
root.style.removeProperty(name);
|
|
33
|
+
for (const [name, value, priority] of state.properties)
|
|
34
|
+
root.style.setProperty(name, value, priority);
|
|
35
|
+
delete doc[LOCK];
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Enhance a document, element or Drupal behaviour scope. Idempotent. */
|
|
40
|
+
export function mgDialog(scope = document) {
|
|
41
|
+
for (const root of elements(scope)) {
|
|
42
|
+
// Nesting is unsupported: leave every involved disclosure readable rather
|
|
43
|
+
// than moving a descendant root's content outside its language/context.
|
|
44
|
+
if (
|
|
45
|
+
root[KEY] ||
|
|
46
|
+
root.parentElement?.closest(SELECTOR) ||
|
|
47
|
+
root.querySelector(SELECTOR)
|
|
48
|
+
)
|
|
49
|
+
continue;
|
|
50
|
+
const fallback = root.querySelector('[data-mg-dialog-fallback]');
|
|
51
|
+
const summary = fallback?.querySelector(':scope > summary');
|
|
52
|
+
const content = fallback?.querySelector('[data-mg-dialog-content]');
|
|
53
|
+
const trigger = root.querySelector('[data-mg-dialog-trigger]');
|
|
54
|
+
const closeButtons = [
|
|
55
|
+
...(content?.querySelectorAll('[data-mg-dialog-close]') || []),
|
|
56
|
+
];
|
|
57
|
+
const close = closeButtons[0];
|
|
58
|
+
const title = content?.querySelector('[data-mg-dialog-title]');
|
|
59
|
+
const doc = root.ownerDocument;
|
|
60
|
+
const dialog = doc.createElement('dialog');
|
|
61
|
+
// Incomplete markup and unsupported browsers retain the disclosure.
|
|
62
|
+
if (
|
|
63
|
+
!summary ||
|
|
64
|
+
!content ||
|
|
65
|
+
!trigger ||
|
|
66
|
+
!close ||
|
|
67
|
+
!title ||
|
|
68
|
+
!title.id ||
|
|
69
|
+
typeof dialog.showModal !== 'function'
|
|
70
|
+
)
|
|
71
|
+
continue;
|
|
72
|
+
dialog.className = 'mg-dialog';
|
|
73
|
+
dialog.setAttribute('aria-labelledby', title.id);
|
|
74
|
+
// A sticky header must leave useful space for reading. Normal flow is the
|
|
75
|
+
// safe base when measurements cannot follow font/viewport/content changes.
|
|
76
|
+
const header = title.closest('.mg-dialog__header');
|
|
77
|
+
const ResizeObserver = doc.defaultView?.ResizeObserver;
|
|
78
|
+
const updateHeader = () => {
|
|
79
|
+
dialog.classList.toggle(
|
|
80
|
+
'mg-dialog--sticky-header',
|
|
81
|
+
Boolean(
|
|
82
|
+
ResizeObserver &&
|
|
83
|
+
dialog.open &&
|
|
84
|
+
header &&
|
|
85
|
+
header.getBoundingClientRect().height <= dialog.clientHeight / 2
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
const observer =
|
|
90
|
+
header && ResizeObserver ? new ResizeObserver(updateHeader) : null;
|
|
91
|
+
observer?.observe(dialog);
|
|
92
|
+
if (header) observer?.observe(header);
|
|
93
|
+
const placeholder = doc.createComment('mg-dialog content');
|
|
94
|
+
content.before(placeholder);
|
|
95
|
+
const original = [fallback.hidden, trigger.hidden];
|
|
96
|
+
const originalClose = closeButtons.map(button => button.hidden);
|
|
97
|
+
dialog.append(content);
|
|
98
|
+
root.append(dialog);
|
|
99
|
+
fallback.hidden = true;
|
|
100
|
+
trigger.hidden = false;
|
|
101
|
+
for (const button of closeButtons) button.hidden = false;
|
|
102
|
+
const oldPopup = trigger.getAttribute('aria-haspopup');
|
|
103
|
+
trigger.setAttribute('aria-haspopup', 'dialog');
|
|
104
|
+
let release;
|
|
105
|
+
let opener;
|
|
106
|
+
let pointerOutside = false;
|
|
107
|
+
const finish = event => {
|
|
108
|
+
if (event && dialog.open) return;
|
|
109
|
+
if (!release) return;
|
|
110
|
+
release();
|
|
111
|
+
release = undefined;
|
|
112
|
+
if (opener?.isConnected) opener.focus({ preventScroll: true });
|
|
113
|
+
};
|
|
114
|
+
const open = () => {
|
|
115
|
+
// Native close events are queued: settle a prior close before reopening.
|
|
116
|
+
if (dialog.open) return;
|
|
117
|
+
finish();
|
|
118
|
+
// Pointer activation does not focus buttons in every browser (Safari).
|
|
119
|
+
opener = trigger;
|
|
120
|
+
try {
|
|
121
|
+
dialog.showModal();
|
|
122
|
+
} catch {
|
|
123
|
+
// A disconnected/inactive document must not strand the content.
|
|
124
|
+
root[KEY]();
|
|
125
|
+
fallback.open = true;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
release = lock(doc);
|
|
129
|
+
dialog.scrollTop = 0;
|
|
130
|
+
updateHeader();
|
|
131
|
+
const initial = dialog.querySelector('[data-mg-dialog-initial-focus]');
|
|
132
|
+
(initial || close).focus({ preventScroll: true });
|
|
133
|
+
};
|
|
134
|
+
const dismiss = () => {
|
|
135
|
+
if (dialog.open) dialog.close();
|
|
136
|
+
finish();
|
|
137
|
+
};
|
|
138
|
+
const outside = event => {
|
|
139
|
+
const rect = dialog.getBoundingClientRect();
|
|
140
|
+
return (
|
|
141
|
+
event.target === dialog &&
|
|
142
|
+
(event.clientX < rect.left ||
|
|
143
|
+
event.clientX > rect.right ||
|
|
144
|
+
event.clientY < rect.top ||
|
|
145
|
+
event.clientY > rect.bottom)
|
|
146
|
+
);
|
|
147
|
+
};
|
|
148
|
+
const pointerdown = event => {
|
|
149
|
+
pointerOutside = outside(event);
|
|
150
|
+
};
|
|
151
|
+
const click = event => {
|
|
152
|
+
if (pointerOutside && outside(event)) dismiss();
|
|
153
|
+
pointerOutside = false;
|
|
154
|
+
};
|
|
155
|
+
trigger.addEventListener('click', open);
|
|
156
|
+
for (const button of closeButtons)
|
|
157
|
+
button.addEventListener('click', dismiss);
|
|
158
|
+
dialog.addEventListener('close', finish);
|
|
159
|
+
dialog.addEventListener('pointerdown', pointerdown);
|
|
160
|
+
dialog.addEventListener('click', click);
|
|
161
|
+
root[KEY] = () => {
|
|
162
|
+
const restoreFocus =
|
|
163
|
+
dialog.contains(doc.activeElement) || doc.activeElement === trigger;
|
|
164
|
+
observer?.disconnect();
|
|
165
|
+
dismiss();
|
|
166
|
+
trigger.removeEventListener('click', open);
|
|
167
|
+
for (const button of closeButtons)
|
|
168
|
+
button.removeEventListener('click', dismiss);
|
|
169
|
+
dialog.removeEventListener('close', finish);
|
|
170
|
+
dialog.removeEventListener('pointerdown', pointerdown);
|
|
171
|
+
dialog.removeEventListener('click', click);
|
|
172
|
+
placeholder.replaceWith(content);
|
|
173
|
+
dialog.remove();
|
|
174
|
+
[fallback.hidden, trigger.hidden] = original;
|
|
175
|
+
closeButtons.forEach((button, index) => {
|
|
176
|
+
button.hidden = originalClose[index];
|
|
177
|
+
});
|
|
178
|
+
if (oldPopup === null) trigger.removeAttribute('aria-haspopup');
|
|
179
|
+
else trigger.setAttribute('aria-haspopup', oldPopup);
|
|
180
|
+
delete root[KEY];
|
|
181
|
+
// The enhanced opener is hidden again. Keep keyboard position in the
|
|
182
|
+
// restored disclosure instead of leaving focus on that hidden button.
|
|
183
|
+
if (
|
|
184
|
+
restoreFocus &&
|
|
185
|
+
root.isConnected &&
|
|
186
|
+
!fallback.hidden &&
|
|
187
|
+
trigger.hidden
|
|
188
|
+
)
|
|
189
|
+
summary.focus({ preventScroll: true });
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Restore authored markup before replacing a component or behaviour scope. */
|
|
195
|
+
export function mgDialogDestroy(scope = document) {
|
|
196
|
+
for (const root of elements(scope)) root[KEY]?.();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (typeof document !== 'undefined') {
|
|
200
|
+
if (document.readyState === 'loading') {
|
|
201
|
+
document.addEventListener('DOMContentLoaded', () => mgDialog(), {
|
|
202
|
+
once: true,
|
|
203
|
+
});
|
|
204
|
+
} else mgDialog();
|
|
205
|
+
}
|
package/package.json
CHANGED
package/scss/Atom/Tag/tag.scss
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
@use "../../../assets/scss/config" as *;
|
|
11
11
|
|
|
12
12
|
.mg-share {
|
|
13
|
+
container: mg-share / inline-size;
|
|
13
14
|
max-inline-size: mg-rem(400);
|
|
14
15
|
min-inline-size: 0;
|
|
15
16
|
display: flex;
|
|
@@ -32,6 +33,23 @@
|
|
|
32
33
|
align-items: center;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
@container mg-share (inline-size < #{mg-rem(265)}) {
|
|
37
|
+
.mg-share__buttons {
|
|
38
|
+
column-gap: 0;
|
|
39
|
+
justify-content: space-between;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@container mg-share (inline-size < #{mg-rem(250)}) {
|
|
44
|
+
.mg-share__buttons {
|
|
45
|
+
display: grid;
|
|
46
|
+
grid-template-columns: repeat(auto-fit, #{mg-rem(40)});
|
|
47
|
+
max-inline-size: mg-rem(130);
|
|
48
|
+
gap: var(--mg-spacing-50);
|
|
49
|
+
justify-content: start;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
35
53
|
.mg-share__button,
|
|
36
54
|
.mg-share__copy-button {
|
|
37
55
|
box-sizing: border-box;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialog: native modal reading surface with an authored disclosure fallback.
|
|
3
|
+
* https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Components/Dialog/Dialog.mdx
|
|
4
|
+
*/
|
|
5
|
+
@use "../../assets/scss/mixins" as *;
|
|
6
|
+
|
|
7
|
+
.mg-dialog {
|
|
8
|
+
box-sizing: border-box;
|
|
9
|
+
position: fixed;
|
|
10
|
+
inline-size: min(40rem, calc(100% - var(--mg-spacing-400)));
|
|
11
|
+
max-block-size: calc(100% - var(--mg-spacing-400));
|
|
12
|
+
padding: 0;
|
|
13
|
+
overflow: auto;
|
|
14
|
+
overscroll-behavior: contain;
|
|
15
|
+
background: rgb(var(--mg-color-white));
|
|
16
|
+
border: 1px solid rgb(var(--mg-color-neutral-500));
|
|
17
|
+
border-radius: var(--mg-radius-button);
|
|
18
|
+
color: rgb(var(--mg-color-text));
|
|
19
|
+
font-family: var(--mg-font-family-text);
|
|
20
|
+
|
|
21
|
+
&::backdrop {
|
|
22
|
+
background: var(--mg-color-modal-scrim);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&__header {
|
|
26
|
+
// Normal flow stays readable when a viewport or enlarged text leaves too
|
|
27
|
+
// little reading space. The enhancer enables sticky mode only when it fits.
|
|
28
|
+
z-index: 1;
|
|
29
|
+
inset-block-start: 0;
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-wrap: wrap;
|
|
32
|
+
align-items: start;
|
|
33
|
+
justify-content: space-between;
|
|
34
|
+
gap: var(--mg-spacing-200);
|
|
35
|
+
padding: var(--mg-spacing-200);
|
|
36
|
+
background: rgb(var(--mg-color-white));
|
|
37
|
+
border-block-end: 1px solid rgb(var(--mg-color-neutral-300));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&--sticky-header &__header {
|
|
41
|
+
// Local stacking keeps Close visible without covering the reading region.
|
|
42
|
+
position: sticky;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&__title {
|
|
46
|
+
flex: 1 1 12rem;
|
|
47
|
+
min-inline-size: 0;
|
|
48
|
+
overflow-wrap: anywhere;
|
|
49
|
+
margin: 0;
|
|
50
|
+
font-family: var(--mg-font-family-ui);
|
|
51
|
+
font-size: var(--mg-font-size-400);
|
|
52
|
+
|
|
53
|
+
&:focus-visible {
|
|
54
|
+
// Header padding leaves room for an outward ring without covering text.
|
|
55
|
+
@include mg-focus-ring;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&__body {
|
|
60
|
+
padding: var(--mg-spacing-200);
|
|
61
|
+
overflow-wrap: anywhere;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
&__close {
|
|
65
|
+
flex: 0 1 auto;
|
|
66
|
+
max-inline-size: 100%;
|
|
67
|
+
margin-inline-start: auto;
|
|
68
|
+
overflow-wrap: anywhere;
|
|
69
|
+
min-block-size: 44px;
|
|
70
|
+
min-inline-size: 44px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@media (forced-colors: active) {
|
|
74
|
+
border-color: CanvasText;
|
|
75
|
+
|
|
76
|
+
&__header {
|
|
77
|
+
border-color: CanvasText;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Button presentation can override the user-agent hidden rule. Preserve the
|
|
83
|
+
// authored no-JavaScript state before the enhancement reveals these controls.
|
|
84
|
+
[data-mg-dialog-trigger][hidden],
|
|
85
|
+
[data-mg-dialog-close][hidden] {
|
|
86
|
+
display: none;
|
|
87
|
+
}
|