@taprootio/espalier 3.0.1 → 3.2.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.
@@ -0,0 +1,64 @@
1
+ import { EspalierElementBase } from "../shared/esp-element-base.js";
2
+ import type { EspalierAlign, EspalierGap } from "../stack/esp-stack.js";
3
+ declare const ROW_JUSTIFIES: readonly ["start", "center", "end", "between"];
4
+ /** A main-axis distribution accepted by `esp-row`. */
5
+ export type EspalierJustify = (typeof ROW_JUSTIFIES)[number];
6
+ export { ROW_JUSTIFIES };
7
+ /**
8
+ * A horizontal flow: children sit in a wrapping row with a themed gap —
9
+ * "row, wrap, gap Y" without writing flexbox (ADR-010's micro half).
10
+ *
11
+ * ```html
12
+ * <esp-row gap="small" justify="between">
13
+ * <span>Yoga · 60 min</span>
14
+ * <esp-button label="Book"></esp-button>
15
+ * </esp-row>
16
+ * ```
17
+ *
18
+ * Rows wrap by default, so a button row or badge cluster degrades to
19
+ * multiple lines at narrow widths instead of overflowing; set `nowrap`
20
+ * for toolbars that must stay on one line. Cross-axis alignment
21
+ * defaults to `center` — the icon-beside-label, text-beside-button
22
+ * case a row exists for.
23
+ *
24
+ * @customElement esp-row
25
+ * @slot - The row children.
26
+ * @csspart row - The flex row.
27
+ * @cssprop --esp-row-gap - Overrides the gap between children. Defaults to the `gap` attribute's step, `normal` when unset.
28
+ * @docPageTitle Row
29
+ * @docUrl /components/row
30
+ * @menuGroup Structure
31
+ * @menuIcon layout
32
+ */
33
+ export declare class EspalierRow extends EspalierElementBase {
34
+ /**
35
+ * Gap between children as a space-scale step name
36
+ * (`none`, `tiny`, `small`, `normal`, `medium`, `big`, `large`,
37
+ * `huge`).
38
+ * @default "normal"
39
+ */
40
+ gap: EspalierGap;
41
+ /**
42
+ * Cross-axis alignment of children: `start`, `center`, `end`, or
43
+ * `stretch`.
44
+ * @default "center"
45
+ */
46
+ align: EspalierAlign;
47
+ /**
48
+ * Main-axis distribution: `start`, `center`, `end`, or `between`.
49
+ * @default "start"
50
+ */
51
+ justify: EspalierJustify;
52
+ /**
53
+ * Keeps every child on one line instead of wrapping.
54
+ * @default false
55
+ */
56
+ nowrap: boolean;
57
+ protected render(): import("lit-html").TemplateResult<1>;
58
+ static styles: import("lit").CSSResult[];
59
+ }
60
+ declare global {
61
+ interface HTMLElementTagNameMap {
62
+ "esp-row": EspalierRow;
63
+ }
64
+ }
@@ -0,0 +1,67 @@
1
+ var o=function(p,r,s,a){var i=arguments.length,e=i<3?r:a===null?a=Object.getOwnPropertyDescriptor(r,s):a,l;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")e=Reflect.decorate(p,r,s,a);else for(var f=p.length-1;f>=0;f--)(l=p[f])&&(e=(i<3?l(e):i>3?l(r,s,e):l(r,s))||e);return i>3&&e&&Object.defineProperty(r,s,e),e};import{css as u,html as g}from"lit";import{customElement as c,property as n}from"lit/decorators.js";import{EspalierElementBase as w}from"../shared/esp-element-base.js";const h=["start","center","end","between"];let t=class extends w{constructor(){super(...arguments),this.gap="normal",this.align="center",this.justify="start",this.nowrap=!1}render(){return g`
2
+ <div class="esp-row" part="row">
3
+ <slot></slot>
4
+ </div>
5
+ `}};t.styles=[...w.styles,u`
6
+ :host {
7
+ display: block;
8
+ --_esp-row-gap: var(--esp-size-normal);
9
+ --_esp-row-align: center;
10
+ --_esp-row-justify: flex-start;
11
+ }
12
+
13
+ :host([gap="none"]) {
14
+ --_esp-row-gap: 0px;
15
+ }
16
+ :host([gap="tiny"]) {
17
+ --_esp-row-gap: var(--esp-size-tiny);
18
+ }
19
+ :host([gap="small"]) {
20
+ --_esp-row-gap: var(--esp-size-small);
21
+ }
22
+ :host([gap="medium"]) {
23
+ --_esp-row-gap: var(--esp-size-medium);
24
+ }
25
+ :host([gap="big"]) {
26
+ --_esp-row-gap: var(--esp-size-big);
27
+ }
28
+ :host([gap="large"]) {
29
+ --_esp-row-gap: var(--esp-size-large);
30
+ }
31
+ :host([gap="huge"]) {
32
+ --_esp-row-gap: var(--esp-size-huge);
33
+ }
34
+
35
+ :host([align="start"]) {
36
+ --_esp-row-align: flex-start;
37
+ }
38
+ :host([align="end"]) {
39
+ --_esp-row-align: flex-end;
40
+ }
41
+ :host([align="stretch"]) {
42
+ --_esp-row-align: stretch;
43
+ }
44
+
45
+ :host([justify="center"]) {
46
+ --_esp-row-justify: center;
47
+ }
48
+ :host([justify="end"]) {
49
+ --_esp-row-justify: flex-end;
50
+ }
51
+ :host([justify="between"]) {
52
+ --_esp-row-justify: space-between;
53
+ }
54
+
55
+ .esp-row {
56
+ display: flex;
57
+ flex-direction: row;
58
+ flex-wrap: wrap;
59
+ gap: var(--esp-row-gap, var(--_esp-row-gap));
60
+ align-items: var(--_esp-row-align);
61
+ justify-content: var(--_esp-row-justify);
62
+ }
63
+
64
+ :host([nowrap]) .esp-row {
65
+ flex-wrap: nowrap;
66
+ }
67
+ `],o([n({reflect:!0,useDefault:!0})],t.prototype,"gap",void 0),o([n({reflect:!0,useDefault:!0})],t.prototype,"align",void 0),o([n({reflect:!0,useDefault:!0})],t.prototype,"justify",void 0),o([n({type:Boolean,reflect:!0})],t.prototype,"nowrap",void 0),t=o([c("esp-row")],t);export{t as EspalierRow,h as ROW_JUSTIFIES};
@@ -0,0 +1,55 @@
1
+ import { EspalierElementBase } from "../shared/esp-element-base.js";
2
+ /**
3
+ * A full-bleed page band with a centered content well — the natural host
4
+ * for a theme zone.
5
+ *
6
+ * `esp-section` paints the local `--esp-color-background` edge to edge
7
+ * and centers its content in a capped well. It carries no card identity:
8
+ * no raised surface, no border radius, no shadow. Give it a `context`
9
+ * (see the theming guide, `/guides/color/theming` on the docs site) and
10
+ * the band renders the zone's complete token table with nothing to
11
+ * neutralize:
12
+ *
13
+ * ```html
14
+ * <esp-section context="inverted">
15
+ * <h2>The reversed band</h2>
16
+ * <p>Everything in here renders on the zone's tokens, and the band
17
+ * spans its container edge to edge.</p>
18
+ * </esp-section>
19
+ * ```
20
+ *
21
+ * Inside `esp-page kind="site"` sections stack full-width and their
22
+ * wells share the page's `--esp-page-well-max-width`, so section
23
+ * content, the header, and the footer all align on one column. Outside
24
+ * a page the well defaults to the same width and the band fills
25
+ * whatever container it is given.
26
+ *
27
+ * The vertical rhythm defaults to the theme's fluid `--esp-size-section`
28
+ * step. For edge-to-edge content (a full-bleed hero image) clear both
29
+ * the well cap and the inline breathing room —
30
+ * `--esp-section-max-width: none; --esp-section-padding-inline: 0;` —
31
+ * and drop `--esp-section-padding-block` too when the media should
32
+ * meet the band's edges; the band's zone behavior is unaffected.
33
+ *
34
+ * @customElement esp-section
35
+ * @slot - The section's content, centered in the well.
36
+ * @csspart section - The full-bleed band.
37
+ * @csspart well - The centered content well.
38
+ * @cssprop --esp-section-background - The band's background. Defaults to the local `--esp-color-background`, which inside a `context` zone is the zone's canvas.
39
+ * @cssprop --esp-section-max-width - The content well's cap. Defaults to `var(--esp-page-well-max-width, 72rem)`; `none` lets content span the band.
40
+ * @cssprop --esp-section-padding-block - Vertical rhythm above and below the well. Defaults to `var(--esp-size-section)`.
41
+ * @cssprop --esp-section-padding-inline - Horizontal breathing room inside the band at narrow viewports. Defaults to `var(--esp-size-medium)`.
42
+ * @docPageTitle Section
43
+ * @docUrl /components/section
44
+ * @menuGroup Structure
45
+ * @menuIcon layout
46
+ */
47
+ export declare class EspalierSection extends EspalierElementBase {
48
+ protected render(): import("lit-html").TemplateResult<1>;
49
+ static styles: import("lit").CSSResult[];
50
+ }
51
+ declare global {
52
+ interface HTMLElementTagNameMap {
53
+ "esp-section": EspalierSection;
54
+ }
55
+ }
@@ -0,0 +1,22 @@
1
+ var a=function(t,i,s,n){var o=arguments.length,e=o<3?i:n===null?n=Object.getOwnPropertyDescriptor(i,s):n,l;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")e=Reflect.decorate(t,i,s,n);else for(var c=t.length-1;c>=0;c--)(l=t[c])&&(e=(o<3?l(e):o>3?l(i,s,e):l(i,s))||e);return o>3&&e&&Object.defineProperty(i,s,e),e};import{css as d,html as m}from"lit";import{customElement as v}from"lit/decorators.js";import{EspalierElementBase as p}from"../shared/esp-element-base.js";let r=class extends p{render(){return m`
2
+ <div class="esp-section" part="section">
3
+ <div class="esp-section-well" part="well">
4
+ <slot></slot>
5
+ </div>
6
+ </div>
7
+ `}};r.styles=[...p.styles,d`
8
+ :host {
9
+ display: block;
10
+ }
11
+
12
+ .esp-section {
13
+ background: var(--esp-section-background, var(--esp-color-background));
14
+ padding-block: var(--esp-section-padding-block, var(--esp-size-section));
15
+ padding-inline: var(--esp-section-padding-inline, var(--esp-size-medium));
16
+ }
17
+
18
+ .esp-section-well {
19
+ max-inline-size: var(--esp-section-max-width, var(--esp-page-well-max-width, 72rem));
20
+ margin-inline: auto;
21
+ }
22
+ `],r=a([v("esp-section")],r);export{r as EspalierSection};
@@ -52,6 +52,27 @@ export interface DataPaletteIssue {
52
52
  * the threshold.
53
53
  */
54
54
  export declare function auditDataPalette(palette: Readonly<DataPalette>, threshold?: number): DataPaletteIssue[];
55
+ /** Vision conditions covered by {@link auditColorDistances}: normal plus every CVD simulation. */
56
+ export type VisionCondition = ColorVisionSimulation | "normal";
57
+ /** One too-close pair found by {@link auditColorDistances}. */
58
+ export interface ColorDistanceIssue {
59
+ /** The two entry names, in input order. */
60
+ pair: [string, string];
61
+ /** The vision condition under which the pair collapses. */
62
+ simulation: VisionCondition;
63
+ /** Post-simulation OKLab distance between the pair. */
64
+ distance: number;
65
+ /** The threshold the distance fell below. */
66
+ threshold: number;
67
+ }
68
+ /**
69
+ * Audit an arbitrary named color set for pairwise distinguishability
70
+ * under normal vision and every CVD simulation (ESP0172 — the intent
71
+ * collision check). Values must be concrete CSS colors, like
72
+ * {@link auditDataPalette}'s — resolve anchors first; the palette-shaped
73
+ * audit remains the categorical-series wrapper.
74
+ */
75
+ export declare function auditColorDistances(entries: ReadonlyArray<readonly [string, string]>, threshold?: number, conditions?: readonly VisionCondition[]): ColorDistanceIssue[];
55
76
  /** Minimum supported number of colors in a generated data ramp. */
56
77
  export declare const MIN_DATA_RAMP_STEPS = 3;
57
78
  /** Maximum supported number of colors in a generated data ramp. */
@@ -1 +1 @@
1
- import{clampChannel as L,gamutMapToSRGB as E,linearRgbToOklab as I,oklabToOklch as C,oklchToOklab as S,oklchToSRGB as w,parseCssColor as M,serializeOklch as b,srgbToLinearRGB as N}from"./color-engine.js";const u=["series1","series2","series3","series4","series5","series6","series7","series8"],y={series1:"#000000",series2:"#E69F00",series3:"#56B4E9",series4:"#009E73",series5:"#F0E442",series6:"#0072B2",series7:"#D55E00",series8:"#CC79A7"},k=["deuteranopia","protanopia","tritanopia"],O=.07,P={protanopia:[[.152286,1.052583,-.204868],[.114503,.786281,.099216],[-.003882,-.048116,1.051998]],deuteranopia:[[.367322,.860646,-.227968],[.280085,.672501,.047413],[-.01182,.04294,.968881]],tritanopia:[[1.255528,-.076749,-.178779],[-.078411,.930809,.147602],[.004733,.691367,.3039]]};function x(e,t){const r=w(E(e)),o=N(r),n=s=>L(s[0]*o.r+s[1]*o.g+s[2]*o.b);return{r:n(t[0]),g:n(t[1]),b:n(t[2])}}function q(e,t){return Math.hypot(e.L-t.L,e.a-t.a,e.b-t.b)}function p(e,t){const r=M(e);if(!r)throw new TypeError(`${t} is not a valid opaque CSS color: "${e}".`);return r}function G(e,t=O){if(!Number.isFinite(t)||t<0)throw new RangeError("Data-palette distance threshold must be a finite number \u2265 0.");const r=new Map;for(const n of u)r.set(n,p(e[n],`dataPalette.${n}`));const o=[];for(const n of k){const s=new Map;for(const a of u)s.set(a,I(x(r.get(a),P[n])));for(let a=0;a<u.length;a+=1)for(let i=a+1;i<u.length;i+=1){const l=u[a],c=u[i],g=q(s.get(l),s.get(c));g<t&&o.push({series:[l,c],simulation:n,distance:g,threshold:t})}}return o}const A=3,f=11,m=7,T=1e-6,v="oklch(0.96 0 0)";function _(e,t){if(!Number.isInteger(e)||e<A||e>f)throw new RangeError(`Data-ramp steps must be an integer from ${A} through ${f}.`);if(t&&e%2===0)throw new RangeError("Diverging data-ramp steps must be odd so the neutral is the midpoint.")}function d(e,t){if(!Number.isFinite(e)||e<0||e>1)throw new RangeError(`${t} must be a finite number from 0 through 1.`)}function U(e,t={}){const r=p(e,"Sequential ramp source"),o=t.steps??m,n=t.lightnessStart??.95,s=t.lightnessEnd??.25;if(_(o,!1),d(n,"Sequential ramp lightnessStart"),d(s,"Sequential ramp lightnessEnd"),n<=s)throw new RangeError("Sequential ramp lightnessStart must be greater than lightnessEnd.");if((n-s)/(o-1)<=T)throw new RangeError(`Sequential ramp lightness bounds must leave more than ${T} lightness between serialized stops.`);return Array.from({length:o},(a,i)=>{const l=i/(o-1),c=n+(s-n)*l;return b(E({l:c,c:r.c,h:r.h}))})}function R(e,t,r){return{L:e.L+(t.L-e.L)*r,a:e.a+(t.a-e.a)*r,b:e.b+(t.b-e.b)*r}}function B(e,t,r={}){const o=p(e,"Diverging ramp start"),n=p(t,"Diverging ramp end"),s=p(r.neutral??v,"Diverging ramp neutral"),a=r.steps??m;_(a,!0);const i=(a-1)/2,l=S(o),c=S(s),g=S(n);return Array.from({length:a},(F,h)=>{if(h===i)return b(E(s));const D=h<i?R(l,c,h/i):R(c,g,(h-i)/i);return b(E(C(D)))})}export{k as COLOR_VISION_SIMULATIONS,u as DATA_SERIES_KEYS,y as DEFAULT_DATA_PALETTE,m as DEFAULT_DATA_RAMP_STEPS,v as DEFAULT_DIVERGING_NEUTRAL,f as MAX_DATA_RAMP_STEPS,O as MIN_DATA_COLOR_DISTANCE,T as MIN_DATA_RAMP_LIGHTNESS_STEP,A as MIN_DATA_RAMP_STEPS,G as auditDataPalette,B as generateDivergingRamp,U as generateSequentialRamp};
1
+ import{clampChannel as x,gamutMapToSRGB as m,linearRgbToOklab as S,oklabToOklch as k,oklchToOklab as b,oklchToSRGB as P,parseCssColor as F,serializeOklch as E,srgbToLinearRGB as q}from"./color-engine.js";const p=["series1","series2","series3","series4","series5","series6","series7","series8"],G={series1:"#000000",series2:"#E69F00",series3:"#56B4E9",series4:"#009E73",series5:"#F0E442",series6:"#0072B2",series7:"#D55E00",series8:"#CC79A7"},A=["deuteranopia","protanopia","tritanopia"],T=.07,_={protanopia:[[.152286,1.052583,-.204868],[.114503,.786281,.099216],[-.003882,-.048116,1.051998]],deuteranopia:[[.367322,.860646,-.227968],[.280085,.672501,.047413],[-.01182,.04294,.968881]],tritanopia:[[1.255528,-.076749,-.178779],[-.078411,.930809,.147602],[.004733,.691367,.3039]]};function d(e,t){const s=P(m(e)),a=q(s),n=o=>x(o[0]*a.r+o[1]*a.g+o[2]*a.b);return{r:n(t[0]),g:n(t[1]),b:n(t[2])}}function R(e,t){return Math.hypot(e.L-t.L,e.a-t.a,e.b-t.b)}function g(e,t){const s=F(e);if(!s)throw new TypeError(`${t} is not a valid opaque CSS color: "${e}".`);return s}function U(e,t=T){if(!Number.isFinite(t)||t<0)throw new RangeError("Data-palette distance threshold must be a finite number \u2265 0.");const s=new Map;for(const n of p)s.set(n,g(e[n],`dataPalette.${n}`));const a=[];for(const n of A){const o=new Map;for(const r of p)o.set(r,S(d(s.get(r),_[n])));for(let r=0;r<p.length;r+=1)for(let i=r+1;i<p.length;i+=1){const l=p[r],c=p[i],u=R(o.get(l),o.get(c));u<t&&a.push({series:[l,c],simulation:n,distance:u,threshold:t})}}return a}const v=[[1,0,0],[0,1,0],[0,0,1]];function B(e,t=T,s=["normal",...A]){if(!Number.isFinite(t)||t<0)throw new RangeError("Color distance threshold must be a finite number \u2265 0.");const a=e.map(([r,i])=>[r,g(i,r)]),n=[],o=s.map(r=>[r,r==="normal"?v:_[r]]);for(const[r,i]of o){const l=a.map(([,c])=>S(d(c,i)));for(let c=0;c<e.length;c+=1)for(let u=c+1;u<e.length;u+=1){const f=R(l[c],l[u]);f<t&&n.push({pair:[e[c][0],e[u][0]],simulation:r,distance:f,threshold:t})}}return n}const D=3,I=11,L=7,C=1e-6,$="oklch(0.96 0 0)";function M(e,t){if(!Number.isInteger(e)||e<D||e>I)throw new RangeError(`Data-ramp steps must be an integer from ${D} through ${I}.`);if(t&&e%2===0)throw new RangeError("Diverging data-ramp steps must be odd so the neutral is the midpoint.")}function w(e,t){if(!Number.isFinite(e)||e<0||e>1)throw new RangeError(`${t} must be a finite number from 0 through 1.`)}function V(e,t={}){const s=g(e,"Sequential ramp source"),a=t.steps??L,n=t.lightnessStart??.95,o=t.lightnessEnd??.25;if(M(a,!1),w(n,"Sequential ramp lightnessStart"),w(o,"Sequential ramp lightnessEnd"),n<=o)throw new RangeError("Sequential ramp lightnessStart must be greater than lightnessEnd.");if((n-o)/(a-1)<=C)throw new RangeError(`Sequential ramp lightness bounds must leave more than ${C} lightness between serialized stops.`);return Array.from({length:a},(r,i)=>{const l=i/(a-1),c=n+(o-n)*l;return E(m({l:c,c:s.c,h:s.h}))})}function N(e,t,s){return{L:e.L+(t.L-e.L)*s,a:e.a+(t.a-e.a)*s,b:e.b+(t.b-e.b)*s}}function z(e,t,s={}){const a=g(e,"Diverging ramp start"),n=g(t,"Diverging ramp end"),o=g(s.neutral??$,"Diverging ramp neutral"),r=s.steps??L;M(r,!0);const i=(r-1)/2,l=b(a),c=b(o),u=b(n);return Array.from({length:r},(f,h)=>{if(h===i)return E(m(o));const O=h<i?N(l,c,h/i):N(c,u,(h-i)/i);return E(m(k(O)))})}export{A as COLOR_VISION_SIMULATIONS,p as DATA_SERIES_KEYS,G as DEFAULT_DATA_PALETTE,L as DEFAULT_DATA_RAMP_STEPS,$ as DEFAULT_DIVERGING_NEUTRAL,I as MAX_DATA_RAMP_STEPS,T as MIN_DATA_COLOR_DISTANCE,C as MIN_DATA_RAMP_LIGHTNESS_STEP,D as MIN_DATA_RAMP_STEPS,B as auditColorDistances,U as auditDataPalette,z as generateDivergingRamp,V as generateSequentialRamp};
@@ -43,9 +43,9 @@ export declare class EspalierElementBase extends LitElement implements SeedColor
43
43
  scheme: "dark" | "light" | "";
44
44
  /**
45
45
  * The element's [intent](/espalier-guides/) — its meaning: `neutral`
46
- * (no pin), `success`, `warning`, or `danger` (each pinned to its
47
- * fixed status family), or `info` (pinned to the complementary
48
- * family). On token-emitting controls a non-neutral intent pins the
46
+ * (no pin), or `success`, `warning`, `danger`, `info` each pinned
47
+ * to its fixed status family (blue for `info`), retunable per theme
48
+ * via `intents`. On token-emitting controls a non-neutral intent pins the
49
49
  * filled-action pair to that family, derived over the governing
50
50
  * zone's theme; class-styled chrome (badges, callouts, status pills)
51
51
  * opts out of inline emission and renders its treatment from CSS
@@ -1,4 +1,4 @@
1
- var h=function(f,e,t,r){var o=arguments.length,s=o<3?e:r===null?r=Object.getOwnPropertyDescriptor(e,t):r,a;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")s=Reflect.decorate(f,e,t,r);else for(var i=f.length-1;i>=0;i--)(a=f[i])&&(s=(o<3?a(s):o>3?a(e,t,s):a(e,t))||s);return o>3&&s&&Object.defineProperty(e,t,s),s};import{css as R,LitElement as C}from"lit";import{property as p,state as S}from"lit/decorators.js";import{subscribeToRootEvent as u}from"./root-event-subscription.js";import{traverseToClosest as b}from"./utilities.js";import{parseCssColor as w,serializeOklch as x,gamutMapToSRGB as P}from"./color-engine.js";import{computeVariants as v}from"../root/helpers/compute-variants.js";import{computeSemanticProperties as m}from"../root/helpers/compute-semantic-properties.js";import{lightnessRampProperties as E}from"../root/helpers/lightness-ramp-properties.js";import{ROLE_NAMES as V,mergeTheme as U,semanticToCSS as A}from"./theme.js";import{alignAttributeTextInheritance as I,focusRing as O}from"./style-fragments.js";import{syncNormalizedAttribute as D}from"./attribute-helpers.js";import{INTENT_VARIANTS as y,normalizeIntentVariant as z}from"./intent-values.js";const k=new WeakMap;class n extends C{constructor(){super(...arguments),this.seedColorBacker="oklch(0.7 0.125 216)",this.espRoot=null,this.contextBacker="",this.scopedTokenOriginalValues=new Map,this.warnedUnknownContexts=new Set,this.pendingInitialThemeRoot=null,this.rootEventSubscriptionsActive=!1,this.subscribedRoot=null,this.rootEventUnsubscribers=[],this.intentBacker="neutral",this.intentEmitsTokens=!0,this.warnedUnknownIntents=new Set,this.correlationId=globalThis.crypto?.randomUUID?.()??Math.random().toString(36),this.scheme="light",this.handleSeedColorChanged=e=>{this.syncRootFromDom(!1)&&(this.seedColor=e.seedColor,this.applyScopedColorTokens())},this.handleSchemeChanged=e=>{!this.syncRootFromDom(!1)||this.scheme===e.scheme||(this.scheme=e.scheme,this.applyScopedColorTokens())},this.handleThemeChanged=()=>{this.syncRootFromDom(!1)&&this.applyScopedColorTokens()},this.handleIconSpriteUrlChanged=()=>{this.syncRootFromDom(!1)&&this.requestUpdate()}}get seedColor(){return this.seedColorBacker}set seedColor(e){this.seedColorBacker=e}focusResolvedElementAfterUpdate(e,t){const r=()=>{const o=e();return o?(o.focus(t),!0):!1};r()||this.updateComplete.then(()=>{r()})}focusShadowElementAfterUpdate(e,t){this.focusResolvedElementAfterUpdate(()=>this.shadowRoot?.querySelector(e),t)}emitValueChanged(e){this.dispatchEvent(new CustomEvent("value-changed",{detail:e,bubbles:!0,composed:!0}))}get intent(){return this.intentBacker}set intent(e){const t=typeof e=="string"?e.trim():"";t!==""&&!y.includes(t)&&!this.warnedUnknownIntents.has(t)&&(this.warnedUnknownIntents.add(t),console.warn(`Espalier intent: "${t}" is not an intent; expected one of ${y.join(", ")}. Treating it as "neutral".`));const r=this.intentBacker,o=z(e);D(this,"intent",o),o!==r&&(this.intentBacker=o,this.requestUpdate("intent",r),this.applyTokensWhenRootReady())}get context(){return this.contextBacker}set context(e){const t=e??"";t!==this.contextBacker&&(this.contextBacker=t,this.applyTokensWhenRootReady(),this.refreshDescendantIntentDerivations())}refreshDescendantIntentDerivations(){const e=new Set,t=o=>{for(const s of Array.from(o.children))r(s)},r=o=>{if(!e.has(o)){if(e.add(o),o instanceof n&&o.applyTokensWhenRootReady(),o.shadowRoot&&t(o.shadowRoot),o instanceof HTMLSlotElement)for(const s of o.assignedElements({flatten:!0}))r(s);t(o)}};t(this),this.shadowRoot&&t(this.shadowRoot)}static flattenedParent(e){return e instanceof Element&&e.assignedSlot?e.assignedSlot:e instanceof ShadowRoot?e.host:e.parentNode}resolveAncestorZoneTheme(e){let t=n.flattenedParent(this);for(;t;){if(t instanceof Element&&t.localName==="esp-root")return null;if(t instanceof n){const r=t.contextBacker.trim();if(r){const o=this.resolveNamedContextTheme(e,r);if(o)return o}}t=n.flattenedParent(t)}return null}connectedCallback(){super.connectedCallback();const e=this.syncRootFromDom(!1);e&&this.subscribeToRootEvents(e)}disconnectedCallback(){super.disconnectedCallback(),this.unsubscribeFromRootEvents(),this.clearScopedTokens(),this.espRoot=null}firstUpdated(e){this.syncRootFromDom(!0),this.applyTokensWhenRootReady()}syncRootFromDom(e){const t=b(this,"esp-root");if(!t){if(this.espRoot&&this.clearScopedTokens(),this.unsubscribeFromRootEvents(),this.espRoot=null,!e)return null;throw new Error("No esp-root ancestor found. Espalier components must be placed inside an <esp-root> element.")}const r=t!==this.espRoot,s=t.scheme==="dark"?"dark":"light",a=this.scheme!==s,i=this.seedColor!==t.seedColor;return this.espRoot=t,r&&this.isConnected&&this.subscribeToRootEvents(t),(r||a||i)&&(this.scheme=s,this.seedColor=t.seedColor,this.applyTokensWhenRootReady()),r&&this.requestUpdate(),t}applyTokensWhenRootReady(){const e=this.espRoot;if(e&&(!e.hasUpdated||e.isUpdatePending)){this.applyTokensAfterInitialRootUpdate(e);return}this.applyScopedColorTokens()}applyTokensAfterInitialRootUpdate(e){if(this.pendingInitialThemeRoot===e)return;this.pendingInitialThemeRoot=e;const t=e.updateComplete;if(!t){this.pendingInitialThemeRoot=null,this.applyScopedColorTokens();return}t.then(()=>{this.pendingInitialThemeRoot===e&&(this.pendingInitialThemeRoot=null),!(!this.isConnected||this.espRoot!==e)&&(this.scheme=e.scheme==="dark"?"dark":"light",this.seedColor=e.seedColor,this.applyScopedColorTokens())})}subscribeToRootEvents(e){this.rootEventSubscriptionsActive&&this.subscribedRoot===e||(this.unsubscribeFromRootEvents(),this.subscribedRoot=e,this.rootEventUnsubscribers=[u(e,"seed-color-changed",this.handleSeedColorChanged),u(e,"scheme-changed",this.handleSchemeChanged),u(e,"theme-changed",this.handleThemeChanged),u(e,"icon-sprite-url-changed",this.handleIconSpriteUrlChanged)],this.rootEventSubscriptionsActive=!0)}unsubscribeFromRootEvents(){if(this.rootEventSubscriptionsActive){for(const e of this.rootEventUnsubscribers)e();this.rootEventUnsubscribers=[],this.subscribedRoot=null,this.rootEventSubscriptionsActive=!1}}traverseToClosest(e){return b(this,e)}applyScopedColorTokens(){if(!this.espRoot)return;const e=this.intentEmitsTokens?n.intentColorSources[this.intentBacker]:"";if(!e&&!this.context.trim()){this.clearScopedTokens();return}const t=this.espRoot.activeTheme;if(!t){this.clearScopedTokens();return}const r=this.resolveContextTheme(t),o=r.theme,s=w(o.seedColor);if(!s)return;const a=v(s,o),i={};if(r.applied&&(Object.assign(i,m(o,a)),Object.assign(i,E(o))),e){let c=o;r.applied||(c=this.resolveAncestorZoneTheme(t)??o);const l=c===o?a:v(s,c),T=m(c,l,{effectiveSource:(g,d)=>n.semanticActionTokens.has(g)?e:d});i["--esp-color-primary"]=x(P(l[e]));for(const g of n.semanticActionTokens){const d=A(g);i[d]=T[d]}}this.applyScopedTokenProperties(i)}resolveContextTheme(e){const t=this.context.trim();if(!t)return{applied:!1,theme:e};const r=this.resolveNamedContextTheme(e,t);return r?{applied:!0,theme:r}:(this.warnedUnknownContexts.has(t)||(this.warnedUnknownContexts.add(t),console.warn(`Espalier context: "${t}" is not defined by the active theme; inheriting root tokens.`)),{applied:!1,theme:e})}resolveNamedContextTheme(e,t){const r=e.contexts&&Object.prototype.hasOwnProperty.call(e.contexts,t)?e.contexts[t]:void 0;if(!r)return null;let o=k.get(e);const s=o?.get(t);if(s)return s;const a={};for(const c of V){const l=r[c];l!==void 0&&(a[c]=l)}const i=U(e,{lightness:r.lightness,roles:a});return o||(o=new Map,k.set(e,o)),o.set(t,i),i}applyScopedTokenProperties(e){for(const t of this.scopedTokenOriginalValues.keys())t in e||this.restoreScopedTokenProperty(t);for(const[t,r]of Object.entries(e))this.setScopedTokenProperty(t,r)}setScopedTokenProperty(e,t){const r=this.scopedTokenOriginalValues.get(e);if(r){const o=this.style.getPropertyValue(e),s=this.style.getPropertyPriority(e);(o!==r.generatedValue||s!==r.generatedPriority)&&(r.originalValue=o.length?o:null,r.originalPriority=s),r.generatedValue=t,r.generatedPriority=""}else{const o=this.style.getPropertyValue(e);this.scopedTokenOriginalValues.set(e,{generatedPriority:"",generatedValue:t,originalPriority:this.style.getPropertyPriority(e),originalValue:o.length?o:null})}this.style.setProperty(e,t)}restoreScopedTokenProperty(e){const t=this.scopedTokenOriginalValues.get(e);if(!t)return;const r=this.style.getPropertyValue(e),o=this.style.getPropertyPriority(e);(r!==t.generatedValue||o!==t.generatedPriority)&&(t.originalValue=r.length?r:null,t.originalPriority=o),t.originalValue===null?this.style.removeProperty(e):this.style.setProperty(e,t.originalValue,t.originalPriority),this.scopedTokenOriginalValues.delete(e)}clearScopedTokens(){for(const e of[...this.scopedTokenOriginalValues.keys()])this.restoreScopedTokenProperty(e)}}n.intentColorSources={danger:"danger",info:"complementary",neutral:"",success:"success",warning:"warning"},n.semanticActionTokens=new Set(["actionBackground","actionText"]),n.styles=[O(".esp-field:focus-within","--esp-field-focus-shadow"),I,R`
1
+ var h=function(f,e,t,r){var o=arguments.length,s=o<3?e:r===null?r=Object.getOwnPropertyDescriptor(e,t):r,a;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")s=Reflect.decorate(f,e,t,r);else for(var i=f.length-1;i>=0;i--)(a=f[i])&&(s=(o<3?a(s):o>3?a(e,t,s):a(e,t))||s);return o>3&&s&&Object.defineProperty(e,t,s),s};import{css as R,LitElement as C}from"lit";import{property as p,state as S}from"lit/decorators.js";import{subscribeToRootEvent as u}from"./root-event-subscription.js";import{traverseToClosest as b}from"./utilities.js";import{parseCssColor as w,serializeOklch as x,gamutMapToSRGB as P}from"./color-engine.js";import{computeVariants as v}from"../root/helpers/compute-variants.js";import{computeSemanticProperties as m}from"../root/helpers/compute-semantic-properties.js";import{lightnessRampProperties as E}from"../root/helpers/lightness-ramp-properties.js";import{ELEVATION_PROPERTIES as V}from"./scale-engine.js";import{ROLE_NAMES as U,mergeTheme as I,semanticToCSS as A}from"./theme.js";import{alignAttributeTextInheritance as O,focusRing as D}from"./style-fragments.js";import{syncNormalizedAttribute as N}from"./attribute-helpers.js";import{INTENT_VARIANTS as y,normalizeIntentVariant as z}from"./intent-values.js";const k=new WeakMap;class n extends C{constructor(){super(...arguments),this.seedColorBacker="oklch(0.7 0.125 216)",this.espRoot=null,this.contextBacker="",this.scopedTokenOriginalValues=new Map,this.warnedUnknownContexts=new Set,this.pendingInitialThemeRoot=null,this.rootEventSubscriptionsActive=!1,this.subscribedRoot=null,this.rootEventUnsubscribers=[],this.intentBacker="neutral",this.intentEmitsTokens=!0,this.warnedUnknownIntents=new Set,this.correlationId=globalThis.crypto?.randomUUID?.()??Math.random().toString(36),this.scheme="light",this.handleSeedColorChanged=e=>{this.syncRootFromDom(!1)&&(this.seedColor=e.seedColor,this.applyScopedColorTokens())},this.handleSchemeChanged=e=>{!this.syncRootFromDom(!1)||this.scheme===e.scheme||(this.scheme=e.scheme,this.applyScopedColorTokens())},this.handleThemeChanged=()=>{this.syncRootFromDom(!1)&&this.applyScopedColorTokens()},this.handleIconSpriteUrlChanged=()=>{this.syncRootFromDom(!1)&&this.requestUpdate()}}get seedColor(){return this.seedColorBacker}set seedColor(e){this.seedColorBacker=e}focusResolvedElementAfterUpdate(e,t){const r=()=>{const o=e();return o?(o.focus(t),!0):!1};r()||this.updateComplete.then(()=>{r()})}focusShadowElementAfterUpdate(e,t){this.focusResolvedElementAfterUpdate(()=>this.shadowRoot?.querySelector(e),t)}emitValueChanged(e){this.dispatchEvent(new CustomEvent("value-changed",{detail:e,bubbles:!0,composed:!0}))}get intent(){return this.intentBacker}set intent(e){const t=typeof e=="string"?e.trim():"";t!==""&&!y.includes(t)&&!this.warnedUnknownIntents.has(t)&&(this.warnedUnknownIntents.add(t),console.warn(`Espalier intent: "${t}" is not an intent; expected one of ${y.join(", ")}. Treating it as "neutral".`));const r=this.intentBacker,o=z(e);N(this,"intent",o),o!==r&&(this.intentBacker=o,this.requestUpdate("intent",r),this.applyTokensWhenRootReady())}get context(){return this.contextBacker}set context(e){const t=e??"";t!==this.contextBacker&&(this.contextBacker=t,this.applyTokensWhenRootReady(),this.refreshDescendantIntentDerivations())}refreshDescendantIntentDerivations(){const e=new Set,t=o=>{for(const s of Array.from(o.children))r(s)},r=o=>{if(!e.has(o)){if(e.add(o),o instanceof n&&o.applyTokensWhenRootReady(),o.shadowRoot&&t(o.shadowRoot),o instanceof HTMLSlotElement)for(const s of o.assignedElements({flatten:!0}))r(s);t(o)}};t(this),this.shadowRoot&&t(this.shadowRoot)}static flattenedParent(e){return e instanceof Element&&e.assignedSlot?e.assignedSlot:e instanceof ShadowRoot?e.host:e.parentNode}resolveAncestorZoneTheme(e){let t=n.flattenedParent(this);for(;t;){if(t instanceof Element&&t.localName==="esp-root")return null;if(t instanceof n){const r=t.contextBacker.trim();if(r){const o=this.resolveNamedContextTheme(e,r);if(o)return o}}t=n.flattenedParent(t)}return null}connectedCallback(){super.connectedCallback();const e=this.syncRootFromDom(!1);e&&this.subscribeToRootEvents(e)}disconnectedCallback(){super.disconnectedCallback(),this.unsubscribeFromRootEvents(),this.clearScopedTokens(),this.espRoot=null}firstUpdated(e){this.syncRootFromDom(!0),this.applyTokensWhenRootReady()}syncRootFromDom(e){const t=b(this,"esp-root");if(!t){if(this.espRoot&&this.clearScopedTokens(),this.unsubscribeFromRootEvents(),this.espRoot=null,!e)return null;throw new Error("No esp-root ancestor found. Espalier components must be placed inside an <esp-root> element.")}const r=t!==this.espRoot,s=t.scheme==="dark"?"dark":"light",a=this.scheme!==s,i=this.seedColor!==t.seedColor;return this.espRoot=t,r&&this.isConnected&&this.subscribeToRootEvents(t),(r||a||i)&&(this.scheme=s,this.seedColor=t.seedColor,this.applyTokensWhenRootReady()),r&&this.requestUpdate(),t}applyTokensWhenRootReady(){const e=this.espRoot;if(e&&(!e.hasUpdated||e.isUpdatePending)){this.applyTokensAfterInitialRootUpdate(e);return}this.applyScopedColorTokens()}applyTokensAfterInitialRootUpdate(e){if(this.pendingInitialThemeRoot===e)return;this.pendingInitialThemeRoot=e;const t=e.updateComplete;if(!t){this.pendingInitialThemeRoot=null,this.applyScopedColorTokens();return}t.then(()=>{this.pendingInitialThemeRoot===e&&(this.pendingInitialThemeRoot=null),!(!this.isConnected||this.espRoot!==e)&&(this.scheme=e.scheme==="dark"?"dark":"light",this.seedColor=e.seedColor,this.applyScopedColorTokens())})}subscribeToRootEvents(e){this.rootEventSubscriptionsActive&&this.subscribedRoot===e||(this.unsubscribeFromRootEvents(),this.subscribedRoot=e,this.rootEventUnsubscribers=[u(e,"seed-color-changed",this.handleSeedColorChanged),u(e,"scheme-changed",this.handleSchemeChanged),u(e,"theme-changed",this.handleThemeChanged),u(e,"icon-sprite-url-changed",this.handleIconSpriteUrlChanged)],this.rootEventSubscriptionsActive=!0)}unsubscribeFromRootEvents(){if(this.rootEventSubscriptionsActive){for(const e of this.rootEventUnsubscribers)e();this.rootEventUnsubscribers=[],this.subscribedRoot=null,this.rootEventSubscriptionsActive=!1}}traverseToClosest(e){return b(this,e)}applyScopedColorTokens(){if(!this.espRoot)return;const e=this.intentEmitsTokens?n.intentColorSources[this.intentBacker]:"";if(!e&&!this.context.trim()){this.clearScopedTokens();return}const t=this.espRoot.activeTheme;if(!t){this.clearScopedTokens();return}const r=this.resolveContextTheme(t),o=r.theme,s=w(o.seedColor);if(!s)return;const a=v(s,o),i={};if(r.applied&&(Object.assign(i,m(o,a)),Object.assign(i,E(o)),Object.assign(i,V)),e){let c=o;r.applied||(c=this.resolveAncestorZoneTheme(t)??o);const l=c===o?a:v(s,c),T=m(c,l,{effectiveSource:(g,d)=>n.semanticActionTokens.has(g)?e:d});i["--esp-color-primary"]=x(P(l[e]));for(const g of n.semanticActionTokens){const d=A(g);i[d]=T[d]}}this.applyScopedTokenProperties(i)}resolveContextTheme(e){const t=this.context.trim();if(!t)return{applied:!1,theme:e};const r=this.resolveNamedContextTheme(e,t);return r?{applied:!0,theme:r}:(this.warnedUnknownContexts.has(t)||(this.warnedUnknownContexts.add(t),console.warn(`Espalier context: "${t}" is not defined by the active theme; inheriting root tokens.`)),{applied:!1,theme:e})}resolveNamedContextTheme(e,t){const r=e.contexts&&Object.prototype.hasOwnProperty.call(e.contexts,t)?e.contexts[t]:void 0;if(!r)return null;let o=k.get(e);const s=o?.get(t);if(s)return s;const a={};for(const c of U){const l=r[c];l!==void 0&&(a[c]=l)}const i=I(e,{lightness:r.lightness,roles:a});return o||(o=new Map,k.set(e,o)),o.set(t,i),i}applyScopedTokenProperties(e){for(const t of this.scopedTokenOriginalValues.keys())t in e||this.restoreScopedTokenProperty(t);for(const[t,r]of Object.entries(e))this.setScopedTokenProperty(t,r)}setScopedTokenProperty(e,t){const r=this.scopedTokenOriginalValues.get(e);if(r){const o=this.style.getPropertyValue(e),s=this.style.getPropertyPriority(e);(o!==r.generatedValue||s!==r.generatedPriority)&&(r.originalValue=o.length?o:null,r.originalPriority=s),r.generatedValue=t,r.generatedPriority=""}else{const o=this.style.getPropertyValue(e);this.scopedTokenOriginalValues.set(e,{generatedPriority:"",generatedValue:t,originalPriority:this.style.getPropertyPriority(e),originalValue:o.length?o:null})}this.style.setProperty(e,t)}restoreScopedTokenProperty(e){const t=this.scopedTokenOriginalValues.get(e);if(!t)return;const r=this.style.getPropertyValue(e),o=this.style.getPropertyPriority(e);(r!==t.generatedValue||o!==t.generatedPriority)&&(t.originalValue=r.length?r:null,t.originalPriority=o),t.originalValue===null?this.style.removeProperty(e):this.style.setProperty(e,t.originalValue,t.originalPriority),this.scopedTokenOriginalValues.delete(e)}clearScopedTokens(){for(const e of[...this.scopedTokenOriginalValues.keys()])this.restoreScopedTokenProperty(e)}}n.intentColorSources={danger:"danger",info:"info",neutral:"",success:"success",warning:"warning"},n.semanticActionTokens=new Set(["actionBackground","actionText"]),n.styles=[D(".esp-field:focus-within","--esp-field-focus-shadow"),O,R`
2
2
  :host {
3
3
 
4
4
  --_esp-field-resolved-hover-bg: var(
@@ -1 +1 @@
1
- const c=["tiny","small","normal","medium","big","large","huge"],T=2,$=1.125,d=1.3333333333333333,_=[["tiny","small"],["small","normal"],["normal","medium"],["medium","big"],["big","large"],["large","huge"]],g=.375,y={rootFontSize:16,typeRatio:1.25,spaceRatio:1.618,borderRadius:.2,viewportMin:320,viewportMax:1200};function l(o,t=4){const e=10**t;return Math.round(o*e)/e}function f(o,t,e,s,n){if(Math.abs(t-o)<1e-4)return`${l(o)}rem`;const m=(t-o)*e*100/(n-s),p=o-m*s/(100*e);return`clamp(${l(o)}rem, ${l(p)}rem + ${l(m)}vw, ${l(t)}rem)`}function A(o){const t={};for(let e=0;e<c.length;e++){const s=e-2,n=1.125*o.typeRatio**s;t[c[e]]={min:n,max:n*d}}return t}function M(o){const t={};for(let e=0;e<c.length;e++){const s=g*o.spaceRatio**e;t[c[e]]={min:s,max:s*d}}return t}function z(o){const t={},{rootFontSize:e,viewportMin:s,viewportMax:n,borderRadius:m}=o,p=16,a=e/p,E=A(o);for(const r of c){const{min:i,max:S}=E[r];t[`--esp-type-${r}`]=f(i*a,S*a,p,s,n)}const u=M(o);for(const r of c){const{min:i,max:S}=u[r];t[`--esp-size-${r}`]=f(i*a,S*a,p,s,n)}for(const[r,i]of _)t[`--esp-size-${r}-to-${i}`]=f(u[r].min*a,u[i].max*a,p,s,n);return t["--esp-size-font"]="var(--esp-type-normal)",t["--esp-size-padding"]="var(--esp-size-tiny-to-small)",t["--esp-size-padding-page"]="var(--esp-size-small-to-normal)",t["--esp-size-border-radius"]=`${l(m*a)}rem`,t}export{y as DEFAULT_SCALE_CONFIG,c as STEP_NAMES,z as generateScaleProperties};
1
+ const i=["tiny","small","normal","medium","big","large","huge"],w=2,z=1.125,E=1.3333333333333333,g=[["tiny","small"],["small","normal"],["normal","medium"],["medium","big"],["big","large"],["large","huge"]],h=.375,T={rootFontSize:16,typeRatio:1.25,spaceRatio:1.618,borderRadius:.2,viewportMin:320,viewportMax:1200};function l(o,e=4){const s=10**e;return Math.round(o*s)/s}function S(o,e,s,t,r){if(Math.abs(e-o)<1e-4)return`${l(o)}rem`;const m=(e-o)*s*100/(r-t),p=o-m*t/(100*s);return`clamp(${l(o)}rem, ${l(p)}rem + ${l(m)}vw, ${l(e)}rem)`}function x(o){const e={};for(let s=0;s<i.length;s++){const t=s-2,r=1.125*o.typeRatio**t;e[i[s]]={min:r,max:r*E}}return e}function _(o){const e={};for(let s=0;s<i.length;s++){const t=h*o.spaceRatio**s;e[i[s]]={min:t,max:t*E}}return e}const v={"--esp-shadow-1":"1px 1px 4px var(--esp-color-shadow)","--esp-shadow-2":"0 2px 8px var(--esp-color-shadow)","--esp-shadow-3":"0 6px 20px var(--esp-color-shadow)"};function A(o){const e={},{rootFontSize:s,viewportMin:t,viewportMax:r,borderRadius:m}=o,p=16,a=s/p,f=x(o);for(const n of i){const{min:c,max:d}=f[n];e[`--esp-type-${n}`]=S(c*a,d*a,p,t,r)}const u=_(o);for(const n of i){const{min:c,max:d}=u[n];e[`--esp-size-${n}`]=S(c*a,d*a,p,t,r)}for(const[n,c]of g)e[`--esp-size-${n}-to-${c}`]=S(u[n].min*a,u[c].max*a,p,t,r);return e["--esp-size-font"]="var(--esp-type-normal)",e["--esp-size-padding"]="var(--esp-size-tiny-to-small)",e["--esp-size-padding-page"]="var(--esp-size-small-to-normal)",e["--esp-size-border-radius"]=`${l(m*a)}rem`,e["--esp-measure"]="66ch",e["--esp-measure-wide"]="90ch",e["--esp-size-section"]="clamp(var(--esp-size-big), 7vw, var(--esp-size-huge))",Object.assign(e,v),e["--esp-card-min"]="16rem",e}export{T as DEFAULT_SCALE_CONFIG,v as ELEVATION_PROPERTIES,i as STEP_NAMES,A as generateScaleProperties};
@@ -1,8 +1,8 @@
1
- import{css as o,unsafeCSS as e}from"lit";const l=o`
1
+ import{css as o,unsafeCSS as e}from"lit";const a=o`
2
2
  :host([align]) {
3
3
  text-align: inherit;
4
4
  }
5
- `,a=o`
5
+ `,l=o`
6
6
  .sr-only {
7
7
  position: absolute;
8
8
  width: 1px;
@@ -50,8 +50,8 @@ import{css as o,unsafeCSS as e}from"lit";const l=o`
50
50
  }
51
51
 
52
52
  .intent-info {
53
- --_esp-intent-background: var(--esp-color-link-hover-bg);
54
- --_esp-intent-border-color: oklch(from var(--esp-color-link) var(--esp-l-border) c h);
55
- --_esp-intent-color: var(--esp-color-link);
53
+ --_esp-intent-background: oklch(from var(--esp-color-info) var(--esp-l-raised-2) c h);
54
+ --_esp-intent-border-color: oklch(from var(--esp-color-info) var(--esp-l-border) c h);
55
+ --_esp-intent-color: oklch(from var(--esp-color-info) var(--esp-l-text) c h);
56
56
  }
57
- `;export{l as alignAttributeTextInheritance,i as disabledControl,p as focusRing,d as intentSurfaceTokens,a as srOnly};
57
+ `;export{a as alignAttributeTextInheritance,i as disabledControl,p as focusRing,d as intentSurfaceTokens,l as srOnly};
@@ -36,21 +36,35 @@ export type LightnessKey = "surface" | "raised1" | "raised2" | "raised3" | "rais
36
36
  /** Lightness values (0–1) for every ramp position. */
37
37
  export type LightnessMap = Record<LightnessKey, number>;
38
38
  /**
39
- * A geometric color-theory variant **or** a fixed semantic hue.
39
+ * A geometric color-theory variant **or** a fixed status family.
40
40
  *
41
- * Geometric variants are derived by rotating the seed hue; semantic
42
- * hues (`danger`, `success`, `warning`) use fixed angles defined
43
- * in `semanticHues`.
41
+ * Geometric variants are derived by rotating the seed hue; the status
42
+ * families (`danger`, `success`, `warning`, `info`) use fixed hue
43
+ * angles from `semanticHues` — or a theme's `intents` override, which
44
+ * retunes a family with a declared absolute color (ESP0172).
44
45
  */
45
- export type ColorSource = "primary" | "analogous-left" | "analogous-right" | "complementary" | "split-complementary-left" | "split-complementary-right" | "triadic-left" | "triadic-right" | "danger" | "success" | "warning";
46
+ export type ColorSource = "primary" | "analogous-left" | "analogous-right" | "complementary" | "split-complementary-left" | "split-complementary-right" | "triadic-left" | "triadic-right" | "danger" | "success" | "warning" | "info";
46
47
  /**
47
- * Non-primary color sources — the ten variants that can carry
48
+ * Non-primary color sources — the eleven variants that can carry
48
49
  * independent chroma overrides.
49
50
  *
50
51
  * Primary is excluded because its chroma is always the seed
51
52
  * color's chroma.
52
53
  */
53
54
  export type VariantColorSource = Exclude<ColorSource, "primary">;
55
+ /**
56
+ * The fixed-meaning status families (ADR-004): the colors
57
+ * `intent="danger|success|warning|info"` and the same-named mapping
58
+ * sources resolve to. A theme may retune each with `intents`.
59
+ */
60
+ export type StatusColorSource = "danger" | "success" | "warning" | "info";
61
+ /**
62
+ * Status-family color overrides, by family (ESP0172). Values are an
63
+ * `anchor:<name>[.<slot>]` reference or a supported color form
64
+ * (`#rrggbb`, `#rgb`, `rgb()`, `hsl()`, `oklch()`); see
65
+ * {@link EspalierTheme.intents}.
66
+ */
67
+ export type ThemeIntents = Partial<Record<StatusColorSource, string>>;
54
68
  /**
55
69
  * The object form of an anchor: a required base `color` plus named
56
70
  * sub-slots — per-family variants the way designers already specify
@@ -175,9 +189,11 @@ export type SemanticMappings = Record<SemanticColorName, SemanticMapping>;
175
189
  * automatically (see {@link ROLE_PAIRED_INK})
176
190
  * - `structure` — borders and shadows
177
191
  *
178
- * `status` is deliberately absent: the danger/success/warning families
179
- * carry meaning through fixed hues (ADR-004) and are not a brand
180
- * decision.
192
+ * `status` is deliberately absent as a role: the four status families
193
+ * (`danger`, `success`, `warning`, `info`) carry reserved *meanings*
194
+ * that no role may reassign (ADR-004, as amended). Their *colors* are
195
+ * a brand decision — retune a family with the theme's `intents` field
196
+ * and every derived token follows.
181
197
  */
182
198
  export type RoleName = "canvas" | "ink" | "accent" | "action" | "structure";
183
199
  /**
@@ -190,7 +206,7 @@ export type RoleName = "canvas" | "ink" | "accent" | "action" | "structure";
190
206
  export type RoleSlotName = {
191
207
  canvas: never;
192
208
  ink: "heading";
193
- accent: "text";
209
+ accent: "text" | "hover";
194
210
  action: "ink";
195
211
  structure: never;
196
212
  };
@@ -232,7 +248,10 @@ export declare const ROLE_NAMES: readonly RoleName[];
232
248
  * The `accent` role has no body-text slot on purpose. "Rose is
233
249
  * decorative only" is a structural guarantee here, not a comment: text
234
250
  * that must come from the accent family comes through `accent.text`,
235
- * the deepened variant a designer picked for legibility.
251
+ * the deepened variant a designer picked for legibility. `accent.hover`
252
+ * lets a hover change *family*, not just lightness — "plum link, rose
253
+ * hover" is an ordinary brand rule (ESP0172); absent, `linkHover` keeps
254
+ * following `accent.text` exactly as before.
236
255
  */
237
256
  export declare const ROLE_TOKEN_PLAN: Readonly<Record<RoleName, Readonly<Record<string, ReadonlyArray<[SemanticColorName, LightnessKey]>>>>>;
238
257
  /**
@@ -356,7 +375,7 @@ export interface EspalierTheme {
356
375
  /** Triadic offset from seed (default 120). */
357
376
  triadic: number;
358
377
  };
359
- /** Fixed hue angles for danger / success / warning colors. */
378
+ /** Fixed hue angles for the danger / success / warning / info families. */
360
379
  semanticHues: {
361
380
  /** Danger hue angle (default 27, red-orange). */
362
381
  danger: number;
@@ -364,7 +383,46 @@ export interface EspalierTheme {
364
383
  success: number;
365
384
  /** Warning hue angle (default 90, yellow-green). */
366
385
  warning: number;
386
+ /** Info hue angle (default 244, blue). */
387
+ info: number;
367
388
  };
389
+ /**
390
+ * Status-family color overrides, by family (ESP0172).
391
+ *
392
+ * Each entry retunes one of the fixed status families — the colors
393
+ * `intent="danger|success|warning|info"` and the same-named mapping
394
+ * sources resolve to — with a declared color: an
395
+ * `anchor:<name>[.<slot>]` reference or a supported color form —
396
+ * `#rrggbb`, `#rgb`, `rgb()`, `hsl()`, or `oklch()`. CSS keywords
397
+ * such as `red` are not parsed.
398
+ *
399
+ * An override is **absolute**, like an anchor: it carries its own
400
+ * lightness, chroma, and hue, and `semanticHues` / `variantChroma`
401
+ * are ignored for that family. Overrides retune a family; they do
402
+ * not reassign its meaning (ADR-004): give `danger` your brand's
403
+ * red, but never point one family at another — a family name as a
404
+ * value (`intents.success: "danger"`) is rejected by validation; a
405
+ * color choice that creates a rendered collision is flagged by the
406
+ * distance warning, while a merely unusual — but separated — color
407
+ * passes silently. `validateTheme` warns when two
408
+ * status families — or a family and the action color — become hard
409
+ * to distinguish as rendered on controls, and its advice is
410
+ * counterfactual-tested per pair: it recommends retuning, widening
411
+ * the chroma band, or moving the lightness stop only when that
412
+ * change can actually separate the pair, and says "then retune"
413
+ * when a band or stop change merely makes a retune possible rather
414
+ * than separating the pair on its own. The built-in warning is
415
+ * normal-vision-only; status meanings need redundant cues (icons,
416
+ * labels) regardless, though lightness and chroma separation chosen
417
+ * here can still genuinely help color-vision-deficient users — see
418
+ * ADR-004's amendment and the data-color guidance.
419
+ *
420
+ * @example
421
+ * ```jsonc
422
+ * "intents": { "info": "anchor:sky", "danger": "#700007" }
423
+ * ```
424
+ */
425
+ intents: ThemeIntents;
368
426
  /**
369
427
  * Per-variant chroma overrides (OKLCH chroma, 0–0.4).
370
428
  *
@@ -489,10 +547,12 @@ export declare const TOKEN_PAIRINGS: Readonly<Record<"actionText", TokenPairing>
489
547
  export declare const SEMANTIC_COLOR_NAMES: readonly SemanticColorName[];
490
548
  /** Valid color-source identifiers for {@link SemanticMapping.source}. */
491
549
  export declare const COLOR_SOURCES: readonly ColorSource[];
550
+ /** The four fixed status families, matching {@link StatusColorSource}. */
551
+ export declare const STATUS_COLOR_SOURCES: readonly StatusColorSource[];
492
552
  /**
493
553
  * Non-primary color sources that support independent chroma overrides.
494
554
  *
495
- * Matches the ten entries in {@link VariantColorSource}.
555
+ * Matches the eleven entries in {@link VariantColorSource}.
496
556
  */
497
557
  export declare const VARIANT_COLOR_SOURCES: readonly VariantColorSource[];
498
558
  /** Valid keys for the lightness ramp. */
@@ -568,7 +628,8 @@ export declare function parseTheme(base64: string): PartialTheme | null;
568
628
  /**
569
629
  * Encode a {@link PartialTheme} as a Base64 JSON string.
570
630
  *
571
- * The inverse of {@link parseTheme}.
631
+ * The inverse of {@link parseTheme}. Unicode-safe: theme strings (font
632
+ * names, anchor slugs' display metadata) may carry any code point.
572
633
  *
573
634
  * @param partial The partial theme to encode.
574
635
  * @returns A Base64-encoded JSON string.
@@ -587,13 +648,26 @@ export declare function encodeTheme(partial: PartialTheme): string;
587
648
  * @returns A new fully-resolved {@link EspalierTheme}.
588
649
  */
589
650
  export declare function mergeTheme(defaults: EspalierTheme, overrides: PartialTheme): EspalierTheme;
651
+ /**
652
+ * Validate a single encoded theme partial.
653
+ *
654
+ * Structural grammar aside, the advisory checks (the status-collision
655
+ * warning and chroma-band provenance) resolve the partial against
656
+ * {@link DEFAULT_LIGHT_THEME}. A partial written for the dark side of a
657
+ * pair should be validated through {@link validateThemePair}, which
658
+ * audits it against the dark defaults instead.
659
+ */
590
660
  export declare function validateTheme(base64: string): ThemeValidationResult;
591
661
  /**
592
662
  * Validate the light and dark partials that form one scheme-paired theme.
593
663
  *
594
- * Each partial first passes through {@link validateTheme}. Once both are
595
- * structurally valid, their context-name sets must match exactly so a stable
596
- * `context` attribute cannot silently change meaning when the scheme changes.
664
+ * Each side runs the same validation as {@link validateTheme}, with one
665
+ * scheme-aware difference: the dark partial's advisory checks resolve
666
+ * against {@link DEFAULT_DARK_THEME}, so a collision that only appears
667
+ * through the dark ramp is caught. Once both sides are structurally
668
+ * valid, their context-name sets must match exactly so a stable
669
+ * `context` attribute cannot silently change meaning when the scheme
670
+ * changes.
597
671
  */
598
672
  export declare function validateThemePair(lightBase64: string, darkBase64: string): ThemeValidationResult;
599
673
  /**
@@ -601,7 +675,7 @@ export declare function validateThemePair(lightBase64: string, darkBase64: strin
601
675
  * and therefore need key-by-key merging instead of wholesale
602
676
  * replacement when combining two {@link PartialTheme} objects.
603
677
  */
604
- export declare const NESTED_THEME_KEYS: readonly ["anchors", "angles", "contexts", "dataPalette", "dataRamps", "roles", "chroma", "lightness", "semanticHues", "semanticMappings", "variantChroma"];
678
+ export declare const NESTED_THEME_KEYS: readonly ["anchors", "angles", "contexts", "dataPalette", "dataRamps", "intents", "roles", "chroma", "lightness", "semanticHues", "semanticMappings", "variantChroma"];
605
679
  /**
606
680
  * Deep-merge two {@link PartialTheme} objects.
607
681
  *