@theme-registry/refract 0.1.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/LICENSE +21 -0
- package/dist/_createTheme-255ff48e.esm.js +2 -0
- package/dist/_createTheme-255ff48e.esm.js.map +1 -0
- package/dist/_createTheme-41c01033.cjs.js +2 -0
- package/dist/_createTheme-41c01033.cjs.js.map +1 -0
- package/dist/_errors-be768b25.cjs.js +2 -0
- package/dist/_errors-be768b25.cjs.js.map +1 -0
- package/dist/_errors-d75a5c74.esm.js +2 -0
- package/dist/_errors-d75a5c74.esm.js.map +1 -0
- package/dist/adapter-kit.cjs.js +2 -0
- package/dist/adapter-kit.cjs.js.map +1 -0
- package/dist/adapter-kit.d.ts +88 -0
- package/dist/adapter-kit.esm.js +2 -0
- package/dist/adapter-kit.esm.js.map +1 -0
- package/dist/build/auditCommand.d.ts +19 -0
- package/dist/build/buildCommand.d.ts +23 -0
- package/dist/build/cli.d.ts +2 -0
- package/dist/build/config.d.ts +58 -0
- package/dist/build/diff.d.ts +56 -0
- package/dist/build/diffCommand.d.ts +29 -0
- package/dist/build/emit.d.ts +20 -0
- package/dist/build/emitTheme.d.ts +39 -0
- package/dist/build/guide.d.ts +34 -0
- package/dist/build/importCommand.d.ts +42 -0
- package/dist/build/index.d.ts +33 -0
- package/dist/build/init.d.ts +32 -0
- package/dist/build/paths.d.ts +42 -0
- package/dist/build/skillsCommand.d.ts +53 -0
- package/dist/build/tokensCommand.d.ts +17 -0
- package/dist/build/vendor.d.ts +39 -0
- package/dist/build.cjs.js +2 -0
- package/dist/build.cjs.js.map +1 -0
- package/dist/build.esm.js +2 -0
- package/dist/build.esm.js.map +1 -0
- package/dist/cli.js +8250 -0
- package/dist/cli.js.map +1 -0
- package/dist/color-math.cjs.js +2 -0
- package/dist/color-math.cjs.js.map +1 -0
- package/dist/color-math.esm.js +2 -0
- package/dist/color-math.esm.js.map +1 -0
- package/dist/core/ThemeAdapter.d.ts +234 -0
- package/dist/core/createTheme.d.ts +96 -0
- package/dist/core/cssProperties.d.ts +8 -0
- package/dist/core/defineAdapter.d.ts +25 -0
- package/dist/core/derive/crossProperty.d.ts +31 -0
- package/dist/core/derive/index.d.ts +11 -0
- package/dist/core/derive/resolveTokens.d.ts +27 -0
- package/dist/core/errors.d.ts +24 -0
- package/dist/core/index.d.ts +16 -0
- package/dist/core/media/containers.d.ts +20 -0
- package/dist/core/media/defaults.d.ts +3 -0
- package/dist/core/media/descriptors.d.ts +12 -0
- package/dist/core/media/index.d.ts +11 -0
- package/dist/core/media/queries.d.ts +20 -0
- package/dist/core/model/buildModel.d.ts +116 -0
- package/dist/core/model/index.d.ts +14 -0
- package/dist/core/model/interpreted.d.ts +37 -0
- package/dist/core/model/mergeRuleSet.d.ts +68 -0
- package/dist/core/model/model.d.ts +260 -0
- package/dist/core/noopAdapter.d.ts +26 -0
- package/dist/core/normalize/index.d.ts +15 -0
- package/dist/core/normalize/properties.d.ts +20 -0
- package/dist/core/normalize/recipeResolver.d.ts +19 -0
- package/dist/core/normalize/recipes.d.ts +38 -0
- package/dist/core/normalize/responsive.d.ts +40 -0
- package/dist/core/normalize/types.d.ts +261 -0
- package/dist/core/rawTheme.d.ts +53 -0
- package/dist/core/ref.d.ts +13 -0
- package/dist/core/subsystem.d.ts +115 -0
- package/dist/core/types.d.ts +31 -0
- package/dist/core/units.d.ts +93 -0
- package/dist/dtcg/export.d.ts +48 -0
- package/dist/dtcg/import.d.ts +43 -0
- package/dist/dtcg/index.d.ts +21 -0
- package/dist/dtcg/parse.d.ts +6 -0
- package/dist/dtcg/types.d.ts +69 -0
- package/dist/dtcg.cjs.js +2 -0
- package/dist/dtcg.cjs.js.map +1 -0
- package/dist/dtcg.esm.js +2 -0
- package/dist/dtcg.esm.js.map +1 -0
- package/dist/index.cjs.js +2 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/subsystems/animation/index.d.ts +13 -0
- package/dist/subsystems/animation/recipes.d.ts +20 -0
- package/dist/subsystems/animation/types.d.ts +52 -0
- package/dist/subsystems/borders/index.d.ts +10 -0
- package/dist/subsystems/borders/recipes.d.ts +29 -0
- package/dist/subsystems/borders/types.d.ts +50 -0
- package/dist/subsystems/colors/audit.d.ts +67 -0
- package/dist/subsystems/colors/colorInput.d.ts +17 -0
- package/dist/subsystems/colors/index.d.ts +12 -0
- package/dist/subsystems/colors/keywords.d.ts +9 -0
- package/dist/subsystems/colors/normalize.d.ts +26 -0
- package/dist/subsystems/colors/recipes.d.ts +21 -0
- package/dist/subsystems/colors/types.d.ts +98 -0
- package/dist/subsystems/colors/utils.d.ts +104 -0
- package/dist/subsystems/components/index.d.ts +13 -0
- package/dist/subsystems/components/recipes.d.ts +22 -0
- package/dist/subsystems/components/types.d.ts +46 -0
- package/dist/subsystems/effects/index.d.ts +9 -0
- package/dist/subsystems/effects/recipes.d.ts +24 -0
- package/dist/subsystems/effects/types.d.ts +63 -0
- package/dist/subsystems/effects/utils.d.ts +57 -0
- package/dist/subsystems/globals/index.d.ts +21 -0
- package/dist/subsystems/globals/presets.d.ts +33 -0
- package/dist/subsystems/globals/types.d.ts +48 -0
- package/dist/subsystems/index.d.ts +21 -0
- package/dist/subsystems/layout/index.d.ts +14 -0
- package/dist/subsystems/layout/normalize.d.ts +35 -0
- package/dist/subsystems/layout/recipes.d.ts +16 -0
- package/dist/subsystems/layout/structural.d.ts +37 -0
- package/dist/subsystems/layout/types.d.ts +154 -0
- package/dist/subsystems/typography/index.d.ts +9 -0
- package/dist/subsystems/typography/normalize.d.ts +17 -0
- package/dist/subsystems/typography/recipes.d.ts +21 -0
- package/dist/subsystems/typography/types.d.ts +49 -0
- package/package.json +83 -0
- package/skills/adapter-scaffold/SKILL.md +85 -0
- package/skills/adapter-usage/SKILL.md +75 -0
- package/skills/build-config/SKILL.md +77 -0
- package/skills/colors/SKILL.md +134 -0
- package/skills/consuming-the-output/SKILL.md +62 -0
- package/skills/dtcg-import/SKILL.md +76 -0
- package/skills/layout/SKILL.md +65 -0
- package/skills/overrides-and-child-themes/SKILL.md +70 -0
- package/skills/recipes-and-composition/SKILL.md +110 -0
- package/skills/theme-authoring/SKILL.md +107 -0
- package/skills/theme-foundations/SKILL.md +127 -0
- package/skills/troubleshooting/SKILL.md +97 -0
- package/skills/typography/SKILL.md +66 -0
- package/skills/visual-effects/SKILL.md +96 -0
- package/src/subsystems/colors/utils.ts +299 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";const t=new Set([3,6]),r=(t,r,e)=>Math.min(e,Math.max(r,t)),e=t=>r(t,0,255),o=(t,r)=>{const e=10**r;return Math.round(t*e)/e},n=t=>Math.round(1e3*r(t,0,1))/1e3,s=r=>{const e=r.replace(/^\s*#|\s*$/g,"");if(!t.has(e.length))throw new Error(`Unsupported hex length: "${e}". Use 3 or 6 digits.`);const o=3===e.length?e.replace(/(.)/g,"$1$1"):e;return[parseInt(o.substring(0,2),16),parseInt(o.substring(2,4),16),parseInt(o.substring(4,6),16)]},a=t=>{if(3!==t.length)throw new Error(`Expected RGB tuple of length 3, received ${t.length}`);return`#${t.map(t=>{const r=e(Math.round(t)).toString(16);return 1===r.length?`0${r}`:r}).join("")}`},h=/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)$/i,c=t=>{const r=t.trim(),e=h.exec(r);return e?{rgb:[Number(e[1]),Number(e[2]),Number(e[3])],a:void 0===e[4]?1:n(Number(e[4]))}:{rgb:s(r),a:1}},u=(t,r=1)=>{const o=n(r),[s,a,h]=t,c=e(Math.round(s)),u=e(Math.round(a)),p=e(Math.round(h));return o>=1?`rgb(${c}, ${u}, ${p})`:`rgba(${c}, ${u}, ${p}, ${o})`},p=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/,l=180/Math.PI,g=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4),i=t=>t>=.0031308?1.055*Math.pow(t,1/2.4)-.055:12.92*t,b=t=>{const[r,e,o]=t,n=((t,r,e)=>{const o=.4122214708*t+.5363325363*r+.0514459929*e,n=.2119034982*t+.6806995451*r+.1073969566*e,s=.0883024619*t+.2817188376*r+.6299787005*e,a=Math.cbrt(o),h=Math.cbrt(n),c=Math.cbrt(s);return{L:.2104542553*a+.793617785*h-.0040720468*c,a:1.9779984951*a-2.428592205*h+.4505937099*c,b:.0259040371*a+.7827717662*h-.808675766*c}})(g(r/255),g(e/255),g(o/255)),s=Math.sqrt(n.a*n.a+n.b*n.b);let a=Math.atan2(n.b,n.a)*l;return a<0&&(a+=360),{L:100*n.L,C:s,h:a}},x=t=>{const e=r(t.L,0,100)/100,o=t.h/l,n=Math.cos(o),s=Math.sin(o),a=t=>((t,r,e)=>{const o=t+.3963377774*r+.2158037573*e,n=t-.1055613458*r-.0638541728*e,s=t-.0894841775*r-1.291485548*e,a=o*o*o,h=n*n*n,c=s*s*s;return[4.0767416621*a-3.3077115913*h+.2309699292*c,-1.2684380046*a+2.6097574011*h-.3413193965*c,-.0041960863*a-.7034186147*h+1.707614701*c]})(e,t*n,t*s),h=t=>t.every(t=>t>=-1e-6&&t<=1.000001);let c=a(t.C);if(!h(c)){let r=0,e=t.C;for(let t=0;t<24;t++){const t=(r+e)/2;h(a(t))?r=t:e=t}c=a(r)}return[255*i(r(c[0],0,1)),255*i(r(c[1],0,1)),255*i(r(c[2],0,1))]},$=(t,r)=>{const{rgb:e,a:o}=c(t);return u(x(r(b(e))),o)},d=(t,r)=>$(t,t=>({...t,h:((t.h+r)%360+360)%360}));exports.adjust=(t,e)=>$(t,t=>({L:void 0===e.l?t.L:r(e.l,0,100),C:void 0===e.c?t.C:Math.max(0,t.C*e.c),h:void 0===e.h?t.h:((t.h+e.h)%360+360)%360})),exports.alpha=(t,e)=>{const{rgb:o}=c(t);return u(o,(t=>r(t,0,100))(e)/100)},exports.complement=t=>d(t,180),exports.convertHexToRGB=s,exports.convertRgbToHex=a,exports.darken=(t,e)=>$(t,t=>({...t,L:r(t.L-e,0,100)})),exports.isHexColor=t=>p.test(t.trim()),exports.lighten=(t,e)=>$(t,t=>({...t,L:r(t.L+e,0,100)})),exports.oklchToRgb=x,exports.parseColor=c,exports.rgbToOklch=b,exports.rotateHue=d,exports.serializeColor=u,exports.setL=(t,e)=>$(t,t=>({...t,L:r(e,0,100)})),exports.toHexColor=t=>{const{rgb:r,a:o}=c(t),n=a(r);if(o>=1)return n;return`${n}${e(Math.round(255*o)).toString(16).padStart(2,"0")}`},exports.toOklchColor=t=>{const{rgb:r,a:e}=c(t),{L:s,C:a,h:h}=b(r),u=`${o(s,2)}% ${o(a,4)} ${o(h,2)}`;return e>=1?`oklch(${u})`:`oklch(${u} / ${n(e)})`};
|
|
2
|
+
//# sourceMappingURL=color-math.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color-math.cjs.js","sources":["../src/subsystems/colors/utils.ts"],"sourcesContent":["/**\n * Pure colour math for palette step + variant synthesis.\n *\n * Lightness / hue / chroma work runs in **OKLCH** (a perceptual space) via Björn Ottosson's\n * public-domain matrices — parse → sRGB → linear → OKLab → OKLCH → apply → gamut-map → linear →\n * sRGB → quantize → serialize — so equal lightness steps look even and one lightness reads the same\n * across hues. hex / `rgba()` strings are only touched at the boundary (`parseColor` in,\n * `serializeColor` out). `lighten` / `darken` shift OKLCH lightness by ΔL points; `setL` places an\n * absolute lightness; `rotateHue` / `complement` turn the hue; `alpha` sets opacity. All are\n * string-in / string-out so the same fns run at normalize time (baking the cached value) and later\n * in the derivation registry — AND are vendored to consumers (see `build/vendor.ts`) so a value\n * computed live in the app matches the emitted CSS variable. Kept dependency-free for that vendoring.\n */\n\n/** An `[r, g, b]` channel triple, each `0–255`. */\nexport type RGBTuple = readonly [number, number, number];\n\n/** An `[r, g, b, a]` quad — channels `0–255`, alpha `0–1`. */\nexport type RGBATuple = readonly [number, number, number, number];\n\n/** A colour authored/worked as a tuple — opaque `[r,g,b]` or translucent `[r,g,b,a]`. */\nexport type ColorTuple = RGBTuple | RGBATuple;\n\nconst HEX_LENGTHS = new Set([3, 6]);\n\nconst clamp = (value: number, min: number, max: number): number => Math.min(max, Math.max(min, value));\nconst clampChannel = (value: number): number => clamp(value, 0, 255);\nconst clampAlpha = (value: number): number => clamp(value, 0, 1);\nconst clampPercent = (percent: number): number => clamp(percent, 0, 100);\n\n/** Round to `dp` decimal places (tidy CSS output — `71.8`, not `71.79999`). */\nconst roundTo = (value: number, dp: number): number => {\n const f = 10 ** dp;\n return Math.round(value * f) / f;\n};\n\n/** Round an alpha to 3 decimals so serialized `rgba()` stays tidy (`0.4`, not `0.4000001`). */\nconst roundAlpha = (a: number): number => Math.round(clampAlpha(a) * 1000) / 1000;\n\n/** Parse a 3- or 6-digit hex string (with or without `#`) into an `[r, g, b]` tuple. */\nexport const convertHexToRGB = (hex: string): RGBTuple => {\n const sanitized = hex.replace(/^\\s*#|\\s*$/g, \"\");\n\n if (!HEX_LENGTHS.has(sanitized.length)) {\n // Plain Error on purpose: this module is vendored import-free (build/vendor.ts) so it can be\n // transpiled standalone into consumer bundles — it cannot import RefractError. Its parse errors are\n // re-thrown as REFRACT_E_COLOR_INPUT by colors/normalize.ts, which is the coded authoring surface.\n throw new Error(`Unsupported hex length: \"${sanitized}\". Use 3 or 6 digits.`);\n }\n\n const normalized = sanitized.length === 3 ? sanitized.replace(/(.)/g, \"$1$1\") : sanitized;\n\n return [\n parseInt(normalized.substring(0, 2), 16),\n parseInt(normalized.substring(2, 4), 16),\n parseInt(normalized.substring(4, 6), 16),\n ] as RGBTuple;\n};\n\n/** Serialize an `[r, g, b]` tuple back to a `#rrggbb` string, clamping each channel to `0–255`. */\nexport const convertRgbToHex = (rgb: RGBTuple): string => {\n if (rgb.length !== 3) {\n throw new Error(`Expected RGB tuple of length 3, received ${rgb.length}`);\n }\n\n const hex = rgb\n .map(value => {\n const next = clampChannel(Math.round(value)).toString(16);\n return next.length === 1 ? `0${next}` : next;\n })\n .join(\"\");\n\n return `#${hex}`;\n};\n\nconst RGB_FN = /^rgba?\\(\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*(?:,\\s*([\\d.]+)\\s*)?\\)$/i;\n\n/**\n * Parse any canonical colour string this subsystem produces — a 3/6-digit hex **or** an\n * `rgb()` / `rgba()` string — into `{ rgb, a }` (alpha defaults to `1`). This is the boundary\n * gate; a value that isn't parseable rgb (e.g. a bare CSS keyword or `var(--…)`) throws, so\n * callers can surface a friendly \"this colour can't be derived from\" error.\n */\nexport const parseColor = (value: string): { rgb: RGBTuple; a: number } => {\n const trimmed = value.trim();\n\n const fn = RGB_FN.exec(trimmed);\n if (fn) {\n return {\n rgb: [Number(fn[1]), Number(fn[2]), Number(fn[3])] as RGBTuple,\n a: fn[4] === undefined ? 1 : roundAlpha(Number(fn[4])),\n };\n }\n\n return { rgb: convertHexToRGB(trimmed), a: 1 };\n};\n\n/**\n * Serialize `[r, g, b]` (+ optional alpha) to the canonical CSS string — always `rgb(r, g, b)`\n * when fully opaque, else `rgba(r, g, b, a)`. The single serialization point for the subsystem, so\n * the Model (and every adapter + `resolveToken`) always carries one canonical `rgb()/rgba()` form\n * per colour — never hex. (`convertRgbToHex` remains for the vendored consumer helper + input parsing.)\n */\nexport const serializeColor = (rgb: RGBTuple, a = 1): string => {\n const alpha = roundAlpha(a);\n const [r, g, b] = rgb;\n const R = clampChannel(Math.round(r));\n const G = clampChannel(Math.round(g));\n const B = clampChannel(Math.round(b));\n return alpha >= 1 ? `rgb(${R}, ${G}, ${B})` : `rgba(${R}, ${G}, ${B}, ${alpha})`;\n};\n\nconst HEX_RE = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;\n\n/** Whether a string is a 3- or 6-digit `#`-prefixed hex colour. */\nexport const isHexColor = (value: string): boolean => HEX_RE.test(value.trim());\n\n\n// ─── OKLCH synthesis (Björn Ottosson public-domain matrices) ──────────────────────────────────\n// Lightness / hue / chroma adjustments happen here, in a perceptual space, so equal ΔL steps look\n// even and one lightness reads consistently across hues. `L` is carried on a 0–100 scale (matching\n// the authoring dials); OKLab's native `L` is 0–1. sRGB channels are 0–255 only at the boundary.\n\n/** An OKLCH colour — perceptual lightness `L` (0–100), chroma `C` (≥ 0), hue `h` (degrees, 0–360). */\nexport type OKLCH = { L: number; C: number; h: number };\n\nconst DEG = 180 / Math.PI;\nconst GAMUT_EPS = 1e-6;\nconst GAMUT_ITERATIONS = 24;\n\n/** sRGB channel (0–1) → linear-light (0–1). */\nconst srgbToLinear = (c: number): number =>\n c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n\n/** Linear-light channel (0–1) → sRGB (0–1). */\nconst linearToSrgb = (c: number): number =>\n c >= 0.0031308 ? 1.055 * Math.pow(c, 1 / 2.4) - 0.055 : 12.92 * c;\n\n/** Linear-sRGB `[r, g, b]` (0–1) → OKLab `{ L: 0–1, a, b }`. */\nconst linearRgbToOklab = (\n lr: number,\n lg: number,\n lb: number,\n): { L: number; a: number; b: number } => {\n const l = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb;\n const m = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb;\n const s = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb;\n const l_ = Math.cbrt(l);\n const m_ = Math.cbrt(m);\n const s_ = Math.cbrt(s);\n return {\n L: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,\n a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,\n b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_,\n };\n};\n\n/** OKLab `{ L: 0–1, a, b }` → linear-sRGB `[r, g, b]` (may fall outside 0–1 when out of gamut). */\nconst oklabToLinearRgb = (L: number, a: number, b: number): [number, number, number] => {\n const l_ = L + 0.3963377774 * a + 0.2158037573 * b;\n const m_ = L - 0.1055613458 * a - 0.0638541728 * b;\n const s_ = L - 0.0894841775 * a - 1.291485548 * b;\n const l = l_ * l_ * l_;\n const m = m_ * m_ * m_;\n const s = s_ * s_ * s_;\n return [\n 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,\n -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,\n -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s,\n ];\n};\n\n/** Convert an sRGB `[r, g, b]` (0–255) triple to OKLCH (`L` on a 0–100 scale). */\nexport const rgbToOklch = (rgb: RGBTuple): OKLCH => {\n const [r, g, b] = rgb;\n const lab = linearRgbToOklab(srgbToLinear(r / 255), srgbToLinear(g / 255), srgbToLinear(b / 255));\n const C = Math.sqrt(lab.a * lab.a + lab.b * lab.b);\n let h = Math.atan2(lab.b, lab.a) * DEG;\n if (h < 0) h += 360;\n return { L: lab.L * 100, C, h };\n};\n\n/**\n * Convert OKLCH back to an sRGB `[r, g, b]` triple (**unquantized** 0–255 floats). Holds `L` and\n * `h`; when the requested chroma is outside sRGB it binary-searches `C` downward — fixed iterations,\n * no data-dependent early exit, so build- and runtime-computed values agree (§20.8) — until the\n * linear result is in `[0, 1]`. Lightness and hue are never adjusted, so a synthesized ramp keeps\n * its hue and even lightness spacing; only saturation gives way at the gamut boundary. The floats\n * are quantized exactly once, later, by `serializeColor`, so the 0–255 channel clamp never fires.\n */\nexport const oklchToRgb = (color: OKLCH): RGBTuple => {\n const L = clamp(color.L, 0, 100) / 100;\n const hr = color.h / DEG;\n const cosh = Math.cos(hr);\n const sinh = Math.sin(hr);\n\n const linearAt = (c: number): [number, number, number] => oklabToLinearRgb(L, c * cosh, c * sinh);\n const inGamut = (lin: [number, number, number]): boolean =>\n lin.every(v => v >= -GAMUT_EPS && v <= 1 + GAMUT_EPS);\n\n let lin = linearAt(color.C);\n if (!inGamut(lin)) {\n // `C = 0` (a neutral grey at this L) is always in gamut, so `lo` starts from a valid floor.\n let lo = 0;\n let hi = color.C;\n for (let i = 0; i < GAMUT_ITERATIONS; i++) {\n const mid = (lo + hi) / 2;\n if (inGamut(linearAt(mid))) lo = mid;\n else hi = mid;\n }\n lin = linearAt(lo);\n }\n\n return [\n linearToSrgb(clamp(lin[0], 0, 1)) * 255,\n linearToSrgb(clamp(lin[1], 0, 1)) * 255,\n linearToSrgb(clamp(lin[2], 0, 1)) * 255,\n ] as RGBTuple;\n};\n\n/** Run a colour string through OKLCH, apply `transform`, and re-serialize (alpha preserved). */\nconst withOklch = (value: string, transform: (lch: OKLCH) => OKLCH): string => {\n const { rgb, a } = parseColor(value);\n return serializeColor(oklchToRgb(transform(rgbToOklch(rgb))), a);\n};\n\n/** Lighten a colour by `delta` OKLCH lightness points (0–100 scale); hue, chroma, alpha preserved. */\nexport const lighten = (value: string, delta: number): string =>\n withOklch(value, lch => ({ ...lch, L: clamp(lch.L + delta, 0, 100) }));\n\n/** Darken a colour by `delta` OKLCH lightness points (0–100 scale); hue, chroma, alpha preserved. */\nexport const darken = (value: string, delta: number): string =>\n withOklch(value, lch => ({ ...lch, L: clamp(lch.L - delta, 0, 100) }));\n\n/** Set a colour's absolute OKLCH lightness to `L` (0–100); hue, chroma, alpha preserved. */\nexport const setL = (value: string, L: number): string =>\n withOklch(value, lch => ({ ...lch, L: clamp(L, 0, 100) }));\n\n/** Rotate a colour's OKLCH hue by `deg` (signed) around the perceptual wheel; L, C, alpha preserved. */\nexport const rotateHue = (value: string, deg: number): string =>\n withOklch(value, lch => ({ ...lch, h: (((lch.h + deg) % 360) + 360) % 360 }));\n\n/** The perceptual complement — a 180° hue rotation at the same lightness and chroma. */\nexport const complement = (value: string): string => rotateHue(value, 180);\n\n/**\n * An `adjust` dial set — any subset. `l` = **absolute** OKLCH lightness target (0–100, raw L, the\n * opposite direction from a numeric-`steps` label); `c` = chroma **multiplier** (`1` keep, `0` grey,\n * `>1` more saturated where the gamut allows); `h` = **signed** hue rotation in degrees. Numbers\n * only — keeps the vendored math string-free.\n */\nexport type AdjustDials = { l?: number; c?: number; h?: number };\n\n/**\n * One-shot OKLCH placement — set an absolute lightness, scale chroma, and/or rotate hue in a single\n * derivation (each dial optional; an omitted dial leaves that component untouched). Alpha preserved.\n */\nexport const adjust = (value: string, dials: AdjustDials): string =>\n withOklch(value, lch => ({\n L: dials.l === undefined ? lch.L : clamp(dials.l, 0, 100),\n C: dials.c === undefined ? lch.C : Math.max(0, lch.C * dials.c),\n h: dials.h === undefined ? lch.h : (((lch.h + dials.h) % 360) + 360) % 360,\n }));\n\n/**\n * Convert any canonical colour string (the subsystem's `rgb()` / `rgba()` form, or a hex string)\n * to hex — 6-digit `#rrggbb` when opaque, 8-digit `#rrggbbaa` when translucent. Used by the DTCG\n * exporter, whose `color` token type is conventionally hex (every other output stays `rgb()`).\n */\nexport const toHexColor = (value: string): string => {\n const { rgb, a } = parseColor(value);\n const hex = convertRgbToHex(rgb);\n if (a >= 1) return hex;\n const aa = clampChannel(Math.round(a * 255)).toString(16).padStart(2, \"0\");\n return `${hex}${aa}`;\n};\n\n/**\n * Convert any canonical colour string (the subsystem's `rgb()` / `rgba()` form, or a hex string) to\n * a CSS Color 4 `oklch(L% C H)` string — `oklch(L% C H / a)` when translucent. `L` is a percentage\n * (0–100), `C` the absolute chroma, `H` in degrees, derived from the stored (quantized) rgb. Used by\n * the CSS adapter's `colorFormat: \"oklch\"` output; the colour is identical to the `rgb()` form.\n */\nexport const toOklchColor = (value: string): string => {\n const { rgb, a } = parseColor(value);\n const { L, C, h } = rgbToOklch(rgb);\n const body = `${roundTo(L, 2)}% ${roundTo(C, 4)} ${roundTo(h, 2)}`;\n return a >= 1 ? `oklch(${body})` : `oklch(${body} / ${roundAlpha(a)})`;\n};\n\n/**\n * Set a colour's opacity to `percent` (0–100) — an **absolute** alpha, not a relative fade\n * (`alpha(x, 40)` ⇒ 40% opaque). The rgb channels are untouched; only the alpha channel is\n * replaced. Result serializes to `rgba(…)` (or hex when `percent` is 100).\n */\nexport const alpha = (value: string, percent: number): string => {\n const { rgb } = parseColor(value);\n return serializeColor(rgb, clampPercent(percent) / 100);\n};\n"],"names":["HEX_LENGTHS","Set","clamp","value","min","max","Math","clampChannel","roundTo","dp","f","round","roundAlpha","a","convertHexToRGB","hex","sanitized","replace","has","length","Error","normalized","parseInt","substring","convertRgbToHex","rgb","map","next","toString","join","RGB_FN","parseColor","trimmed","trim","fn","exec","Number","undefined","serializeColor","alpha","r","g","b","R","G","B","HEX_RE","DEG","PI","srgbToLinear","c","pow","linearToSrgb","rgbToOklch","lab","lr","lg","lb","l","m","s","l_","cbrt","m_","s_","L","linearRgbToOklab","C","sqrt","h","atan2","oklchToRgb","color","hr","cosh","cos","sinh","sin","linearAt","oklabToLinearRgb","inGamut","lin","every","v","lo","hi","i","mid","withOklch","transform","rotateHue","deg","lch","dials","percent","clampPercent","delta","test","padStart","body"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const t=new Set([3,6]),r=(t,r,n)=>Math.min(n,Math.max(r,t)),n=t=>r(t,0,255),e=(t,r)=>{const n=10**r;return Math.round(t*n)/n},a=t=>Math.round(1e3*r(t,0,1))/1e3,o=r=>{const n=r.replace(/^\s*#|\s*$/g,"");if(!t.has(n.length))throw new Error(`Unsupported hex length: "${n}". Use 3 or 6 digits.`);const e=3===n.length?n.replace(/(.)/g,"$1$1"):n;return[parseInt(e.substring(0,2),16),parseInt(e.substring(2,4),16),parseInt(e.substring(4,6),16)]},h=t=>{if(3!==t.length)throw new Error(`Expected RGB tuple of length 3, received ${t.length}`);return`#${t.map(t=>{const r=n(Math.round(t)).toString(16);return 1===r.length?`0${r}`:r}).join("")}`},s=/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)$/i,u=t=>{const r=t.trim(),n=s.exec(r);return n?{rgb:[Number(n[1]),Number(n[2]),Number(n[3])],a:void 0===n[4]?1:a(Number(n[4]))}:{rgb:o(r),a:1}},c=(t,r=1)=>{const e=a(r),[o,h,s]=t,u=n(Math.round(o)),c=n(Math.round(h)),g=n(Math.round(s));return e>=1?`rgb(${u}, ${c}, ${g})`:`rgba(${u}, ${c}, ${g}, ${e})`},g=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/,$=t=>g.test(t.trim()),b=180/Math.PI,i=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4),d=t=>t>=.0031308?1.055*Math.pow(t,1/2.4)-.055:12.92*t,M=t=>{const[r,n,e]=t,a=((t,r,n)=>{const e=.4122214708*t+.5363325363*r+.0514459929*n,a=.2119034982*t+.6806995451*r+.1073969566*n,o=.0883024619*t+.2817188376*r+.6299787005*n,h=Math.cbrt(e),s=Math.cbrt(a),u=Math.cbrt(o);return{L:.2104542553*h+.793617785*s-.0040720468*u,a:1.9779984951*h-2.428592205*s+.4505937099*u,b:.0259040371*h+.7827717662*s-.808675766*u}})(i(r/255),i(n/255),i(e/255)),o=Math.sqrt(a.a*a.a+a.b*a.b);let h=Math.atan2(a.b,a.a)*b;return h<0&&(h+=360),{L:100*a.L,C:o,h:h}},l=t=>{const n=r(t.L,0,100)/100,e=t.h/b,a=Math.cos(e),o=Math.sin(e),h=t=>((t,r,n)=>{const e=t+.3963377774*r+.2158037573*n,a=t-.1055613458*r-.0638541728*n,o=t-.0894841775*r-1.291485548*n,h=e*e*e,s=a*a*a,u=o*o*o;return[4.0767416621*h-3.3077115913*s+.2309699292*u,-1.2684380046*h+2.6097574011*s-.3413193965*u,-.0041960863*h-.7034186147*s+1.707614701*u]})(n,t*a,t*o),s=t=>t.every(t=>t>=-1e-6&&t<=1.000001);let u=h(t.C);if(!s(u)){let r=0,n=t.C;for(let t=0;t<24;t++){const t=(r+n)/2;s(h(t))?r=t:n=t}u=h(r)}return[255*d(r(u[0],0,1)),255*d(r(u[1],0,1)),255*d(r(u[2],0,1))]},p=(t,r)=>{const{rgb:n,a:e}=u(t);return c(l(r(M(n))),e)},L=(t,n)=>p(t,t=>({...t,L:r(t.L+n,0,100)})),m=(t,n)=>p(t,t=>({...t,L:r(t.L-n,0,100)})),f=(t,n)=>p(t,t=>({...t,L:r(n,0,100)})),w=(t,r)=>p(t,t=>({...t,h:((t.h+r)%360+360)%360})),C=t=>w(t,180),v=(t,n)=>p(t,t=>({L:void 0===n.l?t.L:r(n.l,0,100),C:void 0===n.c?t.C:Math.max(0,t.C*n.c),h:void 0===n.h?t.h:((t.h+n.h)%360+360)%360})),x=t=>{const{rgb:r,a:e}=u(t),a=h(r);if(e>=1)return a;return`${a}${n(Math.round(255*e)).toString(16).padStart(2,"0")}`},I=t=>{const{rgb:r,a:n}=u(t),{L:o,C:h,h:s}=M(r),c=`${e(o,2)}% ${e(h,4)} ${e(s,2)}`;return n>=1?`oklch(${c})`:`oklch(${c} / ${a(n)})`},N=(t,n)=>{const{rgb:e}=u(t);return c(e,(t=>r(t,0,100))(n)/100)};export{v as adjust,N as alpha,C as complement,o as convertHexToRGB,h as convertRgbToHex,m as darken,$ as isHexColor,L as lighten,l as oklchToRgb,u as parseColor,M as rgbToOklch,w as rotateHue,c as serializeColor,f as setL,x as toHexColor,I as toOklchColor};
|
|
2
|
+
//# sourceMappingURL=color-math.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color-math.esm.js","sources":["../src/subsystems/colors/utils.ts"],"sourcesContent":["/**\n * Pure colour math for palette step + variant synthesis.\n *\n * Lightness / hue / chroma work runs in **OKLCH** (a perceptual space) via Björn Ottosson's\n * public-domain matrices — parse → sRGB → linear → OKLab → OKLCH → apply → gamut-map → linear →\n * sRGB → quantize → serialize — so equal lightness steps look even and one lightness reads the same\n * across hues. hex / `rgba()` strings are only touched at the boundary (`parseColor` in,\n * `serializeColor` out). `lighten` / `darken` shift OKLCH lightness by ΔL points; `setL` places an\n * absolute lightness; `rotateHue` / `complement` turn the hue; `alpha` sets opacity. All are\n * string-in / string-out so the same fns run at normalize time (baking the cached value) and later\n * in the derivation registry — AND are vendored to consumers (see `build/vendor.ts`) so a value\n * computed live in the app matches the emitted CSS variable. Kept dependency-free for that vendoring.\n */\n\n/** An `[r, g, b]` channel triple, each `0–255`. */\nexport type RGBTuple = readonly [number, number, number];\n\n/** An `[r, g, b, a]` quad — channels `0–255`, alpha `0–1`. */\nexport type RGBATuple = readonly [number, number, number, number];\n\n/** A colour authored/worked as a tuple — opaque `[r,g,b]` or translucent `[r,g,b,a]`. */\nexport type ColorTuple = RGBTuple | RGBATuple;\n\nconst HEX_LENGTHS = new Set([3, 6]);\n\nconst clamp = (value: number, min: number, max: number): number => Math.min(max, Math.max(min, value));\nconst clampChannel = (value: number): number => clamp(value, 0, 255);\nconst clampAlpha = (value: number): number => clamp(value, 0, 1);\nconst clampPercent = (percent: number): number => clamp(percent, 0, 100);\n\n/** Round to `dp` decimal places (tidy CSS output — `71.8`, not `71.79999`). */\nconst roundTo = (value: number, dp: number): number => {\n const f = 10 ** dp;\n return Math.round(value * f) / f;\n};\n\n/** Round an alpha to 3 decimals so serialized `rgba()` stays tidy (`0.4`, not `0.4000001`). */\nconst roundAlpha = (a: number): number => Math.round(clampAlpha(a) * 1000) / 1000;\n\n/** Parse a 3- or 6-digit hex string (with or without `#`) into an `[r, g, b]` tuple. */\nexport const convertHexToRGB = (hex: string): RGBTuple => {\n const sanitized = hex.replace(/^\\s*#|\\s*$/g, \"\");\n\n if (!HEX_LENGTHS.has(sanitized.length)) {\n // Plain Error on purpose: this module is vendored import-free (build/vendor.ts) so it can be\n // transpiled standalone into consumer bundles — it cannot import RefractError. Its parse errors are\n // re-thrown as REFRACT_E_COLOR_INPUT by colors/normalize.ts, which is the coded authoring surface.\n throw new Error(`Unsupported hex length: \"${sanitized}\". Use 3 or 6 digits.`);\n }\n\n const normalized = sanitized.length === 3 ? sanitized.replace(/(.)/g, \"$1$1\") : sanitized;\n\n return [\n parseInt(normalized.substring(0, 2), 16),\n parseInt(normalized.substring(2, 4), 16),\n parseInt(normalized.substring(4, 6), 16),\n ] as RGBTuple;\n};\n\n/** Serialize an `[r, g, b]` tuple back to a `#rrggbb` string, clamping each channel to `0–255`. */\nexport const convertRgbToHex = (rgb: RGBTuple): string => {\n if (rgb.length !== 3) {\n throw new Error(`Expected RGB tuple of length 3, received ${rgb.length}`);\n }\n\n const hex = rgb\n .map(value => {\n const next = clampChannel(Math.round(value)).toString(16);\n return next.length === 1 ? `0${next}` : next;\n })\n .join(\"\");\n\n return `#${hex}`;\n};\n\nconst RGB_FN = /^rgba?\\(\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*(?:,\\s*([\\d.]+)\\s*)?\\)$/i;\n\n/**\n * Parse any canonical colour string this subsystem produces — a 3/6-digit hex **or** an\n * `rgb()` / `rgba()` string — into `{ rgb, a }` (alpha defaults to `1`). This is the boundary\n * gate; a value that isn't parseable rgb (e.g. a bare CSS keyword or `var(--…)`) throws, so\n * callers can surface a friendly \"this colour can't be derived from\" error.\n */\nexport const parseColor = (value: string): { rgb: RGBTuple; a: number } => {\n const trimmed = value.trim();\n\n const fn = RGB_FN.exec(trimmed);\n if (fn) {\n return {\n rgb: [Number(fn[1]), Number(fn[2]), Number(fn[3])] as RGBTuple,\n a: fn[4] === undefined ? 1 : roundAlpha(Number(fn[4])),\n };\n }\n\n return { rgb: convertHexToRGB(trimmed), a: 1 };\n};\n\n/**\n * Serialize `[r, g, b]` (+ optional alpha) to the canonical CSS string — always `rgb(r, g, b)`\n * when fully opaque, else `rgba(r, g, b, a)`. The single serialization point for the subsystem, so\n * the Model (and every adapter + `resolveToken`) always carries one canonical `rgb()/rgba()` form\n * per colour — never hex. (`convertRgbToHex` remains for the vendored consumer helper + input parsing.)\n */\nexport const serializeColor = (rgb: RGBTuple, a = 1): string => {\n const alpha = roundAlpha(a);\n const [r, g, b] = rgb;\n const R = clampChannel(Math.round(r));\n const G = clampChannel(Math.round(g));\n const B = clampChannel(Math.round(b));\n return alpha >= 1 ? `rgb(${R}, ${G}, ${B})` : `rgba(${R}, ${G}, ${B}, ${alpha})`;\n};\n\nconst HEX_RE = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;\n\n/** Whether a string is a 3- or 6-digit `#`-prefixed hex colour. */\nexport const isHexColor = (value: string): boolean => HEX_RE.test(value.trim());\n\n\n// ─── OKLCH synthesis (Björn Ottosson public-domain matrices) ──────────────────────────────────\n// Lightness / hue / chroma adjustments happen here, in a perceptual space, so equal ΔL steps look\n// even and one lightness reads consistently across hues. `L` is carried on a 0–100 scale (matching\n// the authoring dials); OKLab's native `L` is 0–1. sRGB channels are 0–255 only at the boundary.\n\n/** An OKLCH colour — perceptual lightness `L` (0–100), chroma `C` (≥ 0), hue `h` (degrees, 0–360). */\nexport type OKLCH = { L: number; C: number; h: number };\n\nconst DEG = 180 / Math.PI;\nconst GAMUT_EPS = 1e-6;\nconst GAMUT_ITERATIONS = 24;\n\n/** sRGB channel (0–1) → linear-light (0–1). */\nconst srgbToLinear = (c: number): number =>\n c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n\n/** Linear-light channel (0–1) → sRGB (0–1). */\nconst linearToSrgb = (c: number): number =>\n c >= 0.0031308 ? 1.055 * Math.pow(c, 1 / 2.4) - 0.055 : 12.92 * c;\n\n/** Linear-sRGB `[r, g, b]` (0–1) → OKLab `{ L: 0–1, a, b }`. */\nconst linearRgbToOklab = (\n lr: number,\n lg: number,\n lb: number,\n): { L: number; a: number; b: number } => {\n const l = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb;\n const m = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb;\n const s = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb;\n const l_ = Math.cbrt(l);\n const m_ = Math.cbrt(m);\n const s_ = Math.cbrt(s);\n return {\n L: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,\n a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,\n b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_,\n };\n};\n\n/** OKLab `{ L: 0–1, a, b }` → linear-sRGB `[r, g, b]` (may fall outside 0–1 when out of gamut). */\nconst oklabToLinearRgb = (L: number, a: number, b: number): [number, number, number] => {\n const l_ = L + 0.3963377774 * a + 0.2158037573 * b;\n const m_ = L - 0.1055613458 * a - 0.0638541728 * b;\n const s_ = L - 0.0894841775 * a - 1.291485548 * b;\n const l = l_ * l_ * l_;\n const m = m_ * m_ * m_;\n const s = s_ * s_ * s_;\n return [\n 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,\n -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,\n -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s,\n ];\n};\n\n/** Convert an sRGB `[r, g, b]` (0–255) triple to OKLCH (`L` on a 0–100 scale). */\nexport const rgbToOklch = (rgb: RGBTuple): OKLCH => {\n const [r, g, b] = rgb;\n const lab = linearRgbToOklab(srgbToLinear(r / 255), srgbToLinear(g / 255), srgbToLinear(b / 255));\n const C = Math.sqrt(lab.a * lab.a + lab.b * lab.b);\n let h = Math.atan2(lab.b, lab.a) * DEG;\n if (h < 0) h += 360;\n return { L: lab.L * 100, C, h };\n};\n\n/**\n * Convert OKLCH back to an sRGB `[r, g, b]` triple (**unquantized** 0–255 floats). Holds `L` and\n * `h`; when the requested chroma is outside sRGB it binary-searches `C` downward — fixed iterations,\n * no data-dependent early exit, so build- and runtime-computed values agree (§20.8) — until the\n * linear result is in `[0, 1]`. Lightness and hue are never adjusted, so a synthesized ramp keeps\n * its hue and even lightness spacing; only saturation gives way at the gamut boundary. The floats\n * are quantized exactly once, later, by `serializeColor`, so the 0–255 channel clamp never fires.\n */\nexport const oklchToRgb = (color: OKLCH): RGBTuple => {\n const L = clamp(color.L, 0, 100) / 100;\n const hr = color.h / DEG;\n const cosh = Math.cos(hr);\n const sinh = Math.sin(hr);\n\n const linearAt = (c: number): [number, number, number] => oklabToLinearRgb(L, c * cosh, c * sinh);\n const inGamut = (lin: [number, number, number]): boolean =>\n lin.every(v => v >= -GAMUT_EPS && v <= 1 + GAMUT_EPS);\n\n let lin = linearAt(color.C);\n if (!inGamut(lin)) {\n // `C = 0` (a neutral grey at this L) is always in gamut, so `lo` starts from a valid floor.\n let lo = 0;\n let hi = color.C;\n for (let i = 0; i < GAMUT_ITERATIONS; i++) {\n const mid = (lo + hi) / 2;\n if (inGamut(linearAt(mid))) lo = mid;\n else hi = mid;\n }\n lin = linearAt(lo);\n }\n\n return [\n linearToSrgb(clamp(lin[0], 0, 1)) * 255,\n linearToSrgb(clamp(lin[1], 0, 1)) * 255,\n linearToSrgb(clamp(lin[2], 0, 1)) * 255,\n ] as RGBTuple;\n};\n\n/** Run a colour string through OKLCH, apply `transform`, and re-serialize (alpha preserved). */\nconst withOklch = (value: string, transform: (lch: OKLCH) => OKLCH): string => {\n const { rgb, a } = parseColor(value);\n return serializeColor(oklchToRgb(transform(rgbToOklch(rgb))), a);\n};\n\n/** Lighten a colour by `delta` OKLCH lightness points (0–100 scale); hue, chroma, alpha preserved. */\nexport const lighten = (value: string, delta: number): string =>\n withOklch(value, lch => ({ ...lch, L: clamp(lch.L + delta, 0, 100) }));\n\n/** Darken a colour by `delta` OKLCH lightness points (0–100 scale); hue, chroma, alpha preserved. */\nexport const darken = (value: string, delta: number): string =>\n withOklch(value, lch => ({ ...lch, L: clamp(lch.L - delta, 0, 100) }));\n\n/** Set a colour's absolute OKLCH lightness to `L` (0–100); hue, chroma, alpha preserved. */\nexport const setL = (value: string, L: number): string =>\n withOklch(value, lch => ({ ...lch, L: clamp(L, 0, 100) }));\n\n/** Rotate a colour's OKLCH hue by `deg` (signed) around the perceptual wheel; L, C, alpha preserved. */\nexport const rotateHue = (value: string, deg: number): string =>\n withOklch(value, lch => ({ ...lch, h: (((lch.h + deg) % 360) + 360) % 360 }));\n\n/** The perceptual complement — a 180° hue rotation at the same lightness and chroma. */\nexport const complement = (value: string): string => rotateHue(value, 180);\n\n/**\n * An `adjust` dial set — any subset. `l` = **absolute** OKLCH lightness target (0–100, raw L, the\n * opposite direction from a numeric-`steps` label); `c` = chroma **multiplier** (`1` keep, `0` grey,\n * `>1` more saturated where the gamut allows); `h` = **signed** hue rotation in degrees. Numbers\n * only — keeps the vendored math string-free.\n */\nexport type AdjustDials = { l?: number; c?: number; h?: number };\n\n/**\n * One-shot OKLCH placement — set an absolute lightness, scale chroma, and/or rotate hue in a single\n * derivation (each dial optional; an omitted dial leaves that component untouched). Alpha preserved.\n */\nexport const adjust = (value: string, dials: AdjustDials): string =>\n withOklch(value, lch => ({\n L: dials.l === undefined ? lch.L : clamp(dials.l, 0, 100),\n C: dials.c === undefined ? lch.C : Math.max(0, lch.C * dials.c),\n h: dials.h === undefined ? lch.h : (((lch.h + dials.h) % 360) + 360) % 360,\n }));\n\n/**\n * Convert any canonical colour string (the subsystem's `rgb()` / `rgba()` form, or a hex string)\n * to hex — 6-digit `#rrggbb` when opaque, 8-digit `#rrggbbaa` when translucent. Used by the DTCG\n * exporter, whose `color` token type is conventionally hex (every other output stays `rgb()`).\n */\nexport const toHexColor = (value: string): string => {\n const { rgb, a } = parseColor(value);\n const hex = convertRgbToHex(rgb);\n if (a >= 1) return hex;\n const aa = clampChannel(Math.round(a * 255)).toString(16).padStart(2, \"0\");\n return `${hex}${aa}`;\n};\n\n/**\n * Convert any canonical colour string (the subsystem's `rgb()` / `rgba()` form, or a hex string) to\n * a CSS Color 4 `oklch(L% C H)` string — `oklch(L% C H / a)` when translucent. `L` is a percentage\n * (0–100), `C` the absolute chroma, `H` in degrees, derived from the stored (quantized) rgb. Used by\n * the CSS adapter's `colorFormat: \"oklch\"` output; the colour is identical to the `rgb()` form.\n */\nexport const toOklchColor = (value: string): string => {\n const { rgb, a } = parseColor(value);\n const { L, C, h } = rgbToOklch(rgb);\n const body = `${roundTo(L, 2)}% ${roundTo(C, 4)} ${roundTo(h, 2)}`;\n return a >= 1 ? `oklch(${body})` : `oklch(${body} / ${roundAlpha(a)})`;\n};\n\n/**\n * Set a colour's opacity to `percent` (0–100) — an **absolute** alpha, not a relative fade\n * (`alpha(x, 40)` ⇒ 40% opaque). The rgb channels are untouched; only the alpha channel is\n * replaced. Result serializes to `rgba(…)` (or hex when `percent` is 100).\n */\nexport const alpha = (value: string, percent: number): string => {\n const { rgb } = parseColor(value);\n return serializeColor(rgb, clampPercent(percent) / 100);\n};\n"],"names":["HEX_LENGTHS","Set","clamp","value","min","max","Math","clampChannel","roundTo","dp","f","round","roundAlpha","a","convertHexToRGB","hex","sanitized","replace","has","length","Error","normalized","parseInt","substring","convertRgbToHex","rgb","map","next","toString","join","RGB_FN","parseColor","trimmed","trim","fn","exec","Number","undefined","serializeColor","alpha","r","g","b","R","G","B","HEX_RE","isHexColor","test","DEG","PI","srgbToLinear","c","pow","linearToSrgb","rgbToOklch","lab","lr","lg","lb","l","m","s","l_","cbrt","m_","s_","L","linearRgbToOklab","C","sqrt","h","atan2","oklchToRgb","color","hr","cosh","cos","sinh","sin","linearAt","oklabToLinearRgb","inGamut","lin","every","v","lo","hi","i","mid","withOklch","transform","lighten","delta","lch","darken","setL","rotateHue","deg","complement","adjust","dials","toHexColor","padStart","toOklchColor","body","percent","clampPercent"],"mappings":"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"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The adapter contract — the ONE interface `src/next/` core commits to.
|
|
3
|
+
*
|
|
4
|
+
* An adapter turns the format-neutral `ThemeModel` into some output format (CSS,
|
|
5
|
+
* SCSS, styled-components, JSON, React-Native). Core builds and enumerates the
|
|
6
|
+
* Model; it knows no output format — the adapter does. Core imports only this
|
|
7
|
+
* interface, never a concrete adapter.
|
|
8
|
+
*
|
|
9
|
+
* Two-level shape:
|
|
10
|
+
* ThemeAdapter identity (`name`/`version`) + `bind(model, ctx)`
|
|
11
|
+
* BoundAdapter the render surface, with `model`/`ctx` already bound so the
|
|
12
|
+
* methods read cleanly: `renderRecipe(subsystem, group, variant)`.
|
|
13
|
+
*
|
|
14
|
+
* `createTheme` builds the Model, calls `bind(model, ctx)` ONCE, and curries the
|
|
15
|
+
* bound surface onto the public theme (`theme.renderRecipe(group, variant)`, …).
|
|
16
|
+
*
|
|
17
|
+
* @typeParam TUnit the output unit. CSS/SCSS → `string`; JSON → a doc fragment;
|
|
18
|
+
* React-Native → a style object. Only `join` + the render methods vary by it;
|
|
19
|
+
* `recipeName` is always `string` (identity is format-independent).
|
|
20
|
+
*/
|
|
21
|
+
import type { ThemeModel, Literal } from "./model";
|
|
22
|
+
import type { MediaDescriptor, ContainerDescriptors } from "./media";
|
|
23
|
+
/**
|
|
24
|
+
* Build-time emit output — self-contained files an adapter writes to disk.
|
|
25
|
+
*
|
|
26
|
+
* `files` are the static artifacts a downstream project consumes directly (a CSS stylesheet, a
|
|
27
|
+
* theme module, …). `vendorHelpers` are **live** helper modules the adapter vendors for the app's
|
|
28
|
+
* runtime needs (color-math for adjusting a colour live, an SC media module, …); each MUST be
|
|
29
|
+
* self-contained ES-module source — zero refract import — so the app keeps working after it drops
|
|
30
|
+
* refract at runtime. The build layer (`emitTheme`) writes both to disk.
|
|
31
|
+
*/
|
|
32
|
+
export interface EmitOutput {
|
|
33
|
+
/** Static consume-as-is artifacts, keyed by filename (`"theme.css"`). */
|
|
34
|
+
files: Record<string, string>;
|
|
35
|
+
/** Self-contained live helper modules, keyed by filename (`"color-math.js"`, `"media.ts"`). */
|
|
36
|
+
vendorHelpers?: Record<string, string>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* `emit` — the per-target directive for HOW a theme is written to disk (§9). Core owns only this
|
|
40
|
+
* **vocabulary** (so config authors get autocomplete) + light normalization (see `resolveEmitPlan`
|
|
41
|
+
* in `src/build/emit.ts`); each ADAPTER decides which modes it honors and how it realizes them.
|
|
42
|
+
* An adapter that doesn't recognize a mode throws a clear error rather than mis-emitting.
|
|
43
|
+
*
|
|
44
|
+
* The four intents:
|
|
45
|
+
* - `single` — one file (default `theme.css`); `file` renames it.
|
|
46
|
+
* - `split` — two files: rules (`file`, default `styles.css`) + variables (`variables`,
|
|
47
|
+
* default `variables.css`). Load-order contract, no `@import`.
|
|
48
|
+
* - `subsystem` — a styles+variables pair per subsystem (`filename(subsystem, kind)` names both).
|
|
49
|
+
* - `components` — the merged/flattened per-component export (`inline` defaults true).
|
|
50
|
+
*
|
|
51
|
+
* Discriminator inference (only `single`/`split` may omit `type`): `undefined`/`{}`/`{ file }` →
|
|
52
|
+
* single; presence of the `variables` key → split. `subsystem`/`components` always need `type` (or
|
|
53
|
+
* the string form).
|
|
54
|
+
*
|
|
55
|
+
* ---
|
|
56
|
+
* **CSS adapter semantics** (the reference realization — the only adapter shipping `emit` today; SC
|
|
57
|
+
* and future adapters may honor a subset or map the intent onto their own shape):
|
|
58
|
+
*
|
|
59
|
+
* - **single** — one stylesheet: all subsystems' `:root` variables + every rule, in one file.
|
|
60
|
+
* The global adapter `inline` option bakes resolved values instead of `var(--…)` whole-file.
|
|
61
|
+
*
|
|
62
|
+
* - **split / subsystem** — every multi-file mode obeys a **load-order contract, NO `@import`**: the
|
|
63
|
+
* styles file(s) reference vars by name and assume the variables file loads first. Responsive
|
|
64
|
+
* `:root` var overrides route to the variables side, responsive rule overrides to the styles side.
|
|
65
|
+
* The global `inline` option is **rejected** here (baking values leaves no variables to split out —
|
|
66
|
+
* the adapter throws). In `subsystem` mode the `components` subsystem owns no properties, so it
|
|
67
|
+
* emits **styles only** (no `components.variables.css`), and that file is the normal option-C
|
|
68
|
+
* composition (delta classes referencing sibling-subsystem classes) — NOT flattened.
|
|
69
|
+
*
|
|
70
|
+
* - **components** — the ONLY mode that flattens each component variant into one self-contained
|
|
71
|
+
* rule-set (its referenced recipes' declarations + own `css` delta, delta winning on conflict).
|
|
72
|
+
* `inline` **defaults true** → baked literal values, zero `var(`, fully portable/dependency-free.
|
|
73
|
+
* `inline: false` → the same rules with `var(--…)` refs PLUS a **tree-shaken** variables file
|
|
74
|
+
* containing ONLY the tokens the exported components reference. `variables` names that file
|
|
75
|
+
* (default `variables.css`); `variables: false` **suppresses** it (the consumer supplies the vars).
|
|
76
|
+
* **File assignment = the `filename` return value** — variants returning the same name are
|
|
77
|
+
* concatenated into that file (default `` `${group}-${variant}.css` `` = one file per variant;
|
|
78
|
+
* `() => "components.css"` collapses all; a per-group fn bundles a group while others stay split).
|
|
79
|
+
*
|
|
80
|
+
* NOTE (docs location): post-cutover author docs (AGENT / examples) were removed in the clean-room
|
|
81
|
+
* rebuild and not yet re-authored, so this TSDoc + the `tmp-build-example/theme.config.ts` comments
|
|
82
|
+
* are the current author-facing reference for `emit`.
|
|
83
|
+
*/
|
|
84
|
+
export type Emit = undefined | "single" | "split" | "subsystem" | "components" | {
|
|
85
|
+
type?: "single";
|
|
86
|
+
file?: string;
|
|
87
|
+
} | {
|
|
88
|
+
type?: "split";
|
|
89
|
+
file?: string;
|
|
90
|
+
variables?: string;
|
|
91
|
+
} | {
|
|
92
|
+
type: "subsystem";
|
|
93
|
+
filename?: (subsystem: string, kind: "styles" | "variables") => string;
|
|
94
|
+
} | {
|
|
95
|
+
type: "components";
|
|
96
|
+
inline?: boolean;
|
|
97
|
+
filename?: (c: {
|
|
98
|
+
group: string;
|
|
99
|
+
variant: string;
|
|
100
|
+
}) => string;
|
|
101
|
+
variables?: string | false;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* The normalized, discriminated form of {@link Emit} — every default filled, `type` always present.
|
|
105
|
+
* `resolveEmitPlan` (build layer) produces it; an adapter's `emit(plan)` switches on `plan.type`.
|
|
106
|
+
*/
|
|
107
|
+
export type NormalizedEmit = {
|
|
108
|
+
type: "single";
|
|
109
|
+
file: string;
|
|
110
|
+
} | {
|
|
111
|
+
type: "split";
|
|
112
|
+
file: string;
|
|
113
|
+
variables: string;
|
|
114
|
+
} | {
|
|
115
|
+
type: "subsystem";
|
|
116
|
+
filename: (subsystem: string, kind: "styles" | "variables") => string;
|
|
117
|
+
} | {
|
|
118
|
+
type: "components";
|
|
119
|
+
inline: boolean;
|
|
120
|
+
filename: (c: {
|
|
121
|
+
group: string;
|
|
122
|
+
variant: string;
|
|
123
|
+
}) => string;
|
|
124
|
+
variables: string | false;
|
|
125
|
+
};
|
|
126
|
+
/** One recipe's real identity in a format — a CSS class, an SC export, a JSON key. */
|
|
127
|
+
export interface UsageRecipe {
|
|
128
|
+
readonly subsystem: string;
|
|
129
|
+
readonly group: string;
|
|
130
|
+
readonly variant: string;
|
|
131
|
+
/** The name a downstream consumer actually uses (`recipeName` for this rule-set). */
|
|
132
|
+
readonly name: string;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* How to consume an adapter's built output — the source the build-time self-documenting guide
|
|
136
|
+
* (`llms.txt` + `manifest.json`) is rendered from. The adapter that produced the format is the
|
|
137
|
+
* thing that knows its consumption shape, so it describes it here from its REAL naming surface;
|
|
138
|
+
* `defineAdapter` supplies a generic default (recipe identities via `recipeName`) for adapters that
|
|
139
|
+
* don't override it.
|
|
140
|
+
*/
|
|
141
|
+
export interface UsageDescriptor {
|
|
142
|
+
/** The output format id (the adapter's `name`). */
|
|
143
|
+
readonly format: string;
|
|
144
|
+
/** Prose lines describing how to consume the output — the `llms.txt` narrative. Relative-path first. */
|
|
145
|
+
readonly summary: readonly string[];
|
|
146
|
+
/** The real recipe identities in this format, so a downstream agent never guesses a name. */
|
|
147
|
+
readonly recipes: readonly UsageRecipe[];
|
|
148
|
+
}
|
|
149
|
+
/** The Model-derived context bound alongside the Model. */
|
|
150
|
+
export interface RenderContext<TBreakpoint extends string = string> {
|
|
151
|
+
/** Core-built media descriptor (breakpoints → `@media` builder) for responsive rules. */
|
|
152
|
+
readonly media: MediaDescriptor<TBreakpoint>;
|
|
153
|
+
/** Per-named-container descriptors (§10.5) — `name → @container builder`. Empty when no `containers`. */
|
|
154
|
+
readonly containers: ContainerDescriptors;
|
|
155
|
+
/** Resolve a token path to its concrete literal — for inline / value-mode output. */
|
|
156
|
+
readonly resolve: (path: string) => Literal;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The render surface an author's `bind` returns: the required primitives, plus
|
|
160
|
+
* optional overrides / lifecycle. All methods are already bound to `model`/`ctx`,
|
|
161
|
+
* so none of them take those. `defineAdapter` fills in the defaulted aggregators.
|
|
162
|
+
*/
|
|
163
|
+
export interface BoundSpec<TUnit = string> {
|
|
164
|
+
/** A recipe's identity in this format (CSS class, SCSS mixin, RN key, …). Always a string. */
|
|
165
|
+
recipeName(subsystem: string, group: string, variant: string): string;
|
|
166
|
+
/** Render ONE rule-set: base declarations + all its state/responsive rules. */
|
|
167
|
+
renderRecipe(subsystem: string, group: string, variant: string): TUnit;
|
|
168
|
+
/** Render ONE subsystem's tokens (its `:root` variables in CSS). */
|
|
169
|
+
renderVariables(subsystem: string): TUnit;
|
|
170
|
+
/** Combine units — the one primitive the defaulted aggregators need.
|
|
171
|
+
* CSS/SCSS: `parts.join("\n\n")`; JSON: merge fragments; RN: merge objects. */
|
|
172
|
+
join(parts: TUnit[]): TUnit;
|
|
173
|
+
/** Render ONE variable by token path, e.g. `"colors.primary"` → `"--dt-color--primary: #4dabf7;"`.
|
|
174
|
+
* The finer-grained sibling of `renderVariables`; feeds the core `renderRecipeStandalone`
|
|
175
|
+
* helper. Optional until lazy delivery lands. (*Which* paths a recipe needs is core
|
|
176
|
+
* knowledge — a Model walk — not an adapter concern.) */
|
|
177
|
+
renderToken?(path: string): TUnit;
|
|
178
|
+
renderAllRecipes?(): TUnit;
|
|
179
|
+
renderAllVariables?(): TUnit;
|
|
180
|
+
renderAll?(): TUnit;
|
|
181
|
+
/** Attach runtime-only helpers to the theme root (the `theme.media` pattern). */
|
|
182
|
+
extend?(theme: Record<string, unknown>): Record<string, unknown>;
|
|
183
|
+
/**
|
|
184
|
+
* Describe how to consume this output — feeds the build-time self-documenting guide (`llms.txt` +
|
|
185
|
+
* `manifest.json`) so a published/zipped theme travels with instructions for a downstream agent
|
|
186
|
+
* that has neither refract nor its skills. Optional: `defineAdapter` supplies a generic default
|
|
187
|
+
* (recipe identities via `recipeName`); an adapter overrides it to add format-specific import prose. */
|
|
188
|
+
describeUsage?(): UsageDescriptor;
|
|
189
|
+
/**
|
|
190
|
+
* Build-time emit: self-contained files (adapter vendors its own runtime helpers). `plan` is the
|
|
191
|
+
* normalized output-shape directive (§9); omitted ⇒ `single`/`theme.css` (back-compat). The
|
|
192
|
+
* adapter switches on `plan.type` and throws a clear error for modes it doesn't support. */
|
|
193
|
+
emit?(plan?: NormalizedEmit): EmitOutput;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* The full bound render surface — a `BoundSpec` with the DEFAULTED aggregators
|
|
197
|
+
* guaranteed present (supplied by `defineAdapter`). This is what `createTheme`
|
|
198
|
+
* curries onto the public theme.
|
|
199
|
+
*/
|
|
200
|
+
export interface BoundAdapter<TUnit = string> extends BoundSpec<TUnit> {
|
|
201
|
+
/** All rule-sets combined (default: `join` over every `renderRecipe`). */
|
|
202
|
+
renderAllRecipes(): TUnit;
|
|
203
|
+
/** All subsystems' variables combined (default: `join` over every `renderVariables`). */
|
|
204
|
+
renderAllVariables(): TUnit;
|
|
205
|
+
/** The full output (default: `join([renderAllVariables, renderAllRecipes])`). */
|
|
206
|
+
renderAll(): TUnit;
|
|
207
|
+
/** How to consume the output (default: format + recipe identities from `recipeName`). */
|
|
208
|
+
describeUsage(): UsageDescriptor;
|
|
209
|
+
}
|
|
210
|
+
/** What an adapter AUTHOR writes: identity + a `bind` that returns the render surface. */
|
|
211
|
+
export interface AdapterSpec<TUnit = string> {
|
|
212
|
+
/** Stable adapter id, e.g. `"css"`, `"scss"`, `"styled-components"`, `"json"`. */
|
|
213
|
+
readonly name: string;
|
|
214
|
+
/** Contract version external packages pin to. */
|
|
215
|
+
readonly version: number;
|
|
216
|
+
/**
|
|
217
|
+
* The states this adapter can render (`["hover", "disabled", …]`) — the validation set for recipe
|
|
218
|
+
* `state:` refs. **The adapter owns state knowledge** (CSS knows `:hover`/`[disabled]`; an inline
|
|
219
|
+
* or JSON adapter may know none). Surfaced at the adapter level (not `bind`) because recipe
|
|
220
|
+
* normalization runs *before* `bind`; core threads it into `normalizeRecipeGroup` generically,
|
|
221
|
+
* with no CSS import. Absent ⇒ no state validation (any state name accepted).
|
|
222
|
+
*/
|
|
223
|
+
readonly allowedStates?: ReadonlyArray<string>;
|
|
224
|
+
/** Bind the Model + context once; return the render primitives. */
|
|
225
|
+
bind(model: ThemeModel, ctx: RenderContext): BoundSpec<TUnit>;
|
|
226
|
+
}
|
|
227
|
+
/** The full adapter core/`createTheme` consumes: `bind` returns a complete `BoundAdapter`. */
|
|
228
|
+
export interface ThemeAdapter<TUnit = string> {
|
|
229
|
+
readonly name: string;
|
|
230
|
+
readonly version: number;
|
|
231
|
+
/** The adapter's known-state set (see {@link AdapterSpec.allowedStates}); core validates against it. */
|
|
232
|
+
readonly allowedStates?: ReadonlyArray<string>;
|
|
233
|
+
bind(model: ThemeModel, ctx: RenderContext): BoundAdapter<TUnit>;
|
|
234
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The clean-room `createTheme` — the public entry point.
|
|
3
|
+
*
|
|
4
|
+
* A **walking skeleton** (Step 0d): a live spine that grows one seam per step, so every
|
|
5
|
+
* later step is reviewed through the real entry point rather than isolated unit tests.
|
|
6
|
+
* The control flow is fixed; coverage grows by pushing a {@link Subsystem} onto `SUBSYSTEMS`
|
|
7
|
+
* or filling a hook — never by editing the spine.
|
|
8
|
+
*
|
|
9
|
+
* `adapter` is a **required** option (no `createCssAdapter()` default), which is what lets
|
|
10
|
+
* this module stay in core importing nothing but the `ThemeAdapter` interface.
|
|
11
|
+
*
|
|
12
|
+
* Flow: read breakpoints → build the media descriptor → normalize each subsystem's slice
|
|
13
|
+
* into a per-subsystem Model input (`buildSubsystemInput`) → `buildThemeModel` → assemble the
|
|
14
|
+
* theme surface (`assembleTheme`: token map + `resolveToken` + `adapter.bind` + `extend`).
|
|
15
|
+
*
|
|
16
|
+
* `theme.override(partial)` (Step 5) is a **delta merge**, not a re-run: it normalizes ONLY the
|
|
17
|
+
* partial's changed subsystem slices (through the very same `buildSubsystemInput`), immutably
|
|
18
|
+
* merges those Model fragments into the current Model at property / rule-set-group granularity,
|
|
19
|
+
* and re-assembles onto the **new** Model. The parent Model (and its bound outputs) are untouched
|
|
20
|
+
* — real child themes. There is no raw retention: the Model is the only held state, and the
|
|
21
|
+
* reference-resolution context recipe interpretation needs is reconstructed from it
|
|
22
|
+
* (`reconstructNormalizedProperties`). Synthesized steps are derived refs, so overriding a base
|
|
23
|
+
* re-derives its steps for free; the merge code itself carries no subsystem-specific logic.
|
|
24
|
+
*/
|
|
25
|
+
import type { ThemeAdapter } from "./ThemeAdapter";
|
|
26
|
+
import type { Literal, Ref, ThemeModel, PropertyModel, RuleSetGroup, Keyframe, ContainerModel } from "./model";
|
|
27
|
+
import { type UnitsConfig } from "./units";
|
|
28
|
+
import type { RawTheme } from "./rawTheme";
|
|
29
|
+
import { type MediaConfig } from "./media";
|
|
30
|
+
export interface CreateThemeOptions<TAdapter extends ThemeAdapter = ThemeAdapter> {
|
|
31
|
+
/** Required — the format target. Core ships no default adapter. */
|
|
32
|
+
readonly adapter: TAdapter;
|
|
33
|
+
/**
|
|
34
|
+
* Media-query output config (§10.5) — `{ unit: "px" | "em" | "rem"; baseFontSize }`. Breakpoint and
|
|
35
|
+
* container thresholds are authored as px numbers; `unit` controls the emitted unit (em/rem = value ÷
|
|
36
|
+
* baseFontSize). Defaults to px. Stable across `override()` children.
|
|
37
|
+
*/
|
|
38
|
+
readonly media?: MediaConfig;
|
|
39
|
+
/**
|
|
40
|
+
* Declaration-value length units (§21) — a token-path-prefix role map. `units.default` (global),
|
|
41
|
+
* `units["<subsystem>"]`, `units["<subsystem>.<property>"]`; most-specific wins, over a built-in seed
|
|
42
|
+
* (length subsystems → px, `lineHeight` → none, `letterSpacing` → em). A bare authored number is
|
|
43
|
+
* deferred (resolved here); an explicit unit (`"1.5rem"`) is pinned. Unit intent is a theme fact, so it
|
|
44
|
+
* lives here (not on the adapter): every adapter receives a Model with fully-resolved `{ value, unit }`.
|
|
45
|
+
*/
|
|
46
|
+
readonly units?: UnitsConfig;
|
|
47
|
+
/** Divisor for a deferred length resolving to `rem` (§21). Defaults to 16. */
|
|
48
|
+
readonly baseFontSize?: number;
|
|
49
|
+
/**
|
|
50
|
+
* DTCG round-trip (§12 Phase 2) — prebuilt property Model to splice in **after** property build,
|
|
51
|
+
* keyed `subsystem → property`. Carries the lossless bits the resolved DTCG token surface can't
|
|
52
|
+
* (appearance `modes` / `responsive` / derivation refs / `external`); a property present here
|
|
53
|
+
* REPLACES the one the standard tokens rebuilt (property-level merge). `fromDTCGTheme` sets it from
|
|
54
|
+
* the `com.theme-registry.refract` extension. Absent → the standard build is untouched.
|
|
55
|
+
*/
|
|
56
|
+
readonly propertiesOverlay?: Record<string, Record<string, PropertyModel>>;
|
|
57
|
+
/**
|
|
58
|
+
* DTCG round-trip — prebuilt, already-interpreted rule-set IR to splice into the Model **after**
|
|
59
|
+
* property build, keyed `subsystem → group → variant`. Bypasses authoring/normalization (the IR is
|
|
60
|
+
* a built Model slice, lengths already resolved), then flows through the normal `{ ref }` validation.
|
|
61
|
+
* `fromDTCGTheme` sets this to restore recipes a DTCG document carried in its
|
|
62
|
+
* `com.theme-registry.refract` extension. Rarely set by hand. Absent → the standard build is untouched.
|
|
63
|
+
*/
|
|
64
|
+
readonly ruleSetsOverlay?: Record<string, Record<string, RuleSetGroup>>;
|
|
65
|
+
/** DTCG round-trip — prebuilt keyframes to splice in, keyed `subsystem → name`. See {@link ruleSetsOverlay}. */
|
|
66
|
+
readonly keyframesOverlay?: Record<string, Record<string, Keyframe>>;
|
|
67
|
+
/** DTCG round-trip — prebuilt query containers to splice into the Model root. See {@link ruleSetsOverlay}. */
|
|
68
|
+
readonly containersOverlay?: Record<string, ContainerModel>;
|
|
69
|
+
}
|
|
70
|
+
/** The public theme surface. Grows: `css`/recipe helpers (step 1). */
|
|
71
|
+
export interface Theme {
|
|
72
|
+
/** The format-neutral held state — the single source of truth. */
|
|
73
|
+
readonly model: ThemeModel;
|
|
74
|
+
/**
|
|
75
|
+
* Flat, lazy, cached `path -> Ref` view of the Model's property tokens
|
|
76
|
+
* (`"<subsystem>.<property>[.<variant|extra>]"`). Aliases / derived steps stay as refs
|
|
77
|
+
* (`{ ref }` / `{ ref, fn, arg }`), so the map is override-safe. Rule-sets are excluded.
|
|
78
|
+
*/
|
|
79
|
+
readonly tokens: Record<string, Ref>;
|
|
80
|
+
/**
|
|
81
|
+
* Resolve one token path to its concrete literal — following aliases and running derivations
|
|
82
|
+
* (`lighten` / `darken` / …) through the subsystem derivation registry. Throws on unknown paths.
|
|
83
|
+
*/
|
|
84
|
+
readonly resolveToken: (path: string) => Literal;
|
|
85
|
+
/**
|
|
86
|
+
* Derive a **child** theme by immutably merging `partial` (a partial raw theme) into this theme's
|
|
87
|
+
* Model. Only the partial's subsystem slices are re-normalized; the merge is a property /
|
|
88
|
+
* rule-set-variant replace. The parent theme (Model, tokens, css) is untouched — call it again to
|
|
89
|
+
* derive siblings. Overriding a colour base re-derives its synthesized steps automatically.
|
|
90
|
+
*
|
|
91
|
+
* Accepts a (partial) {@link RawTheme} — every key is already optional, so a delta touching one
|
|
92
|
+
* subsystem slice is a valid `RawTheme`.
|
|
93
|
+
*/
|
|
94
|
+
readonly override: (partial: RawTheme) => Theme;
|
|
95
|
+
}
|
|
96
|
+
export declare function createTheme(rawTheme: RawTheme, options: CreateThemeOptions): Theme;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Known CSS property names (kebab-case). Membership test drives {@link isKnownCssProperty}. */
|
|
2
|
+
export declare const KNOWN_CSS_PROPERTIES: ReadonlySet<string>;
|
|
3
|
+
/**
|
|
4
|
+
* Is `name` a real CSS property? Accepts camelCase or kebab-case (`borderColor` / `border-color`),
|
|
5
|
+
* custom properties (`--brand`), and vendor-prefixed names (`-webkit-mask`). Everything else — a
|
|
6
|
+
* reserved recipe key that leaked, or a typo — is rejected so the caller can fail loud.
|
|
7
|
+
*/
|
|
8
|
+
export declare const isKnownCssProperty: (name: string) => boolean;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `defineAdapter` — turns an `AdapterSpec` (identity + a `bind` returning the four
|
|
3
|
+
* required primitives) into a full `ThemeAdapter` whose `bind` yields a complete
|
|
4
|
+
* `BoundAdapter` with the DEFAULTED aggregators supplied.
|
|
5
|
+
*
|
|
6
|
+
* The defaults are pure Model walks: the Model enumerates every subsystem and every
|
|
7
|
+
* (group, variant), so core loops it, calls the author's per-unit renderers, and
|
|
8
|
+
* `join`s the results. Model/ctx are already bound, so the aggregators take no args.
|
|
9
|
+
* An author can still override any aggregator (a format whose full document isn't a
|
|
10
|
+
* flat concatenation).
|
|
11
|
+
*/
|
|
12
|
+
import type { AdapterSpec, ThemeAdapter } from "./ThemeAdapter";
|
|
13
|
+
/**
|
|
14
|
+
* Complete an `AdapterSpec` into a `ThemeAdapter`.
|
|
15
|
+
*
|
|
16
|
+
* Wraps the author's `bind` so the returned `BoundAdapter` gains the defaulted
|
|
17
|
+
* aggregators (`renderAllRecipes` / `renderAllVariables` / `renderAll`) built from
|
|
18
|
+
* the required primitives — unless the spec already provides its own override.
|
|
19
|
+
* `name` / `version` pass through unchanged.
|
|
20
|
+
*
|
|
21
|
+
* @param spec identity (`name`/`version`) + a `bind(model, ctx)` returning the
|
|
22
|
+
* required render primitives (and any optional overrides).
|
|
23
|
+
* @returns a full adapter whose `bind(model, ctx)` yields a complete `BoundAdapter`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function defineAdapter<TUnit>(spec: AdapterSpec<TUnit>): ThemeAdapter<TUnit>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dec.4 — cross-property derivation resolve pass.
|
|
3
|
+
*
|
|
4
|
+
* A variant / mode value may derive from ANOTHER property (`surface.modes.dark =
|
|
5
|
+
* { ref: "colors.brand", modifiers: [{ adjust: { l: 12 } }] }`). Colours bakes derivations one
|
|
6
|
+
* property at a time, so a cross-property source isn't available at bake time — the owning subsystem
|
|
7
|
+
* emits such a derivation **unbaked** (a `Ref` carrying `ref` + `modifiers`/`fn` but no cached
|
|
8
|
+
* `value`). This post-build pass runs once the full `path -> Ref` token map exists and fills each
|
|
9
|
+
* cross-property derived `Ref`'s `.value` by resolving its source and folding its chain.
|
|
10
|
+
*
|
|
11
|
+
* - **Variants** are addressable tokens, so their source resolves through `resolveToken` (which itself
|
|
12
|
+
* follows the ref + folds modifiers across the whole map — chains of cross-property derivations
|
|
13
|
+
* resolve for free, no ordering needed).
|
|
14
|
+
* - **Modes are NOT tokens** (they're emit-only), so a mode's source is resolved via `resolveToken`
|
|
15
|
+
* on its `ref` and the chain folded here by hand, rather than through a mode token that doesn't exist.
|
|
16
|
+
*
|
|
17
|
+
* The pass is **structural, not value-based**: it re-bakes any derived Ref whose source property
|
|
18
|
+
* differs from the owner, regardless of whether a value is already present. That makes it correct on
|
|
19
|
+
* `override()` too — re-running it against the merged token map re-derives a child's cross-property
|
|
20
|
+
* values when the source changed, exactly like intra-property derivations. It rebuilds immutably
|
|
21
|
+
* (new `Ref`s only where it re-bakes; every untouched branch keeps its reference), so a shared parent
|
|
22
|
+
* Model is never mutated, and a Model with no cross-property derivation is returned by reference
|
|
23
|
+
* (goldens byte-identical).
|
|
24
|
+
*/
|
|
25
|
+
import type { ThemeModel } from "../model";
|
|
26
|
+
import { type DerivationRegistry } from "./resolveTokens";
|
|
27
|
+
/**
|
|
28
|
+
* Fill every cross-property derived variant / mode Ref's `.value` against the Model's token map.
|
|
29
|
+
* Immutable — returns the Model by reference when nothing is cross-property (byte-identical output).
|
|
30
|
+
*/
|
|
31
|
+
export declare const bakeCrossPropertyDerivations: (model: ThemeModel, registry: DerivationRegistry) => ThemeModel;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derivation layer for the clean-room tree (Step 0e).
|
|
3
|
+
*
|
|
4
|
+
* The cycle-safe derivation resolver (`resolveToken` / `resolveTokenMap`) + the registry
|
|
5
|
+
* merge (`buildDerivationRegistry`). Ported from `core/common/resolveTokens.ts`; pure
|
|
6
|
+
* Model/Ref data → literals, no CSS. Not yet wired into `theme.tokens` / `theme.resolveToken`
|
|
7
|
+
* — that's Step 0f.
|
|
8
|
+
*/
|
|
9
|
+
export type { DerivationFn, DerivationRegistry } from "./resolveTokens";
|
|
10
|
+
export { buildDerivationRegistry, resolveToken, resolveTokenMap } from "./resolveTokens";
|
|
11
|
+
export { bakeCrossPropertyDerivations } from "./crossProperty";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derivation resolver (model-first).
|
|
3
|
+
*
|
|
4
|
+
* Resolves a `path -> Ref` token map to concrete literals, running **value derivations** — a
|
|
5
|
+
* `Ref` of the form `{ ref, fn, arg }` means `value = registry[fn](resolve(ref), arg)`. Plain
|
|
6
|
+
* `{ ref }` is an alias (`= resolve(ref)`), `{ value }` is a terminal literal. Recursive (so
|
|
7
|
+
* chained derivations like `darker = darken(dark)` resolve), memoized per pass, and cycle-safe.
|
|
8
|
+
*
|
|
9
|
+
* Subsystems contribute derivation fns (colors → `lighten`/`darken`, typography → `scale`);
|
|
10
|
+
* core assembles them into one {@link DerivationRegistry}. Used by `theme.tokens` (resolved
|
|
11
|
+
* view) and by the CSS adapter's lowering (concrete `:root` / inline values). See
|
|
12
|
+
* `.notes/reference/derivation-resolver-and-merge.md`.
|
|
13
|
+
*/
|
|
14
|
+
import type { Literal, Ref } from "../model";
|
|
15
|
+
/** A value-derivation: derive a literal from a source literal + optional argument. */
|
|
16
|
+
export type DerivationFn = (value: Literal, arg?: unknown) => Literal;
|
|
17
|
+
/** `fn name -> DerivationFn`, assembled from the subsystems (unique names; collisions throw). */
|
|
18
|
+
export type DerivationRegistry = Record<string, DerivationFn>;
|
|
19
|
+
/** Merge subsystem derivation-fn maps into one registry; a duplicate name is a hard error. */
|
|
20
|
+
export declare const buildDerivationRegistry: (contributions: ReadonlyArray<Readonly<DerivationRegistry>>) => DerivationRegistry;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve one token `path` in `map` to a concrete literal, running any derivation. `cache` and
|
|
23
|
+
* `resolving` are threaded through recursion (chaining + cycle detection); callers may omit them.
|
|
24
|
+
*/
|
|
25
|
+
export declare const resolveToken: (map: Record<string, Ref>, registry: DerivationRegistry, path: string, cache?: Map<string, Literal>, resolving?: Set<string>) => Literal;
|
|
26
|
+
/** Resolve every entry in a token `map` to its concrete literal (one shared memo pass). */
|
|
27
|
+
export declare const resolveTokenMap: (map: Record<string, Ref>, registry: DerivationRegistry) => Record<string, Literal>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable, machine-readable error codes (§P2-3). Every error refract throws for a theme authoring /
|
|
3
|
+
* build problem — everything reachable from {@link createTheme} (subsystem normalization, token
|
|
4
|
+
* derivation, DTCG parsing) — is a {@link RefractError} carrying a `code` from {@link RefractErrorCode}:
|
|
5
|
+
* a stable identifier an agent (or a catch block) can branch on without string-matching the human
|
|
6
|
+
* message. The message text stays human-first and unchanged; the code is additive.
|
|
7
|
+
*
|
|
8
|
+
* Scope: authoring/build errors. Pure CLI-tooling failures in `src/build/*` (bad arguments, a missing
|
|
9
|
+
* file, an unknown command) stay plain `Error`s — they're usage errors from `refract <cmd>`, not a
|
|
10
|
+
* problem with the theme an agent is editing.
|
|
11
|
+
*
|
|
12
|
+
* Dependency-free leaf on purpose, so any module (including the standalone `adapter-kit`) can throw one
|
|
13
|
+
* without pulling in the core graph.
|
|
14
|
+
*/
|
|
15
|
+
export type RefractErrorCode = "REFRACT_E_COLOR_INPUT" | "REFRACT_E_COLOR_TUPLE" | "REFRACT_E_STEPS" | "REFRACT_E_VARIANT" | "REFRACT_E_VARIANT_REF" | "REFRACT_E_CYCLE" | "REFRACT_E_ADJUST" | "REFRACT_E_HARMONY" | "REFRACT_E_VALIDATION" | "REFRACT_E_RECIPE_PROPERTY" | "REFRACT_E_TOKEN_PATH" | "REFRACT_E_BREAKPOINT" | "REFRACT_E_STATE" | "REFRACT_E_CONTAINER" | "REFRACT_E_RESPONSIVE" | "REFRACT_E_MEDIA" | "REFRACT_E_MODE" | "REFRACT_E_PRESET" | "REFRACT_E_EFFECTS" | "REFRACT_E_LAYOUT" | "REFRACT_E_UNITS" | "REFRACT_E_PROPERTY" | "REFRACT_E_REFERENCE" | "REFRACT_E_NAMING" | "REFRACT_E_DTCG_VERSION" | "REFRACT_E_AUDIT";
|
|
16
|
+
/**
|
|
17
|
+
* An authoring / build error with a stable {@link RefractErrorCode}. For an aggregate (collect-all)
|
|
18
|
+
* error, `failures` lists each individual message so a caller can report them all at once.
|
|
19
|
+
*/
|
|
20
|
+
export declare class RefractError extends Error {
|
|
21
|
+
readonly code: RefractErrorCode;
|
|
22
|
+
readonly failures?: readonly string[];
|
|
23
|
+
constructor(code: RefractErrorCode, message: string, failures?: readonly string[]);
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Clean-room core barrel. */
|
|
2
|
+
export * from "./errors";
|
|
3
|
+
export * from "./cssProperties";
|
|
4
|
+
export * from "./model";
|
|
5
|
+
export * from "./media";
|
|
6
|
+
export * from "./units";
|
|
7
|
+
export * from "./ref";
|
|
8
|
+
export * from "./normalize";
|
|
9
|
+
export * from "./derive";
|
|
10
|
+
export * from "./types";
|
|
11
|
+
export * from "./subsystem";
|
|
12
|
+
export * from "./rawTheme";
|
|
13
|
+
export * from "./ThemeAdapter";
|
|
14
|
+
export * from "./defineAdapter";
|
|
15
|
+
export * from "./noopAdapter";
|
|
16
|
+
export * from "./createTheme";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Container-query descriptors (§10.5).
|
|
3
|
+
*
|
|
4
|
+
* A named query container has a *size* scale (`sizeName → px`), exactly the shape a breakpoint scale
|
|
5
|
+
* has — so each container reuses {@link buildMediaDescriptor} with an `@container`-prefixing resolver.
|
|
6
|
+
* The result is a per-container descriptor whose `.min/.max/.exact(sizeName)` (and `[sizeName][kind]`)
|
|
7
|
+
* yield `@container <name> (…)` preludes with the same min/max/exact + band-to-next semantics the
|
|
8
|
+
* viewport axis uses. Threshold widths honor the shared media unit config (px default, em/rem).
|
|
9
|
+
*/
|
|
10
|
+
import { type MediaDescriptor } from "./descriptors";
|
|
11
|
+
import { type MediaConfig } from "./queries";
|
|
12
|
+
import type { ContainerModel } from "../model/model";
|
|
13
|
+
/** One descriptor per named container: `containerName → MediaDescriptor` over that container's size scale. */
|
|
14
|
+
export type ContainerDescriptors = Record<string, MediaDescriptor<string>>;
|
|
15
|
+
/**
|
|
16
|
+
* Build a {@link ContainerDescriptors} map from the theme's `containers` config. Each container's
|
|
17
|
+
* `sizes` become the descriptor's "breakpoints"; the resolver emits `@container <name> (…)`. The
|
|
18
|
+
* width unit follows `config` (shared with the viewport media descriptor).
|
|
19
|
+
*/
|
|
20
|
+
export declare const buildContainerDescriptors: (containers: Record<string, ContainerModel> | undefined, config?: MediaConfig) => ContainerDescriptors;
|