@abreen/tada 1.16.9 → 1.16.11

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.
@@ -18,8 +18,8 @@ export const CUSTOM_FONT_FAMILY_DEFINITIONS = Object.freeze({
18
18
  }),
19
19
  });
20
20
 
21
- export type CustomFontFamilyKey = keyof typeof CUSTOM_FONT_FAMILY_DEFINITIONS;
22
- export type CustomFontFaceKey = keyof typeof CUSTOM_FONT_FACE_DEFINITIONS;
21
+ type CustomFontFamilyKey = keyof typeof CUSTOM_FONT_FAMILY_DEFINITIONS;
22
+ type CustomFontFaceKey = keyof typeof CUSTOM_FONT_FACE_DEFINITIONS;
23
23
 
24
24
  const FACE_KEYS = Object.freeze(
25
25
  Object.keys(CUSTOM_FONT_FACE_DEFINITIONS) as CustomFontFaceKey[],
@@ -32,7 +32,7 @@ interface ParsedFont {
32
32
  availableFeatures: readonly string[];
33
33
  }
34
34
 
35
- export interface CustomFontValidationOptions {
35
+ interface CustomFontValidationOptions {
36
36
  fontOverrides: FontOverrides | undefined;
37
37
  publicDir: string;
38
38
  publicFiles: ReadonlySet<string>;
@@ -18,7 +18,7 @@ export const DEFAULT_FONT_PRELOAD_FILES = {
18
18
  ],
19
19
  } as const;
20
20
 
21
- export interface DefaultFontPreloadFile {
21
+ interface DefaultFontPreloadFile {
22
22
  filePath: string;
23
23
  source: 'package' | 'public';
24
24
  }
@@ -68,16 +68,6 @@ export const MATERIAL_SYMBOLS = Object.freeze({
68
68
  symbol: 'smart_display',
69
69
  opticalSize: 24,
70
70
  }),
71
- headerMenu: Object.freeze({
72
- cssVariable: '--icon-header-menu',
73
- symbol: 'menu',
74
- opticalSize: 24,
75
- }),
76
- headerClose: Object.freeze({
77
- cssVariable: '--icon-header-close',
78
- symbol: 'close',
79
- opticalSize: 24,
80
- }),
81
71
  } as const);
82
72
 
83
73
  type MaterialSymbol = (typeof MATERIAL_SYMBOLS)[keyof typeof MATERIAL_SYMBOLS];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abreen/tada",
3
- "version": "1.16.9",
3
+ "version": "1.16.11",
4
4
  "type": "module",
5
5
  "description": "A static site generator",
6
6
  "license": "MIT",
@@ -1,5 +1,5 @@
1
- export type FontPreference = 'sans' | 'serif';
2
- export type ContrastPreference = 'standard' | 'high';
1
+ type FontPreference = 'sans' | 'serif';
2
+ type ContrastPreference = 'standard' | 'high';
3
3
 
4
4
  const FONT_STORAGE_KEY = 'fontPreference';
5
5
  const CONTRAST_STORAGE_KEY = 'contrastPreference';
@@ -25,9 +25,7 @@ function getFontPreferenceLoader(
25
25
  window: Window,
26
26
  ): FontPreferenceLoader | undefined {
27
27
  return (
28
- window as Window & {
29
- __tadaFontPreferenceLoader?: FontPreferenceLoader;
30
- }
28
+ window as Window & { __tadaFontPreferenceLoader?: FontPreferenceLoader }
31
29
  ).__tadaFontPreferenceLoader;
32
30
  }
33
31
 
@@ -171,21 +169,19 @@ export function applyContrastPreference(
171
169
  }
172
170
  }
173
171
 
