@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.
- package/CHANGELOG.md +59 -0
- package/custom-elements.json +16929 -13949
- package/dist/box/esp-box.d.ts +10 -0
- package/dist/box/esp-box.js +4 -4
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -1
- package/dist/page/esp-page.d.ts +23 -7
- package/dist/page/esp-page.js +37 -10
- package/dist/root/esp-root.d.ts +24 -1
- package/dist/root/esp-root.js +3 -3
- package/dist/row/esp-row.d.ts +64 -0
- package/dist/row/esp-row.js +67 -0
- package/dist/section/esp-section.d.ts +55 -0
- package/dist/section/esp-section.js +22 -0
- package/dist/shared/data-colors.d.ts +21 -0
- package/dist/shared/data-colors.js +1 -1
- package/dist/shared/esp-element-base.d.ts +3 -3
- package/dist/shared/esp-element-base.js +1 -1
- package/dist/shared/scale-engine.js +1 -1
- package/dist/shared/style-fragments.js +6 -6
- package/dist/shared/theme.d.ts +92 -18
- package/dist/shared/theme.js +1 -1
- package/dist/shared/variant-engine.js +1 -1
- package/dist/stack/esp-stack.d.ts +62 -0
- package/dist/stack/esp-stack.js +56 -0
- package/espalier.token-manifest.json +89 -1
- package/package.json +29 -1
|
@@ -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
|
|
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`,
|
|
47
|
-
* fixed status family
|
|
48
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
-
`,
|
|
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-
|
|
54
|
-
--_esp-intent-border-color: oklch(from var(--esp-color-
|
|
55
|
-
--_esp-intent-color: var(--esp-color-
|
|
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{
|
|
57
|
+
`;export{a as alignAttributeTextInheritance,i as disabledControl,p as focusRing,d as intentSurfaceTokens,l as srOnly};
|
package/dist/shared/theme.d.ts
CHANGED
|
@@ -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
|
|
39
|
+
* A geometric color-theory variant **or** a fixed status family.
|
|
40
40
|
*
|
|
41
|
-
* Geometric variants are derived by rotating the seed hue;
|
|
42
|
-
*
|
|
43
|
-
*
|
|
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
|
|
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
|
|
179
|
-
*
|
|
180
|
-
*
|
|
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
|
|
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
|
|
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
|
|
595
|
-
*
|
|
596
|
-
*
|
|
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
|
*
|