@astronox/core 0.2.4 → 0.3.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.
Files changed (114) hide show
  1. package/README.md +16 -1
  2. package/package.json +18 -4
  3. package/src/admin/authController.ts +166 -0
  4. package/src/admin/cache.ts +44 -17
  5. package/src/admin/index.ts +3 -0
  6. package/src/admin/preparedDestination.ts +114 -0
  7. package/src/admin/resourceClient.ts +107 -0
  8. package/src/ads/adsense/index.ts +1 -0
  9. package/src/ads/adsense.ts +112 -0
  10. package/src/ads/contracts.ts +11 -0
  11. package/src/ads/index.ts +2 -0
  12. package/src/ads/slot.ts +16 -0
  13. package/src/auth/contracts.ts +1 -4
  14. package/src/auth/firebase/client.ts +19 -6
  15. package/src/auth/firebase/index.ts +1 -0
  16. package/src/client/formSubmission.ts +6 -1
  17. package/src/client/preferences.ts +8 -4
  18. package/src/components/actions/Button/Button.astro +30 -42
  19. package/src/components/actions/CardAction/CardAction.astro +7 -0
  20. package/src/components/actions/CardAction/CardAction.css +31 -10
  21. package/src/components/actions/IdentityProviderAction/IdentityProviderAction.astro +6 -8
  22. package/src/components/actions/MediaTrigger/MediaTrigger.astro +1 -1
  23. package/src/components/actions/ScrollToTop/ScrollToTop.astro +2 -1
  24. package/src/components/actions/ScrollToTop/ScrollToTop.css +2 -6
  25. package/src/components/actions/ThemeToggle/ThemeToggle.astro +3 -3
  26. package/src/components/actions/ThemeToggle/themeToggleController.ts +7 -4
  27. package/src/components/content/CardFooter/CardFooter.css +2 -4
  28. package/src/components/content/CardHeader/CardHeader.astro +1 -4
  29. package/src/components/content/CardLink/CardLink.astro +3 -15
  30. package/src/components/content/CardLink/CardLink.css +4 -15
  31. package/src/components/content/CodeBlock/CodeBlock.astro +7 -6
  32. package/src/components/content/CodeBlock/CodeBlock.css +2 -5
  33. package/src/components/content/CodeBlock/codeBlockController.ts +1 -2
  34. package/src/components/content/MarkdownContent/MarkdownContent.astro +1 -3
  35. package/src/components/content/MarkdownContent/MarkdownContent.css +7 -2
  36. package/src/components/content/MarkdownContent/MarkdownInline.astro +11 -3
  37. package/src/components/content/MarkdownContent/MarkdownNode.astro +28 -33
  38. package/src/components/content/MarkdownContent/MarkdownSymbol.astro +30 -19
  39. package/src/components/content/MarkdownContent/contracts.ts +15 -15
  40. package/src/components/content/MarkdownContent/markdownContentRenderer.ts +27 -83
  41. package/src/components/feedback/PreferenceBootstrap/PreferenceBootstrap.astro +19 -22
  42. package/src/components/feedback/Spinner/Spinner.astro +1 -2
  43. package/src/components/feedback/Tooltip/tooltipController.ts +1 -4
  44. package/src/components/forms/ChoiceGroup/ChoiceGroup.css +1 -4
  45. package/src/components/forms/DateTimeInput/DateTimeInput.astro +2 -1
  46. package/src/components/forms/PreparedFormBootstrap/PreparedFormBootstrap.astro +10 -8
  47. package/src/components/forms/PreparedFormBootstrap/parsePreparedFormState.ts +48 -0
  48. package/src/components/forms/Textarea/Textarea.astro +2 -2
  49. package/src/components/icons/BurnIcon.astro +2 -1
  50. package/src/components/icons/FieldIcon.astro +10 -4
  51. package/src/components/icons/GrassIcon.astro +3 -2
  52. package/src/components/icons/SlowIcon.astro +1 -2
  53. package/src/components/icons/StunIcon.astro +2 -1
  54. package/src/components/icons/WaterIcon.astro +3 -2
  55. package/src/components/index.ts +2 -0
  56. package/src/components/media/CanvasFrame/CanvasFrame.astro +1 -2
  57. package/src/components/media/CanvasFrame/canvasFrameController.ts +1 -4
  58. package/src/components/media/EmbedFrame/EmbedFrame.astro +5 -12
  59. package/src/components/media/EmbedFrame/embedFrameController.ts +4 -4
  60. package/src/components/monetization/AdSlot/AdSlot.astro +33 -0
  61. package/src/components/monetization/AdSlot/AdSlot.css +53 -0
  62. package/src/components/navigation/DesktopNavigation.css +1 -2
  63. package/src/components/navigation/Footer/Footer.css +3 -9
  64. package/src/components/navigation/MobileNavigation.astro +10 -2
  65. package/src/components/navigation/MobileNavigation.css +8 -22
  66. package/src/components/navigation/navigationTypes.ts +1 -4
  67. package/src/components/seo/SeoHead.astro +21 -6
  68. package/src/composition/SurfaceGroup/SurfaceGroup.css +4 -14
  69. package/src/config/environment.ts +52 -0
  70. package/src/config/index.ts +3 -0
  71. package/src/config/integrations.ts +9 -0
  72. package/src/config/siteConfig.ts +7 -0
  73. package/src/config/storage.ts +16 -0
  74. package/src/content/admin.ts +354 -0
  75. package/src/content/cache.ts +177 -0
  76. package/src/content/catalog.ts +72 -0
  77. package/src/content/contracts.ts +77 -0
  78. package/src/content/drafts.ts +49 -0
  79. package/src/content/index.ts +7 -0
  80. package/src/content/markdown.ts +146 -0
  81. package/src/content/snapshot.ts +139 -0
  82. package/src/data/firestore/client/contracts.ts +10 -2
  83. package/src/data/index.ts +1 -0
  84. package/src/data/memoizedLoader.ts +13 -0
  85. package/src/display/contracts.ts +1 -5
  86. package/src/i18n/config.ts +2 -8
  87. package/src/i18n/coreMessages.ts +2 -9
  88. package/src/i18n/preference.ts +2 -6
  89. package/src/patterns/Combobox/Combobox.astro +9 -12
  90. package/src/patterns/Combobox/comboboxController.ts +14 -8
  91. package/src/patterns/Fullscreen/FullscreenControl.astro +5 -1
  92. package/src/patterns/Fullscreen/fullscreenController.ts +1 -3
  93. package/src/patterns/MapViewport/geometry.ts +1 -4
  94. package/src/patterns/Modal/Modal.css +1 -4
  95. package/src/patterns/Modal/ModalOpen.astro +5 -1
  96. package/src/patterns/Table/Table.astro +10 -10
  97. package/src/patterns/Table/TableBody.astro +1 -4
  98. package/src/patterns/Table/TableBody.css +2 -4
  99. package/src/patterns/Table/TablePagination.astro +23 -30
  100. package/src/patterns/Table/TableSkeleton.astro +1 -5
  101. package/src/patterns/Table/tableClientController.ts +2 -2
  102. package/src/patterns/Table/tableFilterController.ts +1 -3
  103. package/src/patterns/Table/tableHeaderOverflowController.ts +1 -4
  104. package/src/patterns/Table/tableProgressiveController.ts +62 -60
  105. package/src/patterns/Table/tableScrollController.ts +3 -2
  106. package/src/primitives/text/TextBlock/TextBlock.astro +1 -3
  107. package/src/publication/contracts.ts +11 -0
  108. package/src/publication/http.ts +31 -0
  109. package/src/publication/index.ts +2 -0
  110. package/src/routing/localized.ts +9 -12
  111. package/src/seo/localizedMeta.ts +2 -6
  112. package/src/styles/interaction.css +14 -7
  113. package/src/styles/style.css +11 -20
  114. package/src/theme/contracts.ts +1 -6