174
- function syncControls(
172
+ function syncSwitch(
175
173
  document: Document,
176
174
  selector: string,
177
- preference: string,
175
+ checked: boolean,
176
+ title: string,
178
177
  ): void {
179
- document.querySelectorAll<HTMLButtonElement>(selector).forEach(button => {
180
- button.disabled = false;
181
- button.setAttribute(
182
- 'aria-pressed',
183
- String(
184
- button.dataset.fontPreferenceValue === preference ||
185
- button.dataset.contrastPreferenceValue === preference,
186
- ),
187
- );
188
- });
178
+ const control = document.querySelector<HTMLButtonElement>(selector);
179
+ if (!control) {
180
+ return;
181
+ }
182
+ control.disabled = false;
183
+ control.setAttribute('aria-checked', String(checked));
184
+ control.title = title;
189
185
  }
190
186
 
191
187
  function syncAppearance(
@@ -193,11 +189,19 @@ function syncAppearance(
193
189
  fontPreference: FontPreference,
194
190
  contrastPreference: ContrastPreference,
195
191
  ): void {
196
- syncControls(document, '[data-font-preference-value]', fontPreference);
197
- syncControls(
192
+ syncSwitch(
198
193
  document,
199
- '[data-contrast-preference-value]',
200
- contrastPreference,
194
+ '[data-font-preference-switch]',
195
+ fontPreference === 'serif',
196
+ fontPreference === 'serif' ? 'Use sans-serif fonts' : 'Use serif fonts',
197
+ );
198
+ syncSwitch(
199
+ document,
200
+ '[data-contrast-preference-switch]',
201
+ contrastPreference === 'high',
202
+ contrastPreference === 'high'
203
+ ? 'Use standard contrast'
204
+ : 'Use high contrast',
201
205
  );
202
206
  }
203
207
 
@@ -238,7 +242,9 @@ export default function mountAppearancePicker(window: Window): () => void {
238
242
 
239
243
  const observedRequests = new Set<number>();
240
244
  const observeFontRequest = (request: FontPreferenceRequest) => {
241
- if (observedRequests.has(request.generation)) return;
245
+ if (observedRequests.has(request.generation)) {
246
+ return;
247
+ }
242
248
  observedRequests.add(request.generation);
243
249
  request.promise.then(
244
250
  () => {
@@ -266,26 +272,26 @@ export default function mountAppearancePicker(window: Window): () => void {
266
272
 
267
273
  const handleClick = (event: Event) => {
268
274
  const button = event.currentTarget as HTMLButtonElement;
269
- const fontValue = button.dataset.fontPreferenceValue;
270
- const contrastValue = button.dataset.contrastPreferenceValue;
271
275
 
272
- if (fontValue === 'sans' || fontValue === 'serif') {
276
+ if (button.dataset.fontPreferenceSwitch !== undefined) {
273
277
  fontPreference = getAppliedFontPreference(document);
274
- if (fontValue === fontPreference) {
278
+ if (fontLoader?.pending) {
275
279
  fontLoader?.cancel();
276
280
  saveFontPreference(storage, fontPreference, defaultFontPreference);
277
- } else {
278
- const request = fontLoader?.request(fontValue) ?? null;
279
- if (request) {
280
- observeFontRequest(request);
281
- return;
282
- }
283
- fontPreference = fontValue;
284
- saveFontPreference(storage, fontPreference, defaultFontPreference);
285
- applyFontPreference(document, fontPreference);
281
+ syncAppearance(document, fontPreference, contrastPreference);
282
+ return;
283
+ }
284
+ const nextFontPreference = fontPreference === 'serif' ? 'sans' : 'serif';
285
+ const request = fontLoader?.request(nextFontPreference) ?? null;
286
+ if (request) {
287
+ observeFontRequest(request);
288
+ return;
286
289
  }
287
- } else if (contrastValue === 'standard' || contrastValue === 'high') {
288
- contrastPreference = contrastValue;
290
+ fontPreference = nextFontPreference;
291
+ saveFontPreference(storage, fontPreference, defaultFontPreference);
292
+ applyFontPreference(document, fontPreference);
293
+ } else if (button.dataset.contrastPreferenceSwitch !== undefined) {
294
+ contrastPreference = contrastPreference === 'high' ? 'standard' : 'high';
289
295
  saveContrastPreference(
290
296
  storage,
291
297
  contrastPreference,
@@ -21,6 +21,7 @@
21
21
  width: fit-content;
22
22
  padding: var(--preference-picker-padding);
23
23
  background: var(--bg2-color);
24
+ border: 0;
24
25
  border-radius: calc(var(--border-radius) + 4px);
25
26
  }
26
27
 
@@ -46,15 +47,15 @@
46
47
  transform: translateX(calc(100% + var(--preference-picker-gap)));
47
48
  }
48
49
 
49
- .preference-picker:has(
50
- .preference-picker-option[aria-pressed='true']:is(:hover, :focus)
51
- )::before {
50
+ .preference-picker:is(:hover, :focus)::before {
52
51
  background: var(--bg2-color);
53
52
  }
54
53
 
55
- .preference-picker:has(
56
- .preference-picker-option[aria-pressed='true']:active:not(:disabled)
57
- )::before {
54
+ .preference-picker:active:not(:disabled) {
55
+ background: var(--bg2-color) !important;
56
+ }
57
+
58
+ .preference-picker:active:not(:disabled)::before {
58
59
  background: var(--link-active-color);
59
60
  }
60
61
 
@@ -65,9 +66,8 @@
65
66
  align-items: center;
66
67
  justify-content: center;
67
68
  width: var(--preference-picker-option-width);
68
- height: 2rem;
69
+ height: 1.8rem;
69
70
  padding: 0;
70
- font-size: var(--font-size-smaller);
71
71
  color: var(--fg2-color);
72
72
  background: transparent;
73
73
  border: 0;
@@ -100,17 +100,22 @@
100
100
  font-size-adjust: var(--font-size-adjust);
101
101
  }
102
102
 
103
- .preference-picker-option[aria-pressed='true'] {
103
+ :root:not([data-font-preference='serif']) .font-picker-option-sans,
104
+ :root[data-font-preference='serif'] .font-picker-option-serif,
105
+ :root:not([data-contrast-preference='high']) .contrast-picker-option-standard,
106
+ :root[data-contrast-preference='high'] .contrast-picker-option-high {
104
107
  color: var(--fg-color);
105
- background: transparent;
106
108
  }
107
109
 
108
- .preference-picker-option[aria-pressed='true']:is(:hover, :focus) {
109
- background: transparent;
110
- }
111
-
112
- .preference-picker-option[aria-pressed='true']:active:not(:disabled) {
113
- background: transparent !important;
110
+ .preference-picker:active:not(:disabled) {
111
+ :root:not([data-font-preference='serif']) & .font-picker-option-sans,
112
+ :root[data-font-preference='serif'] & .font-picker-option-serif,
113
+ :root:not([data-contrast-preference='high'])
114
+ &
115
+ .contrast-picker-option-standard,
116
+ :root[data-contrast-preference='high'] & .contrast-picker-option-high {
117
+ color: var(--bg-color);
118
+ }
114
119
  }
115
120
 
116
121
  @media print {
@@ -1,8 +1,10 @@
1
1
  # The `header` component
2
2
 
3
- Use the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API)
4
- to animate the `<details>` element in the header.
3
+ CSS morphs the header's three-stroke inline SVG between menu and close states
4
+ and, where supported, animates its `<details>` content. The component's
5
+ JavaScript adds dismissal behavior for outside clicks, focus leaving the menu,
6
+ and the Escape key.
5
7
 
6
8
  This component doesn't provide any other functionality related to the header.
7
9
  If JavaScript is turned off in the web browser, the `<details>` element is still
8
- functional but with no animation.
10
+ functional and its disclosure content opens immediately.
@@ -2,6 +2,7 @@
2
2
 
3
3
  :root {
4
4
  --header-collapsed-height: 45px;
5
+ --header-menu-icon-size: 24px;
5
6
  }
6
7
 
7
8
  header {
@@ -15,6 +16,7 @@ header {
15
16
  grid-template-columns: 1fr;
16
17
  width: 100%;
17
18
  background: var(--bg-color-translucent);
19
+ border-bottom: 1px solid var(--bg2-color);
18
20
  backdrop-filter: var(--backdrop-filter);
19
21
  view-transition-name: site-header;
20
22
 
@@ -35,6 +37,25 @@ header {
35
37
  background: var(--bg-color);
36
38
  }
37
39
 
40
+ body > .container::before {
41
+ position: fixed;
42
+ inset: 0;
43
+ z-index: 999;
44
+ pointer-events: none;
45
+ content: '';
46
+ background: var(--bg-color-translucent);
47
+ opacity: 0;
48
+
49
+ @media (prefers-reduced-motion: no-preference) {
50
+ transition: opacity 150ms ease-out;
51
+ }
52
+ }
53
+
54
+ body:has(header details[open]) > .container::before {
55
+ pointer-events: auto;
56
+ opacity: 1;
57
+ }
58
+
38
59
  header > details {
39
60
  min-width: 0;
40
61
  }
@@ -75,21 +96,77 @@ header details summary::marker {
75
96
  content: '';
76
97
  }
77
98
 
78
- header summary::after {
79
- flex: 0 0 var(--icon-header-menu-size);
99
+ header summary .menu-icon {
100
+ flex: 0 0 var(--header-menu-icon-size);
80
101
  order: -1;
81
- width: var(--icon-header-menu-size);
82
- height: var(--icon-header-menu-size);
102
+ width: var(--header-menu-icon-size);
103
+ height: var(--header-menu-icon-size);
83
104
  margin-right: 8px;
84
105
  margin-left: var(--gap);
85
- content: '';
106
+ overflow: visible;
107
+ pointer-events: none;
108
+ }
109
+
110
+ header summary .menu-icon-line {
111
+ transform-origin: center;
112
+ transform-box: fill-box;
113
+ }
114
+
115
+ @media (prefers-reduced-motion: no-preference) {
116
+ header summary .menu-icon-line {
117
+ transition: transform 200ms cubic-bezier(0.2, 0, 0, 1);
118
+ }
119
+
120
+ header summary .menu-icon-line-middle {
121
+ transition:
122
+ opacity 150ms ease-out,
123
+ transform 200ms cubic-bezier(0.2, 0, 0, 1);
124
+ }
125
+ }
126
+
127
+ header details[open] summary .menu-icon-line-top {
128
+ transform: translateY(6px) rotate(45deg);
129
+ }
130
+
131
+ header details[open] summary .menu-icon-line-middle {
132
+ opacity: 0;
133
+ transform: scaleX(0.8);
134
+ }
86
135
 
87
- @include contained-mask(var(--icon-header-menu));
136
+ header details[open] summary .menu-icon-line-bottom {
137
+ transform: translateY(-6px) rotate(-45deg);
88
138
  }
89
139
 
90
- header details[open] summary::after {
91
- -webkit-mask-image: var(--icon-header-close);
92
- mask-image: var(--icon-header-close);
140
+ @supports selector(details::details-content) and
141
+ (transition-behavior: allow-discrete) {
142
+ @media (prefers-reduced-motion: no-preference) {
143
+ :root.js header > details {
144
+ display: grid;
145
+ grid-template-rows: var(--header-collapsed-height) 0fr;
146
+ grid-template-columns: minmax(0, 1fr);
147
+ transition: grid-template-rows 200ms cubic-bezier(0.2, 0, 0, 1);
148
+ }
149
+
150
+ :root.js header > details[open] {
151
+ grid-template-rows: var(--header-collapsed-height) 1fr;
152
+ }
153
+
154
+ :root.js header > details::details-content {
155
+ min-block-size: 0;
156
+ overflow: clip;
157
+ opacity: 0;
158
+ transform: translateY(-4px);
159
+ transition:
160
+ content-visibility 200ms allow-discrete,
161
+ opacity 150ms ease-out,
162
+ transform 200ms cubic-bezier(0.2, 0, 0, 1);
163
+ }
164
+
165
+ :root.js header > details[open]::details-content {
166
+ opacity: 1;
167
+ transform: none;
168
+ }
169
+ }
93
170
  }
94
171
 
95
172
  header .content {
@@ -14,6 +14,11 @@ header {
14
14
  .results-container.is-showing .results {
15
15
  pointer-events: all;
16
16
  opacity: 1;
17
+ transform: none;
18
+ }
19
+
20
+ &.loading .results {
21
+ transition: none;
17
22
  }
18
23
 
19
24
  @media (hover: hover) {
@@ -37,6 +42,14 @@ header {
37
42
  border-radius: var(--border-radius);
38
43
  box-shadow: var(--box-shadow);
39
44
  opacity: 0;
45
+ transform: translateY(-4px) scale(0.98);
46
+ transform-origin: top center;
47
+
48
+ @media (prefers-reduced-motion: no-preference) {
49
+ transition:
50
+ opacity 150ms ease-out,
51
+ transform 200ms cubic-bezier(0.2, 0, 0, 1);
52
+ }
40
53
 
41
54
  @include user-select(none);
42
55
 
@@ -1,20 +1,16 @@
1
1
  <% if (site.features.pickers) { %>
2
2
  <div class="appearance-pickers" data-pagefind-ignore>
3
- <div class="preference-picker font-picker" role="group" aria-label="Font style">
4
- <button type="button" class="preference-picker-option font-picker-option-sans" data-font-preference-value="sans" aria-label="Use sans-serif fonts" aria-pressed="<%= site.defaultFont === 'sans' %>" disabled>
5
- <span aria-hidden="true">Aa</span>
6
- </button>
7
- <button type="button" class="preference-picker-option font-picker-option-serif" data-font-preference-value="serif" aria-label="Use serif fonts" aria-pressed="<%= site.defaultFont === 'serif' %>" disabled>
8
- <span aria-hidden="true">Aa</span>
9
- </button>
10
- </div>
11
- <div class="preference-picker contrast-picker" role="group" aria-label="Contrast">
12
- <button type="button" class="preference-picker-option contrast-picker-option-standard" data-contrast-preference-value="standard" aria-label="Use standard contrast" aria-pressed="<%= site.defaultContrast === 'standard' %>" disabled>
13
- <span class="material-symbol-icon material-symbol-icon-contrast-standard" aria-hidden="true"></span>
14
- </button>
15
- <button type="button" class="preference-picker-option contrast-picker-option-high" data-contrast-preference-value="high" aria-label="Use high contrast" aria-pressed="<%= site.defaultContrast === 'high' %>" disabled>
16
- <span class="material-symbol-icon material-symbol-icon-contrast-high" aria-hidden="true"></span>
17
- </button>
18
- </div>
3
+ <button type="button" class="preference-picker font-picker" role="switch" data-font-preference-switch aria-label="Use serif fonts" aria-checked="<%= site.defaultFont === 'serif' %>" title="<%= site.defaultFont === 'serif' ? 'Use sans-serif fonts' : 'Use serif fonts' %>" disabled>
4
+ <span class="preference-picker-option font-picker-option-sans" data-font-preference-value="sans" aria-hidden="true">Aa</span>
5
+ <span class="preference-picker-option font-picker-option-serif" data-font-preference-value="serif" aria-hidden="true">Aa</span>
6
+ </button>
7
+ <button type="button" class="preference-picker contrast-picker" role="switch" data-contrast-preference-switch aria-label="Use high contrast" aria-checked="<%= site.defaultContrast === 'high' %>" title="<%= site.defaultContrast === 'high' ? 'Use standard contrast' : 'Use high contrast' %>" disabled>
8
+ <span class="preference-picker-option contrast-picker-option-standard" data-contrast-preference-value="standard" aria-hidden="true">
9
+ <span class="material-symbol-icon material-symbol-icon-contrast-standard"></span>
10
+ </span>
11
+ <span class="preference-picker-option contrast-picker-option-high" data-contrast-preference-value="high" aria-hidden="true">
12
+ <span class="material-symbol-icon material-symbol-icon-contrast-high"></span>
13
+ </span>
14
+ </button>
19
15
  </div>
20
16
  <% } %>
@@ -208,6 +208,12 @@
208
208
  </div>
209
209
  <details>
210
210
  <summary aria-label="Toggle site navigation">
211
+ <svg class="menu-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"
212
+ fill="none" stroke="currentColor" stroke-width="1.65" stroke-linecap="round">
213
+ <path class="menu-icon-line menu-icon-line-top" d="M3 6h18" />
214
+ <path class="menu-icon-line menu-icon-line-middle" d="M3 12h18" />
215
+ <path class="menu-icon-line menu-icon-line-bottom" d="M3 18h18" />
216
+ </svg>
211
217
  <% if (site.symbol) { %><span class="logo" aria-hidden="true"><%= site.symbol %></span><% } %>
212
218
  <span class="site-title" aria-hidden="true"><%= site.title %></span>
213
219
  </summary>
@@ -1 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M252-203.69 205.69-252l227-228-227-230L252-758.31l229 230 227-230L754.31-710l-227 230 227 228L708-203.69l-227-230-229 230Z"/></svg>
@@ -1 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M128-251.23v-66h704v66H128ZM128-448v-66h704v66H128Zm0-196.77v-66h704v66H128Z"/></svg>