@adea-ai/ui 0.123.1 → 0.125.0
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/dist/NOTICE +36 -0
- package/dist/components/theme/appearance-panel.js +1 -1
- package/dist/components/theme/index.d.ts +1 -1
- package/dist/components/theme/index.d.ts.map +1 -1
- package/dist/components/theme/index.js +5 -2
- package/dist/components/theme/theme-context.d.ts +13 -0
- package/dist/components/theme/theme-context.d.ts.map +1 -0
- package/dist/components/theme/theme-context.js +41 -0
- package/dist/components/theme/theme-context.js.map +1 -0
- package/dist/components/theme/theme-document.d.ts +82 -0
- package/dist/components/theme/theme-document.d.ts.map +1 -0
- package/dist/components/theme/theme-document.js +98 -0
- package/dist/components/theme/theme-document.js.map +1 -0
- package/dist/components/theme/theme-persistence.d.ts +50 -0
- package/dist/components/theme/theme-persistence.d.ts.map +1 -0
- package/dist/components/theme/theme-persistence.js +87 -0
- package/dist/components/theme/theme-persistence.js.map +1 -0
- package/dist/components/theme/theme-provider.d.ts +52 -75
- package/dist/components/theme/theme-provider.d.ts.map +1 -1
- package/dist/components/theme/theme-provider.js +60 -116
- package/dist/components/theme/theme-provider.js.map +1 -1
- package/dist/components/theme/theme-state.d.ts +167 -0
- package/dist/components/theme/theme-state.d.ts.map +1 -0
- package/dist/components/theme/theme-state.js +122 -0
- package/dist/components/theme/theme-state.js.map +1 -0
- package/dist/components/theme/theme-toggle.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/lib/accent-roles.d.ts +124 -0
- package/dist/lib/accent-roles.d.ts.map +1 -0
- package/dist/lib/accent-roles.js +163 -0
- package/dist/lib/accent-roles.js.map +1 -0
- package/dist/lib/appearance-font-settings.d.ts +1 -1
- package/dist/lib/appearance-font-settings.d.ts.map +1 -1
- package/dist/lib/appearance-font-settings.js.map +1 -1
- package/dist/lib/themes.d.ts +71 -1
- package/dist/lib/themes.d.ts.map +1 -1
- package/dist/lib/themes.js +172 -48
- package/dist/lib/themes.js.map +1 -1
- package/dist/r/lib.json +5 -0
- package/dist/r/registry.json +25 -0
- package/dist/r/src/components/theme/index.ts +3 -0
- package/dist/r/src/components/theme/theme-context.ts +43 -0
- package/dist/r/src/components/theme/theme-document.ts +184 -0
- package/dist/r/src/components/theme/theme-persistence.ts +143 -0
- package/dist/r/src/components/theme/theme-provider.tsx +147 -253
- package/dist/r/src/components/theme/theme-state.ts +274 -0
- package/dist/r/src/lib/accent-roles.ts +338 -0
- package/dist/r/src/lib/appearance-font-settings.ts +1 -1
- package/dist/r/src/lib/themes.ts +225 -59
- package/dist/r/theming.json +20 -0
- package/package.json +1 -1
- package/registry.json +25 -0
- package/src/components/theme/index.ts +3 -0
- package/src/components/theme/theme-context.ts +43 -0
- package/src/components/theme/theme-document.ts +184 -0
- package/src/components/theme/theme-persistence.ts +143 -0
- package/src/components/theme/theme-provider.tsx +147 -253
- package/src/components/theme/theme-state.ts +274 -0
- package/src/index.ts +11 -0
- package/src/lib/accent-roles.ts +338 -0
- package/src/lib/appearance-font-settings.ts +1 -1
- package/src/lib/themes.ts +225 -59
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { ACCENTS, accentRoles, accentValue, primarySubtleCss } from "@adea-ai/themes";
|
|
2
|
+
import { contrastRatio, oklchToHex, parseColor, repairContrast } from "@adea-ai/themes/oklch";
|
|
3
|
+
//#region src/lib/accent-roles.ts
|
|
4
|
+
var WHITE_OKLCH = {
|
|
5
|
+
l: 1,
|
|
6
|
+
c: 0,
|
|
7
|
+
h: 0
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The nearest colour to `source` that reaches `minimum` against `canvas`, as hex.
|
|
11
|
+
*
|
|
12
|
+
* The catalogue's `repairContrast` is asked for a little more than the floor, in
|
|
13
|
+
* 0.001 steps up to 0.05, because the result is rounded to 8-bit hex and the
|
|
14
|
+
* rounded colour is what is measured: asking for exactly the floor can land a hair
|
|
15
|
+
* under it after rounding. If nothing in that range survives the rounding, white or
|
|
16
|
+
* black stands in, whichever clears the floor.
|
|
17
|
+
*/
|
|
18
|
+
function repairedHex(source, canvas, minimum) {
|
|
19
|
+
for (let margin = 0; margin <= .05; margin = Number((margin + .001).toFixed(3))) {
|
|
20
|
+
const repaired = repairContrast(source, canvas, minimum + margin);
|
|
21
|
+
if (!repaired.satisfied) continue;
|
|
22
|
+
const hex = oklchToHex(repaired.color);
|
|
23
|
+
const rounded = parseColor(hex);
|
|
24
|
+
if (rounded && contrastRatio(rounded, canvas) >= minimum) return hex;
|
|
25
|
+
}
|
|
26
|
+
return contrastRatio(WHITE_OKLCH, canvas) >= minimum ? "#ffffff" : "#000000";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The stored form of a custom accent: `#rrggbb`, six digits, opaque, lower case.
|
|
30
|
+
*
|
|
31
|
+
* Deliberately narrower than {@link parseColor}. A stored accent is compared,
|
|
32
|
+
* deduplicated and used as an attribute, so it has one spelling; a host that
|
|
33
|
+
* accepts `#abc` in a field expands it before storing. Anything else — a name, an
|
|
34
|
+
* `rgb()`, a short or alpha form, a non-string — is not an accent and returns
|
|
35
|
+
* `undefined`.
|
|
36
|
+
*/
|
|
37
|
+
function normalizeCustomAccent(value) {
|
|
38
|
+
return typeof value === "string" && /^#[0-9a-fA-F]{6}$/.test(value) ? value.toLowerCase() : void 0;
|
|
39
|
+
}
|
|
40
|
+
/** Whether a selection's accent is the custom `#rrggbb` form (any letter case). */
|
|
41
|
+
function isCustomAccent(value) {
|
|
42
|
+
return normalizeCustomAccent(value) !== void 0;
|
|
43
|
+
}
|
|
44
|
+
function appearanceOf(target) {
|
|
45
|
+
return typeof target === "string" ? target : target.appearance;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* `value` raised to `floor` against `background` as hex, or `value` itself when it
|
|
49
|
+
* already clears the floor, is not a colour, or there is no surface to measure it on.
|
|
50
|
+
*
|
|
51
|
+
* Returning the argument itself is what makes the floor the identity for a preset
|
|
52
|
+
* that already reads: the caller compares and keeps the catalogue's own roles.
|
|
53
|
+
*/
|
|
54
|
+
function gateColor(value, background, floor) {
|
|
55
|
+
const source = parseColor(value);
|
|
56
|
+
const canvas = background ? parseColor(background) : void 0;
|
|
57
|
+
if (!source || !canvas) return value;
|
|
58
|
+
if (contrastRatio(source, canvas) >= floor) return value;
|
|
59
|
+
return repairedHex(source, canvas, floor);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The roles for a preset (or a custom colour made into one), held to the floor.
|
|
63
|
+
*
|
|
64
|
+
* A preset that clears the floor keeps the catalogue's roles untouched. One that
|
|
65
|
+
* does not is repaired to the nearest passing colour and the whole role set is
|
|
66
|
+
* rebuilt from the repaired value, so the label, hover and ring follow it instead
|
|
67
|
+
* of describing a colour that is no longer on screen.
|
|
68
|
+
*/
|
|
69
|
+
function accentRolesForPreset(preset, context) {
|
|
70
|
+
const appearance = appearanceOf(context.target);
|
|
71
|
+
const requested = accentValue(preset, appearance);
|
|
72
|
+
if (!parseColor(requested)) return void 0;
|
|
73
|
+
const floor = context.floor;
|
|
74
|
+
if (floor === void 0) return accentRoles(preset, context.target);
|
|
75
|
+
const gated = gateColor(requested, context.background, floor);
|
|
76
|
+
if (gated === requested) return accentRoles(preset, context.target);
|
|
77
|
+
return {
|
|
78
|
+
...accentRoles({
|
|
79
|
+
...preset,
|
|
80
|
+
light: gated,
|
|
81
|
+
dark: gated
|
|
82
|
+
}, context.target),
|
|
83
|
+
primarySubtle: primarySubtleCss(context.target, "--primary", [gated])
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resolve an accent selection to its roles, or `undefined` when it overrides nothing.
|
|
88
|
+
*
|
|
89
|
+
* `undefined` is "use the theme's own primary": the literal `theme`, an id that is
|
|
90
|
+
* neither a preset, an offered theme accent nor a colour, and a colour that does not
|
|
91
|
+
* parse. A host that wants one of those to mean something else decides before
|
|
92
|
+
* asking (Adea's codec maps a stored `theme` to its default preset when it reads).
|
|
93
|
+
*/
|
|
94
|
+
function resolveAccentRoles(accent, context) {
|
|
95
|
+
if (accent === "theme") return void 0;
|
|
96
|
+
const custom = normalizeCustomAccent(accent);
|
|
97
|
+
if (custom) {
|
|
98
|
+
const floor = context.floor ?? 3;
|
|
99
|
+
return accentRolesForPreset({
|
|
100
|
+
id: "custom",
|
|
101
|
+
label: "Custom",
|
|
102
|
+
description: "",
|
|
103
|
+
light: custom,
|
|
104
|
+
dark: custom
|
|
105
|
+
}, {
|
|
106
|
+
...context,
|
|
107
|
+
floor
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const preset = ACCENTS.find((candidate) => candidate.id === accent) ?? context.themeAccents?.find((candidate) => candidate.id === accent);
|
|
111
|
+
return preset ? accentRolesForPreset(preset, context) : void 0;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The custom properties an accent override owns on `<html>`.
|
|
115
|
+
*
|
|
116
|
+
* `--primary-subtle` is deliberately not among them. It is a `color-mix()` over
|
|
117
|
+
* `var(--primary)`, so on the same element it follows an accent by itself, and its
|
|
118
|
+
* strength is the theme's, measured to keep body text at 4.5:1 on the tint.
|
|
119
|
+
* Writing one here would replace that measurement with the argument's.
|
|
120
|
+
*/
|
|
121
|
+
function accentOverrideVariables(roles) {
|
|
122
|
+
return {
|
|
123
|
+
"--primary": roles.primary,
|
|
124
|
+
"--primary-foreground": roles.primaryForeground,
|
|
125
|
+
"--primary-hover": roles.primaryHover,
|
|
126
|
+
"--ring": roles.ring
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** The custom properties {@link paintAccentRoles} writes and clears. */
|
|
130
|
+
var ACCENT_PAINT_PROPERTIES = [
|
|
131
|
+
"--primary",
|
|
132
|
+
"--primary-foreground",
|
|
133
|
+
"--primary-hover",
|
|
134
|
+
"--primary-subtle",
|
|
135
|
+
"--ring"
|
|
136
|
+
];
|
|
137
|
+
/**
|
|
138
|
+
* Scope an accent to one element and its descendants.
|
|
139
|
+
*
|
|
140
|
+
* Unlike the document, an element must write `--primary-subtle` too. A custom
|
|
141
|
+
* property that uses `var()` is resolved where it is declared, so the tint inherited
|
|
142
|
+
* from `<html>` is already a mix over *`<html>`'s* primary and an element that only
|
|
143
|
+
* overrides `--primary` would keep the old tint. Declared here, the same expression
|
|
144
|
+
* resolves against this element's primary.
|
|
145
|
+
*
|
|
146
|
+
* `undefined` clears every property, and the element inherits the document's accent
|
|
147
|
+
* again.
|
|
148
|
+
*/
|
|
149
|
+
function paintAccentRoles(element, roles) {
|
|
150
|
+
if (!roles) {
|
|
151
|
+
for (const property of ACCENT_PAINT_PROPERTIES) element.style.removeProperty(property);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
element.style.setProperty("--primary", roles.primary);
|
|
155
|
+
element.style.setProperty("--primary-foreground", roles.primaryForeground);
|
|
156
|
+
element.style.setProperty("--primary-hover", roles.primaryHover);
|
|
157
|
+
element.style.setProperty("--primary-subtle", roles.primarySubtle);
|
|
158
|
+
element.style.setProperty("--ring", roles.ring);
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
export { ACCENT_PAINT_PROPERTIES, accentOverrideVariables, accentRolesForPreset, isCustomAccent, normalizeCustomAccent, paintAccentRoles, resolveAccentRoles };
|
|
162
|
+
|
|
163
|
+
//# sourceMappingURL=accent-roles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accent-roles.js","names":["parseCanonicalColor","canonicalContrastRatio"],"sources":["../../src/lib/accent-roles.ts"],"sourcesContent":["/*\n * Copyright (c) 2026 Wing\n * SPDX-License-Identifier: MIT\n *\n * The colour helpers, the contrast gate and the custom-accent grammar are\n * substantially translated from Zeron's `Color` and `AccentRoles`\n * (https://github.com/zeronsh/zeron), as Adea's accepted port of them in\n * `packages/ui/src/components/appearance.ts` expresses it. Extracted here so the\n * shared provider and Adea run one implementation; `@adea-ai/themes` owns the\n * colour engine (parsing, contrast, repair, the preset values and their roles).\n * See NOTICE.\n */\n\nimport {\n ACCENTS,\n accentRoles,\n accentValue,\n primarySubtleCss,\n type AccentPreset,\n type AccentRoles as CatalogueAccentRoles,\n} from '@adea-ai/themes'\nimport {\n contrastRatio as canonicalContrastRatio,\n oklchToHex,\n parseColor as parseCanonicalColor,\n repairContrast,\n type Oklch,\n} from '@adea-ai/themes/oklch'\nimport type { ThemeAppearance } from './themes'\n\n/**\n * Accent colour maths and the roles an accent paints.\n *\n * Three questions live here, and none of them touches the document or a signal:\n *\n * - **What colour is this?** `parseColor`, `colorToHex`, `contrastRatio` and\n * `ensureContrast` are the hex helpers a host needs around an accent (an\n * imported theme's colours, a colour field). Hex is the host preference format;\n * the catalogue's colour engine does the maths.\n * - **Is this accent readable on this surface?** The *contrast floor*. A preset is\n * curated, so the catalogue already measured it against its own themes and the\n * floor is the identity for it; a custom `#rrggbb` is anyone's choice, so it is\n * raised until it clears the floor and can never ship unreadable. A host that\n * imports themes (whose backgrounds the catalogue never saw) turns the floor on\n * for presets too.\n * - **Which custom properties does an accent own?** `AccentRoles`, written on\n * `<html>` by the provider and onto one element by {@link paintAccentRoles}.\n */\n\n/* Hex is the host preference format; the shared Themes package owns colour maths. */\n\nexport type RgbColor = Readonly<{ r: number; g: number; b: number; a: number }>\n\nconst HEX = /^#([0-9a-fA-F]+)$/\n\n/** Parse a hex colour (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`). `undefined` for anything else. */\nexport function parseColor(value: string): RgbColor | undefined {\n const match = HEX.exec(value.trim())\n const digits = match?.[1]\n if (!digits) return undefined\n if (digits.length === 3 || digits.length === 4) {\n // Each nibble is doubled: `#abc` is `#aabbcc`.\n const [r, g, b, a] = [...digits].map((digit) => Number.parseInt(digit + digit, 16))\n return { r: r!, g: g!, b: b!, a: a ?? 255 }\n }\n if (digits.length === 6 || digits.length === 8) {\n const byte = (index: number) => Number.parseInt(digits.slice(index, index + 2), 16)\n return { r: byte(0), g: byte(2), b: byte(4), a: digits.length === 8 ? byte(6) : 255 }\n }\n return undefined\n}\n\nfunction toHexByte(value: number): string {\n return value.toString(16).padStart(2, '0')\n}\n\n/** `#rrggbb`, or `#rrggbbaa` when the colour is not opaque. */\nexport function colorToHex(color: RgbColor): string {\n const opaque = `#${toHexByte(color.r)}${toHexByte(color.g)}${toHexByte(color.b)}`\n return color.a === 255 ? opaque : `${opaque}${toHexByte(color.a)}`\n}\n\nfunction blendOver(foreground: RgbColor, background: RgbColor): RgbColor {\n const alpha = foreground.a / 255\n const blend = (front: number, back: number) => Math.round(front * alpha + back * (1 - alpha))\n return {\n r: blend(foreground.r, background.r),\n g: blend(foreground.g, background.g),\n b: blend(foreground.b, background.b),\n a: 255,\n }\n}\n\nfunction toCanonicalColor(color: RgbColor): Oklch | undefined {\n return parseCanonicalColor(colorToHex({ ...color, a: 255 }))\n}\n\n/** WCAG contrast ratio between two colours. Alpha composites over the background first. */\nexport function contrastRatio(\n foreground: RgbColor | string,\n background: RgbColor | string\n): number {\n const front = typeof foreground === 'string' ? parseColor(foreground) : foreground\n const back = typeof background === 'string' ? parseColor(background) : background\n if (!front || !back) return 0\n const opaqueFront = front.a === 255 ? front : blendOver(front, back)\n const foregroundColor = toCanonicalColor(opaqueFront)\n const backgroundColor = toCanonicalColor(back)\n if (!foregroundColor || !backgroundColor) return 0\n return canonicalContrastRatio(foregroundColor, backgroundColor)\n}\n\nconst WHITE: RgbColor = { r: 255, g: 255, b: 255, a: 255 }\nconst WHITE_OKLCH: Oklch = { l: 1, c: 0, h: 0 }\n\n/**\n * The nearest colour to `source` that reaches `minimum` against `canvas`, as hex.\n *\n * The catalogue's `repairContrast` is asked for a little more than the floor, in\n * 0.001 steps up to 0.05, because the result is rounded to 8-bit hex and the\n * rounded colour is what is measured: asking for exactly the floor can land a hair\n * under it after rounding. If nothing in that range survives the rounding, white or\n * black stands in, whichever clears the floor.\n */\nfunction repairedHex(source: Oklch, canvas: Oklch, minimum: number): string {\n for (let margin = 0; margin <= 0.05; margin = Number((margin + 0.001).toFixed(3))) {\n const repaired = repairContrast(source, canvas, minimum + margin)\n if (!repaired.satisfied) continue\n const hex = oklchToHex(repaired.color)\n const rounded = parseCanonicalColor(hex)\n if (rounded && canonicalContrastRatio(rounded, canvas) >= minimum) return hex\n }\n return canonicalContrastRatio(WHITE_OKLCH, canvas) >= minimum ? '#ffffff' : '#000000'\n}\n\n/** Preserve the host hex shape while delegating contrast repair to the catalogue. */\nexport function ensureContrast(color: RgbColor, background: RgbColor, minimum: number): RgbColor {\n if (contrastRatio(color, background) >= minimum) return color\n\n const renderedColor = color.a === 255 ? color : blendOver(color, background)\n const source = toCanonicalColor(renderedColor)\n const canvas = toCanonicalColor(background)\n if (!source || !canvas) return color\n return parseColor(repairedHex(source, canvas, minimum))!\n}\n\n/** The contrast an accent is held to against its surface unless the host sets another. */\nexport const DEFAULT_ACCENT_CONTRAST_FLOOR = 3\n\n/**\n * Normalise a custom accent against a surface: unparseable input is rejected\n * (`undefined`), and any accepted colour is raised to `minimum` so an accent can\n * never ship unreadable. Accepts every CSS hex length; for the stored grammar use\n * {@link normalizeCustomAccent}.\n */\nexport function normalizeAccentValue(\n value: string,\n background: string,\n minimum: number = DEFAULT_ACCENT_CONTRAST_FLOOR\n): string | undefined {\n const parsed = parseColor(value)\n if (!parsed) return undefined\n const backdrop = parseColor(background) ?? WHITE\n return colorToHex(ensureContrast(parsed, backdrop, minimum))\n}\n\n/**\n * The stored form of a custom accent: `#rrggbb`, six digits, opaque, lower case.\n *\n * Deliberately narrower than {@link parseColor}. A stored accent is compared,\n * deduplicated and used as an attribute, so it has one spelling; a host that\n * accepts `#abc` in a field expands it before storing. Anything else — a name, an\n * `rgb()`, a short or alpha form, a non-string — is not an accent and returns\n * `undefined`.\n */\nexport function normalizeCustomAccent(value: unknown): string | undefined {\n return typeof value === 'string' && /^#[0-9a-fA-F]{6}$/.test(value)\n ? value.toLowerCase()\n : undefined\n}\n\n/** Whether a selection's accent is the custom `#rrggbb` form (any letter case). */\nexport function isCustomAccent(value: unknown): boolean {\n return normalizeCustomAccent(value) !== undefined\n}\n\n/**\n * The roles an accent paints, as CSS values: the catalogue's `AccentRoles`, so the\n * provider, the generated stylesheet and a host's own paint cannot disagree about\n * what a role is. `primarySubtle` is an expression over `var(--primary)`; the\n * document writer never sets it (see {@link accentOverrideVariables}), an element\n * paint must.\n */\nexport type AccentRoles = CatalogueAccentRoles\n\n/** What an accent resolves against: the surface it sits on and the theme it sits in. */\nexport type AccentTarget = {\n /** The theme, or only its appearance. A theme gives the tint its measured strength. */\n target: Parameters<typeof accentRoles>[1]\n /** The surface the accent must read on. Without it nothing can be measured or repaired. */\n background: string | undefined\n /**\n * The contrast to hold the accent to against `background`. Absent, a preset is\n * painted verbatim and a custom colour is held to {@link DEFAULT_ACCENT_CONTRAST_FLOOR}.\n */\n floor?: number\n /**\n * The accents the selected theme pair carries (`ansi-blue`…), for an id that is\n * neither a preset nor a colour.\n */\n themeAccents?: readonly AccentPreset[]\n}\n\nfunction appearanceOf(target: AccentTarget['target']): ThemeAppearance {\n return typeof target === 'string' ? target : target.appearance\n}\n\n/**\n * `value` raised to `floor` against `background` as hex, or `value` itself when it\n * already clears the floor, is not a colour, or there is no surface to measure it on.\n *\n * Returning the argument itself is what makes the floor the identity for a preset\n * that already reads: the caller compares and keeps the catalogue's own roles.\n */\nfunction gateColor(value: string, background: string | undefined, floor: number): string {\n const source = parseCanonicalColor(value)\n const canvas = background ? parseCanonicalColor(background) : undefined\n if (!source || !canvas) return value\n if (canonicalContrastRatio(source, canvas) >= floor) return value\n return repairedHex(source, canvas, floor)\n}\n\n/**\n * The roles for a preset (or a custom colour made into one), held to the floor.\n *\n * A preset that clears the floor keeps the catalogue's roles untouched. One that\n * does not is repaired to the nearest passing colour and the whole role set is\n * rebuilt from the repaired value, so the label, hover and ring follow it instead\n * of describing a colour that is no longer on screen.\n */\nexport function accentRolesForPreset(\n preset: AccentPreset,\n context: AccentTarget\n): AccentRoles | undefined {\n const appearance = appearanceOf(context.target)\n const requested = accentValue(preset, appearance)\n if (!parseCanonicalColor(requested)) return undefined\n\n const floor = context.floor\n if (floor === undefined) return accentRoles(preset, context.target)\n\n const gated = gateColor(requested, context.background, floor)\n if (gated === requested) return accentRoles(preset, context.target)\n\n const roles = accentRoles({ ...preset, light: gated, dark: gated }, context.target)\n // The tint's strength is measured against the primary it will sit over, and the\n // repaired primary is not one the theme's own measurement saw.\n return { ...roles, primarySubtle: primarySubtleCss(context.target, '--primary', [gated]) }\n}\n\n/**\n * Resolve an accent selection to its roles, or `undefined` when it overrides nothing.\n *\n * `undefined` is \"use the theme's own primary\": the literal `theme`, an id that is\n * neither a preset, an offered theme accent nor a colour, and a colour that does not\n * parse. A host that wants one of those to mean something else decides before\n * asking (Adea's codec maps a stored `theme` to its default preset when it reads).\n */\nexport function resolveAccentRoles(accent: string, context: AccentTarget): AccentRoles | undefined {\n if (accent === 'theme') return undefined\n\n const custom = normalizeCustomAccent(accent)\n if (custom) {\n const floor = context.floor ?? DEFAULT_ACCENT_CONTRAST_FLOOR\n return accentRolesForPreset(\n { id: 'custom', label: 'Custom', description: '', light: custom, dark: custom },\n { ...context, floor }\n )\n }\n\n const preset =\n ACCENTS.find((candidate) => candidate.id === accent) ??\n context.themeAccents?.find((candidate) => candidate.id === accent)\n return preset ? accentRolesForPreset(preset, context) : undefined\n}\n\n/**\n * The custom properties an accent override owns on `<html>`.\n *\n * `--primary-subtle` is deliberately not among them. It is a `color-mix()` over\n * `var(--primary)`, so on the same element it follows an accent by itself, and its\n * strength is the theme's, measured to keep body text at 4.5:1 on the tint.\n * Writing one here would replace that measurement with the argument's.\n */\nexport function accentOverrideVariables(roles: AccentRoles): Record<string, string> {\n return {\n '--primary': roles.primary,\n '--primary-foreground': roles.primaryForeground,\n '--primary-hover': roles.primaryHover,\n '--ring': roles.ring,\n }\n}\n\n/** The custom properties {@link paintAccentRoles} writes and clears. */\nexport const ACCENT_PAINT_PROPERTIES = [\n '--primary',\n '--primary-foreground',\n '--primary-hover',\n '--primary-subtle',\n '--ring',\n] as const\n\n/**\n * Scope an accent to one element and its descendants.\n *\n * Unlike the document, an element must write `--primary-subtle` too. A custom\n * property that uses `var()` is resolved where it is declared, so the tint inherited\n * from `<html>` is already a mix over *`<html>`'s* primary and an element that only\n * overrides `--primary` would keep the old tint. Declared here, the same expression\n * resolves against this element's primary.\n *\n * `undefined` clears every property, and the element inherits the document's accent\n * again.\n */\nexport function paintAccentRoles(\n element: { style: Pick<CSSStyleDeclaration, 'setProperty' | 'removeProperty'> },\n roles: AccentRoles | undefined\n): void {\n if (!roles) {\n for (const property of ACCENT_PAINT_PROPERTIES) element.style.removeProperty(property)\n return\n }\n element.style.setProperty('--primary', roles.primary)\n element.style.setProperty('--primary-foreground', roles.primaryForeground)\n element.style.setProperty('--primary-hover', roles.primaryHover)\n element.style.setProperty('--primary-subtle', roles.primarySubtle)\n element.style.setProperty('--ring', roles.ring)\n}\n"],"mappings":"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"}
|
|
@@ -38,7 +38,7 @@ export declare function normalizeAppearanceEditorFontSettings(value: unknown, le
|
|
|
38
38
|
* sizes, and projected CSS variables. The marker activates the optional role
|
|
39
39
|
* stylesheet and survives CSS import reordering.
|
|
40
40
|
*/
|
|
41
|
-
export declare function applyAppearanceFontSettings(root: HTMLElement, value: unknown): NormalizedAppearanceEditorFontSettings;
|
|
41
|
+
export declare function applyAppearanceFontSettings(root: Pick<HTMLElement, 'removeAttribute' | 'setAttribute' | 'style'>, value: unknown): NormalizedAppearanceEditorFontSettings;
|
|
42
42
|
/**
|
|
43
43
|
* Generate a safe inline prepaint script for a host-owned appearance record.
|
|
44
44
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appearance-font-settings.d.ts","sourceRoot":"","sources":["../../src/lib/appearance-font-settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAGhE,eAAO,MAAM,2BAA2B,oCAAqC,CAAA;AAE7E,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAA;AACnF,MAAM,MAAM,4BAA4B,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;AAE3D,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IACjD,6EAA6E;IAC7E,MAAM,EAAE,4BAA4B,CAAA;IACpC,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;CACb,CAAC,CAAA;AAEF,0EAA0E;AAC1E,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CACjD,MAAM,CAAC,wBAAwB,EAAE,2BAA2B,CAAC,CAC9D,CAAA;AAED,eAAO,MAAM,+BAA+B,KAAK,CAAA;AACjD,eAAO,MAAM,+BAA+B,KAAK,CAAA;AAEjD,+DAA+D;AAC/D,eAAO,MAAM,uCAAuC,EAAE,4BAIpD,CAAA;AAEF,MAAM,MAAM,sCAAsC,GAAG,QAAQ,CAAC;IAC5D,QAAQ,EAAE,4BAA4B,CAAA;IACtC,iFAAiF;IACjF,aAAa,EAAE,SAAS,wBAAwB,EAAE,CAAA;CACnD,CAAC,CAAA;AAqEF,oFAAoF;AACpF,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQ1F;AAED;;;;;;;GAOG;AACH,wBAAgB,qCAAqC,CACnD,KAAK,EAAE,OAAO,EACd,cAAc,CAAC,EAAE,OAAO,GACvB,sCAAsC,CA+CxC;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"appearance-font-settings.d.ts","sourceRoot":"","sources":["../../src/lib/appearance-font-settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAGhE,eAAO,MAAM,2BAA2B,oCAAqC,CAAA;AAE7E,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAA;AACnF,MAAM,MAAM,4BAA4B,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;AAE3D,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IACjD,6EAA6E;IAC7E,MAAM,EAAE,4BAA4B,CAAA;IACpC,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;CACb,CAAC,CAAA;AAEF,0EAA0E;AAC1E,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CACjD,MAAM,CAAC,wBAAwB,EAAE,2BAA2B,CAAC,CAC9D,CAAA;AAED,eAAO,MAAM,+BAA+B,KAAK,CAAA;AACjD,eAAO,MAAM,+BAA+B,KAAK,CAAA;AAEjD,+DAA+D;AAC/D,eAAO,MAAM,uCAAuC,EAAE,4BAIpD,CAAA;AAEF,MAAM,MAAM,sCAAsC,GAAG,QAAQ,CAAC;IAC5D,QAAQ,EAAE,4BAA4B,CAAA;IACtC,iFAAiF;IACjF,aAAa,EAAE,SAAS,wBAAwB,EAAE,CAAA;CACnD,CAAC,CAAA;AAqEF,oFAAoF;AACpF,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQ1F;AAED;;;;;;;GAOG;AACH,wBAAgB,qCAAqC,CACnD,KAAK,EAAE,OAAO,EACd,cAAc,CAAC,EAAE,OAAO,GACvB,sCAAsC,CA+CxC;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,iBAAiB,GAAG,cAAc,GAAG,OAAO,CAAC,EACrE,KAAK,EAAE,OAAO,GACb,sCAAsC,CAsBxC;AAED;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAajG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appearance-font-settings.js","names":[],"sources":["../../src/lib/appearance-font-settings.ts"],"sourcesContent":["import { fontOptions, type FontOption } from '#lib/font-catalog'\nimport { safeScriptStringLiteral } from '#lib/safe-script'\n\nexport const APPEARANCE_EDITOR_FONT_AXES = ['ui', 'content', 'code'] as const\n\nexport type AppearanceEditorFontAxis = (typeof APPEARANCE_EDITOR_FONT_AXES)[number]\nexport type AppearanceEditorFontFamilyId = FontOption['id']\n\nexport type AppearanceEditorFontSetting = Readonly<{\n /** A stable id from the shared font catalogue, never a CSS family string. */\n family: AppearanceEditorFontFamilyId\n /** A pixel size, normalized to the supported range. */\n size: number\n}>\n\n/** The host-owned choices edited by the shared appearance composition. */\nexport type AppearanceEditorFontSettings = Readonly<\n Record<AppearanceEditorFontAxis, AppearanceEditorFontSetting>\n>\n\nexport const APPEARANCE_EDITOR_FONT_SIZE_MIN = 10\nexport const APPEARANCE_EDITOR_FONT_SIZE_MAX = 32\n\n/** New preferences use the platform face in each text role. */\nexport const DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS: AppearanceEditorFontSettings = Object.freeze({\n ui: Object.freeze({ family: 'system', size: 14 }),\n content: Object.freeze({ family: 'system', size: 14 }),\n code: Object.freeze({ family: 'system', size: 12 }),\n})\n\nexport type NormalizedAppearanceEditorFontSettings = Readonly<{\n settings: AppearanceEditorFontSettings\n /** Axes whose persisted family or size was unavailable or outside its limits. */\n recoveredAxes: readonly AppearanceEditorFontAxis[]\n}>\n\nconst fontFamilyIds: ReadonlySet<string> = new Set(fontOptions.map((option) => option.id))\nconst fontOptionsById = Object.freeze(\n Object.fromEntries(fontOptions.map((option) => [option.id, option])) as Record<\n AppearanceEditorFontFamilyId,\n FontOption\n >\n)\nconst fontProjectionById = Object.freeze(\n Object.fromEntries(\n fontOptions.map((option) => [\n option.id,\n {\n familyVariables: option.familyVariables,\n uiTracking: option.uiTracking,\n uiWordSpacing: option.uiWordSpacing,\n },\n ])\n ) as Record<\n AppearanceEditorFontFamilyId,\n Pick<FontOption, 'familyVariables' | 'uiTracking' | 'uiWordSpacing'>\n >\n)\nconst fontAxisProjection = Object.freeze({\n ui: Object.freeze({\n familyAttribute: 'data-ui-font',\n familyProperty: '--font-ui',\n sizeProperty: '--font-ui-size',\n scaleProperty: '--font-ui-scale',\n defaultSize: DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS.ui.size,\n }),\n content: Object.freeze({\n familyAttribute: 'data-content-font',\n familyProperty: '--font-content',\n sizeProperty: '--font-content-size',\n scaleProperty: '--font-content-scale',\n defaultSize: DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS.content.size,\n }),\n code: Object.freeze({\n familyAttribute: 'data-code-font',\n familyProperty: '--font-code',\n sizeProperty: '--font-code-size',\n scaleProperty: '--font-code-scale',\n defaultSize: DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS.code.size,\n }),\n})\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction hasOwn(value: Record<string, unknown>, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(value, key)\n}\n\nfunction normalizeFamily(value: unknown): AppearanceEditorFontFamilyId | undefined {\n return typeof value === 'string' && fontFamilyIds.has(value)\n ? (value as AppearanceEditorFontFamilyId)\n : undefined\n}\n\nfunction clampFontSize(value: number): number {\n return Math.min(\n APPEARANCE_EDITOR_FONT_SIZE_MAX,\n Math.max(APPEARANCE_EDITOR_FONT_SIZE_MIN, Math.round(value))\n )\n}\n\n/** Normalize a single number-input value; invalid edits return the current size. */\nexport function normalizeAppearanceEditorFontSize(value: unknown, fallback: number): number {\n const parsed =\n typeof value === 'number'\n ? value\n : typeof value === 'string' && value.trim() !== ''\n ? Number(value)\n : Number.NaN\n return Number.isFinite(parsed) ? clampFontSize(parsed) : clampFontSize(fallback)\n}\n\n/**\n * Recover persisted font choices against the shared font catalogue.\n *\n * `legacyUiFamily` lets a host migrate its previous single font field into the\n * UI axis. Content and code remain on the new System defaults during that\n * migration. Missing settings are ordinary defaults; an unknown saved id or an\n * invalid size is reported through `recoveredAxes` for the host's notice.\n */\nexport function normalizeAppearanceEditorFontSettings(\n value: unknown,\n legacyUiFamily?: unknown\n): NormalizedAppearanceEditorFontSettings {\n const source = isRecord(value) ? value : undefined\n const malformedRoot = value !== undefined && value !== null && source === undefined\n const recovered = new Set<AppearanceEditorFontAxis>()\n const settings = {} as Record<AppearanceEditorFontAxis, AppearanceEditorFontSetting>\n\n for (const axis of APPEARANCE_EDITOR_FONT_AXES) {\n const axisPresent = source ? hasOwn(source, axis) : false\n const axisValue = source?.[axis]\n const axisSource = isRecord(axisValue) ? axisValue : undefined\n const fallback = DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS[axis]\n\n if (malformedRoot || (axisPresent && axisSource === undefined)) recovered.add(axis)\n\n const hasFamily = axisSource ? hasOwn(axisSource, 'family') : false\n const familyInput = hasFamily\n ? axisSource?.['family']\n : axis === 'ui' && legacyUiFamily !== undefined\n ? legacyUiFamily\n : undefined\n const family = normalizeFamily(familyInput) ?? fallback.family\n if (\n (hasFamily || (axis === 'ui' && legacyUiFamily !== undefined)) &&\n !normalizeFamily(familyInput)\n ) {\n recovered.add(axis)\n }\n\n const hasSize = axisSource ? hasOwn(axisSource, 'size') : false\n const sizeInput = axisSource?.['size']\n const size = hasSize\n ? normalizeAppearanceEditorFontSize(sizeInput, fallback.size)\n : fallback.size\n if (\n hasSize &&\n (typeof sizeInput !== 'number' || !Number.isFinite(sizeInput) || sizeInput !== size)\n ) {\n recovered.add(axis)\n }\n\n settings[axis] = Object.freeze({ family, size })\n }\n\n return Object.freeze({\n settings: Object.freeze(settings),\n recoveredAxes: Object.freeze(APPEARANCE_EDITOR_FONT_AXES.filter((axis) => recovered.has(axis))),\n })\n}\n\n/**\n * Project normalized font preferences onto a document root.\n *\n * This is shared by `ThemeProvider` and hosts that own their own appearance\n * persistence, so UI/content/code use the same catalogue ids, theme tokens,\n * sizes, and projected CSS variables. The marker activates the optional role\n * stylesheet and survives CSS import reordering.\n */\nexport function applyAppearanceFontSettings(\n root: HTMLElement,\n value: unknown\n): NormalizedAppearanceEditorFontSettings {\n const normalized = normalizeAppearanceEditorFontSettings(value)\n const { settings } = normalized\n\n root.removeAttribute('data-font')\n root.setAttribute('data-font-settings', '')\n for (const axis of APPEARANCE_EDITOR_FONT_AXES) {\n const projection = fontAxisProjection[axis]\n const setting = settings[axis]\n const option = fontOptionsById[setting.family]\n if (setting.family === 'system') root.removeAttribute(projection.familyAttribute)\n else root.setAttribute(projection.familyAttribute, setting.family)\n root.style.setProperty(projection.familyProperty, `var(${option.familyVariables[axis]})`)\n root.style.setProperty(projection.sizeProperty, String(setting.size) + 'px')\n root.style.setProperty(projection.scaleProperty, String(setting.size / projection.defaultSize))\n if (axis === 'ui') {\n root.style.setProperty('--ui-tracking', option.uiTracking)\n root.style.setProperty('--ui-word-spacing', option.uiWordSpacing)\n }\n }\n\n return normalized\n}\n\n/**\n * Generate a safe inline prepaint script for a host-owned appearance record.\n *\n * The stored object is read from `localStorage[storageKey].fonts`. A legacy\n * top-level `font` family migrates into UI only; content/code use the shared\n * defaults. The script applies the same catalogue whitelist, limits, attributes,\n * and CSS properties as `applyAppearanceFontSettings` before styles paint. When\n * `persistedVersion` is supplied, only a record with that exact top-level version\n * is used; absent or mismatched records receive the System defaults.\n */\nexport function fontSettingsBootstrapScript(storageKey: string, persistedVersion?: number): string {\n if (persistedVersion !== undefined && !Number.isSafeInteger(persistedVersion)) {\n throw new RangeError('persistedVersion must be a safe integer')\n }\n const axisConfig = JSON.stringify(fontAxisProjection)\n const fontProjection = JSON.stringify(fontProjectionById)\n const fontIds = `[${fontOptions.map((option) => safeScriptStringLiteral(option.id)).join(',')}]`\n const persistedVersionGuard =\n persistedVersion === undefined\n ? ''\n : `&&parsed.version===Number(${safeScriptStringLiteral(String(persistedVersion))})`\n\n return `(function(){var r=document.documentElement;var axes=JSON.parse(${safeScriptStringLiteral(axisConfig)});var projection=JSON.parse(${safeScriptStringLiteral(fontProjection)});var ids=${fontIds};var has=function(v,k){return Object.prototype.hasOwnProperty.call(v,k)};var record=function(v){return typeof v==='object'&&v!==null&&!Array.isArray(v)};var p={};try{var raw=localStorage.getItem(${safeScriptStringLiteral(storageKey)});var parsed=raw?JSON.parse(raw):{};if(record(parsed)${persistedVersionGuard})p=parsed;}catch(e){}var saved=record(p.fonts)?p.fonts:{};var valid=function(v,d){return ids.indexOf(v)!==-1?v:d};var size=function(v,d){var n=typeof v==='number'?v:typeof v==='string'&&v.trim()!==''?Number(v):NaN;return Number.isFinite(n)?Math.min(${APPEARANCE_EDITOR_FONT_SIZE_MAX},Math.max(${APPEARANCE_EDITOR_FONT_SIZE_MIN},Math.round(n))):d};r.setAttribute('data-font-settings','');for(var axis in axes){if(!has(axes,axis))continue;var spec=axes[axis];var candidate=saved[axis];var value=record(candidate)?candidate:{};var legacyUi=axis==='ui'&&!has(value,'family');var family=legacyUi?valid(p.font,'system'):valid(value.family,'system');var font=projection[family];if(family==='system')r.removeAttribute(spec.familyAttribute);else r.setAttribute(spec.familyAttribute,family);r.style.setProperty(spec.familyProperty,'var('+font.familyVariables[axis]+')');var px=size(value.size,spec.defaultSize);r.style.setProperty(spec.sizeProperty,px+'px');r.style.setProperty(spec.scaleProperty,String(px/spec.defaultSize));if(axis==='ui'){r.style.setProperty('--ui-tracking',font.uiTracking);r.style.setProperty('--ui-word-spacing',font.uiWordSpacing);}}r.removeAttribute('data-font');})();`\n}\n"],"mappings":";;;AAGA,IAAa,8BAA8B;CAAC;CAAM;CAAW;AAAM;AAiBnE,IAAa,kCAAkC;AAC/C,IAAa,kCAAkC;;AAG/C,IAAa,0CAAwE,OAAO,OAAO;CACjG,IAAI,OAAO,OAAO;EAAE,QAAQ;EAAU,MAAM;CAAG,CAAC;CAChD,SAAS,OAAO,OAAO;EAAE,QAAQ;EAAU,MAAM;CAAG,CAAC;CACrD,MAAM,OAAO,OAAO;EAAE,QAAQ;EAAU,MAAM;CAAG,CAAC;AACpD,CAAC;AAQD,IAAM,gBAAqC,IAAI,IAAI,YAAY,KAAK,WAAW,OAAO,EAAE,CAAC;AACzF,IAAM,kBAAkB,OAAO,OAC7B,OAAO,YAAY,YAAY,KAAK,WAAW,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC,CAIrE;AACA,IAAM,qBAAqB,OAAO,OAChC,OAAO,YACL,YAAY,KAAK,WAAW,CAC1B,OAAO,IACP;CACE,iBAAiB,OAAO;CACxB,YAAY,OAAO;CACnB,eAAe,OAAO;AACxB,CACF,CAAC,CACH,CAIF;AACA,IAAM,qBAAqB,OAAO,OAAO;CACvC,IAAI,OAAO,OAAO;EAChB,iBAAiB;EACjB,gBAAgB;EAChB,cAAc;EACd,eAAe;EACf,aAAa,wCAAwC,GAAG;CAC1D,CAAC;CACD,SAAS,OAAO,OAAO;EACrB,iBAAiB;EACjB,gBAAgB;EAChB,cAAc;EACd,eAAe;EACf,aAAa,wCAAwC,QAAQ;CAC/D,CAAC;CACD,MAAM,OAAO,OAAO;EAClB,iBAAiB;EACjB,gBAAgB;EAChB,cAAc;EACd,eAAe;EACf,aAAa,wCAAwC,KAAK;CAC5D,CAAC;AACH,CAAC;AAED,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,OAAO,OAAgC,KAAsB;CACpE,OAAO,OAAO,UAAU,eAAe,KAAK,OAAO,GAAG;AACxD;AAEA,SAAS,gBAAgB,OAA0D;CACjF,OAAO,OAAO,UAAU,YAAY,cAAc,IAAI,KAAK,IACtD,QACD,KAAA;AACN;AAEA,SAAS,cAAc,OAAuB;CAC5C,OAAO,KAAK,IAAA,IAEV,KAAK,IAAA,IAAqC,KAAK,MAAM,KAAK,CAAC,CAC7D;AACF;;AAGA,SAAgB,kCAAkC,OAAgB,UAA0B;CAC1F,MAAM,SACJ,OAAO,UAAU,WACb,QACA,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,KAC5C,OAAO,KAAK,IACZ;CACR,OAAO,OAAO,SAAS,MAAM,IAAI,cAAc,MAAM,IAAI,cAAc,QAAQ;AACjF;;;;;;;;;AAUA,SAAgB,sCACd,OACA,gBACwC;CACxC,MAAM,SAAS,SAAS,KAAK,IAAI,QAAQ,KAAA;CACzC,MAAM,gBAAgB,UAAU,KAAA,KAAa,UAAU,QAAQ,WAAW,KAAA;CAC1E,MAAM,4BAAY,IAAI,IAA8B;CACpD,MAAM,WAAW,CAAC;CAElB,KAAK,MAAM,QAAQ,6BAA6B;EAC9C,MAAM,cAAc,SAAS,OAAO,QAAQ,IAAI,IAAI;EACpD,MAAM,YAAY,SAAS;EAC3B,MAAM,aAAa,SAAS,SAAS,IAAI,YAAY,KAAA;EACrD,MAAM,WAAW,wCAAwC;EAEzD,IAAI,iBAAkB,eAAe,eAAe,KAAA,GAAY,UAAU,IAAI,IAAI;EAElF,MAAM,YAAY,aAAa,OAAO,YAAY,QAAQ,IAAI;EAC9D,MAAM,cAAc,YAChB,aAAa,YACb,SAAS,QAAQ,mBAAmB,KAAA,IAClC,iBACA,KAAA;EACN,MAAM,SAAS,gBAAgB,WAAW,KAAK,SAAS;EACxD,KACG,aAAc,SAAS,QAAQ,mBAAmB,KAAA,MACnD,CAAC,gBAAgB,WAAW,GAE5B,UAAU,IAAI,IAAI;EAGpB,MAAM,UAAU,aAAa,OAAO,YAAY,MAAM,IAAI;EAC1D,MAAM,YAAY,aAAa;EAC/B,MAAM,OAAO,UACT,kCAAkC,WAAW,SAAS,IAAI,IAC1D,SAAS;EACb,IACE,YACC,OAAO,cAAc,YAAY,CAAC,OAAO,SAAS,SAAS,KAAK,cAAc,OAE/E,UAAU,IAAI,IAAI;EAGpB,SAAS,QAAQ,OAAO,OAAO;GAAE;GAAQ;EAAK,CAAC;CACjD;CAEA,OAAO,OAAO,OAAO;EACnB,UAAU,OAAO,OAAO,QAAQ;EAChC,eAAe,OAAO,OAAO,4BAA4B,QAAQ,SAAS,UAAU,IAAI,IAAI,CAAC,CAAC;CAChG,CAAC;AACH;;;;;;;;;AAUA,SAAgB,4BACd,MACA,OACwC;CACxC,MAAM,aAAa,sCAAsC,KAAK;CAC9D,MAAM,EAAE,aAAa;CAErB,KAAK,gBAAgB,WAAW;CAChC,KAAK,aAAa,sBAAsB,EAAE;CAC1C,KAAK,MAAM,QAAQ,6BAA6B;EAC9C,MAAM,aAAa,mBAAmB;EACtC,MAAM,UAAU,SAAS;EACzB,MAAM,SAAS,gBAAgB,QAAQ;EACvC,IAAI,QAAQ,WAAW,UAAU,KAAK,gBAAgB,WAAW,eAAe;OAC3E,KAAK,aAAa,WAAW,iBAAiB,QAAQ,MAAM;EACjE,KAAK,MAAM,YAAY,WAAW,gBAAgB,OAAO,OAAO,gBAAgB,MAAM,EAAE;EACxF,KAAK,MAAM,YAAY,WAAW,cAAc,OAAO,QAAQ,IAAI,IAAI,IAAI;EAC3E,KAAK,MAAM,YAAY,WAAW,eAAe,OAAO,QAAQ,OAAO,WAAW,WAAW,CAAC;EAC9F,IAAI,SAAS,MAAM;GACjB,KAAK,MAAM,YAAY,iBAAiB,OAAO,UAAU;GACzD,KAAK,MAAM,YAAY,qBAAqB,OAAO,aAAa;EAClE;CACF;CAEA,OAAO;AACT;;;;;;;;;;;AAYA,SAAgB,4BAA4B,YAAoB,kBAAmC;CACjG,IAAI,qBAAqB,KAAA,KAAa,CAAC,OAAO,cAAc,gBAAgB,GAC1E,MAAM,IAAI,WAAW,yCAAyC;CAEhE,MAAM,aAAa,KAAK,UAAU,kBAAkB;CACpD,MAAM,iBAAiB,KAAK,UAAU,kBAAkB;CACxD,MAAM,UAAU,IAAI,YAAY,KAAK,WAAW,wBAAwB,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE;CAC9F,MAAM,wBACJ,qBAAqB,KAAA,IACjB,KACA,6BAA6B,wBAAwB,OAAO,gBAAgB,CAAC,EAAE;CAErF,OAAO,kEAAkE,wBAAwB,UAAU,EAAE,8BAA8B,wBAAwB,cAAc,EAAE,YAAY,QAAQ,qMAAqM,wBAAwB,UAAU,EAAE,uDAAuD,sBAAsB;AAC/f"}
|
|
1
|
+
{"version":3,"file":"appearance-font-settings.js","names":[],"sources":["../../src/lib/appearance-font-settings.ts"],"sourcesContent":["import { fontOptions, type FontOption } from '#lib/font-catalog'\nimport { safeScriptStringLiteral } from '#lib/safe-script'\n\nexport const APPEARANCE_EDITOR_FONT_AXES = ['ui', 'content', 'code'] as const\n\nexport type AppearanceEditorFontAxis = (typeof APPEARANCE_EDITOR_FONT_AXES)[number]\nexport type AppearanceEditorFontFamilyId = FontOption['id']\n\nexport type AppearanceEditorFontSetting = Readonly<{\n /** A stable id from the shared font catalogue, never a CSS family string. */\n family: AppearanceEditorFontFamilyId\n /** A pixel size, normalized to the supported range. */\n size: number\n}>\n\n/** The host-owned choices edited by the shared appearance composition. */\nexport type AppearanceEditorFontSettings = Readonly<\n Record<AppearanceEditorFontAxis, AppearanceEditorFontSetting>\n>\n\nexport const APPEARANCE_EDITOR_FONT_SIZE_MIN = 10\nexport const APPEARANCE_EDITOR_FONT_SIZE_MAX = 32\n\n/** New preferences use the platform face in each text role. */\nexport const DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS: AppearanceEditorFontSettings = Object.freeze({\n ui: Object.freeze({ family: 'system', size: 14 }),\n content: Object.freeze({ family: 'system', size: 14 }),\n code: Object.freeze({ family: 'system', size: 12 }),\n})\n\nexport type NormalizedAppearanceEditorFontSettings = Readonly<{\n settings: AppearanceEditorFontSettings\n /** Axes whose persisted family or size was unavailable or outside its limits. */\n recoveredAxes: readonly AppearanceEditorFontAxis[]\n}>\n\nconst fontFamilyIds: ReadonlySet<string> = new Set(fontOptions.map((option) => option.id))\nconst fontOptionsById = Object.freeze(\n Object.fromEntries(fontOptions.map((option) => [option.id, option])) as Record<\n AppearanceEditorFontFamilyId,\n FontOption\n >\n)\nconst fontProjectionById = Object.freeze(\n Object.fromEntries(\n fontOptions.map((option) => [\n option.id,\n {\n familyVariables: option.familyVariables,\n uiTracking: option.uiTracking,\n uiWordSpacing: option.uiWordSpacing,\n },\n ])\n ) as Record<\n AppearanceEditorFontFamilyId,\n Pick<FontOption, 'familyVariables' | 'uiTracking' | 'uiWordSpacing'>\n >\n)\nconst fontAxisProjection = Object.freeze({\n ui: Object.freeze({\n familyAttribute: 'data-ui-font',\n familyProperty: '--font-ui',\n sizeProperty: '--font-ui-size',\n scaleProperty: '--font-ui-scale',\n defaultSize: DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS.ui.size,\n }),\n content: Object.freeze({\n familyAttribute: 'data-content-font',\n familyProperty: '--font-content',\n sizeProperty: '--font-content-size',\n scaleProperty: '--font-content-scale',\n defaultSize: DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS.content.size,\n }),\n code: Object.freeze({\n familyAttribute: 'data-code-font',\n familyProperty: '--font-code',\n sizeProperty: '--font-code-size',\n scaleProperty: '--font-code-scale',\n defaultSize: DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS.code.size,\n }),\n})\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction hasOwn(value: Record<string, unknown>, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(value, key)\n}\n\nfunction normalizeFamily(value: unknown): AppearanceEditorFontFamilyId | undefined {\n return typeof value === 'string' && fontFamilyIds.has(value)\n ? (value as AppearanceEditorFontFamilyId)\n : undefined\n}\n\nfunction clampFontSize(value: number): number {\n return Math.min(\n APPEARANCE_EDITOR_FONT_SIZE_MAX,\n Math.max(APPEARANCE_EDITOR_FONT_SIZE_MIN, Math.round(value))\n )\n}\n\n/** Normalize a single number-input value; invalid edits return the current size. */\nexport function normalizeAppearanceEditorFontSize(value: unknown, fallback: number): number {\n const parsed =\n typeof value === 'number'\n ? value\n : typeof value === 'string' && value.trim() !== ''\n ? Number(value)\n : Number.NaN\n return Number.isFinite(parsed) ? clampFontSize(parsed) : clampFontSize(fallback)\n}\n\n/**\n * Recover persisted font choices against the shared font catalogue.\n *\n * `legacyUiFamily` lets a host migrate its previous single font field into the\n * UI axis. Content and code remain on the new System defaults during that\n * migration. Missing settings are ordinary defaults; an unknown saved id or an\n * invalid size is reported through `recoveredAxes` for the host's notice.\n */\nexport function normalizeAppearanceEditorFontSettings(\n value: unknown,\n legacyUiFamily?: unknown\n): NormalizedAppearanceEditorFontSettings {\n const source = isRecord(value) ? value : undefined\n const malformedRoot = value !== undefined && value !== null && source === undefined\n const recovered = new Set<AppearanceEditorFontAxis>()\n const settings = {} as Record<AppearanceEditorFontAxis, AppearanceEditorFontSetting>\n\n for (const axis of APPEARANCE_EDITOR_FONT_AXES) {\n const axisPresent = source ? hasOwn(source, axis) : false\n const axisValue = source?.[axis]\n const axisSource = isRecord(axisValue) ? axisValue : undefined\n const fallback = DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS[axis]\n\n if (malformedRoot || (axisPresent && axisSource === undefined)) recovered.add(axis)\n\n const hasFamily = axisSource ? hasOwn(axisSource, 'family') : false\n const familyInput = hasFamily\n ? axisSource?.['family']\n : axis === 'ui' && legacyUiFamily !== undefined\n ? legacyUiFamily\n : undefined\n const family = normalizeFamily(familyInput) ?? fallback.family\n if (\n (hasFamily || (axis === 'ui' && legacyUiFamily !== undefined)) &&\n !normalizeFamily(familyInput)\n ) {\n recovered.add(axis)\n }\n\n const hasSize = axisSource ? hasOwn(axisSource, 'size') : false\n const sizeInput = axisSource?.['size']\n const size = hasSize\n ? normalizeAppearanceEditorFontSize(sizeInput, fallback.size)\n : fallback.size\n if (\n hasSize &&\n (typeof sizeInput !== 'number' || !Number.isFinite(sizeInput) || sizeInput !== size)\n ) {\n recovered.add(axis)\n }\n\n settings[axis] = Object.freeze({ family, size })\n }\n\n return Object.freeze({\n settings: Object.freeze(settings),\n recoveredAxes: Object.freeze(APPEARANCE_EDITOR_FONT_AXES.filter((axis) => recovered.has(axis))),\n })\n}\n\n/**\n * Project normalized font preferences onto a document root.\n *\n * This is shared by `ThemeProvider` and hosts that own their own appearance\n * persistence, so UI/content/code use the same catalogue ids, theme tokens,\n * sizes, and projected CSS variables. The marker activates the optional role\n * stylesheet and survives CSS import reordering.\n */\nexport function applyAppearanceFontSettings(\n root: Pick<HTMLElement, 'removeAttribute' | 'setAttribute' | 'style'>,\n value: unknown\n): NormalizedAppearanceEditorFontSettings {\n const normalized = normalizeAppearanceEditorFontSettings(value)\n const { settings } = normalized\n\n root.removeAttribute('data-font')\n root.setAttribute('data-font-settings', '')\n for (const axis of APPEARANCE_EDITOR_FONT_AXES) {\n const projection = fontAxisProjection[axis]\n const setting = settings[axis]\n const option = fontOptionsById[setting.family]\n if (setting.family === 'system') root.removeAttribute(projection.familyAttribute)\n else root.setAttribute(projection.familyAttribute, setting.family)\n root.style.setProperty(projection.familyProperty, `var(${option.familyVariables[axis]})`)\n root.style.setProperty(projection.sizeProperty, String(setting.size) + 'px')\n root.style.setProperty(projection.scaleProperty, String(setting.size / projection.defaultSize))\n if (axis === 'ui') {\n root.style.setProperty('--ui-tracking', option.uiTracking)\n root.style.setProperty('--ui-word-spacing', option.uiWordSpacing)\n }\n }\n\n return normalized\n}\n\n/**\n * Generate a safe inline prepaint script for a host-owned appearance record.\n *\n * The stored object is read from `localStorage[storageKey].fonts`. A legacy\n * top-level `font` family migrates into UI only; content/code use the shared\n * defaults. The script applies the same catalogue whitelist, limits, attributes,\n * and CSS properties as `applyAppearanceFontSettings` before styles paint. When\n * `persistedVersion` is supplied, only a record with that exact top-level version\n * is used; absent or mismatched records receive the System defaults.\n */\nexport function fontSettingsBootstrapScript(storageKey: string, persistedVersion?: number): string {\n if (persistedVersion !== undefined && !Number.isSafeInteger(persistedVersion)) {\n throw new RangeError('persistedVersion must be a safe integer')\n }\n const axisConfig = JSON.stringify(fontAxisProjection)\n const fontProjection = JSON.stringify(fontProjectionById)\n const fontIds = `[${fontOptions.map((option) => safeScriptStringLiteral(option.id)).join(',')}]`\n const persistedVersionGuard =\n persistedVersion === undefined\n ? ''\n : `&&parsed.version===Number(${safeScriptStringLiteral(String(persistedVersion))})`\n\n return `(function(){var r=document.documentElement;var axes=JSON.parse(${safeScriptStringLiteral(axisConfig)});var projection=JSON.parse(${safeScriptStringLiteral(fontProjection)});var ids=${fontIds};var has=function(v,k){return Object.prototype.hasOwnProperty.call(v,k)};var record=function(v){return typeof v==='object'&&v!==null&&!Array.isArray(v)};var p={};try{var raw=localStorage.getItem(${safeScriptStringLiteral(storageKey)});var parsed=raw?JSON.parse(raw):{};if(record(parsed)${persistedVersionGuard})p=parsed;}catch(e){}var saved=record(p.fonts)?p.fonts:{};var valid=function(v,d){return ids.indexOf(v)!==-1?v:d};var size=function(v,d){var n=typeof v==='number'?v:typeof v==='string'&&v.trim()!==''?Number(v):NaN;return Number.isFinite(n)?Math.min(${APPEARANCE_EDITOR_FONT_SIZE_MAX},Math.max(${APPEARANCE_EDITOR_FONT_SIZE_MIN},Math.round(n))):d};r.setAttribute('data-font-settings','');for(var axis in axes){if(!has(axes,axis))continue;var spec=axes[axis];var candidate=saved[axis];var value=record(candidate)?candidate:{};var legacyUi=axis==='ui'&&!has(value,'family');var family=legacyUi?valid(p.font,'system'):valid(value.family,'system');var font=projection[family];if(family==='system')r.removeAttribute(spec.familyAttribute);else r.setAttribute(spec.familyAttribute,family);r.style.setProperty(spec.familyProperty,'var('+font.familyVariables[axis]+')');var px=size(value.size,spec.defaultSize);r.style.setProperty(spec.sizeProperty,px+'px');r.style.setProperty(spec.scaleProperty,String(px/spec.defaultSize));if(axis==='ui'){r.style.setProperty('--ui-tracking',font.uiTracking);r.style.setProperty('--ui-word-spacing',font.uiWordSpacing);}}r.removeAttribute('data-font');})();`\n}\n"],"mappings":"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"}
|
package/dist/lib/themes.d.ts
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
* `surfaceHover`, which is what shadcn components actually use it for.
|
|
34
34
|
*/
|
|
35
35
|
import { type AccentPreset, type AdeaTheme, type AdeaThemeRecord } from '@adea-ai/themes';
|
|
36
|
+
import { type AccentRoles } from './accent-roles';
|
|
36
37
|
export { isThemeAccentId } from './theme-accents';
|
|
37
38
|
export type ThemeAppearance = 'light' | 'dark';
|
|
38
39
|
/**
|
|
@@ -147,7 +148,26 @@ export type ThemeVariant = {
|
|
|
147
148
|
/** The categorical series, in order. */
|
|
148
149
|
chart: readonly string[];
|
|
149
150
|
provenance: ThemeProvenance;
|
|
151
|
+
/**
|
|
152
|
+
* Custom properties written verbatim over {@link themeCssVariables}, last.
|
|
153
|
+
*
|
|
154
|
+
* For a variant a host built from data it does not control the derivation of —
|
|
155
|
+
* an imported theme whose painted colours must stay exactly what the user
|
|
156
|
+
* imported. The catalogue's own variants never carry it, so the derived roles
|
|
157
|
+
* stay the only source of truth for them.
|
|
158
|
+
*/
|
|
159
|
+
tokens?: Readonly<Record<string, string>>;
|
|
150
160
|
};
|
|
161
|
+
/**
|
|
162
|
+
* The design system's view of one catalogue record, built in full and at once.
|
|
163
|
+
*
|
|
164
|
+
* For a host that holds a record the catalogue does not — an imported theme, a
|
|
165
|
+
* reconstruction from stored data — and wants a variant it can hand to
|
|
166
|
+
* `ThemeProvider`'s `themes`. It throws for a record the shadcn projection cannot
|
|
167
|
+
* map, here and now, rather than on first paint. The catalogue itself goes through
|
|
168
|
+
* {@link lazyVariant} instead.
|
|
169
|
+
*/
|
|
170
|
+
export declare function variantFromRecord(theme: AdeaThemeRecord): ThemeVariant;
|
|
151
171
|
/**
|
|
152
172
|
* A variant's `--primary-subtle`: the tint over `var(--primary)` at the strength its
|
|
153
173
|
* catalogue record measures (`primarySubtleAlpha`). A variant that is not in the
|
|
@@ -157,6 +177,24 @@ export declare function primarySubtleFor(theme: Pick<ThemeVariant, 'id' | 'appea
|
|
|
157
177
|
/** The catalogue, in catalogue order. */
|
|
158
178
|
export declare const builtinThemes: readonly ThemeVariant[];
|
|
159
179
|
export declare function themeById(id: string): ThemeVariant | undefined;
|
|
180
|
+
/**
|
|
181
|
+
* The variant a stored id means, or the default of the same appearance.
|
|
182
|
+
*
|
|
183
|
+
* An id the registry does not hold — an imported theme that was deleted, a
|
|
184
|
+
* catalogue entry that was renamed — must still paint *something of the appearance
|
|
185
|
+
* being shown*. Falling back to whichever variant happens to be first in the
|
|
186
|
+
* registry painted a dark palette under a light `color-scheme` for a light id, so
|
|
187
|
+
* the fallback is the appearance's own default, then any variant of that
|
|
188
|
+
* appearance, then the registry's first entry only when it holds nothing else.
|
|
189
|
+
*
|
|
190
|
+
* `registry` is the builtin catalogue plus whatever extras the host supplies; the
|
|
191
|
+
* stored id is never rewritten, only resolved, so a host that keeps an unknown id
|
|
192
|
+
* keeps resolving it if the theme comes back.
|
|
193
|
+
*
|
|
194
|
+
* The fallback order is translated from Zeron's `ThemeRegistry::resolve` (MIT,
|
|
195
|
+
* Copyright (c) 2026 Wing); see NOTICE.
|
|
196
|
+
*/
|
|
197
|
+
export declare function resolveVariant(registry: readonly ThemeVariant[], id: string, appearance: ThemeAppearance): ThemeVariant;
|
|
160
198
|
/** The variants of one appearance, which is what a picker for that mode shows. */
|
|
161
199
|
export declare function themesForAppearance(appearance: ThemeAppearance): readonly ThemeVariant[];
|
|
162
200
|
/** The families in the catalogue, in catalogue order. */
|
|
@@ -252,7 +290,28 @@ export declare function appearancePreviewThemes(selection: {
|
|
|
252
290
|
export declare function accentVariables(accentId: string, appearance: ThemeAppearance, pair?: {
|
|
253
291
|
lightThemeId: string;
|
|
254
292
|
darkThemeId: string;
|
|
255
|
-
}): Record<string, string> | undefined;
|
|
293
|
+
}, options?: AccentVariableOptions): Record<string, string> | undefined;
|
|
294
|
+
/** What an accent is held to when it is resolved against a surface. */
|
|
295
|
+
export type AccentVariableOptions = {
|
|
296
|
+
/** The surface the accent must read on: the variant's `colors.background`. */
|
|
297
|
+
background?: string;
|
|
298
|
+
/**
|
|
299
|
+
* The contrast floor against `background`. Absent, a preset is painted verbatim
|
|
300
|
+
* and only a custom `#rrggbb` is held to the default floor.
|
|
301
|
+
*/
|
|
302
|
+
floor?: number;
|
|
303
|
+
/** The variant the accent is applied to, so the tint takes its measured strength. */
|
|
304
|
+
variantId?: string;
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
307
|
+
* An accent selection resolved to the full {@link AccentRoles}, against a theme
|
|
308
|
+
* pair. {@link accentVariables} is the document half of this; an element-scoped
|
|
309
|
+
* paint (`paintAccentRoles`) wants all of it, including the tint.
|
|
310
|
+
*/
|
|
311
|
+
export declare function accentRolesForSelection(accentId: string, appearance: ThemeAppearance, pair?: {
|
|
312
|
+
lightThemeId: string;
|
|
313
|
+
darkThemeId: string;
|
|
314
|
+
}, options?: AccentVariableOptions): AccentRoles | undefined;
|
|
256
315
|
/**
|
|
257
316
|
* The roles a variant sets, as CSS custom properties.
|
|
258
317
|
*
|
|
@@ -262,6 +321,17 @@ export declare function accentVariables(accentId: string, appearance: ThemeAppea
|
|
|
262
321
|
* than separate roles, which is why they are built here and not in the catalogue.
|
|
263
322
|
*/
|
|
264
323
|
export declare function themeCssVariables(theme: ThemeVariant): Record<string, string>;
|
|
324
|
+
/**
|
|
325
|
+
* Token values with every `oklch()` colour written as `#rrggbb`.
|
|
326
|
+
*
|
|
327
|
+
* For a host whose stylesheet, tests or native shell speak hex: the catalogue's
|
|
328
|
+
* roles are `oklch()` strings, and this is the one conversion, gamut-mapped by the
|
|
329
|
+
* catalogue's own `oklchToHex`. A value that is already hex, carries alpha
|
|
330
|
+
* (`rgba()`), or is not a plain colour (`color-mix()`, `var()`) is left byte for
|
|
331
|
+
* byte, because converting would either lose the alpha or be a guess. Suitable as
|
|
332
|
+
* `ThemeProvider`'s `transformTokens`.
|
|
333
|
+
*/
|
|
334
|
+
export declare function tokensAsHex(tokens: Readonly<Record<string, string>>): Record<string, string>;
|
|
265
335
|
export type ThemeFinding = {
|
|
266
336
|
themeId: string;
|
|
267
337
|
role: string;
|
package/dist/lib/themes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"themes.d.ts","sourceRoot":"","sources":["../../src/lib/themes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"themes.d.ts","sourceRoot":"","sources":["../../src/lib/themes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAiBL,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,eAAe,EAErB,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EAA+C,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAG9F,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjD,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,CAAA;AAE9C;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,MAAM,CAAA;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,MAAM,CAAA;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,eAAe,EAAE,MAAM,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,gBAAgB,EAAE,MAAM,CAAA;IACxB,WAAW,EAAE,MAAM,CAAA;IACnB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,iBAAiB,EAAE,MAAM,CAAA;IACzB,sFAAsF;IACtF,iBAAiB,EAAE,MAAM,CAAA;IACzB,2BAA2B,EAAE,MAAM,CAAA;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,MAAM,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,MAAM,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,cAAc,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,MAAM,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,sBAAsB,EAAE,MAAM,CAAA;CAC/B,CAAA;AAED,gFAAgF;AAChF,MAAM,MAAM,oBAAoB,GAAG;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,0CAA0C;AAC1C,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,iEAAiE;AACjE,MAAM,MAAM,eAAe,GAAG;IAC5B,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,6DAA6D;IAC7D,EAAE,EAAE,MAAM,CAAA;IACV,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,eAAe,CAAA;IAC3B,mCAAmC;IACnC,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,WAAW,CAAA;IACnB,QAAQ,EAAE,oBAAoB,CAAA;IAC9B,MAAM,EAAE,kBAAkB,CAAA;IAC1B,wCAAwC;IACxC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,UAAU,EAAE,eAAe,CAAA;IAC3B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAC1C,CAAA;AAuGD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY,CActE;AAkDD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,GAAG,YAAY,CAAC,GAAG,MAAM,CAGvF;AAED,yCAAyC;AACzC,eAAO,MAAM,aAAa,EAAE,SAAS,YAAY,EAA8C,CAAA;AAE/F,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAG9D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,SAAS,YAAY,EAAE,EACjC,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,eAAe,GAC1B,YAAY,CASd;AAED,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,eAAe,GAAG,SAAS,YAAY,EAAE,CAGxF;AAED,yDAAyD;AACzD,wBAAgB,aAAa,IAAI;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,YAAY,EAAE,CAAA;CAAE,EAAE,CAY3F;AAED,kEAAkE;AAClE,eAAO,MAAM,OAAO,yBAAU,CAAA;AAE9B;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,eAAe,EAAc,CAAA;AAEjE,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAEvE;AASD;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,GAClB,SAAS,YAAY,EAAE,CAGzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,SAAS,EAChB,IAAI,EAAE,SAAS,GACd,YAAY,GAAG,SAAS,CAM1B;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,CAAC,CAO7F;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE;IACjD,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;CACf,GAAG;IAAE,UAAU,EAAE,eAAe,CAAC;IAAC,SAAS,EAAE,eAAe,CAAA;CAAE,CAI9D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,eAAe,EAC3B,IAAI,GAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAGhD,EACD,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAGpC;AAED,uEAAuE;AACvE,MAAM,MAAM,qBAAqB,GAAG;IAClC,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,eAAe,EAC3B,IAAI,GAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAGhD,EACD,OAAO,GAAE,qBAA0B,GAClC,WAAW,GAAG,SAAS,CAYzB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA8G7E;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAU5F;AAUD,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAMD,+FAA+F;AAC/F,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,GAAG,YAAY,EAAE,CAMjE;AAED,8EAA8E;AAC9E,wBAAgB,qBAAqB,CACnC,MAAM,GAAE,SAAS,YAAY,EAAkB,GAC9C,YAAY,EAAE,CAchB;AAED,eAAO,MAAM,mBAAmB,eAAe,CAAA;AAC/C,eAAO,MAAM,kBAAkB,cAAc,CAAA;AAE7C,kFAAkF;AAClF,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA"}
|