@abreen/tada 1.16.7 → 1.16.9
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/package.json +1 -1
- package/src/appearance-picker/index.ts +89 -5
- package/src/header/_base.scss +22 -12
- package/src/question/style.scss +1 -1
- package/src/style.scss +17 -3
- package/templates/_top.html +84 -3
package/package.json
CHANGED
|
@@ -4,6 +4,33 @@ export type ContrastPreference = 'standard' | 'high';
|
|
|
4
4
|
const FONT_STORAGE_KEY = 'fontPreference';
|
|
5
5
|
const CONTRAST_STORAGE_KEY = 'contrastPreference';
|
|
6
6
|
|
|
7
|
+
interface FontPreferenceRequest {
|
|
8
|
+
generation: number;
|
|
9
|
+
preference: FontPreference;
|
|
10
|
+
promise: Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface FontPreferenceLoader {
|
|
14
|
+
supported: boolean;
|
|
15
|
+
pending: FontPreferenceRequest | null;
|
|
16
|
+
failedPreference: FontPreference | null;
|
|
17
|
+
request(preference: FontPreference): FontPreferenceRequest | null;
|
|
18
|
+
isCurrent(request: FontPreferenceRequest): boolean;
|
|
19
|
+
complete(request: FontPreferenceRequest): void;
|
|
20
|
+
fail(request: FontPreferenceRequest): void;
|
|
21
|
+
cancel(): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getFontPreferenceLoader(
|
|
25
|
+
window: Window,
|
|
26
|
+
): FontPreferenceLoader | undefined {
|
|
27
|
+
return (
|
|
28
|
+
window as Window & {
|
|
29
|
+
__tadaFontPreferenceLoader?: FontPreferenceLoader;
|
|
30
|
+
}
|
|
31
|
+
).__tadaFontPreferenceLoader;
|
|
32
|
+
}
|
|
33
|
+
|
|
7
34
|
function getStorage(window: Window): Storage | null {
|
|
8
35
|
try {
|
|
9
36
|
return window.localStorage;
|
|
@@ -91,6 +118,12 @@ function getDefaultFontPreference(document: Document): FontPreference {
|
|
|
91
118
|
: 'sans';
|
|
92
119
|
}
|
|
93
120
|
|
|
121
|
+
function getAppliedFontPreference(document: Document): FontPreference {
|
|
122
|
+
return document.documentElement.dataset.fontPreference === 'serif'
|
|
123
|
+
? 'serif'
|
|
124
|
+
: 'sans';
|
|
125
|
+
}
|
|
126
|
+
|
|
94
127
|
function getDefaultContrastPreference(window: Window): ContrastPreference {
|
|
95
128
|
const configured =
|
|
96
129
|
window.document.documentElement.dataset.defaultContrastPreference === 'high'
|
|
@@ -179,27 +212,78 @@ export default function mountAppearancePicker(window: Window): () => void {
|
|
|
179
212
|
container.querySelectorAll<HTMLButtonElement>('button'),
|
|
180
213
|
);
|
|
181
214
|
const storage = getStorage(window);
|
|
215
|
+
const fontLoader = getFontPreferenceLoader(window);
|
|
182
216
|
const defaultFontPreference = getDefaultFontPreference(document);
|
|
183
217
|
const defaultContrastPreference = getDefaultContrastPreference(window);
|
|
184
|
-
|
|
218
|
+
const requestedFontPreference = getFontPreference(
|
|
219
|
+
storage,
|
|
220
|
+
defaultFontPreference,
|
|
221
|
+
);
|
|
222
|
+
let fontPreference = getAppliedFontPreference(document);
|
|
185
223
|
let contrastPreference = getContrastPreference(
|
|
186
224
|
storage,
|
|
187
225
|
defaultContrastPreference,
|
|
188
226
|
);
|
|
189
227
|
|
|
190
|
-
|
|
228
|
+
const requestedFontIsWaiting =
|
|
229
|
+
(fontLoader?.pending?.preference === requestedFontPreference ||
|
|
230
|
+
fontLoader?.failedPreference === requestedFontPreference) ??
|
|
231
|
+
false;
|
|
232
|
+
if (!requestedFontIsWaiting) {
|
|
233
|
+
fontPreference = requestedFontPreference;
|
|
234
|
+
applyFontPreference(document, fontPreference);
|
|
235
|
+
}
|
|
191
236
|
applyContrastPreference(document, contrastPreference);
|
|
192
237
|
syncAppearance(document, fontPreference, contrastPreference);
|
|
193
238
|
|
|
239
|
+
const observedRequests = new Set<number>();
|
|
240
|
+
const observeFontRequest = (request: FontPreferenceRequest) => {
|
|
241
|
+
if (observedRequests.has(request.generation)) return;
|
|
242
|
+
observedRequests.add(request.generation);
|
|
243
|
+
request.promise.then(
|
|
244
|
+
() => {
|
|
245
|
+
if (fontLoader?.isCurrent(request)) {
|
|
246
|
+
fontPreference = request.preference;
|
|
247
|
+
saveFontPreference(storage, fontPreference, defaultFontPreference);
|
|
248
|
+
applyFontPreference(document, fontPreference);
|
|
249
|
+
fontLoader.complete(request);
|
|
250
|
+
} else {
|
|
251
|
+
fontPreference = getAppliedFontPreference(document);
|
|
252
|
+
}
|
|
253
|
+
syncAppearance(document, fontPreference, contrastPreference);
|
|
254
|
+
},
|
|
255
|
+
() => {
|
|
256
|
+
fontLoader?.fail(request);
|
|
257
|
+
fontPreference = getAppliedFontPreference(document);
|
|
258
|
+
syncAppearance(document, fontPreference, contrastPreference);
|
|
259
|
+
},
|
|
260
|
+
);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
if (fontLoader?.pending) {
|
|
264
|
+
observeFontRequest(fontLoader.pending);
|
|
265
|
+
}
|
|
266
|
+
|
|
194
267
|
const handleClick = (event: Event) => {
|
|
195
268
|
const button = event.currentTarget as HTMLButtonElement;
|
|
196
269
|
const fontValue = button.dataset.fontPreferenceValue;
|
|
197
270
|
const contrastValue = button.dataset.contrastPreferenceValue;
|
|
198
271
|
|
|
199
272
|
if (fontValue === 'sans' || fontValue === 'serif') {
|
|
200
|
-
fontPreference =
|
|
201
|
-
|
|
202
|
-
|
|
273
|
+
fontPreference = getAppliedFontPreference(document);
|
|
274
|
+
if (fontValue === fontPreference) {
|
|
275
|
+
fontLoader?.cancel();
|
|
276
|
+
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);
|
|
286
|
+
}
|
|
203
287
|
} else if (contrastValue === 'standard' || contrastValue === 'high') {
|
|
204
288
|
contrastPreference = contrastValue;
|
|
205
289
|
saveContrastPreference(
|
package/src/header/_base.scss
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
header {
|
|
8
|
+
--header-search-reservation: 0px;
|
|
9
|
+
--header-to-top-reservation: 0px;
|
|
10
|
+
|
|
8
11
|
position: fixed;
|
|
9
12
|
top: 0;
|
|
10
13
|
z-index: 1000;
|
|
@@ -14,19 +17,36 @@ header {
|
|
|
14
17
|
background: var(--bg-color-translucent);
|
|
15
18
|
backdrop-filter: var(--backdrop-filter);
|
|
16
19
|
view-transition-name: site-header;
|
|
20
|
+
|
|
21
|
+
&:has(.search-controls) {
|
|
22
|
+
--header-search-reservation: 200px;
|
|
23
|
+
|
|
24
|
+
@media (width <= 400px) {
|
|
25
|
+
--header-search-reservation: 0px;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&:has(.to-top-container .is-visible) {
|
|
30
|
+
--header-to-top-reservation: 120px;
|
|
31
|
+
}
|
|
17
32
|
}
|
|
18
33
|
|
|
19
34
|
:root[data-contrast-preference='high'] header {
|
|
20
35
|
background: var(--bg-color);
|
|
21
36
|
}
|
|
22
37
|
|
|
38
|
+
header > details {
|
|
39
|
+
min-width: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
23
42
|
header details summary {
|
|
24
43
|
position: relative;
|
|
25
44
|
display: flex;
|
|
26
45
|
align-items: center;
|
|
27
46
|
height: var(--header-collapsed-height);
|
|
28
|
-
padding: 0
|
|
29
|
-
|
|
47
|
+
padding: 0
|
|
48
|
+
calc(var(--header-search-reservation) + var(--header-to-top-reservation)) 0
|
|
49
|
+
0;
|
|
30
50
|
cursor: pointer;
|
|
31
51
|
list-style-type: none;
|
|
32
52
|
|
|
@@ -47,10 +67,6 @@ header details summary {
|
|
|
47
67
|
}
|
|
48
68
|
}
|
|
49
69
|
|
|
50
|
-
header:has(.to-top-container .is-visible) details summary {
|
|
51
|
-
padding-right: 320px;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
70
|
header details summary::-webkit-details-marker {
|
|
55
71
|
display: none;
|
|
56
72
|
}
|
|
@@ -105,12 +121,6 @@ header summary .site-title {
|
|
|
105
121
|
pointer-events: none;
|
|
106
122
|
}
|
|
107
123
|
|
|
108
|
-
@container (max-width: 200px) {
|
|
109
|
-
header summary .site-title {
|
|
110
|
-
display: none;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
124
|
header > .header-controls {
|
|
115
125
|
position: absolute;
|
|
116
126
|
top: 0;
|
package/src/question/style.scss
CHANGED
package/src/style.scss
CHANGED
|
@@ -93,14 +93,28 @@ a.disabled {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
.external-link-tail::after,
|
|
96
|
+
a.external:not(:has(.external-link-tail))::after {
|
|
97
|
+
vertical-align: text-top;
|
|
98
|
+
|
|
99
|
+
@include contained-mask(var(--icon-external-link));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.external-link-tail::after {
|
|
103
|
+
display: inline;
|
|
104
|
+
|
|
105
|
+
// With zero font metrics, block-end padding paints down from text-top.
|
|
106
|
+
padding-block-end: var(--icon-external-link-size);
|
|
107
|
+
padding-inline-start: var(--icon-external-link-size);
|
|
108
|
+
font-size: 0;
|
|
109
|
+
line-height: 0;
|
|
110
|
+
content: '\2060' / '';
|
|
111
|
+
}
|
|
112
|
+
|
|
96
113
|
a.external:not(:has(.external-link-tail))::after {
|
|
97
114
|
display: inline-block;
|
|
98
115
|
width: var(--icon-external-link-size);
|
|
99
116
|
height: var(--icon-external-link-size);
|
|
100
|
-
vertical-align: text-top;
|
|
101
117
|
content: '';
|
|
102
|
-
|
|
103
|
-
@include contained-mask(var(--icon-external-link));
|
|
104
118
|
}
|
|
105
119
|
|
|
106
120
|
.footnotes {
|
package/templates/_top.html
CHANGED
|
@@ -38,7 +38,70 @@
|
|
|
38
38
|
<% } %>
|
|
39
39
|
<title><%= page.title %><%= site.titlePostfix %></title>
|
|
40
40
|
<script>
|
|
41
|
+
(() => {
|
|
41
42
|
document.documentElement.classList.add('js');
|
|
43
|
+
const fontPreferenceFamilies = {
|
|
44
|
+
sans: ['Inter', 'Google Sans Code'],
|
|
45
|
+
serif: [
|
|
46
|
+
'<%= site.fontOverrides?.serif ? 'Tada Custom Serif' : 'Source Serif 4' %>',
|
|
47
|
+
'<%= site.fontOverrides?.serifMono ? 'Tada Custom Serif Mono' : 'Libertinus Mono' %>',
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
const fontPreferenceLoader = {
|
|
51
|
+
generation: 0,
|
|
52
|
+
pending: null,
|
|
53
|
+
failedPreference: null,
|
|
54
|
+
supported:
|
|
55
|
+
document.fonts && typeof document.fonts.load === 'function',
|
|
56
|
+
request(preference) {
|
|
57
|
+
if (!this.supported) return null;
|
|
58
|
+
if (this.pending?.preference === preference) return this.pending;
|
|
59
|
+
|
|
60
|
+
const generation = ++this.generation;
|
|
61
|
+
const [bodyFamily, monoFamily] = fontPreferenceFamilies[preference];
|
|
62
|
+
const promise = Promise.all([
|
|
63
|
+
document.fonts.load('normal 400 1em "' + bodyFamily + '"'),
|
|
64
|
+
document.fonts.load('normal 700 1em "' + bodyFamily + '"'),
|
|
65
|
+
document.fonts.load('normal 400 1em "' + monoFamily + '"'),
|
|
66
|
+
]).then(matches => {
|
|
67
|
+
if (matches.some(faces => faces.length === 0)) {
|
|
68
|
+
throw new Error('A required font face did not load');
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
const request = { generation, preference, promise };
|
|
72
|
+
this.pending = request;
|
|
73
|
+
this.failedPreference = null;
|
|
74
|
+
return request;
|
|
75
|
+
},
|
|
76
|
+
isCurrent(request) {
|
|
77
|
+
return (
|
|
78
|
+
this.pending === request &&
|
|
79
|
+
this.generation === request.generation
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
complete(request) {
|
|
83
|
+
if (this.isCurrent(request)) this.pending = null;
|
|
84
|
+
},
|
|
85
|
+
fail(request) {
|
|
86
|
+
if (!this.isCurrent(request)) return;
|
|
87
|
+
this.pending = null;
|
|
88
|
+
this.failedPreference = request.preference;
|
|
89
|
+
},
|
|
90
|
+
cancel() {
|
|
91
|
+
++this.generation;
|
|
92
|
+
this.pending = null;
|
|
93
|
+
this.failedPreference = null;
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
window.__tadaFontPreferenceLoader = fontPreferenceLoader;
|
|
97
|
+
|
|
98
|
+
const applyFontPreference = preference => {
|
|
99
|
+
if (preference === 'serif') {
|
|
100
|
+
document.documentElement.dataset.fontPreference = 'serif';
|
|
101
|
+
} else {
|
|
102
|
+
delete document.documentElement.dataset.fontPreference;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
42
105
|
try {
|
|
43
106
|
const preference = window.localStorage.getItem('fontPreference');
|
|
44
107
|
const configured =
|
|
@@ -47,10 +110,27 @@
|
|
|
47
110
|
(preference === 'serif' || preference === 'sans') &&
|
|
48
111
|
preference !== configured
|
|
49
112
|
) {
|
|
50
|
-
|
|
51
|
-
|
|
113
|
+
const request = fontPreferenceLoader.request(preference);
|
|
114
|
+
if (!request) {
|
|
115
|
+
applyFontPreference(preference);
|
|
52
116
|
} else {
|
|
53
|
-
|
|
117
|
+
request.promise.then(
|
|
118
|
+
() => {
|
|
119
|
+
let storedPreference;
|
|
120
|
+
try {
|
|
121
|
+
storedPreference =
|
|
122
|
+
window.localStorage.getItem('fontPreference');
|
|
123
|
+
} catch {}
|
|
124
|
+
if (
|
|
125
|
+
fontPreferenceLoader.isCurrent(request) &&
|
|
126
|
+
storedPreference === preference
|
|
127
|
+
) {
|
|
128
|
+
applyFontPreference(preference);
|
|
129
|
+
fontPreferenceLoader.complete(request);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
() => fontPreferenceLoader.fail(request),
|
|
133
|
+
);
|
|
54
134
|
}
|
|
55
135
|
}
|
|
56
136
|
} catch {}
|
|
@@ -81,6 +161,7 @@
|
|
|
81
161
|
} else {
|
|
82
162
|
delete document.documentElement.dataset.contrastPreference;
|
|
83
163
|
}
|
|
164
|
+
})();
|
|
84
165
|
</script>
|
|
85
166
|
<% if (!isWatchMode) { %>
|
|
86
167
|
<script type="speculationrules">
|