#2 Semantic leading/tracking config-driven: the per-role `--leading-{role}` (ui/prose/text/heading/display) + `--tracking-{role}` (badge/label/ui/prose/ heading/display) tokens were hardcoded in render-css; moved to `typography.semanticLeading` / `semanticTracking` (STATIC_TYPOGRAPHY), emitted config-driven + validated, so a theme can retune them. Byte-identical output (generated/base.css unchanged — same values, same order). #3 Font preloads: `collectFontPreloads(typography)` (pure) + `eidos.fontPreloads()` surface `<link rel=preload>` descriptors for families flagged `preload: true` (the engine emits CSS, not head markup), for the app `<svelte:head>`. Inert until a theme opts in. Docs: TYPOGRAPHY_ENGINE_RFC fases marked closed + Fase 4 (the two-zone scale is intentional — documented, not rewired); THEMING §10 — applyColorScheme + applyTypeScale system builders. `npm run check` 0 errors; new font-preload tests + config/generated tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>active-uix
parent
11fc93b97f
commit
4fe2ee27df
@ -0,0 +1,34 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { collectFontPreloads } from './font-preload';
|
||||
import type { TypographyPrimitiveSet } from './config-types';
|
||||
|
||||
function typography(families: Record<string, unknown>): TypographyPrimitiveSet {
|
||||
return { families, sizes: {}, weights: {}, styles: {} } as unknown as TypographyPrimitiveSet;
|
||||
}
|
||||
|
||||
describe('collectFontPreloads', () => {
|
||||
it('returns descriptors only for preload-flagged families, deduped by url', () => {
|
||||
const t = typography({
|
||||
primary: { family: 'A', preload: true, faces: [{ sources: [{ url: '/a.woff2', format: 'woff2' }] }] },
|
||||
secondary: { family: 'B', faces: [{ sources: [{ url: '/b.woff2', format: 'woff2' }] }] },
|
||||
display: { family: 'C', preload: true, faces: [{ sources: [{ url: '/a.woff2', format: 'woff2' }] }] }
|
||||
});
|
||||
expect(collectFontPreloads(t)).toEqual([{ href: '/a.woff2', type: 'font/woff2' }]);
|
||||
});
|
||||
|
||||
it('maps the face format to a MIME type (or omits it when unknown)', () => {
|
||||
const t = typography({
|
||||
primary: { family: 'A', preload: true, faces: [{ sources: [{ url: '/a.ttf', format: 'truetype' }] }] },
|
||||
mono: { family: 'M', preload: true, faces: [{ sources: [{ url: '/m.bin' }] }] }
|
||||
});
|
||||
expect(collectFontPreloads(t)).toEqual([
|
||||
{ href: '/a.ttf', type: 'font/ttf' },
|
||||
{ href: '/m.bin', type: undefined }
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns [] for undefined typography or no preload flags', () => {
|
||||
expect(collectFontPreloads(undefined)).toEqual([]);
|
||||
expect(collectFontPreloads(typography({ primary: { family: 'A', faces: [] } }))).toEqual([]);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Collect `<link rel="preload">` descriptors for the font families flagged `preload: true`
|
||||
* (the critical-path face — typically the primary regular). The eidos engine emits CSS,
|
||||
* not `<head>` markup, so it surfaces this list for the app to render in its
|
||||
* `<svelte:head>`:
|
||||
*
|
||||
* ```svelte
|
||||
* {#each eidos.fontPreloads() as p}
|
||||
* <link rel="preload" as="font" type={p.type} href={p.href} crossorigin />
|
||||
* {/each}
|
||||
* ```
|
||||
*
|
||||
* Pure + DOM-free. (TYPOGRAPHY_ENGINE_RFC §5)
|
||||
*/
|
||||
import type { TypographyPrimitiveSet } from './config-types';
|
||||
|
||||
export interface FontPreload {
|
||||
/** The font file URL (the family's primary face). */
|
||||
readonly href: string;
|
||||
/** MIME type for `<link type>` (e.g. `font/woff2`), when derivable from the face format. */
|
||||
readonly type?: string;
|
||||
}
|
||||
|
||||
const FORMAT_MIME: Record<string, string> = {
|
||||
woff2: 'font/woff2',
|
||||
'woff2-variations': 'font/woff2',
|
||||
woff: 'font/woff',
|
||||
truetype: 'font/ttf',
|
||||
opentype: 'font/otf'
|
||||
};
|
||||
|
||||
/**
|
||||
* Descriptors for every family flagged `preload: true`, deduped by URL. Picks each
|
||||
* family's first declared face / source (its primary). Returns `[]` when no family opts in.
|
||||
*/
|
||||
export function collectFontPreloads(typography: TypographyPrimitiveSet | undefined): FontPreload[] {
|
||||
if (!typography) return [];
|
||||
const preloads: FontPreload[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const family of Object.values(typography.families)) {
|
||||
if (!family.preload) continue;
|
||||
const source = family.faces?.[0]?.sources?.[0];
|
||||
if (!source || seen.has(source.url)) continue;
|
||||
seen.add(source.url);
|
||||
preloads.push({
|
||||
href: source.url,
|
||||
type: source.format ? FORMAT_MIME[source.format] : undefined
|
||||
});
|
||||
}
|
||||
return preloads;
|
||||
}
|
||||
Loading…
Reference in new issue