@@ -100,9 +100,7 @@ const payload = definitions.map((definition) => ({
100
100
  if (from in next && !(to in next)) next[to] = next[from];
101
101
  delete next[from];
102
102
  }
103
- for (const [key, fallback] of Object.entries(
104
- migration.defaults ?? {},
105
- )) {
103
+ for (const [key, fallback] of Object.entries(migration.defaults ?? {})) {
106
104
  if (!(key in next)) next[key] = fallback;
107
105
  }
108
106
  return next;
@@ -117,11 +115,7 @@ const payload = definitions.map((definition) => ({
117
115
  for (const migration of migrations) {
118
116
  const previous = parse(
119
117
  read(
120
- storageKey(
121
- definition,
122
- migration.fromVersion,
123
- migration.fromKey,
124
- ),
118
+ storageKey(definition, migration.fromVersion, migration.fromKey),
125
119
  ),
126
120
  );
127
121
  const migrated = migrate(previous, migration);
@@ -138,9 +132,7 @@ const payload = definitions.map((definition) => ({
138
132
 
139
133
  const previousSnapshot = window[globalKey];
140
134
  const snapshot = {
141
- values: isRecord(previousSnapshot?.values)
142
- ? previousSnapshot.values
143
- : {},
135
+ values: isRecord(previousSnapshot?.values) ? previousSnapshot.values : {},
144
136
  rawValues: isRecord(previousSnapshot?.rawValues)
145
137
  ? previousSnapshot.rawValues
146
138
  : {},
@@ -190,7 +182,9 @@ const payload = definitions.map((definition) => ({
190
182
  const options = [...root.querySelectorAll('[data-combobox-option]')];
191
183
  const available = options
192
184
  .map((option) => Number(option.dataset.comboboxOption))
193
- .filter((value) => Number.isFinite(value) && value >= min && value <= max)
185
+ .filter(
186
+ (value) => Number.isFinite(value) && value >= min && value <= max,
187
+ )
194
188
  .sort((left, right) => left - right);
195
189
  const bounded = Math.min(max, Math.max(min, Number(requestedValue)));
196
190
  const value =
@@ -202,10 +196,7 @@ const payload = definitions.map((definition) => ({
202
196
  )
203
197
  : Math.min(
204
198
  max,
205
- Math.max(
206
- min,
207
- min + Math.round((bounded - min) / step) * step,
208
- ),
199
+ Math.max(min, min + Math.round((bounded - min) / step) * step),
209
200
  );
210
201
 
211
202
  root.dataset.comboboxNumberMin = String(min);
@@ -228,8 +219,10 @@ const payload = definitions.map((definition) => ({
228
219
  const increment = root.querySelector(
229
220
  '[data-combobox-number-step="increment"]',
230
221
  );
231
- if (decrement instanceof HTMLButtonElement) decrement.disabled = value <= min;
232
- if (increment instanceof HTMLButtonElement) increment.disabled = value >= max;
222
+ if (decrement instanceof HTMLButtonElement)
223
+ decrement.disabled = value <= min;
224
+ if (increment instanceof HTMLButtonElement)
225
+ increment.disabled = value >= max;
233
226
  };
234
227
  const applyOptions = (root, requestedValue) => {
235
228
  const requested = Array.isArray(requestedValue)
@@ -291,7 +284,8 @@ const payload = definitions.map((definition) => ({
291
284
  if (root instanceof HTMLElement && root.matches('[data-preference-id]')) {
292
285
  applyBinding(root);
293
286
  }
294
- for (const element of root.querySelectorAll?.('[data-preference-id]') ?? []) {
287
+ for (const element of root.querySelectorAll?.('[data-preference-id]') ??
288
+ []) {
295
289
  applyBinding(element);
296
290
  }
297
291
  };
@@ -350,8 +344,8 @@ const payload = definitions.map((definition) => ({
350
344
  detail?.mode === 'number'
351
345
  ? detail.value
352
346
  : root.dataset.comboboxSelectionMode === 'multiple'
353
- ? detail?.values ?? []
354
- : detail?.values?.[0] ?? '',
347
+ ? (detail?.values ?? [])
348
+ : (detail?.values?.[0] ?? ''),
355
349
  );
356
350
  };
357
351
  const handlePreferenceChange = (event) => {
@@ -385,7 +379,10 @@ const payload = definitions.map((definition) => ({
385
379
  for (const node of record.addedNodes) applyBindings(node);
386
380
  }
387
381
  });
388
- observer.observe(document.documentElement, { childList: true, subtree: true });
382
+ observer.observe(document.documentElement, {
383
+ childList: true,
384
+ subtree: true,
385
+ });
389
386
  document.addEventListener('change', handleChange, true);
390
387
  document.addEventListener('combobox:change', handleComboboxChange);
391
388
  document.addEventListener(changeEvent, handlePreferenceChange);
@@ -28,5 +28,4 @@ const resolvedTone = tone ?? dataTone;
28
28
  data-tone={resolvedTone}
29
29
  data-widget="spinner"
30
30
  role="status"
31
- aria-label={label}
32
- ></span>
31
+ aria-label={label}></span>
@@ -135,10 +135,7 @@ export function initTooltip(root: HTMLElement): () => void {
135
135
  viewport.right - viewportGap,
136
136
  boundaryRect?.right ?? viewport.right - viewportGap,
137
137
  );
138
- const maximumInlineSize = Math.max(
139
- 0,
140
- horizontalRight - horizontalLeft,
141
- );
138
+ const maximumInlineSize = Math.max(0, horizontalRight - horizontalLeft);
142
139
  const maximumBlockSize = Math.max(
143
140
  0,
144
141
  viewport.bottom - viewport.top - viewportGap * 2,
@@ -19,10 +19,7 @@
19
19
  .choice-group__options {
20
20
  display: grid;
21
21
  min-inline-size: 0;
22
- grid-template-columns: repeat(
23
- var(--choice-group-columns),
24
- minmax(0, 1fr)
25
- );
22
+ grid-template-columns: repeat(var(--choice-group-columns), minmax(0, 1fr));
26
23
  gap: var(--nox-space-2);
27
24
  }
28
25
 
@@ -3,7 +3,8 @@ import type { HTMLAttributes } from 'astro/types';
3
3
  import type { Tone } from '../../../theme/contracts';
4
4
  import TextInput from '../TextInput/TextInput.astro';
5
5
 
6
- type Props = Omit<HTMLAttributes<'input'>, 'type'> & {
6
+ type Props = Omit<HTMLAttributes<'input'>, 'autofocus' | 'type'> & {
7
+ autofocus?: boolean | 'desktop';
7
8
  invalid?: boolean;
8
9
  tone?: Tone;
9
10
  };
@@ -34,8 +34,9 @@ const { formId, stateKey }: Props = Astro.props;
34
34
  return item ? [item] : [];
35
35
  };
36
36
  const getComboboxRoot = (form, key) =>
37
- [...form.querySelectorAll('[data-prepared-form-combobox]')]
38
- .find((candidate) => candidate.dataset.preparedFormCombobox === key);
37
+ [...form.querySelectorAll('[data-prepared-form-combobox]')].find(
38
+ (candidate) => candidate.dataset.preparedFormCombobox === key,
39
+ );
39
40
  const isComboboxReady = (root) =>
40
41
  root instanceof HTMLElement &&
41
42
  root.querySelector('[data-combobox-options]') instanceof HTMLElement &&
@@ -110,7 +111,10 @@ const { formId, stateKey }: Props = Astro.props;
110
111
  button.type = 'button';
111
112
  button.role = 'option';
112
113
  button.tabIndex = -1;
113
- button.setAttribute('aria-selected', String(selected.has(option.value)));
114
+ button.setAttribute(
115
+ 'aria-selected',
116
+ String(selected.has(option.value)),
117
+ );
114
118
  button.dataset.comboboxOption = option.value;
115
119
  button.dataset.comboboxSearchText = option.label;
116
120
  button.dataset.interactionAccent = 'start';
@@ -194,10 +198,8 @@ const { formId, stateKey }: Props = Astro.props;
194
198
  childList: true,
195
199
  subtree: true,
196
200
  });
197
- document.addEventListener(
198
- 'DOMContentLoaded',
199
- () => observer.disconnect(),
200
- { once: true },
201
- );
201
+ document.addEventListener('DOMContentLoaded', () => observer.disconnect(), {
202
+ once: true,
203
+ });
202
204
  })();
203
205
  </script>
@@ -0,0 +1,48 @@
1
+ import type { PreparedFormState } from './contracts';
2
+
3
+ export function parsePreparedFormState(
4
+ value: unknown,
5
+ ): PreparedFormState | null {
6
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
7
+ const state = value as PreparedFormState;
8
+
9
+ for (const record of [state.values, state.choices]) {
10
+ if (
11
+ record !== undefined &&
12
+ (!record ||
13
+ typeof record !== 'object' ||
14
+ Array.isArray(record) ||
15
+ Object.values(record).some((item) => typeof item !== 'string'))
16
+ ) {
17
+ return null;
18
+ }
19
+ }
20
+
21
+ if (state.comboboxes === undefined) return state;
22
+ if (
23
+ !state.comboboxes ||
24
+ typeof state.comboboxes !== 'object' ||
25
+ Array.isArray(state.comboboxes)
26
+ ) {
27
+ return null;
28
+ }
29
+ for (const combobox of Object.values(state.comboboxes)) {
30
+ if (
31
+ !combobox ||
32
+ typeof combobox !== 'object' ||
33
+ !Array.isArray(combobox.options) ||
34
+ !Array.isArray(combobox.values) ||
35
+ combobox.values.some((item) => typeof item !== 'string') ||
36
+ combobox.options.some(
37
+ (option) =>
38
+ !option ||
39
+ typeof option !== 'object' ||
40
+ typeof option.label !== 'string' ||
41
+ typeof option.value !== 'string',
42
+ )
43
+ ) {
44
+ return null;
45
+ }
46
+ }
47
+ return state;
48
+ }
@@ -31,5 +31,5 @@ const resolvedTone = tone ?? dataTone;
31
31
  data-invalid={resolvedInvalid ? true : undefined}
32
32
  data-size={size}
33
33
  data-tone={resolvedTone}
34
- aria-invalid={resolvedInvalid ? 'true' : ariaInvalid}
35
- >{value}</textarea>
34
+ aria-invalid={resolvedInvalid ? 'true' : ariaInvalid}>{value}</textarea
35
+ >
@@ -20,5 +20,6 @@ const {
20
20
  <circle class="circle-symbol-icon__surface" cx="16" cy="16" r="13"></circle>
21
21
  <path
22
22
  class="circle-symbol-icon__fill"
23
- d="M17 7c1 5-3 6-3 10 0 1.5.8 2.7 2 3.4-.3-2.4 1.3-3.4 2.2-5.4 2.3 2 3.8 4.1 3.8 6.4 0 3.2-2.7 5.6-6 5.6s-6-2.4-6-5.8C10 15.7 15.7 13 17 7Z"></path>
23
+ d="M17 7c1 5-3 6-3 10 0 1.5.8 2.7 2 3.4-.3-2.4 1.3-3.4 2.2-5.4 2.3 2 3.8 4.1 3.8 6.4 0 3.2-2.7 5.6-6 5.6s-6-2.4-6-5.8C10 15.7 15.7 13 17 7Z"
24
+ ></path>
24
25
  </svg>
@@ -21,8 +21,14 @@ const {
21
21
  focusable={focusable}
22
22
  >
23
23
  <circle class="circle-symbol-icon__surface" cx="16" cy="16" r="13"></circle>
24
- <rect class="circle-symbol-icon__mark" x="9" y="9" width="14" height="14" rx="1"
25
- ></rect>
26
- <path class="circle-symbol-icon__mark" d="M13.7 9v14m4.6-14v14M9 13.7h14M9 18.3h14"
27
- ></path>
24
+ <rect
25
+ class="circle-symbol-icon__mark"
26
+ x="9"
27
+ y="9"
28
+ width="14"
29
+ height="14"
30
+ rx="1"></rect>
31
+ <path
32
+ class="circle-symbol-icon__mark"
33
+ d="M13.7 9v14m4.6-14v14M9 13.7h14M9 18.3h14"></path>
28
34
  </svg>
@@ -24,6 +24,7 @@ const {
24
24
  <path
25
25
  class="circle-symbol-icon__fill"
26
26
  d="M23.5 8.5c.2 8.4-3.7 14-11.7 15.2-3.1-6.8.2-13.5 11.7-15.2Z"></path>
27
- <path class="circle-symbol-icon__highlight" d="M10.4 23.5c2.2-5.3 5.4-8.7 10.2-11.6"
28
- ></path>
27
+ <path
28
+ class="circle-symbol-icon__highlight"
29
+ d="M10.4 23.5c2.2-5.3 5.4-8.7 10.2-11.6"></path>
29
30
  </svg>
@@ -20,6 +20,5 @@ const {
20
20
  <circle class="circle-symbol-icon__surface" cx="16" cy="16" r="13"></circle>
21
21
  <path
22
22
  class="circle-symbol-icon__mark"
23
- d="M16 8v16M9 12l14 8M9 20l14-8M13 10l3 3 3-3M13 22l3-3 3 3"
24
- ></path>
23
+ d="M16 8v16M9 12l14 8M9 20l14-8M13 10l3 3 3-3M13 22l3-3 3 3"></path>
25
24
  </svg>
@@ -18,5 +18,6 @@ const {
18
18
  focusable={focusable}
19
19
  >
20
20
  <circle class="circle-symbol-icon__surface" cx="16" cy="16" r="13"></circle>
21
- <path class="circle-symbol-icon__fill" d="m18 7-8 11h5l-1 7 8-11h-5l1-7Z"></path>
21
+ <path class="circle-symbol-icon__fill" d="m18 7-8 11h5l-1 7 8-11h-5l1-7Z"
22
+ ></path>
22
23
  </svg>
@@ -25,6 +25,7 @@ const {
25
25
  class="circle-symbol-icon__fill"
26
26
  d="M16 5.5c3.1 4.2 7.5 9 7.5 13.3A7.5 7.5 0 1 1 8.5 18.8C8.5 14.5 12.9 9.7 16 5.5Z"
27
27
  ></path>
28
- <path class="circle-symbol-icon__highlight" d="M12.2 19.2a4.5 4.5 0 0 0 4.5 4.1"
29
- ></path>
28
+ <path
29
+ class="circle-symbol-icon__highlight"
30
+ d="M12.2 19.2a4.5 4.5 0 0 0 4.5 4.1"></path>
30
31
  </svg>
@@ -30,6 +30,7 @@ export type {
30
30
  PreparedFormOption,
31
31
  PreparedFormState,
32
32
  } from './forms/PreparedFormBootstrap/contracts';
33
+ export { parsePreparedFormState } from './forms/PreparedFormBootstrap/parsePreparedFormState';
33
34
  export { default as TextInput } from './forms/TextInput/TextInput.astro';
34
35
  export { default as Textarea } from './forms/Textarea/Textarea.astro';
35
36
  export { default as Spinner } from './feedback/Spinner/Spinner.astro';
@@ -39,6 +40,7 @@ export { default as Tooltip } from './feedback/Tooltip/Tooltip.astro';
39
40
  export { default as CanvasFrame } from './media/CanvasFrame/CanvasFrame.astro';
40
41
  export { default as EmbedFrame } from './media/EmbedFrame/EmbedFrame.astro';
41
42
  export { default as MediaFrame } from './media/MediaFrame/MediaFrame.astro';
43
+ export { default as AdSlot } from './monetization/AdSlot/AdSlot.astro';
42
44
  export { default as Breadcrumbs } from './navigation/Breadcrumbs/Breadcrumbs.astro';
43
45
  export { default as DesktopNavigation } from './navigation/DesktopNavigation.astro';
44
46
  export { default as Header } from './navigation/Header.astro';
@@ -87,8 +87,7 @@ const resolvedCanvasId = canvasId ?? `${frameId}-canvas`;
87
87
  class="canvas-frame__feedback"
88
88
  data-canvas-frame-feedback
89
89
  role="alert"
90
- hidden
91
- ></span>
90
+ hidden></span>
92
91
  </nox-canvas-frame>
93
92
 
94
93
  <script>
@@ -53,10 +53,7 @@ export function markCanvasFrameReady(root: HTMLElement): void {
53
53
  root.dataset.canvasFrameReady = 'true';
54
54
  }
55
55
 
56
- export function markCanvasFrameError(
57
- root: HTMLElement,
58
- message: string,
59
- ): void {
56
+ export function markCanvasFrameError(root: HTMLElement, message: string): void {
60
57
  const loading = root.querySelector<HTMLElement>(
61
58
  '[data-canvas-frame-loading]',
62
59
  );
@@ -63,11 +63,9 @@ const resolvedTone = tone ?? dataTone;
63
63
  data-fullscreen-region={fullscreen ? true : undefined}
64
64
  data-widget="embed-frame"
65
65
  data-tone={resolvedTone}
66
- aria-busy={
67
- activation === 'immediate' && loadingIndicator === 'spinner'
68
- ? 'true'
69
- : 'false'
70
- }
66
+ aria-busy={activation === 'immediate' && loadingIndicator === 'spinner'
67
+ ? 'true'
68
+ : 'false'}
71
69
  >
72
70
  <iframe
73
71
  {...attrs}
@@ -78,16 +76,11 @@ const resolvedTone = tone ?? dataTone;
78
76
  width={width}
79
77
  height={height}
80
78
  loading={loading}
81
- allowfullscreen
82
- ></iframe>
79
+ allowfullscreen></iframe>
83
80
  {
84
81
  activation === 'manual' && activationLabel ? (
85
82
  <span class="embed-frame__activation" data-embed-frame-activation-layer>
86
- <Button
87
- type="button"
88
- prominent
89
- data-embed-frame-activate
90
- >
83
+ <Button type="button" prominent data-embed-frame-activate>
91
84
  {activationLabel}
92
85
  </Button>
93
86
  </span>
@@ -1,6 +1,8 @@
1
1
  const activateEvent = 'nox:embed-frame:activate';
2
2
 
3
- export function activateEmbedFrame(root: HTMLElement): HTMLIFrameElement | null {
3
+ export function activateEmbedFrame(
4
+ root: HTMLElement,
5
+ ): HTMLIFrameElement | null {
4
6
  const iframe = root.querySelector<HTMLIFrameElement>(
5
7
  '[data-embed-frame-content]',
6
8
  );
@@ -14,9 +16,7 @@ export const initEmbedFrame = (root: HTMLElement): (() => void) => {
14
16
  const iframe = root.querySelector<HTMLIFrameElement>(
15
17
  '[data-embed-frame-content]',
16
18
  );
17
- const loading = root.querySelector<HTMLElement>(
18
- '[data-embed-frame-loading]',
19
- );
19
+ const loading = root.querySelector<HTMLElement>('[data-embed-frame-loading]');
20
20
  const activationLayer = root.querySelector<HTMLElement>(
21
21
  '[data-embed-frame-activation-layer]',
22
22
  );
@@ -0,0 +1,33 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+ import type { AdSlotState } from '../../../ads/contracts';
4
+
5
+ type Props = Omit<HTMLAttributes<'aside'>, 'aria-label'> & {
6
+ label: string;
7
+ size?: 'compact' | 'standard' | 'tall';
8
+ state?: AdSlotState;
9
+ };
10
+
11
+ const {
12
+ class: className,
13
+ label,
14
+ size = 'standard',
15
+ state = 'idle',
16
+ ...attrs
17
+ }: Props = Astro.props;
18
+ ---
19
+
20
+ <aside
21
+ {...attrs}
22
+ class:list={['ad-slot', className]}
23
+ data-ad-size={size}
24
+ data-ad-state={state}
25
+ data-widget="ad-slot"
26
+ aria-label={label}
27
+ aria-busy={state === 'loading' ? 'true' : 'false'}
28
+ >
29
+ <div class="ad-slot__content"><slot /></div>
30
+ <div class="ad-slot__fallback">
31
+ <slot name="fallback"><div class="ad-slot__fallback-canvas"></div></slot>
32
+ </div>
33
+ </aside>
@@ -0,0 +1,53 @@
1
+ .ad-slot {
2
+ --ad-slot-min-block-size: 6rem;
3
+
4
+ display: grid;
5
+ inline-size: 100%;
6
+ min-block-size: var(--ad-slot-min-block-size);
7
+ overflow: hidden;
8
+ place-items: center;
9
+ }
10
+
11
+ .ad-slot[data-ad-size='compact'] {
12
+ --ad-slot-min-block-size: 4rem;
13
+ }
14
+
15
+ .ad-slot[data-ad-size='tall'] {
16
+ --ad-slot-min-block-size: 15rem;
17
+ }
18
+
19
+ .ad-slot__content,
20
+ .ad-slot__fallback {
21
+ grid-area: 1 / 1;
22
+ inline-size: 100%;
23
+ }
24
+
25
+ .ad-slot__fallback {
26
+ display: none;
27
+ block-size: 100%;
28
+ }
29
+
30
+ .ad-slot__fallback-canvas {
31
+ block-size: 100%;
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 {
47
+ display: none;
48
+ }
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
+ }
@@ -87,8 +87,7 @@
87
87
  min-width: 4.75rem;
88
88
  }
89
89
 
90
- .site-nav__action
91
- > .dropdown[data-dropdown-trigger-appearance='bar'] {
90
+ .site-nav__action > .dropdown[data-dropdown-trigger-appearance='bar'] {
92
91
  min-width: 0;
93
92
  block-size: 100%;
94
93
  border-radius: 0;
@@ -33,15 +33,11 @@
33
33
  column-gap: var(--nox-space-4);
34
34
  }
35
35
 
36
- .site-footer__content[data-footer-columns='2'][
37
- data-footer-column-separator='divider'
38
- ] {
36
+ .site-footer__content[data-footer-columns='2'][data-footer-column-separator='divider'] {
39
37
  column-gap: 0;
40
38
  }
41
39
 
42
- .site-footer__content[data-footer-columns='2'][
43
- data-footer-column-separator='divider'
44
- ]
40
+ .site-footer__content[data-footer-columns='2'][data-footer-column-separator='divider']
45
41
  > .site-footer__group:nth-child(even)::before {
46
42
  display: inline-grid;
47
43
  width: calc(
@@ -146,9 +142,7 @@
146
142
  grid-column: auto;
147
143
  }
148
144
 
149
- .site-footer__content[data-footer-columns='2'][
150
- data-footer-column-separator='divider'
151
- ]
145
+ .site-footer__content[data-footer-columns='2'][data-footer-column-separator='divider']
152
146
  > .site-footer__group:nth-child(even)::before {
153
147
  content: none;
154
148
  }
@@ -51,6 +51,8 @@ const fallbackItems = [...leftItems, ...rightItems].flatMap((item) =>
51
51
  const resolvedMobileItems = mobileItems ?? fallbackItems;
52
52
  const resolvedHasUtilityActions =
53
53
  hasUtilityActions ?? Astro.slots.has('utility-actions');
54
+ const utilityControlCount =
55
+ mobileUtilityItems.length + Number(resolvedHasUtilityActions);
54
56
  ---
55
57
 
56
58
  <div class="site-nav__mobile" data-mobile-navigation>
@@ -124,7 +126,10 @@ const resolvedHasUtilityActions =
124
126
  mobileFooterItems.length > 0 ? (
125
127
  <div class="site-nav__mobile-controls">
126
128
  {mobileUtilityItems.length > 0 || resolvedHasUtilityActions ? (
127
- <ul class="site-nav__mobile-utility">
129
+ <ul
130
+ class="site-nav__mobile-utility"
131
+ style={`--mobile-navigation-control-count: ${utilityControlCount};`}
132
+ >
128
133
  {mobileUtilityItems.map((item) => (
129
134
  <li>
130
135
  <NavigationLink
@@ -147,7 +152,10 @@ const resolvedHasUtilityActions =
147
152
  ) : null}
148
153
 
149
154
  {mobileFooterItems.length > 0 ? (
150
- <ul class="site-nav__mobile-footer">
155
+ <ul
156
+ class="site-nav__mobile-footer"
157
+ style={`--mobile-navigation-control-count: ${mobileFooterItems.length};`}
158
+ >
151
159
  {mobileFooterItems.map((item) => (
152
160
  <li>
153
161
  <NavigationLink
@@ -94,8 +94,7 @@
94
94
  border-radius: var(--nox-radius-md);
95
95
  }
96
96
 
97
- .site-nav__mobile-action
98
- > .dropdown[data-dropdown-trigger-appearance='bar'] {
97
+ .site-nav__mobile-action > .dropdown[data-dropdown-trigger-appearance='bar'] {
99
98
  min-width: 0;
100
99
  block-size: var(--nox-site-header-compact-height);
101
100
  border-radius: 0;
@@ -175,19 +174,14 @@
175
174
  .site-nav__mobile-utility,
176
175
  .site-nav__mobile-footer {
177
176
  display: grid;
177
+ grid-template-columns: repeat(
178
+ var(--mobile-navigation-control-count, 1),
179
+ minmax(0, 1fr)
180
+ );
178
181
  margin: 0;
179
182
  padding: 0;
180
- list-style: none;
181
- }
182
-
183
- .site-nav__mobile-utility {
184
- grid-template-columns: repeat(2, minmax(0, 1fr));
185
- gap: var(--nox-space-3);
186
- }
187
-
188
- .site-nav__mobile-footer {
189
- grid-template-columns: repeat(3, minmax(0, 1fr));
190
183
  gap: var(--nox-space-3);
184
+ list-style: none;
191
185
  }
192
186
 
193
187
  .site-nav__mobile-utility-link,
@@ -239,16 +233,8 @@ html[data-mobile-navigation-open] body {
239
233
  }
240
234
 
241
235
  .site-nav__mobile-footer-link[data-mobile-navigation-accent]:hover {
242
- border-color: color-mix(
243
- in srgb,
244
- var(--mobile-navigation-accent) 88%,
245
- #000
246
- );
247
- background: color-mix(
248
- in srgb,
249
- var(--mobile-navigation-accent) 88%,
250
- #000
251
- );
236
+ border-color: color-mix(in srgb, var(--mobile-navigation-accent) 88%, #000);
237
+ background: color-mix(in srgb, var(--mobile-navigation-accent) 88%, #000);
252
238
  color: #fff;
253
239
  }
254
240
  }
@@ -1,8 +1,5 @@
1
1
  import type { AstroComponent } from '../widgetTypes';
2
- import type {
3
- DisplayMedia,
4
- NavigationLinkItem,
5
- } from '../../display/contracts';
2
+ import type { DisplayMedia, NavigationLinkItem } from '../../display/contracts';
6
3
 
7
4
  export type MobileNavigationLabels = {
8
5
  closeLabel?: string;