@aglyn/shared-ui-theme 1.0.0-beta.230 → 1.0.0-beta.231
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 +4 -4
- package/src/lib/console.theme.js +8 -2
- package/src/lib/console.theme.js.map +1 -1
- package/src/lib/constants.d.ts +2 -0
- package/src/lib/constants.js +1 -0
- package/src/lib/constants.js.map +1 -1
- package/src/lib/platform-brand.d.ts +25 -0
- package/src/lib/platform-brand.js +40 -0
- package/src/lib/platform-brand.js.map +1 -0
- package/src/lib/site-base-fonts.d.ts +54 -0
- package/src/lib/site-base-fonts.js +125 -0
- package/src/lib/site-base-fonts.js.map +1 -0
- package/src/lib/tenant.theme.d.ts +1 -2
- package/src/lib/tenant.theme.js +8 -30
- package/src/lib/tenant.theme.js.map +1 -1
- package/src/lib/util/font-stack.d.ts +44 -0
- package/src/lib/util/font-stack.js +79 -0
- package/src/lib/util/font-stack.js.map +1 -0
- package/src/lib/util/roboto-flex.d.ts +65 -0
- package/src/lib/util/roboto-flex.js +115 -0
- package/src/lib/util/roboto-flex.js.map +1 -0
- package/src/lib/util/self-hosted-fonts.d.ts +111 -1
- package/src/lib/util/self-hosted-fonts.js +349 -2
- package/src/lib/util/self-hosted-fonts.js.map +1 -1
- package/src/lib/util/theme-editor-fields.d.ts +53 -2
- package/src/lib/util/theme-editor-fields.js +171 -20
- package/src/lib/util/theme-editor-fields.js.map +1 -1
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
import { metricFallbackFamily, stackFamilies } from "./font-stack.js";
|
|
18
|
+
export { metricFallbackFamily, themeWebFontFamilies, withMetricFallbacks } from "./font-stack.js";
|
|
17
19
|
/**
|
|
18
20
|
* A theme's Google fonts, served from the site's own origin (AGL-3485).
|
|
19
21
|
*
|
|
@@ -45,8 +47,11 @@
|
|
|
45
47
|
/**
|
|
46
48
|
* A font file's path on {@link GOOGLE_FONT_FILE_ORIGIN} below `/s/`, as the
|
|
47
49
|
* route accepts it: `{family}/v{n}/{file}.woff2`. Anything else is refused,
|
|
48
|
-
* so the route can never be used to fetch an arbitrary URL.
|
|
49
|
-
|
|
50
|
+
* so the route can never be used to fetch an arbitrary URL. A file name runs
|
|
51
|
+
* to 400 characters: a variable family with many axes (Roboto Flex) is named
|
|
52
|
+
* by an id of about 250, which a 200 cap refused, dropping every face of the
|
|
53
|
+
* family (AGL-3656).
|
|
54
|
+
*/ const FONT_FILE_PATH = /^[a-z0-9]+\/v\d{1,4}\/[A-Za-z0-9_-]{1,400}\.woff2$/;
|
|
50
55
|
/** Whether a path is one {@link SELF_HOSTED_FONT_ROUTE} will serve. */ export function isSelfHostedFontPath(path) {
|
|
51
56
|
return FONT_FILE_PATH.test(path);
|
|
52
57
|
}
|
|
@@ -165,5 +170,347 @@ const RANGE = /unicode-range:\s*([U+0-9A-Fa-f?,\s-]{1,4000});/;
|
|
|
165
170
|
...new Set(paths)
|
|
166
171
|
];
|
|
167
172
|
}
|
|
173
|
+
/** A Google face as a {@link SiteFontFace} on the site's own route. */ export function siteFontFaceFromGoogle(face) {
|
|
174
|
+
return _extends({
|
|
175
|
+
family: face.family,
|
|
176
|
+
style: face.style,
|
|
177
|
+
weight: face.weight,
|
|
178
|
+
url: selfHostedFontUrl(face.path)
|
|
179
|
+
}, face.subset ? {
|
|
180
|
+
subset: face.subset
|
|
181
|
+
} : {}, face.unicodeRange ? {
|
|
182
|
+
unicodeRange: face.unicodeRange
|
|
183
|
+
} : {});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The faces with every set of rules that share one file folded into one rule
|
|
187
|
+
* over the weights it covers.
|
|
188
|
+
*
|
|
189
|
+
* Google answers a request for several weights of a variable family with the
|
|
190
|
+
* SAME file once per weight, each rule naming one weight. A browser treats
|
|
191
|
+
* those as separate faces: it matches 500 to nothing between the 400 and 600
|
|
192
|
+
* rules a theme listed and renders it as 400, though the file holds every
|
|
193
|
+
* weight in between. One rule declaring the range (`400 700`) is what the
|
|
194
|
+
* file is.
|
|
195
|
+
*/ export function mergeSharedFiles(faces) {
|
|
196
|
+
const groups = new Map();
|
|
197
|
+
for (const face of faces){
|
|
198
|
+
var _face_unicodeRange;
|
|
199
|
+
const key = [
|
|
200
|
+
face.family.toLowerCase(),
|
|
201
|
+
face.style,
|
|
202
|
+
face.url,
|
|
203
|
+
(_face_unicodeRange = face.unicodeRange) != null ? _face_unicodeRange : ''
|
|
204
|
+
].join('|');
|
|
205
|
+
const group = groups.get(key);
|
|
206
|
+
if (group) group.push(face);
|
|
207
|
+
else groups.set(key, [
|
|
208
|
+
face
|
|
209
|
+
]);
|
|
210
|
+
}
|
|
211
|
+
return [
|
|
212
|
+
...groups.values()
|
|
213
|
+
].map((group)=>{
|
|
214
|
+
if (group.length === 1) return group[0];
|
|
215
|
+
const bounds = group.flatMap((face)=>face.weight.split(' ').map(Number));
|
|
216
|
+
const low = Math.min(...bounds);
|
|
217
|
+
const high = Math.max(...bounds);
|
|
218
|
+
return _extends({}, group[0], {
|
|
219
|
+
weight: low === high ? String(low) : `${low} ${high}`
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/** The rules for a page's faces, every one `font-display: swap`. */ export function siteFontFaceCss(faces) {
|
|
224
|
+
return faces.filter((face)=>isSiteFontUrl(face.url)).map((face)=>[
|
|
225
|
+
'@font-face{',
|
|
226
|
+
`font-family:${cssFamily(face.family)};`,
|
|
227
|
+
`font-style:${face.style};`,
|
|
228
|
+
`font-weight:${/^\d{1,4}( \d{1,4})?$/.test(face.weight) ? face.weight : '400'};`,
|
|
229
|
+
'font-display:swap;',
|
|
230
|
+
`src:url(${face.url}) format('woff2');`,
|
|
231
|
+
face.unicodeRange && /^[U+0-9A-Fa-f?,\s-]{1,4000}$/.test(face.unicodeRange) ? `unicode-range:${face.unicodeRange};` : '',
|
|
232
|
+
'}'
|
|
233
|
+
].join('')).join('');
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Whether a URL is one a rule may name: site-relative, on the font route or
|
|
237
|
+
* the media route, with nothing that could close the `url()` it is written
|
|
238
|
+
* into. A face whose URL fails is dropped rather than escaped.
|
|
239
|
+
*/ export function isSiteFontUrl(url) {
|
|
240
|
+
return /^\/api\/(fonts|media\/cdn)\/[A-Za-z0-9_\-./:%]{1,600}(\?v=[A-Za-z0-9_.-]{1,128})?$/.test(url);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The local faces fallbacks are drawn from, by category. Their metrics are
|
|
244
|
+
* read from the faces themselves with the fonts plugin's reader and its
|
|
245
|
+
* frequency-weighted average width (Arial 2048/1854/-434/67/901), so the ratio
|
|
246
|
+
* to a web font measured the same way is what a line of text changes by.
|
|
247
|
+
* Arial and Times New Roman ship with Windows and macOS; where neither is
|
|
248
|
+
* installed the `local()` source fails and the stack moves on unadjusted.
|
|
249
|
+
*/ const LOCAL_FALLBACKS = {
|
|
250
|
+
'sans-serif': {
|
|
251
|
+
local: [
|
|
252
|
+
'Arial',
|
|
253
|
+
'ArialMT'
|
|
254
|
+
],
|
|
255
|
+
metrics: {
|
|
256
|
+
unitsPerEm: 2048,
|
|
257
|
+
ascent: 1854,
|
|
258
|
+
descent: -434,
|
|
259
|
+
lineGap: 67,
|
|
260
|
+
xWidthAvg: 901
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
serif: {
|
|
264
|
+
local: [
|
|
265
|
+
'Times New Roman',
|
|
266
|
+
'TimesNewRomanPSMT'
|
|
267
|
+
],
|
|
268
|
+
metrics: {
|
|
269
|
+
unitsPerEm: 2048,
|
|
270
|
+
ascent: 1825,
|
|
271
|
+
descent: -443,
|
|
272
|
+
lineGap: 87,
|
|
273
|
+
xWidthAvg: 817
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
monospace: {
|
|
277
|
+
local: [
|
|
278
|
+
'Courier New',
|
|
279
|
+
'CourierNewPSMT'
|
|
280
|
+
],
|
|
281
|
+
metrics: {
|
|
282
|
+
unitsPerEm: 2048,
|
|
283
|
+
ascent: 1705,
|
|
284
|
+
descent: -615,
|
|
285
|
+
lineGap: 0,
|
|
286
|
+
xWidthAvg: 1229
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
function localFallbackFor(category) {
|
|
291
|
+
if (category === 'serif') return LOCAL_FALLBACKS.serif;
|
|
292
|
+
if (category === 'monospace') return LOCAL_FALLBACKS.monospace;
|
|
293
|
+
return LOCAL_FALLBACKS['sans-serif'];
|
|
294
|
+
}
|
|
295
|
+
function percent(value) {
|
|
296
|
+
return `${(Math.round(value * 10000) / 100).toString()}%`;
|
|
297
|
+
}
|
|
298
|
+
/** Whether metrics are numbers a rule can be written from. */ function usableMetrics(metrics) {
|
|
299
|
+
return !!metrics && [
|
|
300
|
+
metrics.unitsPerEm,
|
|
301
|
+
metrics.ascent,
|
|
302
|
+
metrics.descent,
|
|
303
|
+
metrics.lineGap,
|
|
304
|
+
metrics.xWidthAvg
|
|
305
|
+
].every((value)=>typeof value === 'number' && Number.isFinite(value)) && metrics.unitsPerEm > 0 && metrics.xWidthAvg > 0 && metrics.ascent > 0;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* The `@font-face` rule for a family's metric-matched fallback: the local face
|
|
309
|
+
* of its category, scaled so its average character is as wide as the web
|
|
310
|
+
* font's (`size-adjust`) and given the web font's ascent, descent and line gap
|
|
311
|
+
* at that scale. Text set in it occupies the box the web font will, so the
|
|
312
|
+
* swap moves nothing — the CLS a `font-display: swap` page otherwise pays.
|
|
313
|
+
*
|
|
314
|
+
* The overrides are divided by the size adjustment because the browser
|
|
315
|
+
* applies them to the ADJUSTED size: an ascent of 0.97em on a face scaled to
|
|
316
|
+
* 1.07 has to be written as 0.97/1.07 to land at 0.97em.
|
|
317
|
+
*
|
|
318
|
+
* Empty for metrics that are missing or not numbers.
|
|
319
|
+
*/ export function metricFallbackFontFaceCss(family, metrics, category) {
|
|
320
|
+
if (!usableMetrics(metrics)) return '';
|
|
321
|
+
const local = localFallbackFor(category);
|
|
322
|
+
const sizeAdjust = metrics.xWidthAvg / metrics.unitsPerEm / (local.metrics.xWidthAvg / local.metrics.unitsPerEm);
|
|
323
|
+
if (!Number.isFinite(sizeAdjust) || sizeAdjust <= 0) return '';
|
|
324
|
+
const over = (value)=>percent(Math.abs(value) / metrics.unitsPerEm / sizeAdjust);
|
|
325
|
+
return [
|
|
326
|
+
'@font-face{',
|
|
327
|
+
`font-family:${cssFamily(metricFallbackFamily(family))};`,
|
|
328
|
+
`src:${local.local.map((name)=>`local(${cssFamily(name)})`).join(',')};`,
|
|
329
|
+
`size-adjust:${percent(sizeAdjust)};`,
|
|
330
|
+
`ascent-override:${over(metrics.ascent)};`,
|
|
331
|
+
`descent-override:${over(metrics.descent)};`,
|
|
332
|
+
`line-gap-override:${over(metrics.lineGap)};`,
|
|
333
|
+
'}'
|
|
334
|
+
].join('');
|
|
335
|
+
}
|
|
336
|
+
const NAMED_WEIGHTS = {
|
|
337
|
+
normal: 400,
|
|
338
|
+
bold: 700,
|
|
339
|
+
lighter: 300,
|
|
340
|
+
bolder: 700
|
|
341
|
+
};
|
|
342
|
+
function numericWeight(value) {
|
|
343
|
+
if (typeof value === 'number' && Number.isFinite(value)) return Math.round(value);
|
|
344
|
+
if (typeof value !== 'string') return undefined;
|
|
345
|
+
if (/^\d{3,4}$/.test(value.trim())) return Number(value.trim());
|
|
346
|
+
return NAMED_WEIGHTS[value.trim().toLowerCase()];
|
|
347
|
+
}
|
|
348
|
+
/** MUI's four named weights, before a theme adds its own. */ const MUI_WEIGHT_TOKENS = {
|
|
349
|
+
fontWeightLight: 300,
|
|
350
|
+
fontWeightRegular: 400,
|
|
351
|
+
fontWeightMedium: 500,
|
|
352
|
+
fontWeightBold: 700
|
|
353
|
+
};
|
|
354
|
+
/**
|
|
355
|
+
* The faces each of a theme's web font families is drawn with (AGL-3656).
|
|
356
|
+
*
|
|
357
|
+
* A theme lists weights for its font (`fonts[].weights`), but the page draws
|
|
358
|
+
* with whatever its text styles say: the platform's ramp sets `h1` in 900 and
|
|
359
|
+
* `h2` in 800, so a theme that loaded Inter at 400, 500 and 700 drew its
|
|
360
|
+
* headlines in a synthesized or substituted weight. The weights here are the
|
|
361
|
+
* union of the listed ones, every text style's weight whose family resolves to
|
|
362
|
+
* this one, and the theme's named weights (`fontWeightLight` … `Black`), which
|
|
363
|
+
* the weight picker offers content.
|
|
364
|
+
*
|
|
365
|
+
* `baseTypography` is the typography the site's theme is layered onto
|
|
366
|
+
* (`siteBaseOptions(...).typography`): a text style the theme leaves alone
|
|
367
|
+
* keeps the base's weight and family.
|
|
368
|
+
*
|
|
369
|
+
* One italic is loaded per family, at the body text's weight, so emphasis in
|
|
370
|
+
* running text is a real italic rather than a slanted upright; a theme can
|
|
371
|
+
* list more under `italics`.
|
|
372
|
+
*/ export function themeFontNeeds(theme, baseTypography, extraFonts = []) {
|
|
373
|
+
var _ref, _ref1;
|
|
374
|
+
var _theme_typography, _theme_typography1;
|
|
375
|
+
const fonts = [
|
|
376
|
+
...(_ref = theme == null ? void 0 : theme.fonts) != null ? _ref : [],
|
|
377
|
+
...extraFonts
|
|
378
|
+
].filter((font)=>{
|
|
379
|
+
var _font_source;
|
|
380
|
+
return font.family && ((_font_source = font.source) != null ? _font_source : 'google') !== 'system';
|
|
381
|
+
});
|
|
382
|
+
if (!fonts.length) return [];
|
|
383
|
+
const base = baseTypography != null ? baseTypography : {};
|
|
384
|
+
const variants = (_ref1 = theme == null ? void 0 : (_theme_typography = theme.typography) == null ? void 0 : _theme_typography.variants) != null ? _ref1 : {};
|
|
385
|
+
const baseStack = typeof (theme == null ? void 0 : (_theme_typography1 = theme.typography) == null ? void 0 : _theme_typography1.fontFamily) === 'string' && theme.typography.fontFamily || (typeof base['fontFamily'] === 'string' ? base['fontFamily'] : '');
|
|
386
|
+
const needs = new Map();
|
|
387
|
+
for (const font of fonts){
|
|
388
|
+
var _needs_get, _font_weights, _font_italics;
|
|
389
|
+
const key = font.family.trim().toLowerCase();
|
|
390
|
+
const need = (_needs_get = needs.get(key)) != null ? _needs_get : {
|
|
391
|
+
family: font.family.trim(),
|
|
392
|
+
weights: new Set(),
|
|
393
|
+
text: new Set(),
|
|
394
|
+
italics: new Set()
|
|
395
|
+
};
|
|
396
|
+
for (const weight of (_font_weights = font.weights) != null ? _font_weights : [])if (numericWeight(weight)) need.weights.add(numericWeight(weight));
|
|
397
|
+
for (const weight of (_font_italics = font.italics) != null ? _font_italics : [])if (numericWeight(weight)) need.italics.add(numericWeight(weight));
|
|
398
|
+
needs.set(key, need);
|
|
399
|
+
}
|
|
400
|
+
const needFor = (stack)=>{
|
|
401
|
+
const first = typeof stack === 'string' ? stackFamilies(stack)[0] : undefined;
|
|
402
|
+
return first ? needs.get(first.toLowerCase()) : undefined;
|
|
403
|
+
};
|
|
404
|
+
const variantKeys = new Set([
|
|
405
|
+
...Object.keys(variants),
|
|
406
|
+
...Object.keys(base).filter((key)=>{
|
|
407
|
+
const value = base[key];
|
|
408
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
409
|
+
})
|
|
410
|
+
]);
|
|
411
|
+
for (const key of variantKeys){
|
|
412
|
+
var _variants_key, _base_key, _ref2, _own_fontFamily, _numericWeight, _own_fontWeight;
|
|
413
|
+
const own = (_variants_key = variants[key]) != null ? _variants_key : {};
|
|
414
|
+
const inherited = (_base_key = base[key]) != null ? _base_key : {};
|
|
415
|
+
const need = needFor((_ref2 = (_own_fontFamily = own['fontFamily']) != null ? _own_fontFamily : inherited['fontFamily']) != null ? _ref2 : baseStack);
|
|
416
|
+
if (!need) continue;
|
|
417
|
+
const weight = (_numericWeight = numericWeight((_own_fontWeight = own['fontWeight']) != null ? _own_fontWeight : inherited['fontWeight'])) != null ? _numericWeight : 400;
|
|
418
|
+
need.weights.add(weight);
|
|
419
|
+
need.text.add(weight);
|
|
420
|
+
}
|
|
421
|
+
const bodyNeed = needFor(baseStack);
|
|
422
|
+
if (bodyNeed) {
|
|
423
|
+
var _variants_body1, _numericWeight1, _body_fontWeight;
|
|
424
|
+
var _base_body1;
|
|
425
|
+
const tokens = _extends({}, MUI_WEIGHT_TOKENS);
|
|
426
|
+
for (const [key, value] of Object.entries(base))if (/^fontWeight[A-Z]/.test(key)) tokens[key] = value;
|
|
427
|
+
for (const [key, value] of Object.entries(tokens)){
|
|
428
|
+
const weight = numericWeight(value);
|
|
429
|
+
if (!weight) continue;
|
|
430
|
+
bodyNeed.weights.add(weight);
|
|
431
|
+
if (key !== 'fontWeightLight' && MUI_WEIGHT_TOKENS[key]) bodyNeed.text.add(weight);
|
|
432
|
+
}
|
|
433
|
+
const body = (_variants_body1 = variants['body1']) != null ? _variants_body1 : {};
|
|
434
|
+
const bodyWeight = (_numericWeight1 = numericWeight((_body_fontWeight = body['fontWeight']) != null ? _body_fontWeight : (_base_body1 = base['body1']) == null ? void 0 : _base_body1['fontWeight'])) != null ? _numericWeight1 : 400;
|
|
435
|
+
bodyNeed.italics.add(bodyWeight);
|
|
436
|
+
}
|
|
437
|
+
return [
|
|
438
|
+
...needs.values()
|
|
439
|
+
].map((need)=>({
|
|
440
|
+
family: need.family,
|
|
441
|
+
weights: [
|
|
442
|
+
...need.weights
|
|
443
|
+
].sort((a, b)=>a - b),
|
|
444
|
+
textWeights: [
|
|
445
|
+
...need.text
|
|
446
|
+
].sort((a, b)=>a - b),
|
|
447
|
+
italics: [
|
|
448
|
+
...need.italics
|
|
449
|
+
].sort((a, b)=>a - b)
|
|
450
|
+
}));
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* The weights to load from what a family offers: each wanted weight as the
|
|
454
|
+
* nearest one offered, without repeats. Unknown availability keeps the
|
|
455
|
+
* wanted weights as they are.
|
|
456
|
+
*/ export function offeredWeights(wanted, offered) {
|
|
457
|
+
if (!(offered == null ? void 0 : offered.length)) return [
|
|
458
|
+
...new Set(wanted)
|
|
459
|
+
].sort((a, b)=>a - b);
|
|
460
|
+
const picked = wanted.map((weight)=>offered.reduce((best, candidate)=>Math.abs(candidate - weight) < Math.abs(best - weight) ? candidate : best));
|
|
461
|
+
return [
|
|
462
|
+
...new Set(picked)
|
|
463
|
+
].sort((a, b)=>a - b);
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Google's CSS2 URL for some faces of one family: `ital,wght` tuples, sorted
|
|
467
|
+
* as the API requires. One weight asks for a static instance, which for a
|
|
468
|
+
* variable family is a file holding that weight alone — Inter's Latin 400 is
|
|
469
|
+
* 24 KB that way and 48 KB as the variable file.
|
|
470
|
+
*/ export function googleFontSheetUrl(family, faces) {
|
|
471
|
+
const name = family.trim();
|
|
472
|
+
if (!name || !/^[A-Za-z0-9 ]{1,80}$/.test(name) || !faces.length) return undefined;
|
|
473
|
+
const tuples = [
|
|
474
|
+
...new Set(faces.map((face)=>`${face.style === 'italic' ? 1 : 0},${face.weight}`))
|
|
475
|
+
].sort((a, b)=>{
|
|
476
|
+
const [ai, aw] = a.split(',').map(Number);
|
|
477
|
+
const [bi, bw] = b.split(',').map(Number);
|
|
478
|
+
return ai - bi || aw - bw;
|
|
479
|
+
});
|
|
480
|
+
const axis = tuples.some((tuple)=>tuple.startsWith('1,')) ? `ital,wght@${tuples.join(';')}` : `wght@${tuples.map((tuple)=>tuple.slice(2)).join(';')}`;
|
|
481
|
+
return `https://fonts.googleapis.com/css2?family=${name.replace(/ /g, '+')}:${axis}&display=swap`;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* The files the first screen paints with, to preload: the body text's face
|
|
485
|
+
* and the headline's, Latin and upright, from whatever faces the page
|
|
486
|
+
* declares. A file covering both is preloaded once.
|
|
487
|
+
*/ export function siteFontPreloads(theme, faces, baseTypography) {
|
|
488
|
+
var _base_h1, _base_body1, _ref, _firstFamily, _numericWeight, _ref1, _ref2, _firstFamily1, _numericWeight1, _ref3;
|
|
489
|
+
var _theme_fonts_find, _theme_fonts, _variants_body1, _variants_h1, _variants_h11;
|
|
490
|
+
const typography = theme == null ? void 0 : theme.typography;
|
|
491
|
+
const variants = typography == null ? void 0 : typography.variants;
|
|
492
|
+
const base = baseTypography != null ? baseTypography : {};
|
|
493
|
+
const baseH1 = (_base_h1 = base['h1']) != null ? _base_h1 : {};
|
|
494
|
+
const baseBody = (_base_body1 = base['body1']) != null ? _base_body1 : {};
|
|
495
|
+
const fallback = theme == null ? void 0 : (_theme_fonts = theme.fonts) == null ? void 0 : (_theme_fonts_find = _theme_fonts.find((font)=>{
|
|
496
|
+
var _font_source;
|
|
497
|
+
return ((_font_source = font.source) != null ? _font_source : 'google') !== 'system';
|
|
498
|
+
})) == null ? void 0 : _theme_fonts_find.family;
|
|
499
|
+
const bodyFamily = (_ref = (_firstFamily = firstFamily(typography == null ? void 0 : typography.fontFamily)) != null ? _firstFamily : firstFamily(base['fontFamily'])) != null ? _ref : fallback;
|
|
500
|
+
const pick = (family, weight)=>{
|
|
501
|
+
if (!family) return undefined;
|
|
502
|
+
const wanted = family.toLowerCase();
|
|
503
|
+
const candidates = faces.filter((face)=>face.family.toLowerCase() === wanted && face.style === 'normal' && (face.subset === undefined || face.subset === 'latin'));
|
|
504
|
+
return candidates.sort((a, b)=>weightDistance(a, weight) - weightDistance(b, weight))[0];
|
|
505
|
+
};
|
|
506
|
+
const body = pick(bodyFamily, (_numericWeight = numericWeight((_ref1 = variants == null ? void 0 : (_variants_body1 = variants.body1) == null ? void 0 : _variants_body1.fontWeight) != null ? _ref1 : baseBody['fontWeight'])) != null ? _numericWeight : 400);
|
|
507
|
+
const heading = pick((_ref2 = (_firstFamily1 = firstFamily(variants == null ? void 0 : (_variants_h1 = variants.h1) == null ? void 0 : _variants_h1.fontFamily)) != null ? _firstFamily1 : firstFamily(baseH1['fontFamily'])) != null ? _ref2 : bodyFamily, (_numericWeight1 = numericWeight((_ref3 = variants == null ? void 0 : (_variants_h11 = variants.h1) == null ? void 0 : _variants_h11.fontWeight) != null ? _ref3 : baseH1['fontWeight'])) != null ? _numericWeight1 : 700);
|
|
508
|
+
return [
|
|
509
|
+
...new Set([
|
|
510
|
+
body,
|
|
511
|
+
heading
|
|
512
|
+
].filter((face)=>!!face).map((face)=>face.url))
|
|
513
|
+
];
|
|
514
|
+
}
|
|
168
515
|
|
|
169
516
|
//# sourceMappingURL=self-hosted-fonts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/shared/ui/theme/src/lib/util/self-hosted-fonts.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { HostTheme } from '@aglyn/shared-data-types'\n\n/**\n * A theme's Google fonts, served from the site's own origin (AGL-3485).\n *\n * A published page loaded its theme font as a `<link rel=\"stylesheet\">` to\n * `fonts.googleapis.com`: a render-blocking request to a third origin, then a\n * second connection to `fonts.gstatic.com` for the files it names — about\n * 800 ms before first paint on the page Lighthouse measured. Nothing about a\n * theme font needs either round trip at view time. The stylesheet is a short\n * list of `@font-face` rules that changes only when the theme does, and the\n * files are versioned in their own paths (`/s/inter/v18/…woff2`), so neither\n * changes under a URL.\n *\n * So the server fetches the stylesheet once, keeps the rules, and inlines them\n * in the page with each `src` pointing at {@link SELF_HOSTED_FONT_ROUTE} on the\n * site's own origin, which serves the file with a year-long `immutable`\n * policy. The one or two faces the first screen paints with are preloaded.\n * Nothing on the page asks Google for anything, which also means a visitor's\n * address is no longer handed to Google by every page view.\n *\n * This module is the pure half: reading Google's stylesheet, writing the\n * rules back, and choosing the faces to preload. The fetching and the file\n * route live with the tenant (`libs/tenant/runtime/.../self-hosted-fonts.ts`).\n *\n * The rules are REBUILT from validated fields rather than echoed, so the\n * inlined CSS can only ever contain a family name the theme chose, a style, a\n * weight, a unicode range and a path on our own route.\n */\n\n/** Where a site serves its theme's font files from. */\nexport const SELF_HOSTED_FONT_ROUTE = '/api/fonts'\n\n/** The origin Google serves font files from. */\nexport const GOOGLE_FONT_FILE_ORIGIN = 'https://fonts.gstatic.com'\n\n/**\n * A font file's path on {@link GOOGLE_FONT_FILE_ORIGIN} below `/s/`, as the\n * route accepts it: `{family}/v{n}/{file}.woff2`. Anything else is refused,\n * so the route can never be used to fetch an arbitrary URL.\n */\nconst FONT_FILE_PATH = /^[a-z0-9]+\\/v\\d{1,4}\\/[A-Za-z0-9_-]{1,200}\\.woff2$/\n\n/** One `@font-face` rule from Google's stylesheet. */\nexport interface GoogleFontFace {\n family: string\n style: 'normal' | 'italic'\n /** A weight (`400`) or, for a variable font, a range (`100 900`). */\n weight: string\n /** Google's subset comment above the rule (`latin`, `latin-ext`, …). */\n subset?: string\n /** The file's path below `/s/` on Google's origin. */\n path: string\n unicodeRange?: string\n}\n\n/** Whether a path is one {@link SELF_HOSTED_FONT_ROUTE} will serve. */\nexport function isSelfHostedFontPath(path: string): boolean {\n return FONT_FILE_PATH.test(path)\n}\n\n/** The site-relative URL a face's file is served from. */\nexport function selfHostedFontUrl(path: string): string {\n return `${SELF_HOSTED_FONT_ROUTE}/${path}`\n}\n\n/** Google's own URL for a self-hosted path. */\nexport function googleFontFileUrl(path: string): string {\n return `${GOOGLE_FONT_FILE_ORIGIN}/s/${path}`\n}\n\nconst FACE_BLOCK = /(?:\\/\\*\\s*([a-z0-9-]+)\\s*\\*\\/\\s*)?@font-face\\s*\\{([^}]*)\\}/g\nconst FAMILY = /font-family:\\s*'([^']{1,80})'/\nconst STYLE = /font-style:\\s*(normal|italic)/\nconst WEIGHT = /font-weight:\\s*(\\d{3}(?:\\s+\\d{3})?)/\nconst SRC = /src:\\s*url\\((https:\\/\\/fonts\\.gstatic\\.com\\/s\\/[^)\\s]+)\\)\\s*format\\('woff2'\\)/\nconst RANGE = /unicode-range:\\s*([U+0-9A-Fa-f?,\\s-]{1,4000});/\n\n/**\n * Reads the `@font-face` rules out of a Google Fonts CSS2 stylesheet fetched\n * with a browser that accepts WOFF2. A rule that is not exactly the shape\n * Google writes — a missing field, a file off Google's origin, a path the\n * route would refuse — is dropped, never repaired.\n */\nexport function parseGoogleFontFaces(css: string): GoogleFontFace[] {\n const faces: GoogleFontFace[] = []\n for (const match of css.matchAll(FACE_BLOCK)) {\n const [, subset, body] = match\n const family = FAMILY.exec(body)?.[1]\n const style = STYLE.exec(body)?.[1] as GoogleFontFace['style'] | undefined\n const weight = WEIGHT.exec(body)?.[1]\n const url = SRC.exec(body)?.[1]\n if (!family || !style || !weight || !url) continue\n const path = url.slice(`${GOOGLE_FONT_FILE_ORIGIN}/s/`.length)\n if (!isSelfHostedFontPath(path)) continue\n const unicodeRange = RANGE.exec(body)?.[1]?.trim()\n faces.push({\n family,\n style,\n weight: weight.replace(/\\s+/g, ' '),\n ...(subset ? { subset } : {}),\n path,\n ...(unicodeRange ? { unicodeRange } : {}),\n })\n }\n return faces\n}\n\n/** A family name as it may appear quoted in CSS. */\nfunction cssFamily(family: string): string {\n return `'${family.replace(/['\\\\\\n\\r]/g, '')}'`\n}\n\n/**\n * The faces as `@font-face` rules on the site's own route, every one\n * `font-display: swap` so text paints in the fallback while a file loads.\n *\n * Every subset Google declared is kept, each with its `unicode-range`: a\n * browser downloads a subset only when the page uses a character in it, so\n * the Cyrillic file costs an English page nothing, and a Polish or Greek page\n * keeps its typeface. Only the Latin faces are preloaded.\n */\nexport function selfHostedFontFaceCss(faces: readonly GoogleFontFace[]): string {\n return faces\n .map((face) =>\n [\n '@font-face{',\n `font-family:${cssFamily(face.family)};`,\n `font-style:${face.style};`,\n `font-weight:${face.weight};`,\n 'font-display:swap;',\n `src:url(${selfHostedFontUrl(face.path)}) format('woff2');`,\n face.unicodeRange ? `unicode-range:${face.unicodeRange};` : '',\n '}',\n ].join(''),\n )\n .join('')\n}\n\n/** The first family named in a CSS `font-family` stack. */\nfunction firstFamily(stack: unknown): string | undefined {\n if (typeof stack !== 'string') return undefined\n const first = stack.split(',')[0]?.trim().replace(/^['\"]|['\"]$/g, '')\n return first || undefined\n}\n\n/** How far a face is from a wanted weight; 0 when its range covers it. */\nfunction weightDistance(face: GoogleFontFace, wanted: number): number {\n const [low, high = low] = face.weight.split(' ').map(Number)\n if (wanted >= low && wanted <= high) return 0\n return Math.min(Math.abs(wanted - low), Math.abs(wanted - high))\n}\n\n/** The Latin, upright face of `family` closest to `weight`. */\nfunction latinFace(\n faces: readonly GoogleFontFace[],\n family: string | undefined,\n weight: number,\n): GoogleFontFace | undefined {\n if (!family) return undefined\n const wanted = family.toLowerCase()\n const candidates = faces.filter(\n (face) =>\n face.family.toLowerCase() === wanted &&\n face.style === 'normal' &&\n (face.subset === undefined || face.subset === 'latin'),\n )\n return candidates.sort(\n (a, b) => weightDistance(a, weight) - weightDistance(b, weight),\n )[0]\n}\n\n/**\n * The paths of the one or two faces the first screen paints with: the body\n * text's face, and the headline's when it is a different file. Those are the\n * fonts the largest text on the page — usually the LCP element — waits for,\n * so they are worth starting with the document; every other face is fetched\n * when the page first uses it.\n *\n * The body face is the theme's `fontFamily` (else the first Google font it\n * loads) at `body1`'s weight, 400 by default; the headline face is `h1`'s\n * family and weight, 700 by default. A variable font serves every weight\n * from one file, which is then preloaded once.\n */\nexport function selfHostedFontPreloads(\n theme: Pick<HostTheme, 'fonts' | 'typography'> | undefined,\n faces: readonly GoogleFontFace[],\n): string[] {\n const typography = theme?.typography\n const variants = typography?.variants\n const fallback = theme?.fonts?.find(\n (font) => (font.source ?? 'google') === 'google',\n )?.family\n const bodyFamily = firstFamily(typography?.fontFamily) ?? fallback\n const body = latinFace(faces, bodyFamily, variants?.body1?.fontWeight ?? 400)\n const heading = latinFace(\n faces,\n firstFamily(variants?.h1?.fontFamily) ?? bodyFamily,\n variants?.h1?.fontWeight ?? 700,\n )\n const paths = [body, heading]\n .filter((face): face is GoogleFontFace => Boolean(face))\n .map((face) => selfHostedFontUrl(face.path))\n return [...new Set(paths)]\n}\n"],"names":["SELF_HOSTED_FONT_ROUTE","GOOGLE_FONT_FILE_ORIGIN","FONT_FILE_PATH","isSelfHostedFontPath","path","test","selfHostedFontUrl","googleFontFileUrl","FACE_BLOCK","FAMILY","STYLE","WEIGHT","SRC","RANGE","parseGoogleFontFaces","css","faces","match","matchAll","subset","body","family","exec","style","weight","url","slice","length","unicodeRange","trim","push","replace","cssFamily","selfHostedFontFaceCss","map","face","join","firstFamily","stack","undefined","first","split","weightDistance","wanted","low","high","Number","Math","min","abs","latinFace","toLowerCase","candidates","filter","sort","a","b","selfHostedFontPreloads","theme","variants","typography","fallback","fonts","find","font","source","bodyFamily","fontFamily","body1","fontWeight","heading","h1","paths","Boolean","Set"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/shared/ui/theme/src/lib/util/self-hosted-fonts.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n HostTheme,\n HostThemeFont,\n HostThemeFontCategory,\n HostThemeFontMetrics,\n} from '@aglyn/shared-data-types'\nimport { metricFallbackFamily, stackFamilies } from './font-stack'\n\nexport { metricFallbackFamily, themeWebFontFamilies, withMetricFallbacks } from './font-stack'\n\n/**\n * A theme's Google fonts, served from the site's own origin (AGL-3485).\n *\n * A published page loaded its theme font as a `<link rel=\"stylesheet\">` to\n * `fonts.googleapis.com`: a render-blocking request to a third origin, then a\n * second connection to `fonts.gstatic.com` for the files it names — about\n * 800 ms before first paint on the page Lighthouse measured. Nothing about a\n * theme font needs either round trip at view time. The stylesheet is a short\n * list of `@font-face` rules that changes only when the theme does, and the\n * files are versioned in their own paths (`/s/inter/v18/…woff2`), so neither\n * changes under a URL.\n *\n * So the server fetches the stylesheet once, keeps the rules, and inlines them\n * in the page with each `src` pointing at {@link SELF_HOSTED_FONT_ROUTE} on the\n * site's own origin, which serves the file with a year-long `immutable`\n * policy. The one or two faces the first screen paints with are preloaded.\n * Nothing on the page asks Google for anything, which also means a visitor's\n * address is no longer handed to Google by every page view.\n *\n * This module is the pure half: reading Google's stylesheet, writing the\n * rules back, and choosing the faces to preload. The fetching and the file\n * route live with the tenant (`libs/tenant/runtime/.../self-hosted-fonts.ts`).\n *\n * The rules are REBUILT from validated fields rather than echoed, so the\n * inlined CSS can only ever contain a family name the theme chose, a style, a\n * weight, a unicode range and a path on our own route.\n */\n\n/** Where a site serves its theme's font files from. */\nexport const SELF_HOSTED_FONT_ROUTE = '/api/fonts'\n\n/** The origin Google serves font files from. */\nexport const GOOGLE_FONT_FILE_ORIGIN = 'https://fonts.gstatic.com'\n\n/**\n * A font file's path on {@link GOOGLE_FONT_FILE_ORIGIN} below `/s/`, as the\n * route accepts it: `{family}/v{n}/{file}.woff2`. Anything else is refused,\n * so the route can never be used to fetch an arbitrary URL. A file name runs\n * to 400 characters: a variable family with many axes (Roboto Flex) is named\n * by an id of about 250, which a 200 cap refused, dropping every face of the\n * family (AGL-3656).\n */\nconst FONT_FILE_PATH = /^[a-z0-9]+\\/v\\d{1,4}\\/[A-Za-z0-9_-]{1,400}\\.woff2$/\n\n/** One `@font-face` rule from Google's stylesheet. */\nexport interface GoogleFontFace {\n family: string\n style: 'normal' | 'italic'\n /** A weight (`400`) or, for a variable font, a range (`100 900`). */\n weight: string\n /** Google's subset comment above the rule (`latin`, `latin-ext`, …). */\n subset?: string\n /** The file's path below `/s/` on Google's origin. */\n path: string\n unicodeRange?: string\n}\n\n/** Whether a path is one {@link SELF_HOSTED_FONT_ROUTE} will serve. */\nexport function isSelfHostedFontPath(path: string): boolean {\n return FONT_FILE_PATH.test(path)\n}\n\n/** The site-relative URL a face's file is served from. */\nexport function selfHostedFontUrl(path: string): string {\n return `${SELF_HOSTED_FONT_ROUTE}/${path}`\n}\n\n/** Google's own URL for a self-hosted path. */\nexport function googleFontFileUrl(path: string): string {\n return `${GOOGLE_FONT_FILE_ORIGIN}/s/${path}`\n}\n\nconst FACE_BLOCK = /(?:\\/\\*\\s*([a-z0-9-]+)\\s*\\*\\/\\s*)?@font-face\\s*\\{([^}]*)\\}/g\nconst FAMILY = /font-family:\\s*'([^']{1,80})'/\nconst STYLE = /font-style:\\s*(normal|italic)/\nconst WEIGHT = /font-weight:\\s*(\\d{3}(?:\\s+\\d{3})?)/\nconst SRC = /src:\\s*url\\((https:\\/\\/fonts\\.gstatic\\.com\\/s\\/[^)\\s]+)\\)\\s*format\\('woff2'\\)/\nconst RANGE = /unicode-range:\\s*([U+0-9A-Fa-f?,\\s-]{1,4000});/\n\n/**\n * Reads the `@font-face` rules out of a Google Fonts CSS2 stylesheet fetched\n * with a browser that accepts WOFF2. A rule that is not exactly the shape\n * Google writes — a missing field, a file off Google's origin, a path the\n * route would refuse — is dropped, never repaired.\n */\nexport function parseGoogleFontFaces(css: string): GoogleFontFace[] {\n const faces: GoogleFontFace[] = []\n for (const match of css.matchAll(FACE_BLOCK)) {\n const [, subset, body] = match\n const family = FAMILY.exec(body)?.[1]\n const style = STYLE.exec(body)?.[1] as GoogleFontFace['style'] | undefined\n const weight = WEIGHT.exec(body)?.[1]\n const url = SRC.exec(body)?.[1]\n if (!family || !style || !weight || !url) continue\n const path = url.slice(`${GOOGLE_FONT_FILE_ORIGIN}/s/`.length)\n if (!isSelfHostedFontPath(path)) continue\n const unicodeRange = RANGE.exec(body)?.[1]?.trim()\n faces.push({\n family,\n style,\n weight: weight.replace(/\\s+/g, ' '),\n ...(subset ? { subset } : {}),\n path,\n ...(unicodeRange ? { unicodeRange } : {}),\n })\n }\n return faces\n}\n\n/** A family name as it may appear quoted in CSS. */\nfunction cssFamily(family: string): string {\n return `'${family.replace(/['\\\\\\n\\r]/g, '')}'`\n}\n\n/**\n * The faces as `@font-face` rules on the site's own route, every one\n * `font-display: swap` so text paints in the fallback while a file loads.\n *\n * Every subset Google declared is kept, each with its `unicode-range`: a\n * browser downloads a subset only when the page uses a character in it, so\n * the Cyrillic file costs an English page nothing, and a Polish or Greek page\n * keeps its typeface. Only the Latin faces are preloaded.\n */\nexport function selfHostedFontFaceCss(faces: readonly GoogleFontFace[]): string {\n return faces\n .map((face) =>\n [\n '@font-face{',\n `font-family:${cssFamily(face.family)};`,\n `font-style:${face.style};`,\n `font-weight:${face.weight};`,\n 'font-display:swap;',\n `src:url(${selfHostedFontUrl(face.path)}) format('woff2');`,\n face.unicodeRange ? `unicode-range:${face.unicodeRange};` : '',\n '}',\n ].join(''),\n )\n .join('')\n}\n\n/** The first family named in a CSS `font-family` stack. */\nfunction firstFamily(stack: unknown): string | undefined {\n if (typeof stack !== 'string') return undefined\n const first = stack.split(',')[0]?.trim().replace(/^['\"]|['\"]$/g, '')\n return first || undefined\n}\n\n/** How far a face is from a wanted weight; 0 when its range covers it. */\nfunction weightDistance(face: { weight: string }, wanted: number): number {\n const [low, high = low] = face.weight.split(' ').map(Number)\n if (wanted >= low && wanted <= high) return 0\n return Math.min(Math.abs(wanted - low), Math.abs(wanted - high))\n}\n\n/** The Latin, upright face of `family` closest to `weight`. */\nfunction latinFace(\n faces: readonly GoogleFontFace[],\n family: string | undefined,\n weight: number,\n): GoogleFontFace | undefined {\n if (!family) return undefined\n const wanted = family.toLowerCase()\n const candidates = faces.filter(\n (face) =>\n face.family.toLowerCase() === wanted &&\n face.style === 'normal' &&\n (face.subset === undefined || face.subset === 'latin'),\n )\n return candidates.sort(\n (a, b) => weightDistance(a, weight) - weightDistance(b, weight),\n )[0]\n}\n\n/**\n * The paths of the one or two faces the first screen paints with: the body\n * text's face, and the headline's when it is a different file. Those are the\n * fonts the largest text on the page — usually the LCP element — waits for,\n * so they are worth starting with the document; every other face is fetched\n * when the page first uses it.\n *\n * The body face is the theme's `fontFamily` (else the first Google font it\n * loads) at `body1`'s weight, 400 by default; the headline face is `h1`'s\n * family and weight, 700 by default. A variable font serves every weight\n * from one file, which is then preloaded once.\n */\nexport function selfHostedFontPreloads(\n theme: Pick<HostTheme, 'fonts' | 'typography'> | undefined,\n faces: readonly GoogleFontFace[],\n): string[] {\n const typography = theme?.typography\n const variants = typography?.variants\n const fallback = theme?.fonts?.find(\n (font) => (font.source ?? 'google') === 'google',\n )?.family\n const bodyFamily = firstFamily(typography?.fontFamily) ?? fallback\n const body = latinFace(faces, bodyFamily, variants?.body1?.fontWeight ?? 400)\n const heading = latinFace(\n faces,\n firstFamily(variants?.h1?.fontFamily) ?? bodyFamily,\n variants?.h1?.fontWeight ?? 700,\n )\n const paths = [body, heading]\n .filter((face): face is GoogleFontFace => Boolean(face))\n .map((face) => selfHostedFontUrl(face.path))\n return [...new Set(paths)]\n}\n\n/* ------------------------------------------------------------------------ *\n * The loader (AGL-3656): faces per weight, metric-matched fallbacks, and the\n * site's own uploaded fonts.\n * ------------------------------------------------------------------------ */\n\n/**\n * One face a page declares, wherever its file lives: a Google face rewritten\n * onto {@link SELF_HOSTED_FONT_ROUTE}, or a site's own upload on its media\n * route. Both are on the site's own origin.\n */\nexport interface SiteFontFace {\n family: string\n style: 'normal' | 'italic'\n /** A weight (`400`) or, for a variable file, a range (`300 900`). */\n weight: string\n /** Site-relative URL of the WOFF2 file. */\n url: string\n /** Google's subset name (`latin`, …); absent for a file with no split. */\n subset?: string\n unicodeRange?: string\n}\n\n/** A Google face as a {@link SiteFontFace} on the site's own route. */\nexport function siteFontFaceFromGoogle(face: GoogleFontFace): SiteFontFace {\n return {\n family: face.family,\n style: face.style,\n weight: face.weight,\n url: selfHostedFontUrl(face.path),\n ...(face.subset ? { subset: face.subset } : {}),\n ...(face.unicodeRange ? { unicodeRange: face.unicodeRange } : {}),\n }\n}\n\n/**\n * The faces with every set of rules that share one file folded into one rule\n * over the weights it covers.\n *\n * Google answers a request for several weights of a variable family with the\n * SAME file once per weight, each rule naming one weight. A browser treats\n * those as separate faces: it matches 500 to nothing between the 400 and 600\n * rules a theme listed and renders it as 400, though the file holds every\n * weight in between. One rule declaring the range (`400 700`) is what the\n * file is.\n */\nexport function mergeSharedFiles<T extends { url: string; weight: string; family: string; style: string; unicodeRange?: string }>(\n faces: readonly T[],\n): T[] {\n const groups = new Map<string, T[]>()\n for (const face of faces) {\n const key = [face.family.toLowerCase(), face.style, face.url, face.unicodeRange ?? ''].join('|')\n const group = groups.get(key)\n if (group) group.push(face)\n else groups.set(key, [face])\n }\n return [...groups.values()].map((group) => {\n if (group.length === 1) return group[0]\n const bounds = group.flatMap((face) => face.weight.split(' ').map(Number))\n const low = Math.min(...bounds)\n const high = Math.max(...bounds)\n return { ...group[0], weight: low === high ? String(low) : `${low} ${high}` }\n })\n}\n\n/** The rules for a page's faces, every one `font-display: swap`. */\nexport function siteFontFaceCss(faces: readonly SiteFontFace[]): string {\n return faces\n .filter((face) => isSiteFontUrl(face.url))\n .map((face) =>\n [\n '@font-face{',\n `font-family:${cssFamily(face.family)};`,\n `font-style:${face.style};`,\n `font-weight:${/^\\d{1,4}( \\d{1,4})?$/.test(face.weight) ? face.weight : '400'};`,\n 'font-display:swap;',\n `src:url(${face.url}) format('woff2');`,\n face.unicodeRange && /^[U+0-9A-Fa-f?,\\s-]{1,4000}$/.test(face.unicodeRange)\n ? `unicode-range:${face.unicodeRange};`\n : '',\n '}',\n ].join(''),\n )\n .join('')\n}\n\n/**\n * Whether a URL is one a rule may name: site-relative, on the font route or\n * the media route, with nothing that could close the `url()` it is written\n * into. A face whose URL fails is dropped rather than escaped.\n */\nexport function isSiteFontUrl(url: string): boolean {\n return /^\\/api\\/(fonts|media\\/cdn)\\/[A-Za-z0-9_\\-./:%]{1,600}(\\?v=[A-Za-z0-9_.-]{1,128})?$/.test(url)\n}\n\n/** A local face a fallback is drawn from, measured the way a theme font is. */\ninterface LocalFallbackFace {\n /** `local()` names, in order. */\n local: string[]\n metrics: HostThemeFontMetrics\n}\n\n/**\n * The local faces fallbacks are drawn from, by category. Their metrics are\n * read from the faces themselves with the fonts plugin's reader and its\n * frequency-weighted average width (Arial 2048/1854/-434/67/901), so the ratio\n * to a web font measured the same way is what a line of text changes by.\n * Arial and Times New Roman ship with Windows and macOS; where neither is\n * installed the `local()` source fails and the stack moves on unadjusted.\n */\nconst LOCAL_FALLBACKS: Record<'sans-serif' | 'serif' | 'monospace', LocalFallbackFace> = {\n 'sans-serif': {\n local: ['Arial', 'ArialMT'],\n metrics: { unitsPerEm: 2048, ascent: 1854, descent: -434, lineGap: 67, xWidthAvg: 901 },\n },\n serif: {\n local: ['Times New Roman', 'TimesNewRomanPSMT'],\n metrics: { unitsPerEm: 2048, ascent: 1825, descent: -443, lineGap: 87, xWidthAvg: 817 },\n },\n monospace: {\n local: ['Courier New', 'CourierNewPSMT'],\n metrics: { unitsPerEm: 2048, ascent: 1705, descent: -615, lineGap: 0, xWidthAvg: 1229 },\n },\n}\n\nfunction localFallbackFor(category: HostThemeFontCategory | undefined): LocalFallbackFace {\n if (category === 'serif') return LOCAL_FALLBACKS.serif\n if (category === 'monospace') return LOCAL_FALLBACKS.monospace\n return LOCAL_FALLBACKS['sans-serif']\n}\n\nfunction percent(value: number): string {\n return `${(Math.round(value * 10000) / 100).toString()}%`\n}\n\n/** Whether metrics are numbers a rule can be written from. */\nfunction usableMetrics(metrics: HostThemeFontMetrics | undefined): metrics is HostThemeFontMetrics {\n return (\n !!metrics &&\n [metrics.unitsPerEm, metrics.ascent, metrics.descent, metrics.lineGap, metrics.xWidthAvg].every(\n (value) => typeof value === 'number' && Number.isFinite(value),\n ) &&\n metrics.unitsPerEm > 0 &&\n metrics.xWidthAvg > 0 &&\n metrics.ascent > 0\n )\n}\n\n/**\n * The `@font-face` rule for a family's metric-matched fallback: the local face\n * of its category, scaled so its average character is as wide as the web\n * font's (`size-adjust`) and given the web font's ascent, descent and line gap\n * at that scale. Text set in it occupies the box the web font will, so the\n * swap moves nothing — the CLS a `font-display: swap` page otherwise pays.\n *\n * The overrides are divided by the size adjustment because the browser\n * applies them to the ADJUSTED size: an ascent of 0.97em on a face scaled to\n * 1.07 has to be written as 0.97/1.07 to land at 0.97em.\n *\n * Empty for metrics that are missing or not numbers.\n */\nexport function metricFallbackFontFaceCss(\n family: string,\n metrics: HostThemeFontMetrics | undefined,\n category?: HostThemeFontCategory,\n): string {\n if (!usableMetrics(metrics)) return ''\n const local = localFallbackFor(category)\n const sizeAdjust =\n metrics.xWidthAvg / metrics.unitsPerEm / (local.metrics.xWidthAvg / local.metrics.unitsPerEm)\n if (!Number.isFinite(sizeAdjust) || sizeAdjust <= 0) return ''\n const over = (value: number) => percent(Math.abs(value) / metrics.unitsPerEm / sizeAdjust)\n return [\n '@font-face{',\n `font-family:${cssFamily(metricFallbackFamily(family))};`,\n `src:${local.local.map((name) => `local(${cssFamily(name)})`).join(',')};`,\n `size-adjust:${percent(sizeAdjust)};`,\n `ascent-override:${over(metrics.ascent)};`,\n `descent-override:${over(metrics.descent)};`,\n `line-gap-override:${over(metrics.lineGap)};`,\n '}',\n ].join('')\n}\n\n/** What one family of a theme needs loaded: the weights, and which as italics. */\nexport interface ThemeFontNeed {\n family: string\n /** Upright weights the theme's text styles and named weights draw with. */\n weights: number[]\n /** The subset of `weights` text styles alone use, which a delivery choice is measured on. */\n textWeights: number[]\n italics: number[]\n}\n\nconst NAMED_WEIGHTS: Record<string, number> = { normal: 400, bold: 700, lighter: 300, bolder: 700 }\n\nfunction numericWeight(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) return Math.round(value)\n if (typeof value !== 'string') return undefined\n if (/^\\d{3,4}$/.test(value.trim())) return Number(value.trim())\n return NAMED_WEIGHTS[value.trim().toLowerCase()]\n}\n\n/** MUI's four named weights, before a theme adds its own. */\nconst MUI_WEIGHT_TOKENS: Record<string, number> = {\n fontWeightLight: 300,\n fontWeightRegular: 400,\n fontWeightMedium: 500,\n fontWeightBold: 700,\n}\n\n/**\n * The faces each of a theme's web font families is drawn with (AGL-3656).\n *\n * A theme lists weights for its font (`fonts[].weights`), but the page draws\n * with whatever its text styles say: the platform's ramp sets `h1` in 900 and\n * `h2` in 800, so a theme that loaded Inter at 400, 500 and 700 drew its\n * headlines in a synthesized or substituted weight. The weights here are the\n * union of the listed ones, every text style's weight whose family resolves to\n * this one, and the theme's named weights (`fontWeightLight` … `Black`), which\n * the weight picker offers content.\n *\n * `baseTypography` is the typography the site's theme is layered onto\n * (`siteBaseOptions(...).typography`): a text style the theme leaves alone\n * keeps the base's weight and family.\n *\n * One italic is loaded per family, at the body text's weight, so emphasis in\n * running text is a real italic rather than a slanted upright; a theme can\n * list more under `italics`.\n */\nexport function themeFontNeeds(\n theme: Pick<HostTheme, 'fonts' | 'typography'> | undefined,\n baseTypography: Record<string, unknown> | undefined,\n extraFonts: readonly HostThemeFont[] = [],\n): ThemeFontNeed[] {\n const fonts = [...(theme?.fonts ?? []), ...extraFonts].filter(\n (font) => font.family && (font.source ?? 'google') !== 'system',\n )\n if (!fonts.length) return []\n const base = baseTypography ?? {}\n const variants = (theme?.typography?.variants ?? {}) as Record<string, Record<string, unknown> | undefined>\n const baseStack =\n (typeof theme?.typography?.fontFamily === 'string' && theme.typography.fontFamily) ||\n (typeof base['fontFamily'] === 'string' ? (base['fontFamily'] as string) : '')\n const needs = new Map<string, { family: string; weights: Set<number>; text: Set<number>; italics: Set<number> }>()\n for (const font of fonts) {\n const key = font.family.trim().toLowerCase()\n const need = needs.get(key) ?? { family: font.family.trim(), weights: new Set(), text: new Set(), italics: new Set() }\n for (const weight of font.weights ?? []) if (numericWeight(weight)) need.weights.add(numericWeight(weight) as number)\n for (const weight of font.italics ?? []) if (numericWeight(weight)) need.italics.add(numericWeight(weight) as number)\n needs.set(key, need)\n }\n const needFor = (stack: unknown) => {\n const first = typeof stack === 'string' ? stackFamilies(stack)[0] : undefined\n return first ? needs.get(first.toLowerCase()) : undefined\n }\n\n const variantKeys = new Set<string>([\n ...Object.keys(variants),\n ...Object.keys(base).filter((key) => {\n const value = base[key]\n return !!value && typeof value === 'object' && !Array.isArray(value)\n }),\n ])\n for (const key of variantKeys) {\n const own = variants[key] ?? {}\n const inherited = (base[key] as Record<string, unknown> | undefined) ?? {}\n const need = needFor(own['fontFamily'] ?? inherited['fontFamily'] ?? baseStack)\n if (!need) continue\n const weight = numericWeight(own['fontWeight'] ?? inherited['fontWeight']) ?? 400\n need.weights.add(weight)\n need.text.add(weight)\n }\n\n const bodyNeed = needFor(baseStack)\n if (bodyNeed) {\n const tokens: Record<string, unknown> = { ...MUI_WEIGHT_TOKENS }\n for (const [key, value] of Object.entries(base)) if (/^fontWeight[A-Z]/.test(key)) tokens[key] = value\n for (const [key, value] of Object.entries(tokens)) {\n const weight = numericWeight(value)\n if (!weight) continue\n bodyNeed.weights.add(weight)\n if (key !== 'fontWeightLight' && MUI_WEIGHT_TOKENS[key]) bodyNeed.text.add(weight)\n }\n const body = variants['body1'] ?? {}\n const bodyWeight =\n numericWeight(body['fontWeight'] ?? (base['body1'] as Record<string, unknown> | undefined)?.['fontWeight']) ?? 400\n bodyNeed.italics.add(bodyWeight)\n }\n\n return [...needs.values()].map((need) => ({\n family: need.family,\n weights: [...need.weights].sort((a, b) => a - b),\n textWeights: [...need.text].sort((a, b) => a - b),\n italics: [...need.italics].sort((a, b) => a - b),\n }))\n}\n\n/**\n * The weights to load from what a family offers: each wanted weight as the\n * nearest one offered, without repeats. Unknown availability keeps the\n * wanted weights as they are.\n */\nexport function offeredWeights(wanted: readonly number[], offered: readonly number[] | undefined): number[] {\n if (!offered?.length) return [...new Set(wanted)].sort((a, b) => a - b)\n const picked = wanted.map((weight) =>\n offered.reduce((best, candidate) =>\n Math.abs(candidate - weight) < Math.abs(best - weight) ? candidate : best,\n ),\n )\n return [...new Set(picked)].sort((a, b) => a - b)\n}\n\n/**\n * Google's CSS2 URL for some faces of one family: `ital,wght` tuples, sorted\n * as the API requires. One weight asks for a static instance, which for a\n * variable family is a file holding that weight alone — Inter's Latin 400 is\n * 24 KB that way and 48 KB as the variable file.\n */\nexport function googleFontSheetUrl(\n family: string,\n faces: ReadonlyArray<{ weight: number; style: 'normal' | 'italic' }>,\n): string | undefined {\n const name = family.trim()\n if (!name || !/^[A-Za-z0-9 ]{1,80}$/.test(name) || !faces.length) return undefined\n const tuples = [...new Set(faces.map((face) => `${face.style === 'italic' ? 1 : 0},${face.weight}`))].sort(\n (a, b) => {\n const [ai, aw] = a.split(',').map(Number)\n const [bi, bw] = b.split(',').map(Number)\n return ai - bi || aw - bw\n },\n )\n const axis = tuples.some((tuple) => tuple.startsWith('1,'))\n ? `ital,wght@${tuples.join(';')}`\n : `wght@${tuples.map((tuple) => tuple.slice(2)).join(';')}`\n return `https://fonts.googleapis.com/css2?family=${name.replace(/ /g, '+')}:${axis}&display=swap`\n}\n\n/**\n * The files the first screen paints with, to preload: the body text's face\n * and the headline's, Latin and upright, from whatever faces the page\n * declares. A file covering both is preloaded once.\n */\nexport function siteFontPreloads(\n theme: Pick<HostTheme, 'fonts' | 'typography'> | undefined,\n faces: readonly SiteFontFace[],\n baseTypography?: Record<string, unknown>,\n): string[] {\n const typography = theme?.typography\n const variants = typography?.variants\n const base = baseTypography ?? {}\n const baseH1 = (base['h1'] as Record<string, unknown> | undefined) ?? {}\n const baseBody = (base['body1'] as Record<string, unknown> | undefined) ?? {}\n const fallback = theme?.fonts?.find((font) => (font.source ?? 'google') !== 'system')?.family\n const bodyFamily =\n firstFamily(typography?.fontFamily) ?? firstFamily(base['fontFamily']) ?? fallback\n const pick = (family: string | undefined, weight: number) => {\n if (!family) return undefined\n const wanted = family.toLowerCase()\n const candidates = faces.filter(\n (face) =>\n face.family.toLowerCase() === wanted &&\n face.style === 'normal' &&\n (face.subset === undefined || face.subset === 'latin'),\n )\n return candidates.sort(\n (a, b) => weightDistance(a, weight) - weightDistance(b, weight),\n )[0]\n }\n const body = pick(bodyFamily, numericWeight(variants?.body1?.fontWeight ?? baseBody['fontWeight']) ?? 400)\n const heading = pick(\n firstFamily(variants?.h1?.fontFamily) ?? firstFamily(baseH1['fontFamily']) ?? bodyFamily,\n numericWeight(variants?.h1?.fontWeight ?? baseH1['fontWeight']) ?? 700,\n )\n return [...new Set([body, heading].filter((face): face is SiteFontFace => !!face).map((face) => face.url))]\n}\n"],"names":["metricFallbackFamily","stackFamilies","themeWebFontFamilies","withMetricFallbacks","SELF_HOSTED_FONT_ROUTE","GOOGLE_FONT_FILE_ORIGIN","FONT_FILE_PATH","isSelfHostedFontPath","path","test","selfHostedFontUrl","googleFontFileUrl","FACE_BLOCK","FAMILY","STYLE","WEIGHT","SRC","RANGE","parseGoogleFontFaces","css","faces","match","matchAll","subset","body","family","exec","style","weight","url","slice","length","unicodeRange","trim","push","replace","cssFamily","selfHostedFontFaceCss","map","face","join","firstFamily","stack","undefined","first","split","weightDistance","wanted","low","high","Number","Math","min","abs","latinFace","toLowerCase","candidates","filter","sort","a","b","selfHostedFontPreloads","theme","variants","typography","fallback","fonts","find","font","source","bodyFamily","fontFamily","body1","fontWeight","heading","h1","paths","Boolean","Set","siteFontFaceFromGoogle","mergeSharedFiles","groups","Map","key","group","get","set","values","bounds","flatMap","max","String","siteFontFaceCss","isSiteFontUrl","LOCAL_FALLBACKS","local","metrics","unitsPerEm","ascent","descent","lineGap","xWidthAvg","serif","monospace","localFallbackFor","category","percent","value","round","toString","usableMetrics","every","isFinite","metricFallbackFontFaceCss","sizeAdjust","over","name","NAMED_WEIGHTS","normal","bold","lighter","bolder","numericWeight","MUI_WEIGHT_TOKENS","fontWeightLight","fontWeightRegular","fontWeightMedium","fontWeightBold","themeFontNeeds","baseTypography","extraFonts","base","baseStack","needs","need","weights","text","italics","add","needFor","variantKeys","Object","keys","Array","isArray","own","inherited","bodyNeed","tokens","entries","bodyWeight","textWeights","offeredWeights","offered","picked","reduce","best","candidate","googleFontSheetUrl","tuples","ai","aw","bi","bw","axis","some","tuple","startsWith","siteFontPreloads","baseH1","baseBody","pick"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAQD,SAASA,oBAAoB,EAAEC,aAAa,QAAQ,kBAAc;AAElE,SAASD,oBAAoB,EAAEE,oBAAoB,EAAEC,mBAAmB,QAAQ,kBAAc;AAE9F;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BC,GAED,qDAAqD,GACrD,OAAO,MAAMC,yBAAyB,aAAY;AAElD,8CAA8C,GAC9C,OAAO,MAAMC,0BAA0B,4BAA2B;AAElE;;;;;;;CAOC,GACD,MAAMC,iBAAiB;AAevB,qEAAqE,GACrE,OAAO,SAASC,qBAAqBC,IAAY;IAC/C,OAAOF,eAAeG,IAAI,CAACD;AAC7B;AAEA,wDAAwD,GACxD,OAAO,SAASE,kBAAkBF,IAAY;IAC5C,OAAO,GAAGJ,uBAAuB,CAAC,EAAEI,MAAM;AAC5C;AAEA,6CAA6C,GAC7C,OAAO,SAASG,kBAAkBH,IAAY;IAC5C,OAAO,GAAGH,wBAAwB,GAAG,EAAEG,MAAM;AAC/C;AAEA,MAAMI,aAAa;AACnB,MAAMC,SAAS;AACf,MAAMC,QAAQ;AACd,MAAMC,SAAS;AACf,MAAMC,MAAM;AACZ,MAAMC,QAAQ;AAEd;;;;;CAKC,GACD,OAAO,SAASC,qBAAqBC,GAAW;IAC9C,MAAMC,QAA0B,EAAE;IAClC,KAAK,MAAMC,SAASF,IAAIG,QAAQ,CAACV,YAAa;YAE7BC,cACDC,aACCC,cACHC,WAISC,cAAAA;QARrB,MAAM,GAAGM,QAAQC,KAAK,GAAGH;QACzB,MAAMI,UAASZ,eAAAA,OAAOa,IAAI,CAACF,0BAAZX,YAAmB,CAAC,EAAE;QACrC,MAAMc,SAAQb,cAAAA,MAAMY,IAAI,CAACF,0BAAXV,WAAkB,CAAC,EAAE;QACnC,MAAMc,UAASb,eAAAA,OAAOW,IAAI,CAACF,0BAAZT,YAAmB,CAAC,EAAE;QACrC,MAAMc,OAAMb,YAAAA,IAAIU,IAAI,CAACF,0BAATR,SAAgB,CAAC,EAAE;QAC/B,IAAI,CAACS,UAAU,CAACE,SAAS,CAACC,UAAU,CAACC,KAAK;QAC1C,MAAMrB,OAAOqB,IAAIC,KAAK,CAAC,GAAGzB,wBAAwB,GAAG,CAAC,CAAC0B,MAAM;QAC7D,IAAI,CAACxB,qBAAqBC,OAAO;QACjC,MAAMwB,gBAAef,cAAAA,MAAMS,IAAI,CAACF,2BAAXP,eAAAA,WAAkB,CAAC,EAAE,qBAArBA,aAAuBgB,IAAI;QAChDb,MAAMc,IAAI,CAAC;YACTT;YACAE;YACAC,QAAQA,OAAOO,OAAO,CAAC,QAAQ;WAC3BZ,SAAS;YAAEA;QAAO,IAAI,CAAC;YAC3Bf;WACIwB,eAAe;YAAEA;QAAa,IAAI,CAAC;IAE3C;IACA,OAAOZ;AACT;AAEA,kDAAkD,GAClD,SAASgB,UAAUX,MAAc;IAC/B,OAAO,CAAC,CAAC,EAAEA,OAAOU,OAAO,CAAC,cAAc,IAAI,CAAC,CAAC;AAChD;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASE,sBAAsBjB,KAAgC;IACpE,OAAOA,MACJkB,GAAG,CAAC,CAACC,OACJ;YACE;YACA,CAAC,YAAY,EAAEH,UAAUG,KAAKd,MAAM,EAAE,CAAC,CAAC;YACxC,CAAC,WAAW,EAAEc,KAAKZ,KAAK,CAAC,CAAC,CAAC;YAC3B,CAAC,YAAY,EAAEY,KAAKX,MAAM,CAAC,CAAC,CAAC;YAC7B;YACA,CAAC,QAAQ,EAAElB,kBAAkB6B,KAAK/B,IAAI,EAAE,kBAAkB,CAAC;YAC3D+B,KAAKP,YAAY,GAAG,CAAC,cAAc,EAAEO,KAAKP,YAAY,CAAC,CAAC,CAAC,GAAG;YAC5D;SACD,CAACQ,IAAI,CAAC,KAERA,IAAI,CAAC;AACV;AAEA,yDAAyD,GACzD,SAASC,YAAYC,KAAc;QAEnBA;IADd,IAAI,OAAOA,UAAU,UAAU,OAAOC;IACtC,MAAMC,SAAQF,gBAAAA,MAAMG,KAAK,CAAC,IAAI,CAAC,EAAE,qBAAnBH,cAAqBT,IAAI,GAAGE,OAAO,CAAC,gBAAgB;IAClE,OAAOS,SAASD;AAClB;AAEA,wEAAwE,GACxE,SAASG,eAAeP,IAAwB,EAAEQ,MAAc;IAC9D,MAAM,CAACC,KAAKC,OAAOD,GAAG,CAAC,GAAGT,KAAKX,MAAM,CAACiB,KAAK,CAAC,KAAKP,GAAG,CAACY;IACrD,IAAIH,UAAUC,OAAOD,UAAUE,MAAM,OAAO;IAC5C,OAAOE,KAAKC,GAAG,CAACD,KAAKE,GAAG,CAACN,SAASC,MAAMG,KAAKE,GAAG,CAACN,SAASE;AAC5D;AAEA,6DAA6D,GAC7D,SAASK,UACPlC,KAAgC,EAChCK,MAA0B,EAC1BG,MAAc;IAEd,IAAI,CAACH,QAAQ,OAAOkB;IACpB,MAAMI,SAAStB,OAAO8B,WAAW;IACjC,MAAMC,aAAapC,MAAMqC,MAAM,CAC7B,CAAClB,OACCA,KAAKd,MAAM,CAAC8B,WAAW,OAAOR,UAC9BR,KAAKZ,KAAK,KAAK,YACdY,CAAAA,KAAKhB,MAAM,KAAKoB,aAAaJ,KAAKhB,MAAM,KAAK,OAAM;IAExD,OAAOiC,WAAWE,IAAI,CACpB,CAACC,GAAGC,IAAMd,eAAea,GAAG/B,UAAUkB,eAAec,GAAGhC,QACzD,CAAC,EAAE;AACN;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,SAASiC,uBACdC,KAA0D,EAC1D1C,KAAgC;QAObqB,oBAIjBA;QAPeqB,mBAAAA,cAIyBC,iBAG5BA,cACZA;IAVF,MAAMC,aAAaF,yBAAAA,MAAOE,UAAU;IACpC,MAAMD,WAAWC,8BAAAA,WAAYD,QAAQ;IACrC,MAAME,WAAWH,0BAAAA,eAAAA,MAAOI,KAAK,sBAAZJ,oBAAAA,aAAcK,IAAI,CACjC,CAACC;YAAUA;eAAD,EAACA,eAAAA,KAAKC,MAAM,YAAXD,eAAe,cAAc;2BADzBN,kBAEdrC,MAAM;IACT,MAAM6C,cAAa7B,eAAAA,YAAYuB,8BAAAA,WAAYO,UAAU,aAAlC9B,eAAuCwB;IAC1D,MAAMzC,OAAO8B,UAAUlC,OAAOkD,oBAAYP,6BAAAA,kBAAAA,SAAUS,KAAK,qBAAfT,gBAAiBU,UAAU,mBAAI;IACzE,MAAMC,UAAUpB,UACdlC,QACAqB,gBAAAA,YAAYsB,6BAAAA,eAAAA,SAAUY,EAAE,qBAAZZ,aAAcQ,UAAU,aAApC9B,gBAAyC6B,qBACzCP,6BAAAA,gBAAAA,SAAUY,EAAE,qBAAZZ,cAAcU,UAAU,oBAAI;IAE9B,MAAMG,QAAQ;QAACpD;QAAMkD;KAAQ,CAC1BjB,MAAM,CAAC,CAAClB,OAAiCsC,QAAQtC,OACjDD,GAAG,CAAC,CAACC,OAAS7B,kBAAkB6B,KAAK/B,IAAI;IAC5C,OAAO;WAAI,IAAIsE,IAAIF;KAAO;AAC5B;AAwBA,qEAAqE,GACrE,OAAO,SAASG,uBAAuBxC,IAAoB;IACzD,OAAO;QACLd,QAAQc,KAAKd,MAAM;QACnBE,OAAOY,KAAKZ,KAAK;QACjBC,QAAQW,KAAKX,MAAM;QACnBC,KAAKnB,kBAAkB6B,KAAK/B,IAAI;OAC5B+B,KAAKhB,MAAM,GAAG;QAAEA,QAAQgB,KAAKhB,MAAM;IAAC,IAAI,CAAC,GACzCgB,KAAKP,YAAY,GAAG;QAAEA,cAAcO,KAAKP,YAAY;IAAC,IAAI,CAAC;AAEnE;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASgD,iBACd5D,KAAmB;IAEnB,MAAM6D,SAAS,IAAIC;IACnB,KAAK,MAAM3C,QAAQnB,MAAO;YACsCmB;QAA9D,MAAM4C,MAAM;YAAC5C,KAAKd,MAAM,CAAC8B,WAAW;YAAIhB,KAAKZ,KAAK;YAAEY,KAAKV,GAAG;aAAEU,qBAAAA,KAAKP,YAAY,YAAjBO,qBAAqB;SAAG,CAACC,IAAI,CAAC;QAC5F,MAAM4C,QAAQH,OAAOI,GAAG,CAACF;QACzB,IAAIC,OAAOA,MAAMlD,IAAI,CAACK;aACjB0C,OAAOK,GAAG,CAACH,KAAK;YAAC5C;SAAK;IAC7B;IACA,OAAO;WAAI0C,OAAOM,MAAM;KAAG,CAACjD,GAAG,CAAC,CAAC8C;QAC/B,IAAIA,MAAMrD,MAAM,KAAK,GAAG,OAAOqD,KAAK,CAAC,EAAE;QACvC,MAAMI,SAASJ,MAAMK,OAAO,CAAC,CAAClD,OAASA,KAAKX,MAAM,CAACiB,KAAK,CAAC,KAAKP,GAAG,CAACY;QAClE,MAAMF,MAAMG,KAAKC,GAAG,IAAIoC;QACxB,MAAMvC,OAAOE,KAAKuC,GAAG,IAAIF;QACzB,OAAO,aAAKJ,KAAK,CAAC,EAAE;YAAExD,QAAQoB,QAAQC,OAAO0C,OAAO3C,OAAO,GAAGA,IAAI,CAAC,EAAEC,MAAM;;IAC7E;AACF;AAEA,kEAAkE,GAClE,OAAO,SAAS2C,gBAAgBxE,KAA8B;IAC5D,OAAOA,MACJqC,MAAM,CAAC,CAAClB,OAASsD,cAActD,KAAKV,GAAG,GACvCS,GAAG,CAAC,CAACC,OACJ;YACE;YACA,CAAC,YAAY,EAAEH,UAAUG,KAAKd,MAAM,EAAE,CAAC,CAAC;YACxC,CAAC,WAAW,EAAEc,KAAKZ,KAAK,CAAC,CAAC,CAAC;YAC3B,CAAC,YAAY,EAAE,uBAAuBlB,IAAI,CAAC8B,KAAKX,MAAM,IAAIW,KAAKX,MAAM,GAAG,MAAM,CAAC,CAAC;YAChF;YACA,CAAC,QAAQ,EAAEW,KAAKV,GAAG,CAAC,kBAAkB,CAAC;YACvCU,KAAKP,YAAY,IAAI,+BAA+BvB,IAAI,CAAC8B,KAAKP,YAAY,IACtE,CAAC,cAAc,EAAEO,KAAKP,YAAY,CAAC,CAAC,CAAC,GACrC;YACJ;SACD,CAACQ,IAAI,CAAC,KAERA,IAAI,CAAC;AACV;AAEA;;;;CAIC,GACD,OAAO,SAASqD,cAAchE,GAAW;IACvC,OAAO,qFAAqFpB,IAAI,CAACoB;AACnG;AASA;;;;;;;CAOC,GACD,MAAMiE,kBAAmF;IACvF,cAAc;QACZC,OAAO;YAAC;YAAS;SAAU;QAC3BC,SAAS;YAAEC,YAAY;YAAMC,QAAQ;YAAMC,SAAS,CAAC;YAAKC,SAAS;YAAIC,WAAW;QAAI;IACxF;IACAC,OAAO;QACLP,OAAO;YAAC;YAAmB;SAAoB;QAC/CC,SAAS;YAAEC,YAAY;YAAMC,QAAQ;YAAMC,SAAS,CAAC;YAAKC,SAAS;YAAIC,WAAW;QAAI;IACxF;IACAE,WAAW;QACTR,OAAO;YAAC;YAAe;SAAiB;QACxCC,SAAS;YAAEC,YAAY;YAAMC,QAAQ;YAAMC,SAAS,CAAC;YAAKC,SAAS;YAAGC,WAAW;QAAK;IACxF;AACF;AAEA,SAASG,iBAAiBC,QAA2C;IACnE,IAAIA,aAAa,SAAS,OAAOX,gBAAgBQ,KAAK;IACtD,IAAIG,aAAa,aAAa,OAAOX,gBAAgBS,SAAS;IAC9D,OAAOT,eAAe,CAAC,aAAa;AACtC;AAEA,SAASY,QAAQC,KAAa;IAC5B,OAAO,GAAG,AAACxD,CAAAA,KAAKyD,KAAK,CAACD,QAAQ,SAAS,GAAE,EAAGE,QAAQ,GAAG,CAAC,CAAC;AAC3D;AAEA,4DAA4D,GAC5D,SAASC,cAAcd,OAAyC;IAC9D,OACE,CAAC,CAACA,WACF;QAACA,QAAQC,UAAU;QAAED,QAAQE,MAAM;QAAEF,QAAQG,OAAO;QAAEH,QAAQI,OAAO;QAAEJ,QAAQK,SAAS;KAAC,CAACU,KAAK,CAC7F,CAACJ,QAAU,OAAOA,UAAU,YAAYzD,OAAO8D,QAAQ,CAACL,WAE1DX,QAAQC,UAAU,GAAG,KACrBD,QAAQK,SAAS,GAAG,KACpBL,QAAQE,MAAM,GAAG;AAErB;AAEA;;;;;;;;;;;;CAYC,GACD,OAAO,SAASe,0BACdxF,MAAc,EACduE,OAAyC,EACzCS,QAAgC;IAEhC,IAAI,CAACK,cAAcd,UAAU,OAAO;IACpC,MAAMD,QAAQS,iBAAiBC;IAC/B,MAAMS,aACJlB,QAAQK,SAAS,GAAGL,QAAQC,UAAU,GAAIF,CAAAA,MAAMC,OAAO,CAACK,SAAS,GAAGN,MAAMC,OAAO,CAACC,UAAU,AAAD;IAC7F,IAAI,CAAC/C,OAAO8D,QAAQ,CAACE,eAAeA,cAAc,GAAG,OAAO;IAC5D,MAAMC,OAAO,CAACR,QAAkBD,QAAQvD,KAAKE,GAAG,CAACsD,SAASX,QAAQC,UAAU,GAAGiB;IAC/E,OAAO;QACL;QACA,CAAC,YAAY,EAAE9E,UAAUpC,qBAAqByB,SAAS,CAAC,CAAC;QACzD,CAAC,IAAI,EAAEsE,MAAMA,KAAK,CAACzD,GAAG,CAAC,CAAC8E,OAAS,CAAC,MAAM,EAAEhF,UAAUgF,MAAM,CAAC,CAAC,EAAE5E,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1E,CAAC,YAAY,EAAEkE,QAAQQ,YAAY,CAAC,CAAC;QACrC,CAAC,gBAAgB,EAAEC,KAAKnB,QAAQE,MAAM,EAAE,CAAC,CAAC;QAC1C,CAAC,iBAAiB,EAAEiB,KAAKnB,QAAQG,OAAO,EAAE,CAAC,CAAC;QAC5C,CAAC,kBAAkB,EAAEgB,KAAKnB,QAAQI,OAAO,EAAE,CAAC,CAAC;QAC7C;KACD,CAAC5D,IAAI,CAAC;AACT;AAYA,MAAM6E,gBAAwC;IAAEC,QAAQ;IAAKC,MAAM;IAAKC,SAAS;IAAKC,QAAQ;AAAI;AAElG,SAASC,cAAcf,KAAc;IACnC,IAAI,OAAOA,UAAU,YAAYzD,OAAO8D,QAAQ,CAACL,QAAQ,OAAOxD,KAAKyD,KAAK,CAACD;IAC3E,IAAI,OAAOA,UAAU,UAAU,OAAOhE;IACtC,IAAI,YAAYlC,IAAI,CAACkG,MAAM1E,IAAI,KAAK,OAAOiB,OAAOyD,MAAM1E,IAAI;IAC5D,OAAOoF,aAAa,CAACV,MAAM1E,IAAI,GAAGsB,WAAW,GAAG;AAClD;AAEA,2DAA2D,GAC3D,MAAMoE,oBAA4C;IAChDC,iBAAiB;IACjBC,mBAAmB;IACnBC,kBAAkB;IAClBC,gBAAgB;AAClB;AAEA;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC,eACdlE,KAA0D,EAC1DmE,cAAmD,EACnDC,aAAuC,EAAE;;QAOvBpE,mBAERA;IAPV,MAAMI,QAAQ;mBAAKJ,yBAAAA,MAAOI,KAAK,mBAAI,EAAE;WAAMgE;KAAW,CAACzE,MAAM,CAC3D,CAACW;YAAyBA;eAAhBA,KAAK3C,MAAM,IAAI,EAAC2C,eAAAA,KAAKC,MAAM,YAAXD,eAAe,cAAc;;IAEzD,IAAI,CAACF,MAAMnC,MAAM,EAAE,OAAO,EAAE;IAC5B,MAAMoG,OAAOF,yBAAAA,iBAAkB,CAAC;IAChC,MAAMlE,oBAAYD,0BAAAA,oBAAAA,MAAOE,UAAU,qBAAjBF,kBAAmBC,QAAQ,oBAAI,CAAC;IAClD,MAAMqE,YACJ,AAAC,QAAOtE,0BAAAA,qBAAAA,MAAOE,UAAU,qBAAjBF,mBAAmBS,UAAU,MAAK,YAAYT,MAAME,UAAU,CAACO,UAAU,IAChF,CAAA,OAAO4D,IAAI,CAAC,aAAa,KAAK,WAAYA,IAAI,CAAC,aAAa,GAAc,EAAC;IAC9E,MAAME,QAAQ,IAAInD;IAClB,KAAK,MAAMd,QAAQF,MAAO;YAEXmE,YACQjE,eACAA;QAHrB,MAAMe,MAAMf,KAAK3C,MAAM,CAACQ,IAAI,GAAGsB,WAAW;QAC1C,MAAM+E,QAAOD,aAAAA,MAAMhD,GAAG,CAACF,gBAAVkD,aAAkB;YAAE5G,QAAQ2C,KAAK3C,MAAM,CAACQ,IAAI;YAAIsG,SAAS,IAAIzD;YAAO0D,MAAM,IAAI1D;YAAO2D,SAAS,IAAI3D;QAAM;QACrH,KAAK,MAAMlD,WAAUwC,gBAAAA,KAAKmE,OAAO,YAAZnE,gBAAgB,EAAE,CAAE,IAAIsD,cAAc9F,SAAS0G,KAAKC,OAAO,CAACG,GAAG,CAAChB,cAAc9F;QACnG,KAAK,MAAMA,WAAUwC,gBAAAA,KAAKqE,OAAO,YAAZrE,gBAAgB,EAAE,CAAE,IAAIsD,cAAc9F,SAAS0G,KAAKG,OAAO,CAACC,GAAG,CAAChB,cAAc9F;QACnGyG,MAAM/C,GAAG,CAACH,KAAKmD;IACjB;IACA,MAAMK,UAAU,CAACjG;QACf,MAAME,QAAQ,OAAOF,UAAU,WAAWzC,cAAcyC,MAAM,CAAC,EAAE,GAAGC;QACpE,OAAOC,QAAQyF,MAAMhD,GAAG,CAACzC,MAAMW,WAAW,MAAMZ;IAClD;IAEA,MAAMiG,cAAc,IAAI9D,IAAY;WAC/B+D,OAAOC,IAAI,CAAC/E;WACZ8E,OAAOC,IAAI,CAACX,MAAM1E,MAAM,CAAC,CAAC0B;YAC3B,MAAMwB,QAAQwB,IAAI,CAAChD,IAAI;YACvB,OAAO,CAAC,CAACwB,SAAS,OAAOA,UAAU,YAAY,CAACoC,MAAMC,OAAO,CAACrC;QAChE;KACD;IACD,KAAK,MAAMxB,OAAOyD,YAAa;YACjB7E,eACOoE,WACEc,OAAAA,iBAENvB,gBAAcuB;QAJ7B,MAAMA,OAAMlF,gBAAAA,QAAQ,CAACoB,IAAI,YAAbpB,gBAAiB,CAAC;QAC9B,MAAMmF,aAAaf,YAAAA,IAAI,CAAChD,IAAI,YAATgD,YAAqD,CAAC;QACzE,MAAMG,OAAOK,SAAQM,SAAAA,kBAAAA,GAAG,CAAC,aAAa,YAAjBA,kBAAqBC,SAAS,CAAC,aAAa,YAA5CD,QAAgDb;QACrE,IAAI,CAACE,MAAM;QACX,MAAM1G,UAAS8F,iBAAAA,eAAcuB,kBAAAA,GAAG,CAAC,aAAa,YAAjBA,kBAAqBC,SAAS,CAAC,aAAa,aAA1DxB,iBAA+D;QAC9EY,KAAKC,OAAO,CAACG,GAAG,CAAC9G;QACjB0G,KAAKE,IAAI,CAACE,GAAG,CAAC9G;IAChB;IAEA,MAAMuH,WAAWR,QAAQP;IACzB,IAAIe,UAAU;YASCpF,iBAEX2D,iBAAclG;YAAuB2G;QAVvC,MAAMiB,SAAkC,aAAKzB;QAC7C,KAAK,MAAM,CAACxC,KAAKwB,MAAM,IAAIkC,OAAOQ,OAAO,CAAClB,MAAO,IAAI,mBAAmB1H,IAAI,CAAC0E,MAAMiE,MAAM,CAACjE,IAAI,GAAGwB;QACjG,KAAK,MAAM,CAACxB,KAAKwB,MAAM,IAAIkC,OAAOQ,OAAO,CAACD,QAAS;YACjD,MAAMxH,SAAS8F,cAAcf;YAC7B,IAAI,CAAC/E,QAAQ;YACbuH,SAASZ,OAAO,CAACG,GAAG,CAAC9G;YACrB,IAAIuD,QAAQ,qBAAqBwC,iBAAiB,CAACxC,IAAI,EAAEgE,SAASX,IAAI,CAACE,GAAG,CAAC9G;QAC7E;QACA,MAAMJ,QAAOuC,kBAAAA,QAAQ,CAAC,QAAQ,YAAjBA,kBAAqB,CAAC;QACnC,MAAMuF,cACJ5B,kBAAAA,eAAclG,mBAAAA,IAAI,CAAC,aAAa,YAAlBA,oBAAuB2G,cAAAA,IAAI,CAAC,QAAQ,qBAAd,AAACA,WAAuD,CAAC,aAAa,aAA1GT,kBAA+G;QACjHyB,SAASV,OAAO,CAACC,GAAG,CAACY;IACvB;IAEA,OAAO;WAAIjB,MAAM9C,MAAM;KAAG,CAACjD,GAAG,CAAC,CAACgG,OAAU,CAAA;YACxC7G,QAAQ6G,KAAK7G,MAAM;YACnB8G,SAAS;mBAAID,KAAKC,OAAO;aAAC,CAAC7E,IAAI,CAAC,CAACC,GAAGC,IAAMD,IAAIC;YAC9C2F,aAAa;mBAAIjB,KAAKE,IAAI;aAAC,CAAC9E,IAAI,CAAC,CAACC,GAAGC,IAAMD,IAAIC;YAC/C6E,SAAS;mBAAIH,KAAKG,OAAO;aAAC,CAAC/E,IAAI,CAAC,CAACC,GAAGC,IAAMD,IAAIC;QAChD,CAAA;AACF;AAEA;;;;CAIC,GACD,OAAO,SAAS4F,eAAezG,MAAyB,EAAE0G,OAAsC;IAC9F,IAAI,EAACA,2BAAAA,QAAS1H,MAAM,GAAE,OAAO;WAAI,IAAI+C,IAAI/B;KAAQ,CAACW,IAAI,CAAC,CAACC,GAAGC,IAAMD,IAAIC;IACrE,MAAM8F,SAAS3G,OAAOT,GAAG,CAAC,CAACV,SACzB6H,QAAQE,MAAM,CAAC,CAACC,MAAMC,YACpB1G,KAAKE,GAAG,CAACwG,YAAYjI,UAAUuB,KAAKE,GAAG,CAACuG,OAAOhI,UAAUiI,YAAYD;IAGzE,OAAO;WAAI,IAAI9E,IAAI4E;KAAQ,CAAChG,IAAI,CAAC,CAACC,GAAGC,IAAMD,IAAIC;AACjD;AAEA;;;;;CAKC,GACD,OAAO,SAASkG,mBACdrI,MAAc,EACdL,KAAoE;IAEpE,MAAMgG,OAAO3F,OAAOQ,IAAI;IACxB,IAAI,CAACmF,QAAQ,CAAC,uBAAuB3G,IAAI,CAAC2G,SAAS,CAAChG,MAAMW,MAAM,EAAE,OAAOY;IACzE,MAAMoH,SAAS;WAAI,IAAIjF,IAAI1D,MAAMkB,GAAG,CAAC,CAACC,OAAS,GAAGA,KAAKZ,KAAK,KAAK,WAAW,IAAI,EAAE,CAAC,EAAEY,KAAKX,MAAM,EAAE;KAAG,CAAC8B,IAAI,CACxG,CAACC,GAAGC;QACF,MAAM,CAACoG,IAAIC,GAAG,GAAGtG,EAAEd,KAAK,CAAC,KAAKP,GAAG,CAACY;QAClC,MAAM,CAACgH,IAAIC,GAAG,GAAGvG,EAAEf,KAAK,CAAC,KAAKP,GAAG,CAACY;QAClC,OAAO8G,KAAKE,MAAMD,KAAKE;IACzB;IAEF,MAAMC,OAAOL,OAAOM,IAAI,CAAC,CAACC,QAAUA,MAAMC,UAAU,CAAC,SACjD,CAAC,UAAU,EAAER,OAAOvH,IAAI,CAAC,MAAM,GAC/B,CAAC,KAAK,EAAEuH,OAAOzH,GAAG,CAAC,CAACgI,QAAUA,MAAMxI,KAAK,CAAC,IAAIU,IAAI,CAAC,MAAM;IAC7D,OAAO,CAAC,yCAAyC,EAAE4E,KAAKjF,OAAO,CAAC,MAAM,KAAK,CAAC,EAAEiI,KAAK,aAAa,CAAC;AACnG;AAEA;;;;CAIC,GACD,OAAO,SAASI,iBACd1G,KAA0D,EAC1D1C,KAA8B,EAC9B6G,cAAwC;QAKxBE,UACEA,aAGhB1F,MAAAA,cAc4BiF,uBAE5BjF,OAAAA,eACAiF;QAnBe5D,mBAAAA,cAgB2BC,iBAE9BA,cACEA;IAxBhB,MAAMC,aAAaF,yBAAAA,MAAOE,UAAU;IACpC,MAAMD,WAAWC,8BAAAA,WAAYD,QAAQ;IACrC,MAAMoE,OAAOF,yBAAAA,iBAAkB,CAAC;IAChC,MAAMwC,UAAUtC,WAAAA,IAAI,CAAC,KAAK,YAAVA,WAAsD,CAAC;IACvE,MAAMuC,YAAYvC,cAAAA,IAAI,CAAC,QAAQ,YAAbA,cAAyD,CAAC;IAC5E,MAAMlE,WAAWH,0BAAAA,eAAAA,MAAOI,KAAK,sBAAZJ,oBAAAA,aAAcK,IAAI,CAAC,CAACC;YAAUA;eAAD,EAACA,eAAAA,KAAKC,MAAM,YAAXD,eAAe,cAAc;2BAA3DN,kBAAsErC,MAAM;IAC7F,MAAM6C,cACJ7B,QAAAA,eAAAA,YAAYuB,8BAAAA,WAAYO,UAAU,aAAlC9B,eAAuCA,YAAY0F,IAAI,CAAC,aAAa,aAArE1F,OAA0EwB;IAC5E,MAAM0G,OAAO,CAAClJ,QAA4BG;QACxC,IAAI,CAACH,QAAQ,OAAOkB;QACpB,MAAMI,SAAStB,OAAO8B,WAAW;QACjC,MAAMC,aAAapC,MAAMqC,MAAM,CAC7B,CAAClB,OACCA,KAAKd,MAAM,CAAC8B,WAAW,OAAOR,UAC9BR,KAAKZ,KAAK,KAAK,YACdY,CAAAA,KAAKhB,MAAM,KAAKoB,aAAaJ,KAAKhB,MAAM,KAAK,OAAM;QAExD,OAAOiC,WAAWE,IAAI,CACpB,CAACC,GAAGC,IAAMd,eAAea,GAAG/B,UAAUkB,eAAec,GAAGhC,QACzD,CAAC,EAAE;IACN;IACA,MAAMJ,OAAOmJ,KAAKrG,aAAYoD,iBAAAA,uBAAc3D,6BAAAA,kBAAAA,SAAUS,KAAK,qBAAfT,gBAAiBU,UAAU,oBAAIiG,QAAQ,CAAC,aAAa,aAAnEhD,iBAAwE;IACtG,MAAMhD,UAAUiG,MACdlI,SAAAA,gBAAAA,YAAYsB,6BAAAA,eAAAA,SAAUY,EAAE,qBAAZZ,aAAcQ,UAAU,aAApC9B,gBAAyCA,YAAYgI,MAAM,CAAC,aAAa,aAAzEhI,QAA8E6B,aAC9EoD,kBAAAA,uBAAc3D,6BAAAA,gBAAAA,SAAUY,EAAE,qBAAZZ,cAAcU,UAAU,oBAAIgG,MAAM,CAAC,aAAa,aAA9D/C,kBAAmE;IAErE,OAAO;WAAI,IAAI5C,IAAI;YAACtD;YAAMkD;SAAQ,CAACjB,MAAM,CAAC,CAAClB,OAA+B,CAAC,CAACA,MAAMD,GAAG,CAAC,CAACC,OAASA,KAAKV,GAAG;KAAG;AAC7G"}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
import type { HostTheme, HostThemeScheme, HostThemeSchemeColors } from '@aglyn/shared-data-types';
|
|
17
|
+
import type { HostTheme, HostThemeFontCategory, HostThemeTypographyVariantKey, HostThemeScheme, HostThemeSchemeColors } from '@aglyn/shared-data-types';
|
|
18
18
|
import { type HostThemeComponentKey } from './host-theme';
|
|
19
19
|
/**
|
|
20
20
|
* The host theme editor's field catalog (AGL-2938): every control the editor
|
|
@@ -140,7 +140,7 @@ export declare const GOOGLE_FONT_OPTIONS: Array<{
|
|
|
140
140
|
category: 'sans-serif' | 'serif' | 'monospace' | 'display';
|
|
141
141
|
weights: Array<number>;
|
|
142
142
|
}>;
|
|
143
|
-
export declare function fontFamilyStack(family: string, category:
|
|
143
|
+
export declare function fontFamilyStack(family: string, category: HostThemeFontCategory): string;
|
|
144
144
|
export declare function getSchemeColor(colors: HostThemeSchemeColors | undefined, path: SurfaceColorPath): string | undefined;
|
|
145
145
|
/** The two color schemes the editor edits, in tab order. */
|
|
146
146
|
export declare const THEME_EDITOR_SCHEMES: readonly HostThemeScheme[];
|
|
@@ -277,6 +277,57 @@ export declare function readFontFamily(theme: HostTheme | undefined): string;
|
|
|
277
277
|
* theme default. A family outside the curated list changes nothing.
|
|
278
278
|
*/
|
|
279
279
|
export declare function writeFontFamily(theme: HostTheme, value: string): HostTheme;
|
|
280
|
+
/**
|
|
281
|
+
* The text styles a separate heading font is set on: `h1`…`h6`, and
|
|
282
|
+
* `displayXl`, the platform's rung above `h1`, which a heading font left off
|
|
283
|
+
* would draw in the body font — and load the body font's heaviest weight for.
|
|
284
|
+
*/
|
|
285
|
+
export declare const THEME_HEADING_VARIANTS: readonly HostThemeTypographyVariantKey[];
|
|
286
|
+
/**
|
|
287
|
+
* One family a theme sets text in, as the font picker shows and writes it
|
|
288
|
+
* (AGL-3656): a `fonts` entry and the role it plays.
|
|
289
|
+
*/
|
|
290
|
+
export interface ThemeFontChoice {
|
|
291
|
+
family: string;
|
|
292
|
+
category: HostThemeFontCategory;
|
|
293
|
+
/** Upright weights to load, ascending. */
|
|
294
|
+
weights: number[];
|
|
295
|
+
/** Italic weights to load, ascending; absent or empty for none. */
|
|
296
|
+
italics?: number[];
|
|
297
|
+
source: 'google' | 'custom';
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* The theme's body font and, when it differs, its heading font. `null` is the
|
|
301
|
+
* theme default: the base stack, nothing loaded.
|
|
302
|
+
*/
|
|
303
|
+
export interface ThemeFontSelection {
|
|
304
|
+
body: ThemeFontChoice | null;
|
|
305
|
+
heading: ThemeFontChoice | null;
|
|
306
|
+
}
|
|
307
|
+
/** The first family a CSS font-family stack names, unquoted. */
|
|
308
|
+
export declare function firstStackFamily(stack: unknown): string | undefined;
|
|
309
|
+
/**
|
|
310
|
+
* The body and heading fonts a theme sets text in (AGL-3656).
|
|
311
|
+
*
|
|
312
|
+
* The body font is the family the theme-wide stack names first, when the
|
|
313
|
+
* theme loads it: a font the theme lists that no stack names draws nothing,
|
|
314
|
+
* which is what the theme default is. The heading font is the family `h1`
|
|
315
|
+
* names, when that is a loaded font other than the body's.
|
|
316
|
+
*/
|
|
317
|
+
export declare function readThemeFonts(theme: HostTheme | undefined): ThemeFontSelection;
|
|
318
|
+
/**
|
|
319
|
+
* Sets the theme's body and heading fonts (AGL-3656): the `fonts` entries the
|
|
320
|
+
* page loads, the theme-wide stack, and the stack of each style in
|
|
321
|
+
* {@link THEME_HEADING_VARIANTS} for a heading font of its own. `null` goes
|
|
322
|
+
* back to the theme default for that role.
|
|
323
|
+
*
|
|
324
|
+
* An uploaded (`custom`) font the theme lists but no longer sets text in is
|
|
325
|
+
* kept, because it is the site's own and is picked again from the list; a
|
|
326
|
+
* Google font nothing sets text in any more is dropped, so the page stops
|
|
327
|
+
* loading it. Everything else in the typography — sizes, weights, a variant's own
|
|
328
|
+
* family that is not a heading — is left as it was.
|
|
329
|
+
*/
|
|
330
|
+
export declare function writeThemeFonts(theme: HostTheme, selection: ThemeFontSelection): HostTheme;
|
|
280
331
|
/** Sets the corner radius, or clears it with `undefined` so it inherits. */
|
|
281
332
|
export declare function writeBorderRadius(theme: HostTheme, value: number | undefined): HostTheme;
|
|
282
333
|
/** Sets the spacing unit; anything but a positive number clears it. */
|