@astronox/core 0.3.1 → 0.4.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 +2 -2
- package/package.json +1 -1
- package/src/ads/adsense/AdsenseHead.astro +55 -0
- package/src/ads/adsense/AdsenseUnit.astro +45 -0
- package/src/ads/adsense/index.ts +3 -1
- package/src/ads/adsense/provider.ts +41 -0
- package/src/ads/adsense/runtime.ts +284 -0
- package/src/ads/contracts.ts +67 -5
- package/src/components/content/CardHeader/CardHeader.astro +20 -0
- package/src/components/content/CardHeader/CardHeader.css +21 -0
- package/src/components/feedback/Tooltip/tooltipController.ts +37 -5
- package/src/components/monetization/AdSlot/AdSlot.astro +0 -3
- package/src/components/monetization/AdSlot/AdSlot.css +5 -28
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.astro +2 -0
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.css +12 -6
- package/src/components/navigation/Footer/Footer.astro +8 -0
- package/src/components/navigation/Footer/Footer.css +20 -6
- package/src/components/navigation/Footer/contracts.ts +1 -0
- package/src/composition/Grid/Grid.css +1 -1
- package/src/composition/SurfaceGroup/SurfaceGroup.astro +2 -1
- package/src/composition/SurfaceGroup/SurfaceGroup.css +18 -18
- package/src/composition/SurfaceGroup/stickySurfaceGroupController.ts +9 -0
- package/src/config/environment.ts +66 -2
- package/src/layouts/BaseLayout.astro +6 -1
- package/src/patterns/Combobox/comboboxController.ts +51 -12
- package/src/patterns/Dropdown/dropdownController.ts +17 -6
- package/src/patterns/Fullscreen/FullscreenControl.astro +3 -30
- package/src/patterns/Fullscreen/fullscreenController.ts +18 -22
- package/src/patterns/Table/Table.css +8 -2
- package/src/patterns/Table/TableFilterPanel.astro +84 -0
- package/src/patterns/Table/TableFilterPanel.css +139 -0
- package/src/patterns/Table/TableSkeleton.css +0 -3
- package/src/patterns/Table/tableFilterPanelController.ts +91 -0
- package/src/patterns/Table/tableProgressiveController.ts +78 -16
- package/src/patterns/Table/tableScrollPosition.ts +41 -0
- package/src/patterns/Table/tableSortController.ts +6 -3
- package/src/patterns/index.ts +2 -0
- package/src/styles/style.css +1 -0
- package/src/ads/adsense.ts +0 -112
|
@@ -72,6 +72,7 @@ export function initTooltip(root: HTMLElement): () => void {
|
|
|
72
72
|
':scope > [data-tooltip-panel]',
|
|
73
73
|
);
|
|
74
74
|
if (!trigger || !panel) return noop;
|
|
75
|
+
const activeTrigger = trigger;
|
|
75
76
|
|
|
76
77
|
const hoverOnly = root.dataset.tooltipActivation === 'hover';
|
|
77
78
|
root.setAttribute(initializedAttribute, 'true');
|
|
@@ -106,6 +107,7 @@ export function initTooltip(root: HTMLElement): () => void {
|
|
|
106
107
|
let hoveredPanel = false;
|
|
107
108
|
let openListenersActive = false;
|
|
108
109
|
let pinned = false;
|
|
110
|
+
let lastPointerType = '';
|
|
109
111
|
|
|
110
112
|
const isOpen = (): boolean => panel.matches(':popover-open');
|
|
111
113
|
|
|
@@ -239,12 +241,12 @@ export function initTooltip(root: HTMLElement): () => void {
|
|
|
239
241
|
};
|
|
240
242
|
|
|
241
243
|
const handleTriggerPointerEnter = (event: PointerEvent): void => {
|
|
242
|
-
if (
|
|
244
|
+
if (event.pointerType === 'touch') return;
|
|
243
245
|
hoveredTrigger = true;
|
|
244
246
|
show();
|
|
245
247
|
};
|
|
246
248
|
const handleTriggerPointerLeave = (event: PointerEvent): void => {
|
|
247
|
-
if (
|
|
249
|
+
if (event.pointerType === 'touch') return;
|
|
248
250
|
hoveredTrigger = false;
|
|
249
251
|
|
|
250
252
|
if (hoverOnly) {
|
|
@@ -263,6 +265,7 @@ export function initTooltip(root: HTMLElement): () => void {
|
|
|
263
265
|
scheduleClose();
|
|
264
266
|
};
|
|
265
267
|
const handleFocusIn = (): void => {
|
|
268
|
+
if (lastPointerType === 'touch') return;
|
|
266
269
|
if (
|
|
267
270
|
hoverOnly &&
|
|
268
271
|
!trigger.matches(':focus-visible') &&
|
|
@@ -274,7 +277,23 @@ export function initTooltip(root: HTMLElement): () => void {
|
|
|
274
277
|
show();
|
|
275
278
|
};
|
|
276
279
|
const handleFocusOut = (): void => scheduleClose();
|
|
277
|
-
const
|
|
280
|
+
const handleTriggerPointerDown = (event: PointerEvent): void => {
|
|
281
|
+
lastPointerType = event.pointerType;
|
|
282
|
+
};
|
|
283
|
+
const handleTriggerKeyDown = (): void => {
|
|
284
|
+
lastPointerType = '';
|
|
285
|
+
};
|
|
286
|
+
const handleTriggerClick = (event: MouseEvent): void => {
|
|
287
|
+
const pointerType =
|
|
288
|
+
'PointerEvent' in window && event instanceof PointerEvent
|
|
289
|
+
? event.pointerType
|
|
290
|
+
: lastPointerType;
|
|
291
|
+
if (hoverOnly && pointerType !== 'touch') return;
|
|
292
|
+
if (pointerType === 'touch') {
|
|
293
|
+
event.preventDefault();
|
|
294
|
+
event.stopPropagation();
|
|
295
|
+
}
|
|
296
|
+
|
|
278
297
|
if (pinned) {
|
|
279
298
|
close();
|
|
280
299
|
return;
|
|
@@ -299,6 +318,12 @@ export function initTooltip(root: HTMLElement): () => void {
|
|
|
299
318
|
if (!(event instanceof CustomEvent) || event.detail?.root === root) return;
|
|
300
319
|
close();
|
|
301
320
|
};
|
|
321
|
+
const visibilityObserver =
|
|
322
|
+
'IntersectionObserver' in window
|
|
323
|
+
? new IntersectionObserver((entries) => {
|
|
324
|
+
if (entries.some((entry) => !entry.isIntersecting)) close();
|
|
325
|
+
})
|
|
326
|
+
: null;
|
|
302
327
|
function addOpenListeners(): void {
|
|
303
328
|
if (openListenersActive) return;
|
|
304
329
|
openListenersActive = true;
|
|
@@ -309,6 +334,7 @@ export function initTooltip(root: HTMLElement): () => void {
|
|
|
309
334
|
window.addEventListener('scroll', requestPosition, true);
|
|
310
335
|
window.visualViewport?.addEventListener('resize', requestPosition);
|
|
311
336
|
window.visualViewport?.addEventListener('scroll', requestPosition);
|
|
337
|
+
visibilityObserver?.observe(activeTrigger);
|
|
312
338
|
}
|
|
313
339
|
function removeOpenListeners(): void {
|
|
314
340
|
if (!openListenersActive) return;
|
|
@@ -320,13 +346,16 @@ export function initTooltip(root: HTMLElement): () => void {
|
|
|
320
346
|
window.removeEventListener('scroll', requestPosition, true);
|
|
321
347
|
window.visualViewport?.removeEventListener('resize', requestPosition);
|
|
322
348
|
window.visualViewport?.removeEventListener('scroll', requestPosition);
|
|
349
|
+
visibilityObserver?.unobserve(activeTrigger);
|
|
323
350
|
}
|
|
324
351
|
|
|
352
|
+
trigger.addEventListener('pointerdown', handleTriggerPointerDown);
|
|
325
353
|
trigger.addEventListener('pointerenter', handleTriggerPointerEnter);
|
|
326
354
|
trigger.addEventListener('pointerleave', handleTriggerPointerLeave);
|
|
327
355
|
trigger.addEventListener('focusin', handleFocusIn);
|
|
328
356
|
trigger.addEventListener('focusout', handleFocusOut);
|
|
329
|
-
|
|
357
|
+
trigger.addEventListener('keydown', handleTriggerKeyDown);
|
|
358
|
+
trigger.addEventListener('click', handleTriggerClick);
|
|
330
359
|
if (!hoverOnly) {
|
|
331
360
|
panel.addEventListener('pointerenter', handlePanelPointerEnter);
|
|
332
361
|
panel.addEventListener('pointerleave', handlePanelPointerLeave);
|
|
@@ -338,11 +367,14 @@ export function initTooltip(root: HTMLElement): () => void {
|
|
|
338
367
|
if (positionFrame !== undefined) {
|
|
339
368
|
window.cancelAnimationFrame(positionFrame);
|
|
340
369
|
}
|
|
370
|
+
visibilityObserver?.disconnect();
|
|
371
|
+
trigger.removeEventListener('pointerdown', handleTriggerPointerDown);
|
|
341
372
|
trigger.removeEventListener('pointerenter', handleTriggerPointerEnter);
|
|
342
373
|
trigger.removeEventListener('pointerleave', handleTriggerPointerLeave);
|
|
343
374
|
trigger.removeEventListener('focusin', handleFocusIn);
|
|
344
375
|
trigger.removeEventListener('focusout', handleFocusOut);
|
|
345
|
-
|
|
376
|
+
trigger.removeEventListener('keydown', handleTriggerKeyDown);
|
|
377
|
+
trigger.removeEventListener('click', handleTriggerClick);
|
|
346
378
|
if (!hoverOnly) {
|
|
347
379
|
panel.removeEventListener('pointerenter', handlePanelPointerEnter);
|
|
348
380
|
panel.removeEventListener('pointerleave', handlePanelPointerLeave);
|
|
@@ -16,38 +16,15 @@
|
|
|
16
16
|
--ad-slot-min-block-size: 15rem;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
.ad-slot__content
|
|
20
|
-
.ad-slot__fallback {
|
|
21
|
-
grid-area: 1 / 1;
|
|
19
|
+
.ad-slot__content {
|
|
22
20
|
inline-size: 100%;
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
.ad-
|
|
26
|
-
|
|
27
|
-
block-size: 100%;
|
|
23
|
+
.ad-slot[data-ad-state='filled'] {
|
|
24
|
+
min-block-size: 0;
|
|
28
25
|
}
|
|
29
26
|
|
|
30
|
-
.ad-
|
|
31
|
-
|
|
32
|
-
min-block-size: var(--ad-slot-min-block-size);
|
|
33
|
-
background:
|
|
34
|
-
linear-gradient(
|
|
35
|
-
135deg,
|
|
36
|
-
transparent 0 46%,
|
|
37
|
-
color-mix(in srgb, var(--nox-theme-border-base) 55%, transparent) 46% 54%,
|
|
38
|
-
transparent 54% 100%
|
|
39
|
-
),
|
|
40
|
-
var(--nox-surface-muted);
|
|
41
|
-
border: 1px solid var(--nox-theme-border-base);
|
|
42
|
-
inline-size: 100%;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.ad-slot[data-ad-state='blocked'] .ad-slot__content,
|
|
46
|
-
.ad-slot[data-ad-state='unavailable'] .ad-slot__content {
|
|
27
|
+
.ad-slot[data-ad-state='blocked'],
|
|
28
|
+
.ad-slot[data-ad-state='unavailable'] {
|
|
47
29
|
display: none;
|
|
48
30
|
}
|
|
49
|
-
|
|
50
|
-
.ad-slot[data-ad-state='blocked'] .ad-slot__fallback,
|
|
51
|
-
.ad-slot[data-ad-state='unavailable'] .ad-slot__fallback {
|
|
52
|
-
display: block;
|
|
53
|
-
}
|
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
'data-tone': dataTone,
|
|
17
17
|
items,
|
|
18
18
|
label = defaultCoreMessages.navigation.breadcrumbs,
|
|
19
|
+
tabindex: tabIndex = 0,
|
|
19
20
|
tone,
|
|
20
21
|
...attrs
|
|
21
22
|
} = Astro.props;
|
|
@@ -28,6 +29,7 @@ const resolvedTone = tone ?? dataTone ?? 'info';
|
|
|
28
29
|
{...attrs}
|
|
29
30
|
class:list={['breadcrumbs', className]}
|
|
30
31
|
aria-label={label}
|
|
32
|
+
tabindex={tabIndex}
|
|
31
33
|
data-widget="breadcrumbs"
|
|
32
34
|
data-tone={resolvedTone}
|
|
33
35
|
>
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
.breadcrumbs {
|
|
2
2
|
min-width: 0;
|
|
3
|
-
overflow:
|
|
3
|
+
overflow-x: auto;
|
|
4
|
+
overflow-y: hidden;
|
|
5
|
+
overscroll-behavior-inline: contain;
|
|
6
|
+
scrollbar-width: thin;
|
|
7
|
+
touch-action: pan-x pan-y;
|
|
4
8
|
color: var(--nox-tone-color);
|
|
5
9
|
font-size: var(--nox-font-size-sm);
|
|
6
10
|
font-weight: var(--nox-font-weight-medium);
|
|
7
11
|
line-height: 1.35;
|
|
8
12
|
}
|
|
9
13
|
|
|
14
|
+
.breadcrumbs:focus-visible {
|
|
15
|
+
outline: var(--nox-focus-ring-width) solid var(--nox-tone-focus-ring);
|
|
16
|
+
outline-offset: calc(var(--nox-focus-ring-width) * -1);
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
.breadcrumbs__list {
|
|
11
20
|
display: flex;
|
|
12
21
|
flex-wrap: nowrap;
|
|
13
22
|
align-items: stretch;
|
|
14
|
-
min-width:
|
|
23
|
+
min-width: max-content;
|
|
15
24
|
min-block-size: calc(
|
|
16
25
|
1.35em + var(--site-main-content-gap, var(--nox-space-5)) +
|
|
17
26
|
var(--site-main-content-gap, var(--nox-space-5))
|
|
@@ -30,8 +39,7 @@
|
|
|
30
39
|
}
|
|
31
40
|
|
|
32
41
|
.breadcrumbs__item:last-child {
|
|
33
|
-
flex:
|
|
34
|
-
overflow: hidden;
|
|
42
|
+
flex: 0 0 auto;
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
.breadcrumbs__item > span {
|
|
@@ -63,8 +71,6 @@
|
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
.breadcrumbs__item:last-child > :is(a, span) {
|
|
66
|
-
overflow: hidden;
|
|
67
|
-
text-overflow: ellipsis;
|
|
68
74
|
white-space: nowrap;
|
|
69
75
|
}
|
|
70
76
|
|
|
@@ -46,6 +46,7 @@ const resolveRel = (target: FooterItem['target'], rel?: FooterItem['rel']) =>
|
|
|
46
46
|
{items.map(
|
|
47
47
|
(
|
|
48
48
|
{
|
|
49
|
+
button,
|
|
49
50
|
href,
|
|
50
51
|
label: itemLabel,
|
|
51
52
|
marker,
|
|
@@ -83,6 +84,13 @@ const resolveRel = (target: FooterItem['target'], rel?: FooterItem['rel']) =>
|
|
|
83
84
|
) : null}
|
|
84
85
|
<span>{itemLabel}</span>
|
|
85
86
|
</a>
|
|
87
|
+
) : button ? (
|
|
88
|
+
<button {...button} type="button" data-tone={itemTone}>
|
|
89
|
+
{marker === 'arrow' ? (
|
|
90
|
+
<ArrowRightIcon class="site-footer__marker" />
|
|
91
|
+
) : null}
|
|
92
|
+
<span>{itemLabel}</span>
|
|
93
|
+
</button>
|
|
86
94
|
) : (
|
|
87
95
|
<span class="site-footer__label" data-tone={itemTone}>
|
|
88
96
|
{marker === 'arrow' ? (
|
|
@@ -78,11 +78,12 @@
|
|
|
78
78
|
flex: 0 0 auto;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
.site-footer__prefix + :is(a, .site-footer__label) {
|
|
81
|
+
.site-footer__prefix + :is(a, button, .site-footer__label) {
|
|
82
82
|
margin-inline-start: var(--nox-space-1);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
.site-footer a,
|
|
86
|
+
.site-footer button,
|
|
86
87
|
.site-footer__label {
|
|
87
88
|
display: inline-flex;
|
|
88
89
|
min-width: 0;
|
|
@@ -98,35 +99,48 @@
|
|
|
98
99
|
color: var(--nox-text-muted);
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
.site-footer a {
|
|
102
|
+
.site-footer :is(a, button) {
|
|
102
103
|
color: var(--nox-content-link-color);
|
|
103
104
|
font-weight: var(--nox-content-link-emphasis-font-weight);
|
|
104
105
|
text-decoration: none;
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
.site-footer button {
|
|
109
|
+
padding: 0;
|
|
110
|
+
border: 0;
|
|
111
|
+
background: transparent;
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
font: inherit;
|
|
114
|
+
}
|
|
115
|
+
|
|
107
116
|
.site-footer a[data-tone],
|
|
117
|
+
.site-footer button[data-tone],
|
|
108
118
|
.site-footer__label[data-tone] {
|
|
109
119
|
color: var(--nox-tone-color);
|
|
110
120
|
}
|
|
111
121
|
|
|
112
|
-
.site-footer a:focus-visible {
|
|
122
|
+
.site-footer :is(a, button):focus-visible {
|
|
113
123
|
border-radius: var(--nox-radius-sm);
|
|
114
124
|
outline: var(--nox-focus-ring-width) solid var(--nox-tone-focus-ring);
|
|
115
125
|
outline-offset: var(--nox-focus-ring-offset);
|
|
116
126
|
color: var(--nox-content-link-color);
|
|
117
127
|
}
|
|
118
128
|
|
|
119
|
-
.site-footer a[data-tone]:focus-visible {
|
|
129
|
+
.site-footer :is(a, button)[data-tone]:focus-visible {
|
|
120
130
|
color: var(--nox-tone-color);
|
|
121
131
|
}
|
|
122
132
|
|
|
133
|
+
.site-footer button[hidden] {
|
|
134
|
+
display: none;
|
|
135
|
+
}
|
|
136
|
+
|
|
123
137
|
@media (hover: hover) and (pointer: fine) {
|
|
124
|
-
.site-footer a:hover {
|
|
138
|
+
.site-footer :is(a, button):hover {
|
|
125
139
|
color: var(--nox-content-link-color);
|
|
126
140
|
text-decoration-line: underline;
|
|
127
141
|
}
|
|
128
142
|
|
|
129
|
-
.site-footer a[data-tone]:hover {
|
|
143
|
+
.site-footer :is(a, button)[data-tone]:hover {
|
|
130
144
|
color: var(--nox-tone-color);
|
|
131
145
|
}
|
|
132
146
|
}
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types';
|
|
3
3
|
import type { Tone } from '../../theme/contracts';
|
|
4
4
|
|
|
5
|
-
type SurfaceGroupElement = 'div' | 'section' | 'nav';
|
|
5
|
+
type SurfaceGroupElement = 'article' | 'div' | 'section' | 'nav';
|
|
6
6
|
type SurfaceGroupAttributes = HTMLAttributes<'div'> &
|
|
7
|
+
HTMLAttributes<'article'> &
|
|
7
8
|
HTMLAttributes<'section'> &
|
|
8
9
|
HTMLAttributes<'nav'>;
|
|
9
10
|
|
|
@@ -17,24 +17,32 @@
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
.surface-group:has(> [data-sticky-surface])::before {
|
|
20
|
-
--surface-group-sticky-occlusion-size: calc(
|
|
21
|
-
var(--nox-layout-gap) + var(--nox-section-responsive-block-size)
|
|
22
|
-
);
|
|
23
|
-
|
|
24
20
|
position: sticky;
|
|
25
21
|
z-index: 39;
|
|
26
22
|
top: 0;
|
|
27
23
|
display: block;
|
|
28
24
|
min-width: 0;
|
|
29
|
-
min-height: var(
|
|
25
|
+
min-height: var(
|
|
26
|
+
--surface-group-sticky-occlusion-size,
|
|
27
|
+
calc(var(--nox-layout-gap) + var(--nox-section-responsive-block-size))
|
|
28
|
+
);
|
|
30
29
|
visibility: hidden;
|
|
31
|
-
flex: 0 0
|
|
32
|
-
|
|
30
|
+
flex: 0 0
|
|
31
|
+
var(
|
|
32
|
+
--surface-group-sticky-occlusion-size,
|
|
33
|
+
calc(var(--nox-layout-gap) + var(--nox-section-responsive-block-size))
|
|
34
|
+
);
|
|
35
|
+
margin-block-end: calc(
|
|
36
|
+
-1 *
|
|
37
|
+
var(
|
|
38
|
+
--surface-group-sticky-occlusion-size,
|
|
39
|
+
calc(var(--nox-layout-gap) + var(--nox-section-responsive-block-size))
|
|
40
|
+
)
|
|
41
|
+
);
|
|
33
42
|
background: var(--nox-page-background);
|
|
34
43
|
content: '';
|
|
35
44
|
}
|
|
36
45
|
|
|
37
|
-
.surface-group:has(> [data-sticky-surface]):not([data-sticky-measured])::before,
|
|
38
46
|
.surface-group[data-sticky-active]::before {
|
|
39
47
|
visibility: visible;
|
|
40
48
|
}
|
|
@@ -61,8 +69,8 @@
|
|
|
61
69
|
|
|
62
70
|
.surface-group
|
|
63
71
|
> [data-surface]:not([hidden]):has(~ [data-surface]:not([hidden])) {
|
|
64
|
-
border-end-start-radius: 0;
|
|
65
|
-
border-end-end-radius: 0;
|
|
72
|
+
border-end-start-radius: var(--surface-group-connected-border-end-radius, 0);
|
|
73
|
+
border-end-end-radius: var(--surface-group-connected-border-end-radius, 0);
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
.surface-group > [data-surface]:not([hidden]) ~ [data-surface]:not([hidden]) {
|
|
@@ -70,11 +78,3 @@
|
|
|
70
78
|
border-start-start-radius: 0;
|
|
71
79
|
border-start-end-radius: 0;
|
|
72
80
|
}
|
|
73
|
-
|
|
74
|
-
@container nox-surface-group (width < 32rem) {
|
|
75
|
-
.surface-group:has(> [data-sticky-surface])::before {
|
|
76
|
-
--surface-group-sticky-occlusion-size: calc(
|
|
77
|
-
var(--nox-layout-gap) + var(--nox-section-compact-block-size)
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
@@ -46,6 +46,10 @@ export const initStickySurfaceGroup = (root: HTMLElement): (() => void) => {
|
|
|
46
46
|
|
|
47
47
|
const stickyOffset =
|
|
48
48
|
Number.parseFloat(window.getComputedStyle(stickySurface).top) || 0;
|
|
49
|
+
root.style.setProperty(
|
|
50
|
+
'--surface-group-sticky-occlusion-size',
|
|
51
|
+
`${stickyOffset + stickySurface.getBoundingClientRect().height}px`,
|
|
52
|
+
);
|
|
49
53
|
observer = new IntersectionObserver(
|
|
50
54
|
([entry]) => {
|
|
51
55
|
if (!entry) return;
|
|
@@ -74,10 +78,14 @@ export const initStickySurfaceGroup = (root: HTMLElement): (() => void) => {
|
|
|
74
78
|
|
|
75
79
|
root.setAttribute('data-sticky-initialized', 'true');
|
|
76
80
|
window.addEventListener('resize', scheduleObserve, { passive: true });
|
|
81
|
+
const resizeObserver =
|
|
82
|
+
'ResizeObserver' in window ? new ResizeObserver(scheduleObserve) : null;
|
|
83
|
+
resizeObserver?.observe(stickySurface);
|
|
77
84
|
observe();
|
|
78
85
|
|
|
79
86
|
return () => {
|
|
80
87
|
window.removeEventListener('resize', scheduleObserve);
|
|
88
|
+
resizeObserver?.disconnect();
|
|
81
89
|
observer?.disconnect();
|
|
82
90
|
sentinel.remove();
|
|
83
91
|
|
|
@@ -88,5 +96,6 @@ export const initStickySurfaceGroup = (root: HTMLElement): (() => void) => {
|
|
|
88
96
|
root.removeAttribute('data-sticky-active');
|
|
89
97
|
root.removeAttribute('data-sticky-initialized');
|
|
90
98
|
root.removeAttribute('data-sticky-measured');
|
|
99
|
+
root.style.removeProperty('--surface-group-sticky-occlusion-size');
|
|
91
100
|
};
|
|
92
101
|
};
|
|
@@ -14,14 +14,64 @@ export function readOptionalEnvironmentValue(
|
|
|
14
14
|
return resolved || undefined;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export function readRequiredEnvironmentBoolean(
|
|
18
|
+
name: string,
|
|
19
|
+
value: string | undefined,
|
|
20
|
+
): boolean {
|
|
21
|
+
const resolved = readRequiredEnvironmentValue(name, value);
|
|
22
|
+
if (resolved === 'true') return true;
|
|
23
|
+
if (resolved === 'false') return false;
|
|
24
|
+
throw new Error(`Invalid environment boolean: ${name}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function readEnvironmentStringArray(
|
|
28
|
+
name: string,
|
|
29
|
+
value: string | undefined,
|
|
30
|
+
): readonly string[] {
|
|
31
|
+
const resolved = readRequiredEnvironmentValue(name, value);
|
|
32
|
+
let entries: unknown[];
|
|
33
|
+
|
|
34
|
+
if (resolved.startsWith('[')) {
|
|
35
|
+
try {
|
|
36
|
+
const parsed: unknown = JSON.parse(resolved);
|
|
37
|
+
if (!Array.isArray(parsed)) throw new Error();
|
|
38
|
+
entries = parsed;
|
|
39
|
+
} catch {
|
|
40
|
+
throw new Error(`Invalid environment string array: ${name}`);
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
entries = [resolved];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (
|
|
47
|
+
entries.length === 0 ||
|
|
48
|
+
entries.some((entry) => typeof entry !== 'string' || !entry.trim())
|
|
49
|
+
) {
|
|
50
|
+
throw new Error(`Invalid environment string array: ${name}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return [...new Set(entries.map((entry) => (entry as string).trim()))];
|
|
54
|
+
}
|
|
55
|
+
|
|
17
56
|
export function readRequiredEnvironmentUrl(
|
|
18
57
|
name: string,
|
|
19
58
|
value: string | undefined,
|
|
20
59
|
): string {
|
|
21
60
|
const resolved = readRequiredEnvironmentValue(name, value);
|
|
61
|
+
return parseEnvironmentUrl(name, resolved);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function readOptionalEnvironmentUrl(
|
|
65
|
+
name: string,
|
|
66
|
+
value: string | undefined,
|
|
67
|
+
): string | undefined {
|
|
68
|
+
const resolved = readOptionalEnvironmentValue(value);
|
|
69
|
+
return resolved ? parseEnvironmentUrl(name, resolved) : undefined;
|
|
70
|
+
}
|
|
22
71
|
|
|
72
|
+
function parseEnvironmentUrl(name: string, value: string): string {
|
|
23
73
|
try {
|
|
24
|
-
const url = new URL(
|
|
74
|
+
const url = new URL(value);
|
|
25
75
|
if (url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
26
76
|
throw new Error();
|
|
27
77
|
return url.toString();
|
|
@@ -35,9 +85,23 @@ export function readEnvironmentRecord(
|
|
|
35
85
|
value: string | undefined,
|
|
36
86
|
): Readonly<Record<string, string>> {
|
|
37
87
|
const resolved = readRequiredEnvironmentValue(name, value);
|
|
88
|
+
return parseEnvironmentRecord(name, resolved);
|
|
89
|
+
}
|
|
38
90
|
|
|
91
|
+
export function readOptionalEnvironmentRecord(
|
|
92
|
+
name: string,
|
|
93
|
+
value: string | undefined,
|
|
94
|
+
): Readonly<Record<string, string>> | undefined {
|
|
95
|
+
const resolved = readOptionalEnvironmentValue(value);
|
|
96
|
+
return resolved ? parseEnvironmentRecord(name, resolved) : undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function parseEnvironmentRecord(
|
|
100
|
+
name: string,
|
|
101
|
+
value: string,
|
|
102
|
+
): Readonly<Record<string, string>> {
|
|
39
103
|
try {
|
|
40
|
-
const parsed: unknown = JSON.parse(
|
|
104
|
+
const parsed: unknown = JSON.parse(value);
|
|
41
105
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
42
106
|
throw new Error();
|
|
43
107
|
}
|
|
@@ -6,6 +6,7 @@ import type { PageMeta } from '../config/pageMeta';
|
|
|
6
6
|
import type { SiteConfig } from '../config/siteConfig';
|
|
7
7
|
import type { JsonLdNode } from '../seo/structuredData';
|
|
8
8
|
import type { Tone } from '../theme/contracts';
|
|
9
|
+
import { adAnnotationExclusionAttributes } from '../ads/contracts';
|
|
9
10
|
import PageSurface from './PageSurface.astro';
|
|
10
11
|
import Breadcrumbs from '../components/navigation/Breadcrumbs/Breadcrumbs.astro';
|
|
11
12
|
import ScrollToTop from '../components/actions/ScrollToTop/ScrollToTop.astro';
|
|
@@ -15,6 +16,7 @@ import { defaultThemeStorageKey } from '../theme/storage';
|
|
|
15
16
|
type Props = HTMLAttributes<'html'> & {
|
|
16
17
|
meta: PageMeta;
|
|
17
18
|
site: SiteConfig;
|
|
19
|
+
pageSurfaceAttributes?: HTMLAttributes<'div'>;
|
|
18
20
|
currentPath?: string;
|
|
19
21
|
breadcrumbs?: readonly BreadcrumbItem[];
|
|
20
22
|
breadcrumbLabel: string;
|
|
@@ -32,6 +34,7 @@ const {
|
|
|
32
34
|
currentPath = Astro.url.pathname,
|
|
33
35
|
'data-tone': dataTone,
|
|
34
36
|
meta,
|
|
37
|
+
pageSurfaceAttributes,
|
|
35
38
|
scrollToTopLabel,
|
|
36
39
|
site,
|
|
37
40
|
structuredData,
|
|
@@ -67,6 +70,7 @@ const resolvedTone = tone ?? dataTone;
|
|
|
67
70
|
{
|
|
68
71
|
breadcrumbs?.length ? (
|
|
69
72
|
<Breadcrumbs
|
|
73
|
+
{...adAnnotationExclusionAttributes}
|
|
70
74
|
items={breadcrumbs}
|
|
71
75
|
label={breadcrumbLabel}
|
|
72
76
|
tone={breadcrumbTone}
|
|
@@ -74,8 +78,9 @@ const resolvedTone = tone ?? dataTone;
|
|
|
74
78
|
) : null
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
<PageSurface>
|
|
81
|
+
<PageSurface {...pageSurfaceAttributes}>
|
|
78
82
|
<slot />
|
|
83
|
+
<slot name="page-end" />
|
|
79
84
|
</PageSurface>
|
|
80
85
|
</div>
|
|
81
86
|
</main>
|