@undrr/undrr-mangrove 2.0.1 → 2.1.1
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 +1 -1
- package/components/Breadcrumbs.js +1 -1
- package/components/Checkbox.js +1 -1
- package/components/Chips.js +1 -1
- package/components/CopyButton.js +1 -1
- package/components/CtaButton.js +1 -1
- package/components/Drawer.js +2 -2
- package/components/EmbedContainer.js +1 -1
- package/components/Footer.js +1 -1
- package/components/FormAction.js +1 -1
- package/components/FormErrorSummary.js +1 -1
- package/components/FormGroup.js +1 -1
- package/components/FullWidth.js +1 -1
- package/components/Gallery.js +2 -2
- package/components/Hero.js +2 -2
- package/components/HighlightBox.js +1 -1
- package/components/HorizontalBookCard.js +1 -1
- package/components/HorizontalCard.js +1 -1
- package/components/IconCard.js +1 -1
- package/components/Legend.js +1 -1
- package/components/Loader.js +2 -2
- package/components/MegaMenu.js +2 -2
- package/components/Notice.js +1 -1
- package/components/PageHeader.js +1 -1
- package/components/Pager.js +1 -1
- package/components/ProgressBar.js +2 -2
- package/components/QuoteHighlight.js +1 -1
- package/components/Radio.js +1 -1
- package/components/Range.js +1 -1
- package/components/ScrollContainer.js +1 -1
- package/components/SegmentedControl.js +1 -1
- package/components/Select.js +1 -1
- package/components/ServiceNotice.js +1 -1
- package/components/ShareButtons.js +1 -1
- package/components/ShowMore.js +2 -2
- package/components/SkipLink.js +1 -1
- package/components/StatsCard.js +1 -1
- package/components/SyndicationSearchWidget.js +2 -2
- package/components/Tab.js +1 -1
- package/components/TextCta.js +1 -1
- package/components/TextInput.js +1 -1
- package/components/Textarea.js +1 -1
- package/components/Tree.js +1 -1
- package/components/UserFeedback.js +1 -1
- package/components/VerticalCard.js +1 -1
- package/components/hydrate.js +2 -2
- package/css/style-all.css +41 -42
- package/css/style-all.css.map +1 -1
- package/css/style-delta.css +41 -42
- package/css/style-delta.css.map +1 -1
- package/css/style-gutenberg.css +4 -4
- package/css/style-gutenberg.css.map +1 -1
- package/css/style-irp.css +41 -42
- package/css/style-irp.css.map +1 -1
- package/css/style-mcr.css +41 -42
- package/css/style-mcr.css.map +1 -1
- package/css/style-preventionweb.css +41 -42
- package/css/style-preventionweb.css.map +1 -1
- package/css/style.css +41 -42
- package/css/style.css.map +1 -1
- package/docs/EDITORIAL-MANUAL.md +1 -1
- package/docs/llms-motion.txt +125 -0
- 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/cite-this.js +685 -43
- package/js/on-this-page-nav.js +80 -1
- package/js/show-more.js +149 -21
- package/package.json +1 -1
- package/scss/Atom/Images/AuthorImage/author-image.scss +9 -2
- package/scss/Atom/ReachElement/Details/details.scss +4 -2
- package/scss/Components/Cards/Card/card.scss +8 -2
- package/scss/Components/Cards/StatsCard/stats-card.scss +8 -1
- package/scss/Components/CiteThis/cite-this.scss +162 -9
- package/scss/Components/Gallery/gallery.scss +40 -15
- package/scss/Components/Hero/hero.scss +165 -0
- package/scss/Components/MegaMenu/mega-menu.scss +42 -121
- package/scss/Components/Navigation/Drawer/drawer.scss +10 -0
- package/scss/Components/OnThisPageNav/on-this-page-nav.scss +108 -3
- package/scss/Components/SyndicationSearchWidget/syndication-search-widget.scss +258 -127
- package/scss/Components/Tab/tab.scss +2 -1
- package/scss/Patterns/RichContent/rich-content.scss +177 -0
- package/scss/Patterns/_shared/pattern-demo.scss +5 -0
- package/scss/Utilities/Loader/loader.scss +89 -11
- package/scss/Utilities/ShowMore/show-more.scss +6 -0
- package/scss/assets/scss/_components.scss +3 -0
- package/scss/assets/scss/_foundational.scss +23 -8
- package/scss/assets/scss/_mixins.scss +5 -0
- package/scss/assets/scss/_variables.scss +1 -1
package/js/on-this-page-nav.js
CHANGED
|
@@ -107,6 +107,7 @@ export function mgOnThisPageNav(scope) {
|
|
|
107
107
|
container._mgOnThisPageNavAbort = ac;
|
|
108
108
|
|
|
109
109
|
setupClickHandlers(container, ac.signal);
|
|
110
|
+
setupBlockSize(container, ac.signal);
|
|
110
111
|
setupScrollSpy(container);
|
|
111
112
|
setupFocusScrolling(container, ac.signal);
|
|
112
113
|
setupScrollButtons(container, ac.signal);
|
|
@@ -132,13 +133,53 @@ export function mgOnThisPageNavDestroy(container) {
|
|
|
132
133
|
container._mgOnThisPageNavResizeObserver.disconnect();
|
|
133
134
|
delete container._mgOnThisPageNavResizeObserver;
|
|
134
135
|
}
|
|
136
|
+
if (container._mgOnThisPageNavSizeObserver) {
|
|
137
|
+
container._mgOnThisPageNavSizeObserver.disconnect();
|
|
138
|
+
delete container._mgOnThisPageNavSizeObserver;
|
|
139
|
+
document.documentElement.style.removeProperty(BLOCK_SIZE);
|
|
140
|
+
}
|
|
135
141
|
container
|
|
136
142
|
.querySelectorAll('.mg-on-this-page-nav__scroll-btn')
|
|
137
143
|
.forEach(btn => btn.remove());
|
|
138
|
-
container.classList.remove(
|
|
144
|
+
container.classList.remove(
|
|
145
|
+
'mg-on-this-page-nav--has-left-overflow',
|
|
146
|
+
'mg-on-this-page-nav--has-right-overflow'
|
|
147
|
+
);
|
|
139
148
|
delete container.dataset.mgOnThisPageNavInitialized;
|
|
140
149
|
}
|
|
141
150
|
|
|
151
|
+
const BLOCK_SIZE = '--mg-on-this-page-nav-block-size';
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Publishes the bar's height on the root element while it is sticky, and 0px
|
|
155
|
+
* when it is not (it un-sticks on short viewports). The stylesheet adds it to
|
|
156
|
+
* `scroll-padding-top`, so anything the browser scrolls into view (focus,
|
|
157
|
+
* anchors, scrollIntoView from other components) lands below the bar rather
|
|
158
|
+
* than under it.
|
|
159
|
+
*
|
|
160
|
+
* @param {HTMLElement} container - The nav element
|
|
161
|
+
* @param {AbortSignal} signal - Removes the resize listener on destroy
|
|
162
|
+
*/
|
|
163
|
+
function setupBlockSize(container, signal) {
|
|
164
|
+
const root = document.documentElement;
|
|
165
|
+
const publish = () => {
|
|
166
|
+
const sticky = getComputedStyle(container).position === 'sticky';
|
|
167
|
+
root.style.setProperty(
|
|
168
|
+
BLOCK_SIZE,
|
|
169
|
+
`${sticky ? container.offsetHeight || 0 : 0}px`
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
publish();
|
|
173
|
+
window.addEventListener('resize', publish, { passive: true, signal });
|
|
174
|
+
if (typeof ResizeObserver === 'function') {
|
|
175
|
+
const observer = new ResizeObserver(publish);
|
|
176
|
+
observer.observe(container);
|
|
177
|
+
container._mgOnThisPageNavSizeObserver = observer;
|
|
178
|
+
} else {
|
|
179
|
+
container._mgOnThisPageNavSizeObserver = { disconnect() {} };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
142
183
|
/**
|
|
143
184
|
* Auto-detect mode: scan headings from the page and generate nav links.
|
|
144
185
|
* Preserves any existing CTA element in the container.
|
|
@@ -399,6 +440,13 @@ function updateScrollButtons(container, list, prevBtn, nextBtn) {
|
|
|
399
440
|
'mg-on-this-page-nav--has-left-overflow',
|
|
400
441
|
rtl ? canScrollFurther : scrolledFromStart
|
|
401
442
|
);
|
|
443
|
+
// has-right-overflow is its mirror. Only the RTL fades read it: in RTL the
|
|
444
|
+
// hidden content can sit on either physical side, and a fade belongs only
|
|
445
|
+
// on a side with something hidden behind it.
|
|
446
|
+
container.classList.toggle(
|
|
447
|
+
'mg-on-this-page-nav--has-right-overflow',
|
|
448
|
+
rtl ? scrolledFromStart : canScrollFurther
|
|
449
|
+
);
|
|
402
450
|
}
|
|
403
451
|
|
|
404
452
|
/**
|
|
@@ -543,6 +591,15 @@ function setupScrollSpy(container) {
|
|
|
543
591
|
if (targets.length === 0) return;
|
|
544
592
|
|
|
545
593
|
let activeId = null;
|
|
594
|
+
|
|
595
|
+
// At the foot of a page that scrolls a real distance. On a page that barely
|
|
596
|
+
// scrolls, the bottom is also the top, and the first section is right.
|
|
597
|
+
const atFoot = () => {
|
|
598
|
+
const doc = document.documentElement;
|
|
599
|
+
const scrollable = doc.scrollHeight - window.innerHeight;
|
|
600
|
+
const minimum = Math.max(120, window.innerHeight * 0.25);
|
|
601
|
+
return scrollable >= minimum && window.scrollY >= scrollable - 2;
|
|
602
|
+
};
|
|
546
603
|
// Track which targets are currently intersecting (avoids DOM expando properties)
|
|
547
604
|
const intersecting = new Set();
|
|
548
605
|
|
|
@@ -584,6 +641,13 @@ function setupScrollSpy(container) {
|
|
|
584
641
|
}
|
|
585
642
|
});
|
|
586
643
|
|
|
644
|
+
// At the foot of the page the last section wins (see onScroll). The
|
|
645
|
+
// observer reports after the scroll event, so without this check it
|
|
646
|
+
// would take the mark straight back.
|
|
647
|
+
if (atFoot()) {
|
|
648
|
+
setActive(targets[targets.length - 1].id);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
587
651
|
// Pick the first intersecting target in DOM order
|
|
588
652
|
const firstVisible = targets.find(t => intersecting.has(t));
|
|
589
653
|
if (firstVisible) {
|
|
@@ -599,12 +663,27 @@ function setupScrollSpy(container) {
|
|
|
599
663
|
targets.forEach(target => observer.observe(target));
|
|
600
664
|
container._mgOnThisPageNavObserver = observer;
|
|
601
665
|
|
|
666
|
+
// A short last section can never scroll up into the observed band, so at
|
|
667
|
+
// the foot of the page the previous section stayed marked, even straight
|
|
668
|
+
// after following the last link. At the bottom, mark the last target. Only
|
|
669
|
+
// on pages that scroll a real distance: on one that barely scrolls, the
|
|
670
|
+
// bottom is also the top, and the first section is the right answer.
|
|
671
|
+
const onScroll = () => {
|
|
672
|
+
if (atFoot()) setActive(targets[targets.length - 1].id);
|
|
673
|
+
};
|
|
674
|
+
window.addEventListener('scroll', onScroll, {
|
|
675
|
+
passive: true,
|
|
676
|
+
signal: container._mgOnThisPageNavAbort?.signal,
|
|
677
|
+
});
|
|
678
|
+
|
|
602
679
|
const hashId = window.location.hash ? window.location.hash.slice(1) : null;
|
|
603
680
|
if (hashId && linkMap.has(hashId)) {
|
|
604
681
|
setActive(hashId);
|
|
605
682
|
} else if (targets.length > 0) {
|
|
606
683
|
setActive(targets[0].id);
|
|
607
684
|
}
|
|
685
|
+
// A page restored at its foot (reload, back button) is checked straight away.
|
|
686
|
+
onScroll();
|
|
608
687
|
}
|
|
609
688
|
|
|
610
689
|
// Auto-wrap so the browser Event object is not passed as scope
|
package/js/show-more.js
CHANGED
|
@@ -61,6 +61,58 @@ function resolveTarget(toggle, selector) {
|
|
|
61
61
|
return document.querySelector(selector);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* The height the content collapses to, read from CSS: the computed
|
|
66
|
+
* max-height of the collapsed state, so `--mg-show-more-height` may be set in
|
|
67
|
+
* any unit, including `lh`. Applying and removing the class in one task
|
|
68
|
+
* forces a style recalculation but never paints.
|
|
69
|
+
*
|
|
70
|
+
* @param {HTMLElement} target - The collapsible content.
|
|
71
|
+
* @returns {number} Pixels, or NaN when CSS gives no length.
|
|
72
|
+
*/
|
|
73
|
+
function collapsedHeight(target) {
|
|
74
|
+
const collapsed = target.classList.contains('mg-show-more--collapsed');
|
|
75
|
+
if (!collapsed) target.classList.add('mg-show-more--collapsed');
|
|
76
|
+
const height = parseFloat(getComputedStyle(target).maxHeight);
|
|
77
|
+
if (!collapsed) target.classList.remove('mg-show-more--collapsed');
|
|
78
|
+
return height;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {HTMLElement} target - The collapsible content.
|
|
83
|
+
* @returns {number} Its line height in pixels.
|
|
84
|
+
*/
|
|
85
|
+
function lineHeight(target) {
|
|
86
|
+
const style = getComputedStyle(target);
|
|
87
|
+
const line = parseFloat(style.lineHeight);
|
|
88
|
+
return Number.isFinite(line) ? line : parseFloat(style.fontSize) * 1.2;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* After a reader collapses content, bring the start of it back into view if
|
|
93
|
+
* the toggle has left the viewport. Scrolls the heading just before the
|
|
94
|
+
* content when there is one, honours scroll-padding (so a sticky bar such as
|
|
95
|
+
* On this page is allowed for) and reduced motion.
|
|
96
|
+
*
|
|
97
|
+
* @param {HTMLElement} toggle - The toggle that was used.
|
|
98
|
+
* @param {HTMLElement} target - The content it collapsed.
|
|
99
|
+
*/
|
|
100
|
+
function returnToContent(toggle, target) {
|
|
101
|
+
const box = toggle.getBoundingClientRect();
|
|
102
|
+
if (box.bottom > 0 && box.top < window.innerHeight) return;
|
|
103
|
+
const previous = target.previousElementSibling;
|
|
104
|
+
const anchor = previous?.matches('h1, h2, h3, h4, h5, h6')
|
|
105
|
+
? previous
|
|
106
|
+
: target;
|
|
107
|
+
const reduce =
|
|
108
|
+
typeof window.matchMedia === 'function' &&
|
|
109
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
110
|
+
anchor.scrollIntoView?.({
|
|
111
|
+
block: 'start',
|
|
112
|
+
behavior: reduce ? 'auto' : 'smooth',
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
64
116
|
/**
|
|
65
117
|
* Initializes "show more" toggle buttons.
|
|
66
118
|
*
|
|
@@ -102,7 +154,6 @@ export function mgShowMore(scope) {
|
|
|
102
154
|
'mg-show-more--collapsed'
|
|
103
155
|
);
|
|
104
156
|
const wasOpen = item.classList.contains('mg-show-more--button--open');
|
|
105
|
-
const initialText = item.textContent;
|
|
106
157
|
const initialControls = item.getAttribute('aria-controls');
|
|
107
158
|
const initialRole = item.getAttribute('role');
|
|
108
159
|
const onKeydown = event => {
|
|
@@ -112,8 +163,27 @@ export function mgShowMore(scope) {
|
|
|
112
163
|
}
|
|
113
164
|
};
|
|
114
165
|
|
|
115
|
-
|
|
116
|
-
|
|
166
|
+
// The click tracker reads data-vf-analytics-label. Report the action the
|
|
167
|
+
// toggle is about to perform, in fixed English so reports do not split by
|
|
168
|
+
// language. A label the author set by hand is kept as it is, unless
|
|
169
|
+
// per-state labels are given.
|
|
170
|
+
const COLLAPSED = 'mg-show-more--collapsed';
|
|
171
|
+
const authorLabel = item.getAttribute('data-vf-analytics-label');
|
|
172
|
+
const { mgShowMoreAnalyticsCollapsed, mgShowMoreAnalyticsOpen } =
|
|
173
|
+
item.dataset;
|
|
174
|
+
const stateLabels =
|
|
175
|
+
mgShowMoreAnalyticsCollapsed || mgShowMoreAnalyticsOpen || !authorLabel
|
|
176
|
+
? {
|
|
177
|
+
collapsed: mgShowMoreAnalyticsCollapsed || 'Show more',
|
|
178
|
+
open: mgShowMoreAnalyticsOpen || 'Show less',
|
|
179
|
+
}
|
|
180
|
+
: null;
|
|
181
|
+
|
|
182
|
+
// Visible text lives in a [data-mg-show-more-label] child when there is
|
|
183
|
+
// one, so visually hidden context beside it survives each toggle.
|
|
184
|
+
const labelNode = item.querySelector('[data-mg-show-more-label]') || item;
|
|
185
|
+
const initialText = labelNode.textContent;
|
|
186
|
+
const initialHidden = item.hidden;
|
|
117
187
|
|
|
118
188
|
// Relate the toggle to the content it acts on. A <button> already
|
|
119
189
|
// announces as a button; an anchor copied from an earlier version of the
|
|
@@ -131,24 +201,35 @@ export function mgShowMore(scope) {
|
|
|
131
201
|
item.addEventListener('keydown', onKeydown);
|
|
132
202
|
}
|
|
133
203
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
'mg-show-more--collapsed'
|
|
142
|
-
)
|
|
204
|
+
// Initialisation and re-measures change state here rather than through
|
|
205
|
+
// item.click(), so they reach no click listener (analytics reporting, the
|
|
206
|
+
// Drupal wrapper). Focus entering, or a click on, collapsed content still
|
|
207
|
+
// clicks the toggle below: those stand for a reader's action.
|
|
208
|
+
const setCollapsed = collapsed => {
|
|
209
|
+
mgShowMoreTarget.classList.toggle(COLLAPSED, collapsed);
|
|
210
|
+
labelNode.textContent = collapsed
|
|
143
211
|
? item.dataset.mgShowMoreLabelCollapsed || 'Show more'
|
|
144
212
|
: item.dataset.mgShowMoreLabelOpen || 'Show less';
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
item.
|
|
148
|
-
|
|
149
|
-
|
|
213
|
+
item.classList.toggle('mg-show-more--button--open', !collapsed);
|
|
214
|
+
if (stateLabels) {
|
|
215
|
+
item.setAttribute(
|
|
216
|
+
'data-vf-analytics-label',
|
|
217
|
+
collapsed ? stateLabels.collapsed : stateLabels.open
|
|
218
|
+
);
|
|
150
219
|
}
|
|
151
220
|
};
|
|
221
|
+
|
|
222
|
+
let readerToggled = false;
|
|
223
|
+
const onClick = event => {
|
|
224
|
+
// Also neutralises the dead `href="#"` on a legacy anchor toggle.
|
|
225
|
+
event.preventDefault();
|
|
226
|
+
readerToggled = true;
|
|
227
|
+
const collapsing = !mgShowMoreTarget.classList.contains(COLLAPSED);
|
|
228
|
+
setCollapsed(collapsing);
|
|
229
|
+
// Collapsing a long block pulls the toggle up the page, often out of
|
|
230
|
+
// view. Bring the reader back to the start of what they collapsed.
|
|
231
|
+
if (collapsing) returnToContent(item, mgShowMoreTarget);
|
|
232
|
+
};
|
|
152
233
|
item.addEventListener('click', onClick);
|
|
153
234
|
|
|
154
235
|
// The collapse is `max-height` + `overflow: hidden`, which clips content
|
|
@@ -158,7 +239,7 @@ export function mgShowMore(scope) {
|
|
|
158
239
|
// entry keeps the focused control visible.
|
|
159
240
|
const onFocusin = event => {
|
|
160
241
|
if (item === event.target || item.contains(event.target)) return;
|
|
161
|
-
if (mgShowMoreTarget.classList.contains(
|
|
242
|
+
if (mgShowMoreTarget.classList.contains(COLLAPSED)) {
|
|
162
243
|
item.click();
|
|
163
244
|
}
|
|
164
245
|
};
|
|
@@ -167,14 +248,61 @@ export function mgShowMore(scope) {
|
|
|
167
248
|
// Allow items to be shown by clicking anywhere on the collapsed item
|
|
168
249
|
// https://gitlab.com/undrr/web-backlog/-/issues/1612
|
|
169
250
|
const onTargetClick = () => {
|
|
170
|
-
if (mgShowMoreTarget.classList.contains(
|
|
251
|
+
if (mgShowMoreTarget.classList.contains(COLLAPSED)) {
|
|
171
252
|
item.click();
|
|
172
253
|
}
|
|
173
254
|
};
|
|
174
255
|
mgShowMoreTarget.addEventListener('click', onTargetClick);
|
|
175
256
|
|
|
176
|
-
|
|
257
|
+
// A template may ship the toggle `hidden` so it never shows without
|
|
258
|
+
// JavaScript; the content is then simply open.
|
|
259
|
+
item.hidden = false;
|
|
260
|
+
setCollapsed(!wasCollapsed);
|
|
261
|
+
|
|
262
|
+
// Opt-in: collapse only when enough would be hidden. Short content stays
|
|
263
|
+
// open and the toggle is hidden, so a template can wrap every body field
|
|
264
|
+
// in ShowMore. The value is a number of lines (empty means 1): collapse
|
|
265
|
+
// when the content runs at least that many lines past the collapsed
|
|
266
|
+
// height. The collapsed height is read from CSS (--mg-show-more-height),
|
|
267
|
+
// so the decision does not depend on the current state and cannot flip
|
|
268
|
+
// back and forth. Measured again when the content resizes, until the
|
|
269
|
+
// reader uses the toggle. Content with no layout yet (inside a closed
|
|
270
|
+
// tab, say) waits until it has one.
|
|
271
|
+
let observer = null;
|
|
272
|
+
let frame = 0;
|
|
273
|
+
if (item.hasAttribute('data-mg-show-more-min-hidden-lines')) {
|
|
274
|
+
const lines =
|
|
275
|
+
parseFloat(item.dataset.mgShowMoreMinHiddenLines) > 0
|
|
276
|
+
? parseFloat(item.dataset.mgShowMoreMinHiddenLines)
|
|
277
|
+
: 1;
|
|
278
|
+
const measure = () => {
|
|
279
|
+
frame = 0;
|
|
280
|
+
if (readerToggled || mgShowMoreTarget.scrollHeight === 0) return;
|
|
281
|
+
const limit = collapsedHeight(mgShowMoreTarget);
|
|
282
|
+
const line = lineHeight(mgShowMoreTarget);
|
|
283
|
+
if (!Number.isFinite(limit) || !Number.isFinite(line)) return;
|
|
284
|
+
const collapse = mgShowMoreTarget.scrollHeight - limit >= lines * line;
|
|
285
|
+
setCollapsed(collapse);
|
|
286
|
+
item.hidden = !collapse;
|
|
287
|
+
};
|
|
288
|
+
measure();
|
|
289
|
+
if (typeof ResizeObserver === 'function') {
|
|
290
|
+
// Deferred to the next frame: changing the observed box inside the
|
|
291
|
+
// callback is what raises "ResizeObserver loop" errors.
|
|
292
|
+
observer = new ResizeObserver(() => {
|
|
293
|
+
if (frame) cancelAnimationFrame(frame);
|
|
294
|
+
frame = requestAnimationFrame(measure);
|
|
295
|
+
});
|
|
296
|
+
observer.observe(mgShowMoreTarget);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
177
300
|
cleanups.push(() => {
|
|
301
|
+
observer?.disconnect();
|
|
302
|
+
if (frame) cancelAnimationFrame(frame);
|
|
303
|
+
item.hidden = initialHidden;
|
|
304
|
+
if (authorLabel === null) item.removeAttribute('data-vf-analytics-label');
|
|
305
|
+
else item.setAttribute('data-vf-analytics-label', authorLabel);
|
|
178
306
|
item.removeEventListener('click', onClick);
|
|
179
307
|
item.removeEventListener('keydown', onKeydown);
|
|
180
308
|
mgShowMoreTarget.removeEventListener('focusin', onFocusin);
|
|
@@ -184,7 +312,7 @@ export function mgShowMore(scope) {
|
|
|
184
312
|
wasCollapsed
|
|
185
313
|
);
|
|
186
314
|
item.classList.toggle('mg-show-more--button--open', wasOpen);
|
|
187
|
-
|
|
315
|
+
labelNode.textContent = initialText;
|
|
188
316
|
for (const [attribute, value] of [
|
|
189
317
|
['aria-controls', initialControls],
|
|
190
318
|
['role', initialRole],
|
package/package.json
CHANGED
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
position: absolute;
|
|
50
50
|
z-index: 1;
|
|
51
51
|
transition:
|
|
52
|
-
opacity
|
|
53
|
-
transform
|
|
52
|
+
opacity var(--mg-motion-duration-fast) var(--mg-motion-easing-standard),
|
|
53
|
+
transform var(--mg-motion-duration-fast) var(--mg-motion-easing-standard);
|
|
54
54
|
transform: translateY(2px);
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -224,3 +224,10 @@
|
|
|
224
224
|
justify-items: center;
|
|
225
225
|
text-align: center;
|
|
226
226
|
}
|
|
227
|
+
|
|
228
|
+
@media (prefers-reduced-motion: reduce) {
|
|
229
|
+
.mg-author-image__avatar::before {
|
|
230
|
+
transition: none;
|
|
231
|
+
transform: none;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -24,7 +24,8 @@ details.mg-details {
|
|
|
24
24
|
border: 1px solid rgb(var(--mg-color-neutral-400)); // #808080 — 3.95:1 on white (passes WCAG 1.4.11)
|
|
25
25
|
border-radius: mg-rem(4);
|
|
26
26
|
padding: mg-rem(10);
|
|
27
|
-
transition: box-shadow
|
|
27
|
+
transition: box-shadow var(--mg-motion-duration-fast)
|
|
28
|
+
var(--mg-motion-easing-standard);
|
|
28
29
|
|
|
29
30
|
&:hover {
|
|
30
31
|
box-shadow: 0 2px 4px rgb(0 0 0 / 0.1);
|
|
@@ -40,7 +41,8 @@ details.mg-details {
|
|
|
40
41
|
padding-block: mg-rem(10);
|
|
41
42
|
padding-inline: mg-rem(10) mg-rem(44);
|
|
42
43
|
position: relative;
|
|
43
|
-
transition: background-color
|
|
44
|
+
transition: background-color var(--mg-motion-duration-fast)
|
|
45
|
+
var(--mg-motion-easing-standard);
|
|
44
46
|
|
|
45
47
|
&:hover {
|
|
46
48
|
background-color: rgb(
|
|
@@ -242,7 +242,8 @@
|
|
|
242
242
|
width: 0.35em;
|
|
243
243
|
|
|
244
244
|
@media (prefers-reduced-motion: no-preference) {
|
|
245
|
-
transition: translate
|
|
245
|
+
transition: translate var(--mg-motion-duration-fast)
|
|
246
|
+
var(--mg-motion-easing-standard);
|
|
246
247
|
}
|
|
247
248
|
}
|
|
248
249
|
|
|
@@ -578,7 +579,12 @@ span.mg-card__label {
|
|
|
578
579
|
// Visual link for logo cards (srOnlyTitle) - stretched link pattern
|
|
579
580
|
.mg-card__visual-link {
|
|
580
581
|
display: block;
|
|
581
|
-
|
|
582
|
+
|
|
583
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
584
|
+
transition: opacity var(--mg-motion-duration-fast)
|
|
585
|
+
var(--mg-motion-easing-standard);
|
|
586
|
+
}
|
|
587
|
+
|
|
582
588
|
width: 100%;
|
|
583
589
|
|
|
584
590
|
&::after {
|
|
@@ -120,7 +120,8 @@
|
|
|
120
120
|
text-decoration: underline;
|
|
121
121
|
position: relative;
|
|
122
122
|
z-index: 2; // above stretched-link cover — local to stats-card stacking context
|
|
123
|
-
transition: opacity
|
|
123
|
+
transition: opacity var(--mg-motion-duration-fast)
|
|
124
|
+
var(--mg-motion-easing-standard);
|
|
124
125
|
|
|
125
126
|
&:hover {
|
|
126
127
|
opacity: 0.7;
|
|
@@ -228,3 +229,9 @@
|
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
/* RTL support is automatic: the highlighted accent uses `border-inline-start`. */
|
|
232
|
+
|
|
233
|
+
@media (prefers-reduced-motion: reduce) {
|
|
234
|
+
.mg-stats-card-item__summary a {
|
|
235
|
+
transition: none;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -43,6 +43,104 @@
|
|
|
43
43
|
@media (prefers-reduced-motion: reduce) {
|
|
44
44
|
transition: none;
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
// The filled trigger relies on its background, which forced colours remove.
|
|
48
|
+
@media (forced-colors: active) {
|
|
49
|
+
border: 1px solid ButtonText;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Default enhanced layout, with the same structure and metrics as
|
|
54
|
+
// ShareButtons: a label, the citation as a copyable field, and a quiet
|
|
55
|
+
// dialog link. `--compact` shows the dialog trigger alone.
|
|
56
|
+
&__panel {
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
align-items: flex-start;
|
|
60
|
+
gap: var(--mg-spacing-100);
|
|
61
|
+
max-inline-size: mg-rem(400);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
&__header {
|
|
65
|
+
margin: 0;
|
|
66
|
+
color: rgb(var(--mg-color-text));
|
|
67
|
+
font-family: var(--mg-font-family-text);
|
|
68
|
+
font-size: var(--mg-font-size-300);
|
|
69
|
+
font-weight: 600;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
&__preview {
|
|
73
|
+
box-sizing: border-box;
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
gap: var(--mg-spacing-100);
|
|
77
|
+
inline-size: 100%;
|
|
78
|
+
min-block-size: mg-rem(40);
|
|
79
|
+
padding: var(--mg-spacing-100);
|
|
80
|
+
background-color: rgb(var(--mg-color-neutral-25));
|
|
81
|
+
border: 1px solid rgb(var(--mg-color-neutral-300) / 0.4);
|
|
82
|
+
border-radius: var(--mg-radius-button);
|
|
83
|
+
color: rgb(var(--mg-color-text));
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
font-family: var(--mg-font-family-text);
|
|
86
|
+
text-align: start;
|
|
87
|
+
|
|
88
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
89
|
+
transition: background-color var(--mg-motion-duration-fast)
|
|
90
|
+
var(--mg-motion-easing-standard);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.mg-icon {
|
|
94
|
+
flex: 0 0 mg-rem(24);
|
|
95
|
+
text-align: center;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
&:hover {
|
|
99
|
+
background-color: rgb(var(--mg-color-neutral-50));
|
|
100
|
+
|
|
101
|
+
.mg-cite-this__preview-text {
|
|
102
|
+
text-decoration: underline;
|
|
103
|
+
text-underline-offset: 0.15em;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
&:focus-visible {
|
|
108
|
+
@include mg-focus-ring;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@media (forced-colors: active) {
|
|
112
|
+
border-color: ButtonText;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
&__preview-text {
|
|
117
|
+
display: -webkit-box;
|
|
118
|
+
flex: 1;
|
|
119
|
+
min-inline-size: 0;
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
font-size: var(--mg-font-size-250);
|
|
122
|
+
line-height: 1.5;
|
|
123
|
+
overflow-wrap: anywhere;
|
|
124
|
+
-webkit-box-orient: vertical;
|
|
125
|
+
-webkit-line-clamp: 2;
|
|
126
|
+
line-clamp: 2;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Doubled selector outranks the filled `__trigger` styles.
|
|
130
|
+
& &__more {
|
|
131
|
+
min-block-size: 44px;
|
|
132
|
+
padding: 0;
|
|
133
|
+
background: transparent;
|
|
134
|
+
color: rgb(var(--mg-color-interactive));
|
|
135
|
+
font-size: var(--mg-font-size-250);
|
|
136
|
+
font-weight: 400;
|
|
137
|
+
text-decoration: underline;
|
|
138
|
+
text-underline-offset: 0.15em;
|
|
139
|
+
|
|
140
|
+
&:hover {
|
|
141
|
+
filter: none;
|
|
142
|
+
text-decoration-thickness: 2px;
|
|
143
|
+
}
|
|
46
144
|
}
|
|
47
145
|
|
|
48
146
|
&__dialog {
|
|
@@ -72,7 +170,7 @@
|
|
|
72
170
|
align-items: center;
|
|
73
171
|
justify-content: space-between;
|
|
74
172
|
gap: var(--mg-spacing-200);
|
|
75
|
-
padding: var(--mg-spacing-
|
|
173
|
+
padding: var(--mg-spacing-200) var(--mg-spacing-200) var(--mg-spacing-150);
|
|
76
174
|
}
|
|
77
175
|
|
|
78
176
|
&__title {
|
|
@@ -134,10 +232,32 @@
|
|
|
134
232
|
padding: 0 var(--mg-spacing-200) var(--mg-spacing-200);
|
|
135
233
|
}
|
|
136
234
|
|
|
137
|
-
|
|
235
|
+
// Doubled selector outranks `.mg-code`, which earlier markup put on the
|
|
236
|
+
// citation; a citation is prose to read, not code.
|
|
237
|
+
& &__text {
|
|
138
238
|
margin: 0;
|
|
239
|
+
padding: var(--mg-spacing-150) var(--mg-spacing-200);
|
|
240
|
+
background: rgb(var(--mg-color-neutral-25));
|
|
241
|
+
border: 1px solid rgb(var(--mg-color-neutral-300) / 0.4);
|
|
242
|
+
border-radius: var(--mg-radius-form-input);
|
|
243
|
+
box-shadow: none;
|
|
244
|
+
color: rgb(var(--mg-color-neutral-800));
|
|
245
|
+
font-family: var(--mg-font-family-text);
|
|
246
|
+
font-size: var(--mg-font-size-300);
|
|
247
|
+
font-weight: 400;
|
|
248
|
+
line-height: 1.6;
|
|
139
249
|
overflow-wrap: anywhere;
|
|
140
250
|
text-wrap: pretty;
|
|
251
|
+
|
|
252
|
+
a {
|
|
253
|
+
color: rgb(var(--mg-color-interactive));
|
|
254
|
+
text-decoration: underline;
|
|
255
|
+
text-underline-offset: 0.15em;
|
|
256
|
+
|
|
257
|
+
&:focus-visible {
|
|
258
|
+
@include mg-focus-ring;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
141
261
|
}
|
|
142
262
|
|
|
143
263
|
&__actions {
|
|
@@ -150,21 +270,31 @@
|
|
|
150
270
|
|
|
151
271
|
&__copy {
|
|
152
272
|
min-block-size: 44px;
|
|
153
|
-
padding: var(--mg-spacing-
|
|
154
|
-
background:
|
|
155
|
-
border:
|
|
273
|
+
padding: var(--mg-spacing-50) var(--mg-spacing-150);
|
|
274
|
+
background: transparent;
|
|
275
|
+
border: 2px solid rgb(var(--mg-color-interactive));
|
|
156
276
|
border-radius: var(--mg-radius-button);
|
|
157
|
-
color: rgb(var(--mg-color-
|
|
277
|
+
color: rgb(var(--mg-color-interactive));
|
|
158
278
|
cursor: pointer;
|
|
159
279
|
font-family: var(--mg-font-family-text);
|
|
160
|
-
font-size: var(--mg-font-size-
|
|
280
|
+
font-size: var(--mg-font-size-250);
|
|
161
281
|
font-weight: 600;
|
|
162
|
-
transition-property:
|
|
282
|
+
transition-property: background-color, color, scale;
|
|
163
283
|
transition-duration: var(--mg-motion-duration-fast);
|
|
164
284
|
transition-timing-function: var(--mg-motion-easing-standard);
|
|
165
285
|
|
|
166
286
|
&:hover {
|
|
167
|
-
|
|
287
|
+
background: rgb(var(--mg-color-interactive));
|
|
288
|
+
color: rgb(var(--mg-color-white));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
&:not(.mg-cite-this__copy-url) {
|
|
292
|
+
background: rgb(var(--mg-color-interactive));
|
|
293
|
+
color: rgb(var(--mg-color-white));
|
|
294
|
+
|
|
295
|
+
&:hover {
|
|
296
|
+
filter: brightness(0.92);
|
|
297
|
+
}
|
|
168
298
|
}
|
|
169
299
|
|
|
170
300
|
&:active {
|
|
@@ -180,6 +310,29 @@
|
|
|
180
310
|
}
|
|
181
311
|
}
|
|
182
312
|
|
|
313
|
+
&__exports {
|
|
314
|
+
display: flex;
|
|
315
|
+
flex-wrap: wrap;
|
|
316
|
+
gap: var(--mg-spacing-50) var(--mg-spacing-200);
|
|
317
|
+
margin-block-start: var(--mg-spacing-150);
|
|
318
|
+
padding-block-start: var(--mg-spacing-150);
|
|
319
|
+
border-block-start: 1px solid rgb(var(--mg-color-neutral-200));
|
|
320
|
+
font-size: var(--mg-font-size-250);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
&__export {
|
|
324
|
+
display: inline-flex;
|
|
325
|
+
align-items: center;
|
|
326
|
+
min-block-size: 44px;
|
|
327
|
+
color: rgb(var(--mg-color-interactive));
|
|
328
|
+
text-decoration: underline;
|
|
329
|
+
text-underline-offset: 0.15em;
|
|
330
|
+
|
|
331
|
+
&:focus-visible {
|
|
332
|
+
@include mg-focus-ring;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
183
336
|
&__status {
|
|
184
337
|
font-size: var(--mg-font-size-250);
|
|
185
338
|
}
|