@uniweb/theming 0.1.4 → 0.1.6
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/css-generator.js +83 -24
package/package.json
CHANGED
package/src/css-generator.js
CHANGED
|
@@ -210,32 +210,56 @@ const GENERIC_FAMILIES = new Set([
|
|
|
210
210
|
'inherit', 'initial', 'revert', 'unset',
|
|
211
211
|
])
|
|
212
212
|
|
|
213
|
+
// Foundation `font-*` vars that are CSS longhands, not typefaces — never
|
|
214
|
+
// treat their value as a family to load.
|
|
215
|
+
const NON_FAMILY_FONT_VARS = new Set([
|
|
216
|
+
'font-weight', 'font-style', 'font-size', 'font-stretch',
|
|
217
|
+
'font-feature-settings', 'font-variation-settings', 'font-variant',
|
|
218
|
+
'font-kerning', 'font-optical-sizing', 'font-synthesis',
|
|
219
|
+
'font-size-adjust', 'font-smoothing', 'font-palette', 'font-language-override',
|
|
220
|
+
])
|
|
221
|
+
|
|
213
222
|
/**
|
|
214
|
-
* Extract font family names
|
|
223
|
+
* Extract the font family names the site actually references.
|
|
215
224
|
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
225
|
+
* Two sources count as "used":
|
|
226
|
+
* 1. The `body`/`heading`/`mono` slots the user explicitly configured
|
|
227
|
+
* (tracked via _userSlots from the processor). Default system stacks are
|
|
228
|
+
* ignored so they don't trigger unnecessary font loading.
|
|
229
|
+
* 2. Foundation `font-*` vars (e.g. an editorial `font-serif`). A foundation
|
|
230
|
+
* can declare a themeable typeface beyond the three built-in roles; the
|
|
231
|
+
* site sets its value and loads it with `fonts.import` / `fonts.faces`.
|
|
232
|
+
* Without counting these, such a family is filtered out as "unused" and
|
|
233
|
+
* never loads — capping a foundation at the three role slots.
|
|
219
234
|
*
|
|
220
|
-
* @param {Object} fonts - Font configuration
|
|
221
|
-
* @
|
|
235
|
+
* @param {Object} fonts - Font configuration (may carry _userSlots)
|
|
236
|
+
* @param {Object} [foundationVars] - Resolved foundation vars (name → { default } | value)
|
|
237
|
+
* @returns {Set<string>} Lowercase family names referenced by the site
|
|
222
238
|
*/
|
|
223
|
-
function extractUsedFamilies(fonts) {
|
|
239
|
+
function extractUsedFamilies(fonts, foundationVars = {}) {
|
|
224
240
|
const used = new Set()
|
|
225
|
-
const slots = fonts._userSlots || ['body', 'heading', 'mono']
|
|
226
|
-
|
|
227
|
-
for (const slot of slots) {
|
|
228
|
-
const value = fonts[slot]
|
|
229
|
-
if (!value) continue
|
|
230
241
|
|
|
242
|
+
const addFamiliesFrom = (value) => {
|
|
243
|
+
if (typeof value !== 'string') return
|
|
231
244
|
for (const segment of value.split(',')) {
|
|
232
245
|
const name = segment.trim().replace(/^["']|["']$/g, '').toLowerCase()
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
246
|
+
// Skip generics, empties, and numeric/length tokens (a weight "600" or
|
|
247
|
+
// size "1rem" a non-family font-* var might carry).
|
|
248
|
+
if (!name || GENERIC_FAMILIES.has(name) || /^\d/.test(name)) continue
|
|
249
|
+
used.add(name)
|
|
236
250
|
}
|
|
237
251
|
}
|
|
238
252
|
|
|
253
|
+
const slots = fonts._userSlots || ['body', 'heading', 'mono']
|
|
254
|
+
for (const slot of slots) {
|
|
255
|
+
addFamiliesFrom(fonts[slot])
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
for (const [name, config] of Object.entries(foundationVars)) {
|
|
259
|
+
if (!name.startsWith('font-') || NON_FAMILY_FONT_VARS.has(name)) continue
|
|
260
|
+
addFamiliesFrom(typeof config === 'object' ? config?.default : config)
|
|
261
|
+
}
|
|
262
|
+
|
|
239
263
|
return used
|
|
240
264
|
}
|
|
241
265
|
|
|
@@ -288,11 +312,11 @@ function isGoogleFontsUrl(url) {
|
|
|
288
312
|
* @param {Object} fonts - Font configuration
|
|
289
313
|
* @returns {{ css: string, links: string }} css for <style>, links for <head>
|
|
290
314
|
*/
|
|
291
|
-
function generateFontCSS(fonts = {}) {
|
|
315
|
+
function generateFontCSS(fonts = {}, foundationVars = {}) {
|
|
292
316
|
const cssLines = []
|
|
293
317
|
const linkTags = []
|
|
294
318
|
|
|
295
|
-
const usedFamilies = extractUsedFamilies(fonts)
|
|
319
|
+
const usedFamilies = extractUsedFamilies(fonts, foundationVars)
|
|
296
320
|
|
|
297
321
|
// --- @font-face rules from faces[] (filtered to used families) ---
|
|
298
322
|
// Also generate <link rel="preload"> hints so browsers fetch fonts early
|
|
@@ -358,16 +382,22 @@ function generateFontCSS(fonts = {}) {
|
|
|
358
382
|
}
|
|
359
383
|
|
|
360
384
|
if (allFamilies.length > 0) {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
385
|
+
// Build the css2 URL by hand. Google's css2 axis syntax uses ':' ',' ';'
|
|
386
|
+
// '@' as LITERAL separators and '+' for spaces. URLSearchParams.toString()
|
|
387
|
+
// percent-encodes all of them (%3A/%2C/%3B/%40), producing a URL that
|
|
388
|
+
// parses but is non-canonical — it only loads because Google's server
|
|
389
|
+
// decodes it leniently. A strict Google Fonts mirror/proxy or a future API
|
|
390
|
+
// change could reject it. Emit the canonical literal form instead.
|
|
391
|
+
// (getAll() already decoded '+'/'%20' to spaces, so restore spaces to '+'.)
|
|
392
|
+
const familyQuery = allFamilies
|
|
393
|
+
.map((f) => `family=${f.trim().replace(/\s+/g, '+')}`)
|
|
394
|
+
.join('&')
|
|
395
|
+
const href = `https://fonts.googleapis.com/css2?${familyQuery}&display=${display}`
|
|
366
396
|
|
|
367
397
|
linkTags.unshift(
|
|
368
398
|
`<link rel="preconnect" href="https://fonts.googleapis.com">`,
|
|
369
399
|
`<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>`,
|
|
370
|
-
`<link rel="stylesheet" href="${
|
|
400
|
+
`<link rel="stylesheet" href="${href}">`,
|
|
371
401
|
)
|
|
372
402
|
}
|
|
373
403
|
}
|
|
@@ -390,6 +420,33 @@ function generateFontCSS(fonts = {}) {
|
|
|
390
420
|
cssLines.push('}')
|
|
391
421
|
}
|
|
392
422
|
|
|
423
|
+
// --- Apply font families to elements ---
|
|
424
|
+
// Defining --font-* on :root is not enough — something must actually set
|
|
425
|
+
// `font-family` on rendered elements, the same way colors get an application
|
|
426
|
+
// rule ([id^="section-"] { background-color: var(--section) }). Without this
|
|
427
|
+
// the font vars are orphaned and text never picks them up, even though the
|
|
428
|
+
// vars are set and the @import/@font-face load. Only apply slots the SITE
|
|
429
|
+
// explicitly set (fonts._userSlots) so a foundation's own default typography
|
|
430
|
+
// is left untouched when theme.yml doesn't override that slot.
|
|
431
|
+
const userSlots = new Set(fonts._userSlots || [])
|
|
432
|
+
const applyRules = []
|
|
433
|
+
if (userSlots.has('body')) {
|
|
434
|
+
applyRules.push('body { font-family: var(--font-body); }')
|
|
435
|
+
}
|
|
436
|
+
if (userSlots.has('heading')) {
|
|
437
|
+
// Only the prominent headings — display/heading faces are drawn for large
|
|
438
|
+
// sizes; h4–h6 usually render at or near body size where a display face
|
|
439
|
+
// reads awkwardly, so they stay in the body font.
|
|
440
|
+
applyRules.push('h1, h2, h3 { font-family: var(--font-heading); }')
|
|
441
|
+
}
|
|
442
|
+
if (userSlots.has('mono')) {
|
|
443
|
+
applyRules.push('code, pre, kbd, samp { font-family: var(--font-mono); }')
|
|
444
|
+
}
|
|
445
|
+
if (applyRules.length > 0) {
|
|
446
|
+
cssLines.push('')
|
|
447
|
+
cssLines.push(...applyRules)
|
|
448
|
+
}
|
|
449
|
+
|
|
393
450
|
return {
|
|
394
451
|
css: cssLines.join('\n'),
|
|
395
452
|
links: linkTags.join('\n'),
|
|
@@ -469,7 +526,9 @@ export function generateThemeCSS(config = {}) {
|
|
|
469
526
|
const sections = []
|
|
470
527
|
|
|
471
528
|
// 1. Font face rules and variables (links returned separately)
|
|
472
|
-
|
|
529
|
+
// foundationVars is passed so families referenced by a foundation `font-*`
|
|
530
|
+
// var (e.g. an editorial `font-serif`) survive the used-family filter.
|
|
531
|
+
const fontResult = generateFontCSS(fonts, foundationVars)
|
|
473
532
|
if (fontResult.css) {
|
|
474
533
|
sections.push('/* Typography */\n' + fontResult.css)
|
|
475
534
|
}
|