@taprootio/espalier 2.17.0 → 2.19.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.
@@ -30,6 +30,7 @@
30
30
  * }
31
31
  * ```
32
32
  */
33
+ import { type DataPalette, type DataRamps, type PartialDataRamps } from "./data-colors.js";
33
34
  /** A key in the lightness ramp. Each maps to a perceptual role. */
34
35
  export type LightnessKey = "surface" | "raised1" | "raised2" | "raised3" | "raised4" | "accent" | "muted" | "text" | "border" | "ink" | "shadow";
35
36
  /** Lightness values (0–1) for every ramp position. */
@@ -123,6 +124,14 @@ export declare function parseAnchorSource(source: string): AnchorReference | nul
123
124
  * resolves `null`, as do unknown names and slots.
124
125
  */
125
126
  export declare function resolveAnchorColor(anchors: ThemeAnchors, reference: AnchorReference): string | null;
127
+ /**
128
+ * Resolve a data color source to a concrete CSS color.
129
+ *
130
+ * Data palettes and ramps accept either a CSS color directly or the same
131
+ * `anchor:<name>[.<slot>]` references as semantic mappings. The returned color
132
+ * is parseable and opaque; invalid or unresolved sources return `null`.
133
+ */
134
+ export declare function resolveDataColorSource(source: string, anchors: ThemeAnchors): string | null;
126
135
  /** Every semantic color token the system computes. */
127
136
  export type SemanticColorName = "background" | "layer1" | "layer2" | "layer3" | "layer4" | "actionBackground" | "actionText" | "border" | "shadow" | "text" | "dangerText" | "headings" | "headingsHover" | "link" | "linkHover" | "linkHoverBg" | "inputCaret" | "inputSelection" | "inputSelectionBg";
128
137
  /** Chroma range enforced on a semantic token before gamut mapping. */
@@ -148,6 +157,100 @@ export interface SemanticMapping {
148
157
  }
149
158
  /** Full mapping table — one entry per semantic color token. */
150
159
  export type SemanticMappings = Record<SemanticColorName, SemanticMapping>;
160
+ /**
161
+ * The designer-facing color roles.
162
+ *
163
+ * `semantic-groups.md` has always described these groups and then warned
164
+ * that exposing the nineteen raw tokens is "handing users the engine
165
+ * room instead of the dashboard" — but the groups were documentation
166
+ * only. Roles make them addressable: a sentence a designer would say
167
+ * becomes a line of theme.
168
+ *
169
+ * - `canvas` — the page and its raised surfaces
170
+ * - `ink` — body copy, with a `heading` slot for titles
171
+ * - `accent` — decoration (rules, icons, hover washes), with a `text`
172
+ * slot for the deepened variant that may carry copy
173
+ * - `action` — filled action surfaces; its paired ink derives
174
+ * automatically (see {@link ROLE_PAIRED_INK})
175
+ * - `structure` — borders and shadows
176
+ *
177
+ * `status` is deliberately absent: the danger/success/warning families
178
+ * carry meaning through fixed hues (ADR-004) and are not a brand
179
+ * decision.
180
+ */
181
+ export type RoleName = "canvas" | "ink" | "accent" | "action" | "structure";
182
+ /**
183
+ * A role binding: a color source, or an object with the base `color`
184
+ * plus role-specific slots.
185
+ *
186
+ * Sources are the same vocabulary mappings use — a geometric family, a
187
+ * status family, or an `anchor:<name>` reference (ADR-015).
188
+ */
189
+ export type RoleSlotName = {
190
+ canvas: never;
191
+ ink: "heading";
192
+ accent: "text";
193
+ action: "ink";
194
+ structure: never;
195
+ };
196
+ export type RoleBinding<R extends RoleName = RoleName> = MappingSource | ({
197
+ color: MappingSource;
198
+ } & Partial<Record<RoleSlotName[R], MappingSource>>);
199
+ /**
200
+ * The roles a theme declares. Every role is optional, and each one
201
+ * admits only its own slots — `ink.heading`, `accent.text`,
202
+ * `action.ink` — so the structural claims the roles layer makes are
203
+ * checked by the type system, not only by validateTheme.
204
+ */
205
+ export type ThemeRoles = {
206
+ [R in RoleName]?: RoleBinding<R>;
207
+ };
208
+ /** Ordered role names, for validation and iteration. */
209
+ export declare const ROLE_NAMES: readonly RoleName[];
210
+ /**
211
+ * Which semantic tokens each role slot paints, and at which ramp stop.
212
+ *
213
+ * This is the compilation table: it turns five designer-facing roles
214
+ * into the nineteen engine-room tokens. `dangerText` is absent by
215
+ * design — the status family is reserved and keeps its own source.
216
+ *
217
+ * The `accent` role has no body-text slot on purpose. "Rose is
218
+ * decorative only" is a structural guarantee here, not a comment: text
219
+ * that must come from the accent family comes through `accent.text`,
220
+ * the deepened variant a designer picked for legibility.
221
+ */
222
+ export declare const ROLE_TOKEN_PLAN: Readonly<Record<RoleName, Readonly<Record<string, ReadonlyArray<[SemanticColorName, LightnessKey]>>>>>;
223
+ /**
224
+ * Ink slots that derive from another role when the theme does not
225
+ * declare them.
226
+ *
227
+ * A role that paints a surface must also answer "what writes on it".
228
+ * `action.ink` defaults to the canvas color: the ink paired with a
229
+ * filled action is the page ground, and APCA enforcement against
230
+ * `actionBackground` then guarantees it is legible — which is exactly
231
+ * how "white on plum for reversed sections" becomes one theme line
232
+ * instead of a hand-picked hex.
233
+ */
234
+ export declare const ROLE_PAIRED_INK: Readonly<Record<string, {
235
+ role: RoleName;
236
+ slot: string;
237
+ }>>;
238
+ interface CompileRoleOptions {
239
+ /** Token mappings declared explicitly at this merge boundary. */
240
+ explicitMappings?: Partial<SemanticMappings>;
241
+ /** Exact maximum paired-ink contrast of an action surface candidate. */
242
+ actionSurfaceContrast?: (source: MappingSource, stop: LightnessKey) => number;
243
+ }
244
+ /**
245
+ * Compile roles into semantic mappings.
246
+ *
247
+ * Only the tokens a declared role owns are emitted, so a theme that
248
+ * declares no roles compiles to nothing and every existing theme keeps
249
+ * its exact output. Token-level `semanticMappings` layer over the
250
+ * result — the engine room stays available for the cases roles do not
251
+ * reach.
252
+ */
253
+ export declare function compileRoles(roles: ThemeRoles, lightness: LightnessMap, baseMappings: SemanticMappings, options?: CompileRoleOptions): Partial<SemanticMappings>;
151
254
  /** The complete, resolved Espalier theme. */
152
255
  export interface EspalierTheme {
153
256
  /**
@@ -284,6 +387,24 @@ export interface EspalierTheme {
284
387
  * of bending hue angles. See {@link ThemeAnchors} and ADR-015.
285
388
  */
286
389
  anchors: ThemeAnchors;
390
+ /**
391
+ * Designer-facing color roles, compiled into
392
+ * {@link EspalierTheme.semanticMappings} at merge time. Empty by
393
+ * default; token-level mappings layer over whatever roles produce.
394
+ * See {@link ThemeRoles} and ADR-016.
395
+ */
396
+ roles: ThemeRoles;
397
+ /**
398
+ * Eight stable categorical data-series colors. Each value is a CSS color or
399
+ * an `anchor:<name>[.<slot>]` reference. Emitted as
400
+ * `--esp-color-series-1`–`--esp-color-series-8`.
401
+ */
402
+ dataPalette: DataPalette;
403
+ /**
404
+ * Named sequential and diverging data ramps. Empty by default; each
405
+ * declaration emits `--esp-color-ramp-<name>-1…<steps>`.
406
+ */
407
+ dataRamps: DataRamps;
287
408
  /** Optional CSS `background-image` for the page surface. */
288
409
  pageBackgroundImage?: string;
289
410
  /** Opacity (0–1) for the page background image. */
@@ -306,8 +427,9 @@ export interface EspalierTheme {
306
427
  * carry slot-only anchor overrides, which the resolved
307
428
  * {@link ThemeAnchor} deliberately does not permit.
308
429
  */
309
- export type PartialTheme = Omit<DeepPartial<EspalierTheme>, "anchors"> & {
430
+ export type PartialTheme = Omit<DeepPartial<EspalierTheme>, "anchors" | "dataRamps"> & {
310
431
  anchors?: PartialThemeAnchors;
432
+ dataRamps?: PartialDataRamps;
311
433
  };
312
434
  /** Validation result returned by {@link validateTheme}. */
313
435
  export interface ThemeValidationResult {
@@ -318,6 +440,29 @@ export interface ThemeValidationResult {
318
440
  /** Soft issues — the theme will work but values are unusual. */
319
441
  warnings: string[];
320
442
  }
443
+ /**
444
+ * Which semantic tokens are ink, what surface each sits on, and the
445
+ * APCA contrast each must clear.
446
+ *
447
+ * This pairing lived as an identical hardcoded table inside both
448
+ * `compute-theme-properties.ts` and `esp-element-base.ts` — invisible to
449
+ * themes and duplicated in exactly the way this repository's adoption
450
+ * rules exist to prevent. It belongs to the model: every serious system
451
+ * makes the surface/ink pair the unit designers see (Material's
452
+ * `on-primary`, shadcn's `-foreground`), and the roles layer derives an
453
+ * action's ink from it rather than asking a designer to hand-pick a
454
+ * legible color.
455
+ *
456
+ * Lc targets follow the APCA guidelines (ADR-002):
457
+ * 90 → body text at any size
458
+ * 75 → 16 px+ text (UI labels, links, body)
459
+ * 60 → 24 px+ bold / 28 px+ normal (headings, carets, decorative)
460
+ */
461
+ type TokenPairing = Readonly<{
462
+ bg: SemanticColorName;
463
+ targetLc: number;
464
+ }>;
465
+ export declare const TOKEN_PAIRINGS: Readonly<Record<"actionText", TokenPairing> & Partial<Record<SemanticColorName, TokenPairing>>>;
321
466
  /** Ordered list of all semantic color token names. */
322
467
  export declare const SEMANTIC_COLOR_NAMES: readonly SemanticColorName[];
323
468
  /** Valid color-source identifiers for {@link SemanticMapping.source}. */
@@ -438,7 +583,7 @@ export declare function validateTheme(base64: string): ThemeValidationResult;
438
583
  * and therefore need key-by-key merging instead of wholesale
439
584
  * replacement when combining two {@link PartialTheme} objects.
440
585
  */
441
- export declare const NESTED_THEME_KEYS: readonly ["anchors", "angles", "chroma", "lightness", "semanticHues", "semanticMappings", "variantChroma"];
586
+ export declare const NESTED_THEME_KEYS: readonly ["anchors", "angles", "dataPalette", "dataRamps", "roles", "chroma", "lightness", "semanticHues", "semanticMappings", "variantChroma"];
442
587
  /**
443
588
  * Deep-merge two {@link PartialTheme} objects.
444
589
  *
@@ -1 +1 @@
1
- import{ACCEPTED_COLOR_FORMS as $,parseCssColor as b,parseOklch as _,serializeOklch as W}from"./color-engine.js";const A=/^[a-z][a-z0-9-]*$/,N=/^anchor:([^.]+)(?:\.(.+))?$/;function F(s){const e=N.exec(s);return e?e[2]===void 0?{name:e[1]}:{name:e[1],slot:e[2]}:null}function X(s,e){const i=s[e.name];if(i===void 0)return null;if(typeof i=="string")return e.slot===void 0?i:null;const r=i[e.slot??"color"];return typeof r=="string"?r:null}const k=["background","layer1","layer2","layer3","layer4","actionBackground","actionText","border","shadow","text","dangerText","headings","headingsHover","link","linkHover","linkHoverBg","inputCaret","inputSelection","inputSelectionBg"],x=["primary","analogous-left","analogous-right","complementary","split-complementary-left","split-complementary-right","triadic-left","triadic-right","danger","success","warning"],D=["analogous-left","analogous-right","complementary","split-complementary-left","split-complementary-right","triadic-left","triadic-right","danger","success","warning"],B=["surface","raised1","raised2","raised3","raised4","accent","muted","text","border","ink","shadow"],L={surface:.99,raised1:.97,raised2:.9,raised3:.8,raised4:.72,accent:.5,muted:.45,text:.35,border:.3,ink:.25,shadow:.2},U={surface:.15,raised1:.2,raised2:.3,raised3:.4,raised4:.36,accent:.6,muted:.75,text:.85,border:.7,ink:.95,shadow:.6},S={background:{source:"primary",lightness:"surface"},layer1:{source:"primary",lightness:"raised1"},layer2:{source:"primary",lightness:"raised2"},layer3:{source:"primary",lightness:"raised3"},layer4:{source:"primary",lightness:"raised4"},actionBackground:{source:"primary",lightness:"raised3"},actionText:{source:"primary",lightness:"ink"},border:{source:"primary",lightness:"border"},shadow:{source:"primary",lightness:"shadow"},text:{source:"primary",lightness:"text"},dangerText:{source:"danger",lightness:"text"},headings:{source:"primary",lightness:"muted"},headingsHover:{source:"primary",lightness:"text"},link:{source:"complementary",lightness:"accent"},linkHover:{source:"complementary",lightness:"text"},linkHoverBg:{source:"triadic-left",lightness:"raised2"},inputCaret:{source:"triadic-right",lightness:"ink"},inputSelection:{source:"complementary",lightness:"text"},inputSelectionBg:{source:"triadic-left",lightness:"raised2"}},d=.4;function M(){const s={};for(const e of k)s[e]={min:0,max:d};return s}const j={seedColor:"oklch(0.7 0.125 216)",fontBody:"",fontHeadings:"",fontBrand:"",fontMonospace:"",fontWeightBody:"normal",fontWeightHeadings:"bold",fontWeightBrand:"bold",fontWeightMonospace:"normal",stylesheets:[],rootFontSize:16,typeRatio:1.25,spaceRatio:1.618,borderRadius:.2,viewportMin:320,viewportMax:1200,angles:{analogous:30,complementary:180,splitComplementary:30,triadic:120},semanticHues:{danger:27,success:150,warning:90},variantChroma:{},chroma:M(),semanticMappings:{...S},anchors:{}},Z={...j,chroma:M(),semanticMappings:{...S},anchors:{},lightness:{...L}},q={...j,chroma:M(),semanticMappings:{...S},anchors:{},lightness:{...U}},H=new Set(["__proto__","constructor","prototype"]);function m(s){return!H.has(s)}function z(s,e){return H.has(s)?void 0:e}function Q(s){return`--esp-color-${s.replace(/([A-Z])/g,"-$1").replace(/(\d)/g,"-$1").toLowerCase()}`}function K(s){try{const e=atob(s),i=JSON.parse(e,z);return typeof i!="object"||i===null||Array.isArray(i)?null:i}catch{return null}}function O(s){return btoa(JSON.stringify(s))}function C(s){if(_(s))return s;const e=b(s);return e?W(e):s}function R(s,e){const i={};for(const[r,u]of Object.entries(s))m(r)&&(i[r]=u);if(!e)return i;for(const[r,u]of Object.entries(e)){if(u===void 0||!m(r))continue;const o=i[r];if(o!==void 0&&typeof u=="object"&&u!==null){const f={};if(typeof o=="string")f.color=o;else for(const[p,g]of Object.entries(o))m(p)&&(f[p]=g);for(const[p,g]of Object.entries(u))g!==void 0&&m(p)&&(f[p]=g);i[r]=f;continue}i[r]=u}return i}function J(s){const e={};for(const[i,r]of Object.entries(s)){if(!m(i))continue;if(typeof r=="string"){e[i]=C(r);continue}const u={};for(const[o,f]of Object.entries(r))m(o)&&(u[o]=typeof f=="string"?C(f):f);e[i]=u}return e}function ee(s,e){const i={...s,seedColor:typeof e.seedColor=="string"?C(e.seedColor):s.seedColor,fontBody:e.fontBody??s.fontBody,fontHeadings:e.fontHeadings??s.fontHeadings,fontBrand:e.fontBrand??s.fontBrand,fontMonospace:e.fontMonospace??s.fontMonospace,fontWeightBody:String(e.fontWeightBody??s.fontWeightBody),fontWeightHeadings:String(e.fontWeightHeadings??s.fontWeightHeadings),fontWeightBrand:String(e.fontWeightBrand??s.fontWeightBrand),fontWeightMonospace:String(e.fontWeightMonospace??s.fontWeightMonospace),stylesheets:e.stylesheets??[...s.stylesheets],rootFontSize:e.rootFontSize??s.rootFontSize,typeRatio:e.typeRatio??s.typeRatio,spaceRatio:e.spaceRatio??s.spaceRatio,borderRadius:e.borderRadius??s.borderRadius,viewportMin:e.viewportMin??s.viewportMin,viewportMax:e.viewportMax??s.viewportMax,angles:{...s.angles,...e.angles},semanticHues:{...s.semanticHues,...e.semanticHues},variantChroma:{...s.variantChroma,...e.variantChroma},lightness:{...s.lightness,...e.lightness},chroma:T(s.chroma,e.chroma),semanticMappings:T(s.semanticMappings,e.semanticMappings),anchors:J(R(s.anchors,e.anchors))};return e.pageBackgroundImage!==void 0&&(i.pageBackgroundImage=e.pageBackgroundImage),e.pageBackgroundImageOpacity!==void 0&&(i.pageBackgroundImageOpacity=e.pageBackgroundImageOpacity),e.boxBackgroundImage!==void 0&&(i.boxBackgroundImage=e.boxBackgroundImage),e.boxBackgroundImageOpacity!==void 0&&(i.boxBackgroundImageOpacity=e.boxBackgroundImageOpacity),e.vellumOpacity!==void 0&&(i.vellumOpacity=e.vellumOpacity),e.vellumBackgroundImage!==void 0&&(i.vellumBackgroundImage=e.vellumBackgroundImage),e.vellumBackgroundImageOpacity!==void 0&&(i.vellumBackgroundImageOpacity=e.vellumBackgroundImageOpacity),i}function T(s,e){if(!e)return{...s};const i={...s};for(const r of Object.keys(e)){const u=e[r];u!==void 0&&(i[r]=u)}return i}function ne(s){const e=[],i=[];let r;try{r=atob(s)}catch{return e.push("Failed to decode Base64 string."),{valid:!1,errors:e,warnings:i}}let u;try{u=JSON.parse(r)}catch{return e.push("Decoded string is not valid JSON."),{valid:!1,errors:e,warnings:i}}if(typeof u!="object"||u===null||Array.isArray(u))return e.push("Theme must be a JSON object."),{valid:!1,errors:e,warnings:i};const o=u;"seedColor"in o&&(typeof o.seedColor!="string"?e.push("seedColor must be a string."):b(o.seedColor)||e.push(`seedColor is not a valid CSS color: "${o.seedColor}". Accepted forms: ${$}.`));for(const n of["fontBody","fontHeadings","fontBrand","fontMonospace"])n in o&&typeof o[n]!="string"&&e.push(`${n} must be a string.`);const f=["normal","bold","lighter","bolder"],p=["inherit","initial","unset","revert","revert-layer"];for(const n of["fontWeightBody","fontWeightHeadings","fontWeightBrand","fontWeightMonospace"])if(n in o){const t=o[n];if(typeof t=="number")(t<1||t>1e3)&&e.push(`${n} numeric value must be 1\u20131000 (got ${t}).`);else if(typeof t=="string"){const a=f.includes(t)||p.includes(t);if(/^\d+$/.test(t)){const l=Number(t);(l<1||l>1e3)&&e.push(`${n} numeric value must be 1\u20131000 (got "${t}").`)}else a||i.push(`${n} = "${t}" is not a standard font-weight value.`)}else e.push(`${n} must be a string or number.`)}"stylesheets"in o&&(Array.isArray(o.stylesheets)?o.stylesheets.some(n=>typeof n!="string")&&e.push("Every entry in stylesheets must be a string."):e.push("stylesheets must be an array of strings."));const g=[["rootFontSize",1,100],["borderRadius",0,10],["viewportMin",100,1e4],["viewportMax",200,1e4]];for(const[n,t,a]of g)if(n in o)if(typeof o[n]!="number"||!isFinite(o[n]))e.push(`${n} must be a finite number.`);else{const c=o[n];t!==void 0&&c<t&&e.push(`${n} must be \u2265 ${t} (got ${c}).`),a!==void 0&&c>a&&i.push(`${n} = ${c} is unusually high (max ${a}).`)}for(const n of["typeRatio","spaceRatio"])if(n in o)if(typeof o[n]!="number"||!isFinite(o[n]))e.push(`${n} must be a finite number.`);else{const t=o[n];t<=1&&e.push(`${n} must be > 1 (got ${t}).`);const a=n==="typeRatio"?1.3:2;t>a&&i.push(`${n} = ${t} is very high (max ${a}); scales may be extreme.`)}if("viewportMin"in o&&"viewportMax"in o){const n=o.viewportMin,t=o.viewportMax;typeof n=="number"&&typeof t=="number"&&n>=t&&e.push(`viewportMin (${n}) must be less than viewportMax (${t}).`)}if("angles"in o)if(typeof o.angles!="object"||o.angles===null)e.push("angles must be an object.");else{const n=o.angles;for(const t of["analogous","complementary","splitComplementary","triadic"])if(t in n)if(typeof n[t]!="number"||!isFinite(n[t]))e.push(`angles.${t} must be a finite number.`);else{const a=n[t];(a<0||a>360)&&i.push(`angles.${t} = ${a} is outside 0\u2013360.`)}}if("semanticHues"in o)if(typeof o.semanticHues!="object"||o.semanticHues===null)e.push("semanticHues must be an object.");else{const n=o.semanticHues;for(const t of["danger","success","warning"])if(t in n)if(typeof n[t]!="number"||!isFinite(n[t]))e.push(`semanticHues.${t} must be a finite number.`);else{const a=n[t];(a<0||a>360)&&i.push(`semanticHues.${t} = ${a} is outside 0\u2013360.`)}}if("variantChroma"in o)if(typeof o.variantChroma!="object"||o.variantChroma===null)e.push("variantChroma must be an object.");else{const n=o.variantChroma;for(const t of D)if(t in n)if(typeof n[t]!="number"||!isFinite(n[t]))e.push(`variantChroma["${t}"] must be a finite number.`);else{const a=n[t];a<0&&e.push(`variantChroma["${t}"] must be \u2265 0 (got ${a}).`),a>d&&i.push(`variantChroma["${t}"] = ${a} exceeds ${d}.`)}}if("lightness"in o)if(typeof o.lightness!="object"||o.lightness===null)e.push("lightness must be an object.");else{const n=o.lightness;for(const t of B)if(t in n)if(typeof n[t]!="number"||!isFinite(n[t]))e.push(`lightness.${t} must be a finite number.`);else{const a=n[t];(a<0||a>1)&&e.push(`lightness.${t} must be 0\u20131 (got ${a}).`)}}if("chroma"in o)if(typeof o.chroma!="object"||o.chroma===null)e.push("chroma must be an object.");else{const n=o.chroma;for(const t of k)if(t in n){const a=n[t];if(typeof a!="object"||a===null){e.push(`chroma.${t} must be { min, max }.`);continue}const c=a;(typeof c.min!="number"||c.min<0)&&e.push(`chroma.${t}.min must be \u2265 0.`),(typeof c.max!="number"||c.max<0||c.max>d)&&e.push(`chroma.${t}.max must be 0\u2013${d}.`),typeof c.min=="number"&&typeof c.max=="number"&&c.min>c.max&&e.push(`chroma.${t}.min (${c.min}) must be \u2264 max (${c.max}).`)}}if("anchors"in o)if(typeof o.anchors!="object"||o.anchors===null||Array.isArray(o.anchors))e.push("anchors must be an object of named colors.");else for(const[n,t]of Object.entries(o.anchors))if(m(n)?A.test(n)||e.push(`anchors: "${n}" is not a valid anchor name; use a lowercase slug (letters, digits, hyphens).`):e.push(`anchors: "${n}" is a reserved JavaScript property name and cannot be an anchor name; rename it.`),x.includes(n)&&e.push(`anchors: "${n}" collides with a reserved color source name.`),typeof t=="string")b(t)||e.push(`anchors.${n} is not a valid CSS color: "${t}". Accepted forms: ${$}.`);else if(typeof t=="object"&&t!==null&&!Array.isArray(t)){const a=t;typeof a.color!="string"&&e.push(`anchors.${n} must declare its base "color".`);for(const[c,l]of Object.entries(a))m(c)?c!=="color"&&!A.test(c)&&e.push(`anchors.${n}: "${c}" is not a valid slot name; use a lowercase slug.`):e.push(`anchors.${n}: "${c}" is a reserved JavaScript property name and cannot be a slot name; rename it.`),typeof l!="string"?e.push(`anchors.${n}.${c} must be a CSS color string.`):b(l)||e.push(`anchors.${n}.${c} is not a valid CSS color: "${l}". Accepted forms: ${$}.`)}else e.push(`anchors.${n} must be a color string or a { color, \u2026slots } object.`);const y=new Map;if("anchors"in o&&typeof o.anchors=="object"&&o.anchors!==null&&!Array.isArray(o.anchors))for(const[n,t]of Object.entries(o.anchors))y.set(n,t);if("semanticMappings"in o)if(typeof o.semanticMappings!="object"||o.semanticMappings===null)e.push("semanticMappings must be an object.");else{const n=o.semanticMappings;for(const t of k)if(t in n){const a=n[t];if(typeof a!="object"||a===null){e.push(`semanticMappings.${t} must be { source, lightness }.`);continue}const c=a;if(typeof c.source!="string")e.push(`semanticMappings.${t}.source must be a string.`);else if(c.source.startsWith("anchor:")){const l=F(c.source);if(!l)e.push(`semanticMappings.${t}.source "${c.source}" is not valid anchor syntax; use anchor:<name> or anchor:<name>.<slot>.`);else if(y.has(l.name)){if(l.slot!==void 0){const h=y.get(l.name);if(typeof(typeof h=="object"&&h!==null&&!Array.isArray(h)?h[l.slot]:void 0)!="string"){const v=typeof h=="object"&&h!==null?Object.keys(h).filter(E=>E!=="color"):[];e.push(`semanticMappings.${t}.source references unknown slot "${l.slot}" on anchor "${l.name}". Declared slots: ${v.length>0?v.join(", "):"(none)"}.`)}}}else{const h=[...y.keys()];e.push(`semanticMappings.${t}.source references undeclared anchor "${l.name}". Declared anchors: ${h.length>0?h.join(", "):"(none)"}.`)}}else x.includes(c.source)||e.push(`semanticMappings.${t}.source must be one of: ${x.join(", ")}, or a declared anchor as anchor:<name> / anchor:<name>.<slot>.`);(typeof c.lightness!="string"||!B.includes(c.lightness))&&e.push(`semanticMappings.${t}.lightness must be one of: ${B.join(", ")}.`)}}for(const n of["pageBackgroundImage","boxBackgroundImage","vellumBackgroundImage"])n in o&&typeof o[n]!="string"&&e.push(`${n} must be a string.`);for(const n of["pageBackgroundImageOpacity","boxBackgroundImageOpacity","vellumOpacity","vellumBackgroundImageOpacity"])if(n in o)if(typeof o[n]!="number"||!isFinite(o[n]))e.push(`${n} must be a finite number.`);else{const t=o[n];(t<0||t>1)&&e.push(`${n} must be 0\u20131 (got ${t}).`)}return{valid:e.length===0,errors:e,warnings:i}}const G=["anchors","angles","chroma","lightness","semanticHues","semanticMappings","variantChroma"];function P(s,e){const i={};for(const[r,u]of Object.entries(s))m(r)&&(i[r]=u);for(const[r,u]of Object.entries(e))u!==void 0&&m(r)&&(i[r]=u);for(const r of G){const u=s[r],o=e[r];if(u&&typeof u=="object"&&!Array.isArray(u)&&o&&typeof o=="object"&&!Array.isArray(o)){if(r==="anchors"){i[r]=R(u,o);continue}const f={};for(const[p,g]of Object.entries(u))m(p)&&(f[p]=g);for(const[p,g]of Object.entries(o))g!==void 0&&m(p)&&(f[p]=g);i[r]=f}}return i}function te(...s){let e={};for(const i of s){if(!i)continue;const r=K(i);r&&(e=P(e,r))}return O(e)}function I(s){return s.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r")}const w={borderRadius:.1,fontBody:'"Noto Sans", sans-serif',fontWeightBody:"normal",fontHeadings:'"Inika", serif',fontWeightHeadings:"normal",fontBrand:'"Inika", serif',fontWeightBrand:"normal",fontMonospace:'"JetBrains Mono", monospace',fontWeightMonospace:"normal",seedColor:"oklch(.07 0.15 216)"};function oe(s){return O({...w,pageBackgroundImage:`url("${I(s)}")`,pageBackgroundImageOpacity:.5})}function se(s){return O({...w,pageBackgroundImage:`url("${I(s)}")`,pageBackgroundImageOpacity:.55})}export{A as ANCHOR_SLUG_PATTERN,x as COLOR_SOURCES,U as DEFAULT_DARK_LIGHTNESS,q as DEFAULT_DARK_THEME,L as DEFAULT_LIGHT_LIGHTNESS,Z as DEFAULT_LIGHT_THEME,S as DEFAULT_SEMANTIC_MAPPINGS,B as LIGHTNESS_KEYS,G as NESTED_THEME_KEYS,k as SEMANTIC_COLOR_NAMES,D as VARIANT_COLOR_SOURCES,se as buildTaprootDarkTheme,oe as buildTaprootLightTheme,O as encodeTheme,te as layerThemes,P as mergePartials,ee as mergeTheme,F as parseAnchorSource,K as parseTheme,X as resolveAnchorColor,Q as semanticToCSS,ne as validateTheme};
1
+ import{ACCEPTED_COLOR_FORMS as T,apcaContrast as ae,deriveSemantic as re,parseCssColor as v,parseOklch as ie,serializeOklch as ce}from"./color-engine.js";import{computeVariants as ue,parseAnchorSource as le,resolveAnchorColor as fe,resolveMappingSource as pe}from"./variant-engine.js";import{auditDataPalette as me,DATA_SERIES_KEYS as B,DEFAULT_DATA_PALETTE as H,DEFAULT_DATA_RAMP_STEPS as ge,MAX_DATA_RAMP_STEPS as P,MIN_DATA_RAMP_LIGHTNESS_STEP as U,MIN_DATA_RAMP_STEPS as L}from"./data-colors.js";const C=/^[a-z][a-z0-9-]*$/;function G(n){return le(n)}function de(n,e){return fe(n,e)}function he(n,e){const c=G(n);if(c){const a=de(e,c);return a&&v(a)?a:null}return v(n)?n:null}const D=["canvas","ink","accent","action","structure"],V={canvas:{color:[["background","surface"],["layer1","raised1"],["layer2","raised2"],["layer3","raised3"],["layer4","raised4"]]},ink:{color:[["text","text"],["inputSelection","text"]],heading:[["headings","muted"],["headingsHover","ink"]]},accent:{color:[["linkHoverBg","raised2"],["inputSelectionBg","raised2"],["inputCaret","ink"]],text:[["link","accent"],["linkHover","text"]]},action:{color:[["actionBackground","muted"]],ink:[["actionText","surface"]]},structure:{color:[["border","border"],["shadow","shadow"]]}},ye=new Set(["action.color"]),be={canvas:"background",ink:"text",accent:"link",action:"actionBackground",structure:"border"},$e={"action.ink":{role:"canvas",slot:"color"}},Se=.45;function J(n,e){const c=Y.actionText,a=[...R].sort((m,h)=>n[m]-n[h]||R.indexOf(m)-R.indexOf(h)),u=e?a.filter(m=>e(m)>=c.targetLc):a,s=u.length>0?u:a;let l,f=1/0;for(const m of s){const h=Math.abs(n[m]-Se);h<f&&(f=h,l=m)}return l??"muted"}function ke(n,e){const c=e[n]>.5;let a=R[0];for(const u of R)(c?e[u]<e[a]:e[u]>e[a])&&(a=u);return a}function _(n,e){if(n===void 0)return;if(typeof n=="string")return e==="color"?n:void 0;const c=n[e];return typeof c=="string"?c:void 0}function q(n,e,c,a={}){const u={},s={};for(const[l,f]of Object.entries(a.explicitMappings??{}))f!==void 0&&(s[l]=f.lightness);for(const l of D){const f=n[l];if(f!==void 0)for(const[m,h]of Object.entries(V[l])){const S=$e[`${l}.${m}`];let y=_(f,m),A=!1;if(y===void 0&&(S&&(y=_(n[S.role],S.slot),y??=c[be[S.role]]?.source,A=y!==void 0),y??=_(f,"color")),y!==void 0)for(const[t,o]of h){let r=o;if(ye.has(`${l}.${m}`))r=J(e,a.actionSurfaceContrast?i=>a.actionSurfaceContrast(y,i):void 0);else if(A&&S){const i=Y[t],p=(i&&s[i.bg])??J(e,a.actionSurfaceContrast?d=>a.actionSurfaceContrast(_(f,"color"),d):void 0);r=ke(p,e)}u[t]={source:y,lightness:r}}}}return u}const Y={text:{bg:"background",targetLc:75},dangerText:{bg:"background",targetLc:75},headings:{bg:"background",targetLc:60},headingsHover:{bg:"background",targetLc:60},link:{bg:"background",targetLc:75},linkHover:{bg:"background",targetLc:75},actionText:{bg:"actionBackground",targetLc:75},inputCaret:{bg:"layer2",targetLc:60},inputSelection:{bg:"inputSelectionBg",targetLc:60}},N=["background","layer1","layer2","layer3","layer4","actionBackground","actionText","border","shadow","text","dangerText","headings","headingsHover","link","linkHover","linkHoverBg","inputCaret","inputSelection","inputSelectionBg"],F=["primary","analogous-left","analogous-right","complementary","split-complementary-left","split-complementary-right","triadic-left","triadic-right","danger","success","warning"],Ae=["analogous-left","analogous-right","complementary","split-complementary-left","split-complementary-right","triadic-left","triadic-right","danger","success","warning"],R=["surface","raised1","raised2","raised3","raised4","accent","muted","text","border","ink","shadow"],xe={surface:.99,raised1:.97,raised2:.9,raised3:.8,raised4:.72,accent:.5,muted:.45,text:.35,border:.3,ink:.25,shadow:.2},ve={surface:.15,raised1:.2,raised2:.3,raised3:.4,raised4:.36,accent:.6,muted:.75,text:.85,border:.7,ink:.95,shadow:.6},W={background:{source:"primary",lightness:"surface"},layer1:{source:"primary",lightness:"raised1"},layer2:{source:"primary",lightness:"raised2"},layer3:{source:"primary",lightness:"raised3"},layer4:{source:"primary",lightness:"raised4"},actionBackground:{source:"primary",lightness:"raised3"},actionText:{source:"primary",lightness:"ink"},border:{source:"primary",lightness:"border"},shadow:{source:"primary",lightness:"shadow"},text:{source:"primary",lightness:"text"},dangerText:{source:"danger",lightness:"text"},headings:{source:"primary",lightness:"muted"},headingsHover:{source:"primary",lightness:"text"},link:{source:"complementary",lightness:"accent"},linkHover:{source:"complementary",lightness:"text"},linkHoverBg:{source:"triadic-left",lightness:"raised2"},inputCaret:{source:"triadic-right",lightness:"ink"},inputSelection:{source:"complementary",lightness:"text"},inputSelectionBg:{source:"triadic-left",lightness:"raised2"}},O=.4;function z(){const n={};for(const e of N)n[e]={min:0,max:O};return n}const X={seedColor:"oklch(0.7 0.125 216)",fontBody:"",fontHeadings:"",fontBrand:"",fontMonospace:"",fontWeightBody:"normal",fontWeightHeadings:"bold",fontWeightBrand:"bold",fontWeightMonospace:"normal",stylesheets:[],rootFontSize:16,typeRatio:1.25,spaceRatio:1.618,borderRadius:.2,viewportMin:320,viewportMax:1200,angles:{analogous:30,complementary:180,splitComplementary:30,triadic:120},semanticHues:{danger:27,success:150,warning:90},variantChroma:{},chroma:z(),semanticMappings:{...W},anchors:{},roles:{},dataPalette:{...H},dataRamps:{}},Z={...X,chroma:z(),semanticMappings:{...W},anchors:{},roles:{},dataPalette:{...H},dataRamps:{},lightness:{...xe}},Re={...X,chroma:z(),semanticMappings:{...W},anchors:{},roles:{},dataPalette:{...H},dataRamps:{},lightness:{...ve}},I=new WeakMap;I.set(Z,new Set),I.set(Re,new Set);const Q=new Set(["__proto__","constructor","prototype"]);function g(n){return!Q.has(n)}function Ce(n,e){return Q.has(n)?void 0:e}function Le(n){return`--esp-color-${n.replace(/([A-Z])/g,"-$1").replace(/(\d)/g,"-$1").toLowerCase()}`}function Oe(n){try{const e=atob(n),c=JSON.parse(e,Ce);return typeof c!="object"||c===null||Array.isArray(c)?null:c}catch{return null}}function K(n){return btoa(JSON.stringify(n))}function w(n){if(ie(n))return n;const e=v(n);return e?ce(e):n}function ee(n,e){const c={};for(const[a,u]of Object.entries(n))g(a)&&(c[a]=u);if(!e)return c;for(const[a,u]of Object.entries(e)){if(u===void 0||!g(a))continue;const s=c[a];if(s!==void 0&&typeof u=="object"&&u!==null){const l={};if(typeof s=="string")l.color=s;else for(const[f,m]of Object.entries(s))g(f)&&(l[f]=m);for(const[f,m]of Object.entries(u))m!==void 0&&g(f)&&(l[f]=m);c[a]=l;continue}c[a]=u}return c}function Me(n){const e={};for(const[c,a]of Object.entries(n)){if(!g(c))continue;if(typeof a=="string"){e[c]=w(a);continue}const u={};for(const[s,l]of Object.entries(a))g(s)&&(u[s]=typeof l=="string"?w(l):l);e[c]=u}return e}function M(n){return typeof n!="string"?"":n.startsWith("anchor:")?n:w(n)}function je(n){const e={};for(const c of B)e[c]=M(n[c]);return e}function te(n,e){const c={};for(const[a,u]of Object.entries(n))g(a)&&C.test(a)&&u&&typeof u=="object"&&!Array.isArray(u)&&(c[a]={...u});if(!e)return c;for(const[a,u]of Object.entries(e)){if(!g(a)||!C.test(a)||!u||typeof u!="object"||Array.isArray(u))continue;const s=c[a],l=s!==void 0&&u.type!==void 0&&u.type!==s.type;c[a]={...l?{}:s??{},...u}}return c}function Ee(n){const e={};for(const[c,a]of Object.entries(n))if(g(c)){if(a.type==="sequential"&&typeof a.source=="string"){e[c]={...a,source:M(a.source)};continue}if(a.type==="diverging"&&typeof a.start=="string"&&typeof a.end=="string"){e[c]={...a,start:M(a.start),end:M(a.end),...typeof a.neutral=="string"?{neutral:M(a.neutral)}:{}};continue}e[c]={...a}}return e}function ne(n){const e=v(n.seedColor);if(!e)return;const c=ue(e,n);return(a,u)=>{const s=pe(a,c,n.anchors)??c.primary,l=n.chroma.actionBackground,f=re(s,n.lightness[u],l.min,l.max),m={l:f.l>.5?0:1,c:0,h:0};return Math.abs(ae(m,f))}}function Te(n){const e=I.get(n);if(e)return new Set(e);const c=new Set,a=q(n.roles,n.lightness,n.semanticMappings,{actionSurfaceContrast:ne(n)});for(const[u,s]of Object.entries(a)){const l=n.semanticMappings[u];(l.source!==s.source||l.lightness!==s.lightness)&&c.add(u)}return c}function Be(n,e){const c={...n.roles,...e.roles},a={...n.lightness,...e.lightness},u=Me(ee(n.anchors,e.anchors)),s=typeof e.seedColor=="string"?w(e.seedColor):n.seedColor,l=e.semanticMappings,f={...n.angles,...e.angles},m={...n.semanticHues,...e.semanticHues},h={...n.variantChroma,...e.variantChroma},S=j(n.chroma,e.chroma),y=je(j(n.dataPalette,e.dataPalette)),A=Ee(te(n.dataRamps,e.dataRamps)),t=Te(n),o=new Set(t),r={};for(const k of t)r[k]=n.semanticMappings[k];for(const[k,E]of Object.entries(l??{}))E!==void 0&&(o.add(k),r[k]=E);const i=j(n.semanticMappings,l),p=ne({angles:f,anchors:u,chroma:S,lightness:a,seedColor:s,semanticHues:m,variantChroma:h}),d=q(c,a,i,{explicitMappings:r,actionSurfaceContrast:p}),b={};for(const[k,E]of Object.entries(d))o.has(k)||(b[k]=E);const x=j(j(n.semanticMappings,b),l),$={...n,seedColor:s,fontBody:e.fontBody??n.fontBody,fontHeadings:e.fontHeadings??n.fontHeadings,fontBrand:e.fontBrand??n.fontBrand,fontMonospace:e.fontMonospace??n.fontMonospace,fontWeightBody:String(e.fontWeightBody??n.fontWeightBody),fontWeightHeadings:String(e.fontWeightHeadings??n.fontWeightHeadings),fontWeightBrand:String(e.fontWeightBrand??n.fontWeightBrand),fontWeightMonospace:String(e.fontWeightMonospace??n.fontWeightMonospace),stylesheets:e.stylesheets??[...n.stylesheets],rootFontSize:e.rootFontSize??n.rootFontSize,typeRatio:e.typeRatio??n.typeRatio,spaceRatio:e.spaceRatio??n.spaceRatio,borderRadius:e.borderRadius??n.borderRadius,viewportMin:e.viewportMin??n.viewportMin,viewportMax:e.viewportMax??n.viewportMax,angles:f,semanticHues:m,variantChroma:h,lightness:a,chroma:S,semanticMappings:x,anchors:u,roles:c,dataPalette:y,dataRamps:A};return e.pageBackgroundImage!==void 0&&($.pageBackgroundImage=e.pageBackgroundImage),e.pageBackgroundImageOpacity!==void 0&&($.pageBackgroundImageOpacity=e.pageBackgroundImageOpacity),e.boxBackgroundImage!==void 0&&($.boxBackgroundImage=e.boxBackgroundImage),e.boxBackgroundImageOpacity!==void 0&&($.boxBackgroundImageOpacity=e.boxBackgroundImageOpacity),e.vellumOpacity!==void 0&&($.vellumOpacity=e.vellumOpacity),e.vellumBackgroundImage!==void 0&&($.vellumBackgroundImage=e.vellumBackgroundImage),e.vellumBackgroundImageOpacity!==void 0&&($.vellumBackgroundImageOpacity=e.vellumBackgroundImageOpacity),I.set($,new Set(o)),$}function j(n,e){if(!e)return{...n};const c={...n};for(const a of Object.keys(e)){const u=e[a];u!==void 0&&(c[a]=u)}return c}function De(n){const e=[],c=[];let a;try{a=atob(n)}catch{return e.push("Failed to decode Base64 string."),{valid:!1,errors:e,warnings:c}}let u;try{u=JSON.parse(a)}catch{return e.push("Decoded string is not valid JSON."),{valid:!1,errors:e,warnings:c}}if(typeof u!="object"||u===null||Array.isArray(u))return e.push("Theme must be a JSON object."),{valid:!1,errors:e,warnings:c};const s=u;"seedColor"in s&&(typeof s.seedColor!="string"?e.push("seedColor must be a string."):v(s.seedColor)||e.push(`seedColor is not a valid CSS color: "${s.seedColor}". Accepted forms: ${T}.`));for(const t of["fontBody","fontHeadings","fontBrand","fontMonospace"])t in s&&typeof s[t]!="string"&&e.push(`${t} must be a string.`);const l=["normal","bold","lighter","bolder"],f=["inherit","initial","unset","revert","revert-layer"];for(const t of["fontWeightBody","fontWeightHeadings","fontWeightBrand","fontWeightMonospace"])if(t in s){const o=s[t];if(typeof o=="number")(o<1||o>1e3)&&e.push(`${t} numeric value must be 1\u20131000 (got ${o}).`);else if(typeof o=="string"){const r=l.includes(o)||f.includes(o);if(/^\d+$/.test(o)){const p=Number(o);(p<1||p>1e3)&&e.push(`${t} numeric value must be 1\u20131000 (got "${o}").`)}else r||c.push(`${t} = "${o}" is not a standard font-weight value.`)}else e.push(`${t} must be a string or number.`)}"stylesheets"in s&&(Array.isArray(s.stylesheets)?s.stylesheets.some(t=>typeof t!="string")&&e.push("Every entry in stylesheets must be a string."):e.push("stylesheets must be an array of strings."));const m=[["rootFontSize",1,100],["borderRadius",0,10],["viewportMin",100,1e4],["viewportMax",200,1e4]];for(const[t,o,r]of m)if(t in s)if(typeof s[t]!="number"||!isFinite(s[t]))e.push(`${t} must be a finite number.`);else{const i=s[t];o!==void 0&&i<o&&e.push(`${t} must be \u2265 ${o} (got ${i}).`),r!==void 0&&i>r&&c.push(`${t} = ${i} is unusually high (max ${r}).`)}for(const t of["typeRatio","spaceRatio"])if(t in s)if(typeof s[t]!="number"||!isFinite(s[t]))e.push(`${t} must be a finite number.`);else{const o=s[t];o<=1&&e.push(`${t} must be > 1 (got ${o}).`);const r=t==="typeRatio"?1.3:2;o>r&&c.push(`${t} = ${o} is very high (max ${r}); scales may be extreme.`)}if("viewportMin"in s&&"viewportMax"in s){const t=s.viewportMin,o=s.viewportMax;typeof t=="number"&&typeof o=="number"&&t>=o&&e.push(`viewportMin (${t}) must be less than viewportMax (${o}).`)}if("angles"in s)if(typeof s.angles!="object"||s.angles===null)e.push("angles must be an object.");else{const t=s.angles;for(const o of["analogous","complementary","splitComplementary","triadic"])if(o in t)if(typeof t[o]!="number"||!isFinite(t[o]))e.push(`angles.${o} must be a finite number.`);else{const r=t[o];(r<0||r>360)&&c.push(`angles.${o} = ${r} is outside 0\u2013360.`)}}if("semanticHues"in s)if(typeof s.semanticHues!="object"||s.semanticHues===null)e.push("semanticHues must be an object.");else{const t=s.semanticHues;for(const o of["danger","success","warning"])if(o in t)if(typeof t[o]!="number"||!isFinite(t[o]))e.push(`semanticHues.${o} must be a finite number.`);else{const r=t[o];(r<0||r>360)&&c.push(`semanticHues.${o} = ${r} is outside 0\u2013360.`)}}if("variantChroma"in s)if(typeof s.variantChroma!="object"||s.variantChroma===null)e.push("variantChroma must be an object.");else{const t=s.variantChroma;for(const o of Ae)if(o in t)if(typeof t[o]!="number"||!isFinite(t[o]))e.push(`variantChroma["${o}"] must be a finite number.`);else{const r=t[o];r<0&&e.push(`variantChroma["${o}"] must be \u2265 0 (got ${r}).`),r>O&&c.push(`variantChroma["${o}"] = ${r} exceeds ${O}.`)}}if("lightness"in s)if(typeof s.lightness!="object"||s.lightness===null)e.push("lightness must be an object.");else{const t=s.lightness;for(const o of R)if(o in t)if(typeof t[o]!="number"||!isFinite(t[o]))e.push(`lightness.${o} must be a finite number.`);else{const r=t[o];(r<0||r>1)&&e.push(`lightness.${o} must be 0\u20131 (got ${r}).`)}}if("chroma"in s)if(typeof s.chroma!="object"||s.chroma===null)e.push("chroma must be an object.");else{const t=s.chroma;for(const o of N)if(o in t){const r=t[o];if(typeof r!="object"||r===null){e.push(`chroma.${o} must be { min, max }.`);continue}const i=r;(typeof i.min!="number"||i.min<0)&&e.push(`chroma.${o}.min must be \u2265 0.`),(typeof i.max!="number"||i.max<0||i.max>O)&&e.push(`chroma.${o}.max must be 0\u2013${O}.`),typeof i.min=="number"&&typeof i.max=="number"&&i.min>i.max&&e.push(`chroma.${o}.min (${i.min}) must be \u2264 max (${i.max}).`)}}if("anchors"in s)if(typeof s.anchors!="object"||s.anchors===null||Array.isArray(s.anchors))e.push("anchors must be an object of named colors.");else for(const[t,o]of Object.entries(s.anchors))if(g(t)?C.test(t)||e.push(`anchors: "${t}" is not a valid anchor name; use a lowercase slug (letters, digits, hyphens).`):e.push(`anchors: "${t}" is a reserved JavaScript property name and cannot be an anchor name; rename it.`),F.includes(t)&&e.push(`anchors: "${t}" collides with a reserved color source name.`),typeof o=="string")v(o)||e.push(`anchors.${t} is not a valid CSS color: "${o}". Accepted forms: ${T}.`);else if(typeof o=="object"&&o!==null&&!Array.isArray(o)){const r=o;typeof r.color!="string"&&e.push(`anchors.${t} must declare its base "color".`);for(const[i,p]of Object.entries(r))g(i)?i!=="color"&&!C.test(i)&&e.push(`anchors.${t}: "${i}" is not a valid slot name; use a lowercase slug.`):e.push(`anchors.${t}: "${i}" is a reserved JavaScript property name and cannot be a slot name; rename it.`),typeof p!="string"?e.push(`anchors.${t}.${i} must be a CSS color string.`):v(p)||e.push(`anchors.${t}.${i} is not a valid CSS color: "${p}". Accepted forms: ${T}.`)}else e.push(`anchors.${t} must be a color string or a { color, \u2026slots } object.`);const h=new Map;if("anchors"in s&&typeof s.anchors=="object"&&s.anchors!==null&&!Array.isArray(s.anchors))for(const[t,o]of Object.entries(s.anchors))h.set(t,o);function S(t,o){const r=G(t);if(!r){e.push(`${o} "${t}" is not valid anchor syntax; use anchor:<name> or anchor:<name>.<slot>.`);return}if(!h.has(r.name)){const i=[...h.keys()];e.push(`${o} references undeclared anchor "${r.name}". Declared anchors: ${i.length>0?i.join(", "):"(none)"}.`);return}if(r.slot!==void 0){const i=h.get(r.name);if(typeof(typeof i=="object"&&i!==null&&!Array.isArray(i)?i[r.slot]:void 0)!="string"){const d=typeof i=="object"&&i!==null?Object.keys(i).filter(b=>b!=="color"):[];e.push(`${o} references unknown slot "${r.slot}" on anchor "${r.name}". Declared slots: ${d.length>0?d.join(", "):"(none)"}.`)}}}function y(t,o){if(t.startsWith("anchor:")){S(t,o);return}F.includes(t)||e.push(`${o} must be one of: ${F.join(", ")}, or a declared anchor as anchor:<name> / anchor:<name>.<slot>.`)}function A(t,o){if(t.startsWith("anchor:")){S(t,o);return}v(t)||e.push(`${o} is not a valid CSS color: "${t}". Accepted forms: ${T}, or a declared anchor as anchor:<name> / anchor:<name>.<slot>.`)}if("dataPalette"in s)if(typeof s.dataPalette!="object"||s.dataPalette===null||Array.isArray(s.dataPalette))e.push("dataPalette must be an object with series1\u2013series8 color values.");else{const t=s.dataPalette;for(const[o,r]of Object.entries(t)){if(!B.includes(o)){e.push(`dataPalette.${o} is not a series slot; expected one of: ${B.join(", ")}.`);continue}typeof r!="string"?e.push(`dataPalette.${o} must be a CSS color or anchor reference string.`):A(r,`dataPalette.${o}`)}}if("dataRamps"in s)if(typeof s.dataRamps!="object"||s.dataRamps===null||Array.isArray(s.dataRamps))e.push("dataRamps must be an object of named ramp declarations.");else for(const[t,o]of Object.entries(s.dataRamps)){if(g(t)?C.test(t)||e.push(`dataRamps: "${t}" is not a valid ramp name; use a lowercase slug (letters, digits, hyphens).`):e.push(`dataRamps: "${t}" is a reserved JavaScript property name and cannot be a ramp name; rename it.`),typeof o!="object"||o===null||Array.isArray(o)){e.push(`dataRamps.${t} must be a sequential or diverging ramp object.`);continue}const r=o,i=r.steps??ge;if((typeof i!="number"||!Number.isInteger(i)||i<L||i>P)&&e.push(`dataRamps.${t}.steps must be an integer from ${L} through ${P}.`),r.type==="sequential"){typeof r.source!="string"?e.push(`dataRamps.${t}.source must be a CSS color or anchor reference string.`):A(r.source,`dataRamps.${t}.source`);const p=r.lightnessStart??.95,d=r.lightnessEnd??.25;for(const[b,x]of[["lightnessStart",p],["lightnessEnd",d]])(typeof x!="number"||!Number.isFinite(x)||x<0||x>1)&&e.push(`dataRamps.${t}.${b} must be a finite number from 0 through 1.`);typeof p=="number"&&typeof d=="number"&&p<=d?e.push(`dataRamps.${t}.lightnessStart must be greater than lightnessEnd.`):typeof p=="number"&&Number.isFinite(p)&&typeof d=="number"&&Number.isFinite(d)&&typeof i=="number"&&Number.isInteger(i)&&i>=L&&i<=P&&(p-d)/(i-1)<=U&&e.push(`dataRamps.${t} lightness bounds must leave more than ${U} lightness between serialized stops.`);continue}if(r.type==="diverging"){typeof i=="number"&&Number.isInteger(i)&&i%2===0&&e.push(`dataRamps.${t}.steps must be odd so the neutral is the exact midpoint.`);for(const p of["start","end"])typeof r[p]!="string"?e.push(`dataRamps.${t}.${p} must be a CSS color or anchor reference string.`):A(r[p],`dataRamps.${t}.${p}`);"neutral"in r&&(typeof r.neutral!="string"?e.push(`dataRamps.${t}.neutral must be a CSS color or anchor reference string.`):A(r.neutral,`dataRamps.${t}.neutral`));continue}e.push(`dataRamps.${t}.type must be "sequential" or "diverging".`)}if("dataPalette"in s&&e.length===0){const t=Be(Z,s),o={};let r=!0;for(const i of B){const p=he(t.dataPalette[i],t.anchors);if(!p){r=!1;break}o[i]=p}if(r)for(const i of me(o))c.push(`dataPalette.${i.series[0]} and dataPalette.${i.series[1]} are only ${i.distance.toFixed(4)} apart under ${i.simulation} simulation (minimum ${i.threshold.toFixed(4)}); also distinguish the series with labels, shapes, or patterns.`)}if("roles"in s)if(typeof s.roles!="object"||s.roles===null||Array.isArray(s.roles))e.push("roles must be an object.");else{const t=s.roles;for(const[o,r]of Object.entries(t)){if(!D.includes(o)){e.push(`roles.${o} is not a role; expected one of: ${D.join(", ")}. (The status family is reserved and is not a role.)`);continue}const i=o,p=V[i];if(typeof r=="string"){y(r,`roles.${i}`);continue}if(typeof r!="object"||r===null||Array.isArray(r)){e.push(`roles.${i} must be a color source or a { color, \u2026slots } object.`);continue}const d=r;typeof d.color!="string"&&e.push(`roles.${i} must declare its base "color".`);for(const[b,x]of Object.entries(d)){if(!Object.prototype.hasOwnProperty.call(p,b)||!g(b)){const $=Object.keys(p).filter(k=>k!=="color");e.push(`roles.${i}.${b} is not a slot of the ${i} role. Declared slots: ${$.length>0?$.join(", "):"(none)"}.`);continue}typeof x!="string"?e.push(`roles.${i}.${b} must be a color source string.`):y(x,`roles.${i}.${b}`)}}}if("semanticMappings"in s)if(typeof s.semanticMappings!="object"||s.semanticMappings===null)e.push("semanticMappings must be an object.");else{const t=s.semanticMappings;for(const o of N)if(o in t){const r=t[o];if(typeof r!="object"||r===null){e.push(`semanticMappings.${o} must be { source, lightness }.`);continue}const i=r;typeof i.source!="string"?e.push(`semanticMappings.${o}.source must be a string.`):y(i.source,`semanticMappings.${o}.source`),(typeof i.lightness!="string"||!R.includes(i.lightness))&&e.push(`semanticMappings.${o}.lightness must be one of: ${R.join(", ")}.`)}}for(const t of["pageBackgroundImage","boxBackgroundImage","vellumBackgroundImage"])t in s&&typeof s[t]!="string"&&e.push(`${t} must be a string.`);for(const t of["pageBackgroundImageOpacity","boxBackgroundImageOpacity","vellumOpacity","vellumBackgroundImageOpacity"])if(t in s)if(typeof s[t]!="number"||!isFinite(s[t]))e.push(`${t} must be a finite number.`);else{const o=s[t];(o<0||o>1)&&e.push(`${t} must be 0\u20131 (got ${o}).`)}return{valid:e.length===0,errors:e,warnings:c}}const _e=["anchors","angles","dataPalette","dataRamps","roles","chroma","lightness","semanticHues","semanticMappings","variantChroma"];function Ie(n,e){const c={};for(const[a,u]of Object.entries(n))g(a)&&(c[a]=u);for(const[a,u]of Object.entries(e))u!==void 0&&g(a)&&(c[a]=u);for(const a of _e){const u=n[a],s=e[a];if(u&&typeof u=="object"&&!Array.isArray(u)&&s&&typeof s=="object"&&!Array.isArray(s)){if(a==="anchors"){c[a]=ee(u,s);continue}if(a==="dataRamps"){c[a]=te(u,s);continue}const l={};for(const[f,m]of Object.entries(u))g(f)&&(l[f]=m);for(const[f,m]of Object.entries(s))m!==void 0&&g(f)&&(l[f]=m);c[a]=l}}return c}function Ne(...n){let e={};for(const c of n){if(!c)continue;const a=Oe(c);a&&(e=Ie(e,a))}return K(e)}function oe(n){return n.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r")}const se={borderRadius:.1,fontBody:'"Noto Sans", sans-serif',fontWeightBody:"normal",fontHeadings:'"Inika", serif',fontWeightHeadings:"normal",fontBrand:'"Inika", serif',fontWeightBrand:"normal",fontMonospace:'"JetBrains Mono", monospace',fontWeightMonospace:"normal",seedColor:"oklch(.07 0.15 216)"};function Fe(n){return K({...se,pageBackgroundImage:`url("${oe(n)}")`,pageBackgroundImageOpacity:.5})}function We(n){return K({...se,pageBackgroundImage:`url("${oe(n)}")`,pageBackgroundImageOpacity:.55})}export{C as ANCHOR_SLUG_PATTERN,F as COLOR_SOURCES,ve as DEFAULT_DARK_LIGHTNESS,Re as DEFAULT_DARK_THEME,xe as DEFAULT_LIGHT_LIGHTNESS,Z as DEFAULT_LIGHT_THEME,W as DEFAULT_SEMANTIC_MAPPINGS,R as LIGHTNESS_KEYS,_e as NESTED_THEME_KEYS,D as ROLE_NAMES,$e as ROLE_PAIRED_INK,V as ROLE_TOKEN_PLAN,N as SEMANTIC_COLOR_NAMES,Y as TOKEN_PAIRINGS,Ae as VARIANT_COLOR_SOURCES,We as buildTaprootDarkTheme,Fe as buildTaprootLightTheme,q as compileRoles,K as encodeTheme,Ne as layerThemes,Ie as mergePartials,Be as mergeTheme,G as parseAnchorSource,Oe as parseTheme,de as resolveAnchorColor,he as resolveDataColorSource,Le as semanticToCSS,De as validateTheme};
@@ -0,0 +1 @@
1
+ import{parseCssColor as u,rotateHue as o}from"./color-engine.js";const m=/^anchor:([^.]+)(?:\.(.+))?$/;function s(t){const l=m.exec(t);return l?l[2]===void 0?{name:l[1]}:{name:l[1],slot:l[2]}:null}function h(t,l){const n=t[l.name];if(n===void 0)return null;if(typeof n=="string")return l.slot===void 0?n:null;const a=n[l.slot??"color"];return typeof a=="string"?a:null}function y(t,l,n){const a=s(t);if(!a)return l[t]??null;const c=h(n,a);return c===null?null:u(c)}const p=.6,g=.85;function S(t,l){const{angles:n,semanticHues:a,variantChroma:c}=l,r=e=>c[e]??t.c,i=Math.min(g,Math.max(p,t.l));return{primary:t,"analogous-left":{l:t.l,c:r("analogous-left"),h:o(t.h,-n.analogous)},"analogous-right":{l:t.l,c:r("analogous-right"),h:o(t.h,n.analogous)},complementary:{l:t.l,c:r("complementary"),h:o(t.h,n.complementary)},"split-complementary-left":{l:t.l,c:r("split-complementary-left"),h:o(t.h,n.complementary-n.splitComplementary)},"split-complementary-right":{l:t.l,c:r("split-complementary-right"),h:o(t.h,n.complementary+n.splitComplementary)},"triadic-left":{l:t.l,c:r("triadic-left"),h:o(t.h,n.triadic)},"triadic-right":{l:t.l,c:r("triadic-right"),h:o(t.h,-n.triadic)},danger:{l:i,c:r("danger"),h:o(a.danger,0)},success:{l:i,c:r("success"),h:o(a.success,0)},warning:{l:i,c:r("warning"),h:o(a.warning,0)}}}export{S as computeVariants,s as parseAnchorSource,h as resolveAnchorColor,y as resolveMappingSource};
@@ -9,6 +9,38 @@
9
9
  "name": "--esp-seed-color",
10
10
  "description": "The OKLCH seed color string that drives the entire palette."
11
11
  },
12
+ {
13
+ "name": "--esp-color-series-1",
14
+ "description": "First categorical data-series color."
15
+ },
16
+ {
17
+ "name": "--esp-color-series-2",
18
+ "description": "Second categorical data-series color."
19
+ },
20
+ {
21
+ "name": "--esp-color-series-3",
22
+ "description": "Third categorical data-series color."
23
+ },
24
+ {
25
+ "name": "--esp-color-series-4",
26
+ "description": "Fourth categorical data-series color."
27
+ },
28
+ {
29
+ "name": "--esp-color-series-5",
30
+ "description": "Fifth categorical data-series color."
31
+ },
32
+ {
33
+ "name": "--esp-color-series-6",
34
+ "description": "Sixth categorical data-series color."
35
+ },
36
+ {
37
+ "name": "--esp-color-series-7",
38
+ "description": "Seventh categorical data-series color."
39
+ },
40
+ {
41
+ "name": "--esp-color-series-8",
42
+ "description": "Eighth categorical data-series color."
43
+ },
12
44
 
13
45
  {
14
46
  "name": "--esp-color-primary",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": 1,
2
+ "version": 2,
3
3
  "tokens": [
4
4
  {
5
5
  "name": "--esp-action-menu-background",
@@ -331,6 +331,46 @@
331
331
  "category": "root-theme",
332
332
  "description": ""
333
333
  },
334
+ {
335
+ "name": "--esp-color-series-1",
336
+ "category": "root-theme",
337
+ "description": "First categorical data-series color."
338
+ },
339
+ {
340
+ "name": "--esp-color-series-2",
341
+ "category": "root-theme",
342
+ "description": "Second categorical data-series color."
343
+ },
344
+ {
345
+ "name": "--esp-color-series-3",
346
+ "category": "root-theme",
347
+ "description": "Third categorical data-series color."
348
+ },
349
+ {
350
+ "name": "--esp-color-series-4",
351
+ "category": "root-theme",
352
+ "description": "Fourth categorical data-series color."
353
+ },
354
+ {
355
+ "name": "--esp-color-series-5",
356
+ "category": "root-theme",
357
+ "description": "Fifth categorical data-series color."
358
+ },
359
+ {
360
+ "name": "--esp-color-series-6",
361
+ "category": "root-theme",
362
+ "description": "Sixth categorical data-series color."
363
+ },
364
+ {
365
+ "name": "--esp-color-series-7",
366
+ "category": "root-theme",
367
+ "description": "Seventh categorical data-series color."
368
+ },
369
+ {
370
+ "name": "--esp-color-series-8",
371
+ "category": "root-theme",
372
+ "description": "Eighth categorical data-series color."
373
+ },
334
374
  {
335
375
  "name": "--esp-color-shadow",
336
376
  "category": "root-theme",
@@ -2455,5 +2495,13 @@
2455
2495
  "category": "root-theme",
2456
2496
  "description": ""
2457
2497
  }
2498
+ ],
2499
+ "patterns": [
2500
+ {
2501
+ "template": "--esp-color-ramp-<name>-<step>",
2502
+ "pattern": "^--esp-color-ramp-[a-z][a-z0-9-]*-(?:[1-9]|1[01])$",
2503
+ "category": "root-theme",
2504
+ "description": "A generated data-ramp stop. <name> is a declared dataRamps slug and <step> is 1 through that ramp's configured step count (maximum 11)."
2505
+ }
2458
2506
  ]
2459
2507
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taprootio/espalier",
3
- "version": "2.17.0",
3
+ "version": "2.19.0",
4
4
  "packageManager": "bun@1.3.12",
5
5
  "description": "Espalier — a themeable, accessible, framework-agnostic, enterprise-grade design system built on web standards and love.",
6
6
  "customElements": "custom-elements.json",
@@ -266,6 +266,10 @@
266
266
  "types": "./dist/shared/theme.d.ts",
267
267
  "import": "./dist/shared/theme.js"
268
268
  },
269
+ "./shared/data-colors": {
270
+ "types": "./dist/shared/data-colors.d.ts",
271
+ "import": "./dist/shared/data-colors.js"
272
+ },
269
273
  "./shared/font-helpers": {
270
274
  "types": "./dist/shared/font-helpers.d.ts",
271
275
  "import": "./dist/shared/font-helpers.js"