@taprootio/espalier 3.1.0 → 3.3.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 +53 -0
- package/custom-elements.json +5656 -2169
- package/dist/box/esp-box.d.ts +10 -0
- package/dist/box/esp-box.js +4 -4
- package/dist/cli/espalier.js +2 -0
- package/dist/cli/theme-check.js +2 -0
- package/dist/index.d.ts +6 -2
- 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 +40 -1
- package/dist/root/esp-root.js +3 -3
- package/dist/root/helpers/compute-theme-properties.js +1 -1
- package/dist/root/helpers/data-companion-properties.js +1 -0
- 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/esp-element-base.js +2 -2
- package/dist/shared/scale-engine.js +1 -1
- package/dist/shared/theme-fit-report.d.ts +94 -4
- package/dist/shared/theme-fit-report.js +1 -1
- package/dist/shared/theme-swatches.d.ts +79 -0
- package/dist/shared/theme-swatches.js +1 -0
- package/dist/shared/theme.d.ts +38 -1
- package/dist/shared/theme.js +1 -1
- package/dist/shared/unsafe-keys.js +1 -0
- package/dist/stack/esp-stack.d.ts +62 -0
- package/dist/stack/esp-stack.js +56 -0
- package/espalier.token-manifest.json +164 -1
- package/package.json +32 -1
package/dist/root/esp-root.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var a=function(d,e,t,o){var l=arguments.length,s=l<3?e:o===null?o=Object.getOwnPropertyDescriptor(e,t):o,r;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")s=Reflect.decorate(d,e,t,o);else for(var n=d.length-1;n>=0;n--)(r=d[n])&&(s=(l<3?r(s):l>3?r(e,t,s):r(e,t))||s);return l>3&&s&&Object.defineProperty(e,t,s),s},h;import{css as
|
|
1
|
+
var a=function(d,e,t,o){var l=arguments.length,s=l<3?e:o===null?o=Object.getOwnPropertyDescriptor(e,t):o,r;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")s=Reflect.decorate(d,e,t,o);else for(var n=d.length-1;n>=0;n--)(r=d[n])&&(s=(l<3?r(s):l>3?r(e,t,s):r(e,t))||s);return l>3&&s&&Object.defineProperty(e,t,s),s},h;import{css as b,html as T,LitElement as C}from"lit";import{customElement as E,property as c}from"lit/decorators.js";import{getEspBus as p}from"../shared/bus-events.js";import{subscribeToRootEvent as x}from"../shared/root-event-subscription.js";import{DEFAULT_LIGHT_THEME as f,DEFAULT_DARK_THEME as y,encodeTheme as v,parseTheme as g,mergeTheme as S}from"../shared/theme.js";import{computeThemeProperties as R}from"./helpers/compute-theme-properties.js";import{registerImageTexture as D,registeredImageTextures as L}from"../shared/image-texture-registry.js";import{registerImageTexture as _,registeredImageTextures as H,BUILT_IN_IMAGE_TEXTURES as M}from"../shared/image-texture-registry.js";let i=h=class extends C{constructor(){super(...arguments),this.schemeBacker="",this.schemeExplicitlySet=!1,this.resolvingDefaultScheme=!1,this.themeEventsReady=!1,this.systemSchemeQuery=null,this.lastSeedColor="",this.resolvedLightTheme={...f},this.resolvedDarkTheme={...y},this.appliedDataRampProperties=new Set,this.documentClickRegistrationActive=!1,this.correlationId=globalThis.crypto?.randomUUID?.()??Math.random().toString(36),this.lightThemeAttr="",this.darkThemeAttr="",this.fontCSSRoot="/css/fonts/",this.fontDefinitionsUrl="",this.googleFontLoading="auto",this.iconSpriteUrl="/assets/icons.svg",this.defaultScheme="light",this.resizeObserver=new ResizeObserver(()=>{p().publish("window-resized",{emWidth:window.innerWidth/parseFloat(getComputedStyle(document.documentElement).fontSize),emHeight:window.innerHeight/parseFloat(getComputedStyle(document.documentElement).fontSize),pxWidth:window.innerWidth,pxHeight:window.innerHeight})}),this.handleSystemSchemeChange=e=>{this.schemeExplicitlySet||this.defaultScheme!=="system"||(this.resolvingDefaultScheme=!0,this.scheme=e.matches?"dark":"light",this.resolvingDefaultScheme=!1)}}static registerTexture(e,t){D(e,t)}static registeredTextures(){return L()}subscribeScoped(e,t){return x(this,e,t)}get lightTheme(){return this.lightThemeAttr?g(this.lightThemeAttr):null}set lightTheme(e){this.lightThemeAttr=e?v(e):""}get darkTheme(){return this.darkThemeAttr?g(this.darkThemeAttr):null}set darkTheme(e){this.darkThemeAttr=e?v(e):""}get scheme(){return this.schemeBacker}set scheme(e){const t=e==="dark"?"dark":e==="light"?"light":"",o=this.schemeBacker,l=this.schemeExplicitlySet;this.resolvingDefaultScheme||(this.schemeExplicitlySet=t.length>0);const s=t||this.resolveDefaultScheme();l!==this.schemeExplicitlySet&&this.isConnected&&this.syncSystemSchemeListener(),s!==o&&(this.schemeBacker=s,this.requestUpdate("scheme",o),this.themeEventsReady&&p().publish("scheme-changed",{scheme:s,correlationId:this.correlationId}))}get seedColor(){return this.activeTheme.seedColor}static extractFamily(e){if(!e)return"";const t=e.match(/^"([^"]+)"|^'([^']+)'/);return t&&(t[1]||t[2])||""}injectGoogleFontLink(e,t,o){const l="data-esp-font",s=`${this.correlationId}-${e}`,r=document.head.querySelector(`link[${l}="${s}"]`);if(this.googleFontLoading==="none"){r?.remove();return}if(r&&t&&r.dataset.espFamily===t&&r.dataset.espWeight===(o??"")||(r?.remove(),!t))return;const n=encodeURIComponent(t),u=o==="bold"?"700":o==="normal"?"400":o,k=u&&u!=="400"?`:wght@${u}`:"",m=document.createElement("link");m.rel="stylesheet",m.href=`https://fonts.googleapis.com/css2?family=${n}${k}&display=swap`,m.setAttribute(l,s),m.dataset.espFamily=t,m.dataset.espWeight=o??"",document.head.appendChild(m)}syncGoogleFontLinks(){this.googleFontLoading==="none"&&this.removeRuntimeGoogleFontLinks();const e=this.activeTheme;this.injectGoogleFontLink("body",h.extractFamily(e.fontBody),e.fontWeightBody),this.injectGoogleFontLink("headings",h.extractFamily(e.fontHeadings),e.fontWeightHeadings),this.injectGoogleFontLink("brand",h.extractFamily(e.fontBrand),e.fontWeightBrand),this.injectGoogleFontLink("monospace",h.extractFamily(e.fontMonospace),e.fontWeightMonospace)}removeRuntimeGoogleFontLinks(){for(const e of document.head.querySelectorAll(`link[data-esp-font^="${this.correlationId}-"]`))e.remove();for(const e of document.head.querySelectorAll(`link[data-esp-font-picker-root="${CSS.escape(this.correlationId)}"]`))e.remove()}get activeTheme(){return this.scheme==="dark"?this.resolvedDarkTheme:this.resolvedLightTheme}resolveThemes(){const e=this.lightThemeAttr?g(this.lightThemeAttr):null;this.resolvedLightTheme=e?S(f,e):{...f};const t=this.darkThemeAttr?g(this.darkThemeAttr):null;this.resolvedDarkTheme=t?S(y,t):{...y}}connectedCallback(){super.connectedCallback(),this.registerDocumentClickBridge(),this.resizeObserver.observe(this),this.hasUpdated&&(this.applyDefaultScheme(),this.syncGoogleFontLinks())}willUpdate(e){const t=["lightThemeAttr","darkThemeAttr"];(!this.lastSeedColor||t.some(n=>e.has(n)))&&(this.resolveThemes(),this.themeEventsReady&&p().publish("theme-changed",{correlationId:this.correlationId}));const l=R(this.activeTheme,this.scheme==="dark"?"dark":"light"),s=new Set(Object.keys(l).filter(n=>n.startsWith("--esp-color-ramp-")));for(const n of this.appliedDataRampProperties)s.has(n)||this.style.removeProperty(n);for(const[n,u]of Object.entries(l))this.style.setProperty(n,u);this.appliedDataRampProperties=s;const r=this.activeTheme.seedColor;this.themeEventsReady&&this.lastSeedColor&&this.lastSeedColor!==r&&p().publish("seed-color-changed",{seedColor:r,correlationId:this.correlationId}),this.lastSeedColor=r,this.themeEventsReady&&e.has("iconSpriteUrl")&&p().publish("icon-sprite-url-changed",{iconSpriteUrl:this.iconSpriteUrl,correlationId:this.correlationId}),this.syncGoogleFontLinks()}firstUpdated(){const e=this.activeTheme.stylesheets;if(e.length)for(const t of e){const o=document.createElement("link");o.rel="stylesheet",o.href=t,document.head.appendChild(o)}if(!document.getElementById("esp-root-base-styles")){const t=document.createElement("style");t.id="esp-root-base-styles",t.textContent=h.lightDomStyles,document.head.appendChild(t)}this.applyDefaultScheme(),this.lastSeedColor=this.activeTheme.seedColor,this.themeEventsReady=!0}updated(e){(e.has("defaultScheme")||e.has("scheme"))&&!this.schemeExplicitlySet&&this.applyDefaultScheme()}disconnectedCallback(){this.unregisterDocumentClickBridge(),this.systemSchemeQuery?.removeEventListener("change",this.handleSystemSchemeChange),this.systemSchemeQuery=null,this.resizeObserver.disconnect(),this.removeRuntimeGoogleFontLinks(),super.disconnectedCallback()}registerDocumentClickBridge(){this.documentClickRegistrationActive||(this.documentClickRegistrationActive=!0,h.connectedRootCount+=1,h.connectedRootCount===1&&document.addEventListener("click",h.handleDocumentClick))}unregisterDocumentClickBridge(){this.documentClickRegistrationActive&&(this.documentClickRegistrationActive=!1,h.connectedRootCount=Math.max(0,h.connectedRootCount-1),h.connectedRootCount===0&&document.removeEventListener("click",h.handleDocumentClick))}applyDefaultScheme(){this.schemeExplicitlySet||(this.resolvingDefaultScheme=!0,this.scheme=this.resolveDefaultScheme(),this.resolvingDefaultScheme=!1,this.syncSystemSchemeListener())}resolveDefaultScheme(){return this.defaultScheme==="dark"||this.defaultScheme==="system"&&window.matchMedia?.("(prefers-color-scheme: dark)").matches?"dark":"light"}syncSystemSchemeListener(){if(this.schemeExplicitlySet||this.defaultScheme!=="system"||!window.matchMedia){this.systemSchemeQuery?.removeEventListener("change",this.handleSystemSchemeChange),this.systemSchemeQuery=null;return}this.systemSchemeQuery||(this.systemSchemeQuery=window.matchMedia("(prefers-color-scheme: dark)"),this.systemSchemeQuery.addEventListener("change",this.handleSystemSchemeChange))}render(){return T`<slot></slot>`}};i.connectedRootCount=0,i.handleDocumentClick=d=>{const e=d.composedPath().filter(t=>t instanceof HTMLElement&&t.tagName==="ESP-POPOVER");p().publish("close-popovers",{source:d.target??void 0,skipPopovers:e})},i.lightDomStyles=`
|
|
2
2
|
|
|
3
3
|
esp-root {
|
|
4
4
|
font-family: var(--esp-font-body);
|
|
@@ -121,8 +121,8 @@ var a=function(d,e,t,o){var l=arguments.length,s=l<3?e:o===null?o=Object.getOwnP
|
|
|
121
121
|
max-width: 100%;
|
|
122
122
|
display: block;
|
|
123
123
|
}
|
|
124
|
-
`,i.styles=
|
|
124
|
+
`,i.styles=b`
|
|
125
125
|
:host {
|
|
126
126
|
display: block;
|
|
127
127
|
}
|
|
128
|
-
`,a([c({attribute:!1})],i.prototype,"correlationId",void 0),a([c({attribute:"light-theme"})],i.prototype,"lightThemeAttr",void 0),a([c({attribute:"dark-theme"})],i.prototype,"darkThemeAttr",void 0),a([c({attribute:"font-css-root",type:String})],i.prototype,"fontCSSRoot",void 0),a([c({attribute:"font-definitions-url",type:String})],i.prototype,"fontDefinitionsUrl",void 0),a([c({attribute:"google-font-loading",type:String})],i.prototype,"googleFontLoading",void 0),a([c({attribute:"icon-sprite-url",type:String})],i.prototype,"iconSpriteUrl",void 0),a([c({attribute:"default-scheme",type:String,reflect:!0})],i.prototype,"defaultScheme",void 0),a([c({attribute:"scheme",type:String,reflect:!0})],i.prototype,"scheme",null),i=h=a([E("esp-root")],i);export{
|
|
128
|
+
`,a([c({attribute:!1})],i.prototype,"correlationId",void 0),a([c({attribute:"light-theme"})],i.prototype,"lightThemeAttr",void 0),a([c({attribute:"dark-theme"})],i.prototype,"darkThemeAttr",void 0),a([c({attribute:!1})],i.prototype,"lightTheme",null),a([c({attribute:!1})],i.prototype,"darkTheme",null),a([c({attribute:"font-css-root",type:String})],i.prototype,"fontCSSRoot",void 0),a([c({attribute:"font-definitions-url",type:String})],i.prototype,"fontDefinitionsUrl",void 0),a([c({attribute:"google-font-loading",type:String})],i.prototype,"googleFontLoading",void 0),a([c({attribute:"icon-sprite-url",type:String})],i.prototype,"iconSpriteUrl",void 0),a([c({attribute:"default-scheme",type:String,reflect:!0})],i.prototype,"defaultScheme",void 0),a([c({attribute:"scheme",type:String,reflect:!0})],i.prototype,"scheme",null),i=h=a([E("esp-root")],i);export{M as BUILT_IN_IMAGE_TEXTURES,i as EspalierRoot,_ as registerImageTexture,H as registeredImageTextures};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{parseCssColor as
|
|
1
|
+
import{parseCssColor as f,serializeOklch as u,gamutMapToSRGB as m}from"../../shared/color-engine.js";import{generateScaleProperties as y}from"../../shared/scale-engine.js";import{DATA_SERIES_KEYS as b,DEFAULT_DATA_PALETTE as k,generateDivergingRamp as v,generateSequentialRamp as B}from"../../shared/data-colors.js";import{resolveDataColorSource as R}from"../../shared/theme.js";import{dataCompanionProperties as O}from"./data-companion-properties.js";import{lightnessRampProperties as w}from"./lightness-ramp-properties.js";import{computeVariants as $}from"./compute-variants.js";import{computeSemanticProperties as E}from"./compute-semantic-properties.js";function I(o){return{rootFontSize:o.rootFontSize,typeRatio:o.typeRatio,spaceRatio:o.spaceRatio,borderRadius:o.borderRadius,viewportMin:o.viewportMin,viewportMax:o.viewportMax}}function x(o,d,g){const i={},a=o.dataPalette??k;for(let r=0;r<b.length;r+=1){const c=b[r],t=f(g(a[c],`dataPalette.${c}`))??d;i[`--esp-color-series-${r+1}`]=u(m(t))}return i}function _(o,d,g){const i=f(o.seedColor);if(!i)return{};const a={};a["--esp-scheme"]=d,a["--esp-seed-color"]=o.seedColor;const r=$(i,o);for(const[e,n]of Object.entries(r))a[`--esp-color-${e}`]=u(m(n));const c=new Set,t=(e,n)=>{const s=typeof e=="string"?R(e,o.anchors):null;if(s)return s;const p=typeof e=="string"?e:String(e);return c.has(p)||(c.add(p),console.warn(`Espalier theme: ${n} color source "${p}" cannot be resolved \u2014 falling back to the seed. Run validateTheme for the declared data colors.`)),o.seedColor};Object.assign(a,x(o,i,t));for(const[e,n]of Object.entries(o.dataRamps??{}))try{(n.type==="sequential"?B(t(n.source,`dataRamps.${e}.source`),{steps:n.steps,lightnessStart:n.lightnessStart,lightnessEnd:n.lightnessEnd}):v(t(n.start,`dataRamps.${e}.start`),t(n.end,`dataRamps.${e}.end`),{steps:n.steps,neutral:n.neutral===void 0?void 0:t(n.neutral,`dataRamps.${e}.neutral`)})).forEach((p,S)=>{a[`--esp-color-ramp-${e}-${S+1}`]=p})}catch(s){console.warn(`Espalier theme: dataRamps.${e} cannot be generated \u2014 ${s instanceof Error?s.message:String(s)}`)}Object.assign(a,w(o));const l=new Set;return Object.assign(a,E(o,r,{trace:g,onUnresolvedSource:e=>{l.has(e)||(l.add(e),console.warn(`Espalier theme: color source "${e}" cannot be resolved \u2014 falling back to the seed. Run validateTheme for the declared anchors.`))}})),Object.assign(a,O(a)),Object.assign(a,y(I(o))),o.fontBody&&(a["--esp-font-body"]=o.fontBody),o.fontHeadings&&(a["--esp-font-headings"]=o.fontHeadings),o.fontBrand&&(a["--esp-font-brand"]=o.fontBrand),o.fontMonospace&&(a["--esp-font-monospace"]=o.fontMonospace),a["--esp-font-weight-body"]=o.fontWeightBody,a["--esp-font-weight-headings"]=o.fontWeightHeadings,a["--esp-font-weight-brand"]=o.fontWeightBrand,a["--esp-font-weight-monospace"]=o.fontWeightMonospace,o.pageBackgroundImage&&(a["--esp-page-background-image"]=o.pageBackgroundImage),o.pageBackgroundImageOpacity!==void 0&&(a["--esp-page-background-image-opacity"]=o.pageBackgroundImageOpacity.toString()),o.boxBackgroundImage&&(a["--esp-box-background-image"]=o.boxBackgroundImage),o.boxBackgroundImageOpacity!==void 0&&(a["--esp-box-background-image-opacity"]=o.boxBackgroundImageOpacity.toString()),o.vellumOpacity!==void 0&&(a["--esp-vellum-opacity"]=o.vellumOpacity.toString()),o.vellumBackgroundImage&&(a["--esp-vellum-background-image"]=o.vellumBackgroundImage),o.vellumBackgroundImageOpacity!==void 0&&(a["--esp-vellum-background-image-opacity"]=o.vellumBackgroundImageOpacity.toString()),a}export{_ as computeThemeProperties,x as dataSeriesProperties};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{apcaContrast as s,ensureContrast as m,gamutMapToSRGB as i,oklabToOklch as E,oklchToOklab as l,parseOklch as p,serializeOklch as S}from"../../shared/color-engine.js";import{DATA_SERIES_KEYS as b}from"../../shared/data-colors.js";import{TOKEN_PAIRINGS as u}from"../../shared/theme.js";const A=.75,f=u.text?.targetLc??75;function _(c,n){const t=m({...c},n,f);if(Math.abs(s(t,n))>=f)return t;const o=i({l:n.l>.5?1:0,c:c.c,h:c.h});return Math.abs(s(o,n))>Math.abs(s(t,n))?o:t}function k(c,n,t){const o=l(c),r=l(n);return E({L:o.L*t+r.L*(1-t),a:o.a*t+r.a*(1-t),b:o.b*t+r.b*(1-t)})}function h(c,n){const t=p(c["--esp-color-background"]??"");if(!t)return{};const o={};for(let r=1;r<=b.length;r+=1){const a=`--esp-color-series-${r}`,e=p(c[a]??n?.[a]??"");e&&(o[`${a}-wash`]=S(i(k(e,t,A))),o[`${a}-ink`]=S(_(e,t)))}return o}export{A as DATA_WASH_SERIES_SHARE,h as dataCompanionProperties};
|
|
@@ -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};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var c=function(u,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(u,e,t,r);else for(var i=u.length-1;i>=0;i--)(a=u[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 T}from"lit";import{property as d,state as C}from"lit/decorators.js";import{subscribeToRootEvent as p}from"./root-event-subscription.js";import{traverseToClosest as b}from"./utilities.js";import{parseCssColor as S,serializeOklch as w,gamutMapToSRGB as P}from"./color-engine.js";import{computeVariants as m}from"../root/helpers/compute-variants.js";import{computeSemanticProperties as v}from"../root/helpers/compute-semantic-properties.js";import{dataCompanionProperties as x}from"../root/helpers/data-companion-properties.js";import{dataSeriesProperties as E}from"../root/helpers/compute-theme-properties.js";import{lightnessRampProperties as V}from"../root/helpers/lightness-ramp-properties.js";import{ELEVATION_PROPERTIES as U}from"./scale-engine.js";import{resolveContextTheme as I,resolveDataColorSource as A,semanticToCSS as O}from"./theme.js";import{alignAttributeTextInheritance as D,focusRing as z}from"./style-fragments.js";import{syncNormalizedAttribute as F}from"./attribute-helpers.js";import{INTENT_VARIANTS as y,normalizeIntentVariant as N}from"./intent-values.js";class n extends T{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=N(e);F(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=[p(e,"seed-color-changed",this.handleSeedColorChanged),p(e,"scheme-changed",this.handleSchemeChanged),p(e,"theme-changed",this.handleThemeChanged),p(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=S(o.seedColor);if(!s)return;const a=m(s,o),i={};if(r.applied&&(Object.assign(i,v(o,a)),Object.assign(i,V(o)),Object.assign(i,U),Object.assign(i,x(i,E(o,s,l=>(typeof l=="string"?A(l,o.anchors):null)??o.seedColor)))),e){let l=o;r.applied||(l=this.resolveAncestorZoneTheme(t)??o);const g=l===o?a:m(s,l),k=v(l,g,{effectiveSource:(f,h)=>n.semanticActionTokens.has(f)?e:h});i["--esp-color-primary"]=w(P(g[e]));for(const f of n.semanticActionTokens){const h=O(f);i[h]=k[h]}}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){return I(e,t)}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=[z(".esp-field:focus-within","--esp-field-focus-shadow"),D,R`
|
|
2
2
|
:host {
|
|
3
3
|
|
|
4
4
|
--_esp-field-resolved-hover-bg: var(
|
|
@@ -122,4 +122,4 @@ var h=function(f,e,t,r){var o=arguments.length,s=o<3?e:r===null?r=Object.getOwnP
|
|
|
122
122
|
.esp-field:has(textarea:disabled):hover {
|
|
123
123
|
background-color: var(--esp-field-background, var(--esp-color-layer-2));
|
|
124
124
|
}
|
|
125
|
-
`],
|
|
125
|
+
`],c([C()],n.prototype,"seedColor",null),c([d({attribute:!1})],n.prototype,"correlationId",void 0),c([d({type:String,reflect:!0})],n.prototype,"scheme",void 0),c([d({type:String,attribute:"intent",reflect:!0,useDefault:!0})],n.prototype,"intent",null),c([d({type:String,attribute:"context",reflect:!0})],n.prototype,"context",null);export{n as EspalierElementBase};
|
|
@@ -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};
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
* report.adjustedTokens; // ['linkHover']
|
|
49
49
|
* ```
|
|
50
50
|
*/
|
|
51
|
-
import { type EspalierTheme, type LightnessKey, type MappingSource, type SemanticColorName } from "./theme.js";
|
|
51
|
+
import { type EspalierTheme, type LightnessKey, type MappingSource, type PartialTheme, type SemanticColorName } from "./theme.js";
|
|
52
52
|
/** What APCA enforcement did to one token. */
|
|
53
53
|
export interface ThemeFitApcaAction {
|
|
54
54
|
/** The requested value, as an `oklch()` string. */
|
|
@@ -105,6 +105,14 @@ export interface ThemeFitToken {
|
|
|
105
105
|
anchor?: ThemeFitAnchor;
|
|
106
106
|
/** Present for the text tokens APCA enforcement guards. */
|
|
107
107
|
apca?: ThemeFitApcaAction;
|
|
108
|
+
/**
|
|
109
|
+
* Present when the mapping was pinned token-level rather than compiled
|
|
110
|
+
* from roles (ESP0175). `"declared"` means this surface's own
|
|
111
|
+
* declaration pinned it; `"inherited"` means a root-level pin survived
|
|
112
|
+
* into this context — the silent case this marker exists to surface,
|
|
113
|
+
* since a zone's unexpected value then traces to a root theme line.
|
|
114
|
+
*/
|
|
115
|
+
explicit?: "declared" | "inherited";
|
|
108
116
|
}
|
|
109
117
|
/**
|
|
110
118
|
* A theme's fit report — the stable JSON shape the docs site renders.
|
|
@@ -135,6 +143,17 @@ export interface ThemeFitRow {
|
|
|
135
143
|
/** The anchor reference and its ΔE; empty for non-anchor sources. */
|
|
136
144
|
anchor: string;
|
|
137
145
|
}
|
|
146
|
+
/** Options for {@link themeFitReport}. */
|
|
147
|
+
export interface ThemeFitReportOptions {
|
|
148
|
+
/**
|
|
149
|
+
* Report a named context's surface instead of the root (ESP0175). The
|
|
150
|
+
* context compiles through the exact zone path a `context` attribute
|
|
151
|
+
* uses — `resolveContextTheme` — so the report cannot drift from what
|
|
152
|
+
* a zone host renders. Unknown names throw, mirroring the zone host's
|
|
153
|
+
* warning for an undefined context.
|
|
154
|
+
*/
|
|
155
|
+
context?: string;
|
|
156
|
+
}
|
|
138
157
|
/**
|
|
139
158
|
* Build the fit report for a resolved theme.
|
|
140
159
|
*
|
|
@@ -142,10 +161,11 @@ export interface ThemeFitRow {
|
|
|
142
161
|
* what it did. An unparseable seed produces an empty token list, exactly
|
|
143
162
|
* as it produces an empty property record.
|
|
144
163
|
*
|
|
145
|
-
* @param theme
|
|
146
|
-
* @param scheme
|
|
164
|
+
* @param theme A resolved theme (as `mergeTheme` returns).
|
|
165
|
+
* @param scheme The scheme to compute.
|
|
166
|
+
* @param options Surface selection; omit for the root surface.
|
|
147
167
|
*/
|
|
148
|
-
export declare function themeFitReport(theme: EspalierTheme, scheme: "light" | "dark"): ThemeFitReport;
|
|
168
|
+
export declare function themeFitReport(theme: EspalierTheme, scheme: "light" | "dark", options?: ThemeFitReportOptions): ThemeFitReport;
|
|
149
169
|
/**
|
|
150
170
|
* Render a report as `console.table` rows.
|
|
151
171
|
*
|
|
@@ -160,3 +180,73 @@ export declare function themeFitRows(report: ThemeFitReport): ThemeFitRow[];
|
|
|
160
180
|
* then a `console.table` of every token.
|
|
161
181
|
*/
|
|
162
182
|
export declare function printThemeFitReport(report: ThemeFitReport): void;
|
|
183
|
+
/** One cross-token finding over a single surface's emitted values. */
|
|
184
|
+
export interface ThemeFitLint {
|
|
185
|
+
/** Stable lint identifier. */
|
|
186
|
+
id: "action-canvas-separation" | "link-hover-ordering";
|
|
187
|
+
/** Lints describe design-quality hazards, not validation failures. */
|
|
188
|
+
severity: "warning";
|
|
189
|
+
/** The tokens the finding is about. */
|
|
190
|
+
tokens: SemanticColorName[];
|
|
191
|
+
/** The measured number the lint fired on (ΔE-OK, or an Lc deficit). */
|
|
192
|
+
measured: number;
|
|
193
|
+
/** Human-readable finding, with the remedy phrased as a retune. */
|
|
194
|
+
message: string;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Cross-token lints over one surface's report (ESP0175).
|
|
198
|
+
*
|
|
199
|
+
* Both checks consume only the report's `resolved` strings — the emitted
|
|
200
|
+
* custom-property values — so the lints, like the report, cannot
|
|
201
|
+
* disagree with the pipeline:
|
|
202
|
+
*
|
|
203
|
+
* - **action-canvas-separation** — a filled action within ΔE-OK 0.045 of
|
|
204
|
+
* the surface's own background vanishes into it. The stop placement
|
|
205
|
+
* avoids this when any viable stop exists; the lint reports the
|
|
206
|
+
* residue (explicit pins, ramps with no separated stop).
|
|
207
|
+
* - **link-hover-ordering** — measured on the surfaces the states
|
|
208
|
+
* actually render: resting `link` on `background`, hovered `linkHover`
|
|
209
|
+
* on the `linkHoverBg` wash. When the theme paints a real wash, the
|
|
210
|
+
* wash itself signals the state and the ink must clear the text tier
|
|
211
|
+
* there; when the wash equals the background (no visible wash), hover
|
|
212
|
+
* must not read weaker than rest — a dimming hover feels disabled.
|
|
213
|
+
*/
|
|
214
|
+
export declare function themeFitLints(report: ThemeFitReport): ThemeFitLint[];
|
|
215
|
+
/**
|
|
216
|
+
* The root surface's identifier in a suite. Deliberately not a valid
|
|
217
|
+
* context slug (slugs are letter-first), so no theme-declared context
|
|
218
|
+
* name — `root` included, which has been a legal name since contexts
|
|
219
|
+
* shipped — can ever collide with it.
|
|
220
|
+
*/
|
|
221
|
+
export declare const ROOT_SURFACE = ":root";
|
|
222
|
+
/** One surface's report inside a {@link ThemeFitSuite}. */
|
|
223
|
+
export interface ThemeFitSurfaceReport extends ThemeFitReport {
|
|
224
|
+
/** {@link ROOT_SURFACE}, or the context name a zone would set. */
|
|
225
|
+
surface: string;
|
|
226
|
+
/** Cross-token lints over this surface's emitted values. */
|
|
227
|
+
lints: ThemeFitLint[];
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Every surface a themed site has, both schemes, in one stable shape.
|
|
231
|
+
*/
|
|
232
|
+
export interface ThemeFitSuite {
|
|
233
|
+
/** Surface names in report order: {@link ROOT_SURFACE}, then contexts, sorted. */
|
|
234
|
+
surfaces: string[];
|
|
235
|
+
/** One report per surface the light theme defines. */
|
|
236
|
+
light: ThemeFitSurfaceReport[];
|
|
237
|
+
/** One report per surface the dark theme defines. */
|
|
238
|
+
dark: ThemeFitSurfaceReport[];
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Fit-report every surface a themed site has (ESP0175): for each scheme,
|
|
242
|
+
* the root plus every declared context, each with its cross-token lints.
|
|
243
|
+
*
|
|
244
|
+
* Takes the partials an `esp-root` would take (they merge over the same
|
|
245
|
+
* scheme defaults the root uses), so the suite describes exactly the
|
|
246
|
+
* site the partials produce — the 448-line hand-rolled check script,
|
|
247
|
+
* minus the client-specific lines. Surfaces are the union of both
|
|
248
|
+
* schemes' context names so an asymmetric pair still reports every
|
|
249
|
+
* surface it can; `validateThemePair` is the gate that rejects the
|
|
250
|
+
* asymmetry itself.
|
|
251
|
+
*/
|
|
252
|
+
export declare function themeFitReportSuite(lightPartial: PartialTheme, darkPartial: PartialTheme): ThemeFitSuite;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{apcaContrast as
|
|
1
|
+
import{apcaContrast as h,deltaEOK as f,parseOklch as p,serializeOklch as v}from"./color-engine.js";import{DEFAULT_DARK_THEME as T,DEFAULT_LIGHT_THEME as R,explicitMappingTokens as $,mergeTheme as m,parseAnchorSource as x,resolveContextTheme as A}from"./theme.js";import{computeThemeProperties as H}from"../root/helpers/compute-theme-properties.js";function _(e,t,n={}){let o=e,r=null;if(n.context!==void 0){const c=A(e,n.context);if(!c)throw new Error(`themeFitReport: context "${n.context}" is not defined by the theme.`);o=c,r=new Set(Object.keys(e.contexts[n.context]?.semanticMappings??{}))}const i=[];H(o,t,i);const l=$(o),u=i.map(c=>{const s=j(c);return l.has(s.token)&&(s.explicit=r===null||r.has(s.token)?"declared":"inherited"),s});return{scheme:t,seedColor:o.seedColor,anchors:M(o.anchors),tokens:u,adjustedTokens:u.filter(c=>c.adjusted).map(c=>c.token)}}function w(e){return e.tokens.map(t=>({token:t.token,source:t.source,requested:t.requested,resolved:t.resolved,apca:I(t),anchor:F(t)}))}function D(e){const t=e.adjustedTokens.length>0?`APCA moved ${e.adjustedTokens.join(", ")}`:"APCA moved nothing";console.info(`Espalier theme fit \u2014 ${e.scheme} scheme, seed ${e.seedColor}. ${t}.`),console.table(w(e))}const L=.045,b=.5,k=75,O=.045;function C(e){const t=[],n=new Map(e.tokens.map(s=>[s.token,s.resolved])),o=s=>{const a=n.get(s);return a===void 0?null:p(a)},r=o("background"),i=o("actionBackground");if(r&&i){const s=f(i,r);if(s<L){const a=d(s,4);t.push({id:"action-canvas-separation",severity:"warning",tokens:["actionBackground","background"],measured:a,message:`actionBackground sits \u0394E ${a} from background (floor ${L}) \u2014 filled actions vanish into this canvas. Retune the surface apart: move the background stop, or pin actionBackground to a separated stop.`})}}const l=o("link"),u=o("linkHover"),c=o("linkHoverBg");if(r&&l&&u&&c){const s=Math.abs(h(l,r)),a=Math.abs(h(u,c));f(c,r)>=O?a+b<k&&t.push({id:"link-hover-ordering",severity:"warning",tokens:["linkHover","linkHoverBg"],measured:d(k-a,1),message:`linkHover reaches only Lc ${d(a,1)} on the linkHoverBg wash it actually paints (tier ${k}) \u2014 retune linkHover or the wash apart until the hovered state clears the text tier.`}):a+b<s&&t.push({id:"link-hover-ordering",severity:"warning",tokens:["linkHover","link"],measured:d(s-a,1),message:`linkHover reads weaker than link on the same canvas (Lc ${d(a,1)} vs ${d(s,1)}) \u2014 a dimming hover feels disabled. Retune linkHover toward more contrast than the resting link.`})}return t}const g=":root";function V(e,t){const n=m(R,e),o=m(T,t),r=[g,...[...new Set([...Object.keys(n.contexts),...Object.keys(o.contexts)])].sort()],i=(l,u)=>{const c=[];for(const s of r){if(s!==g&&!(s in l.contexts))continue;const a=_(l,u,s===g?{}:{context:s});c.push({...a,surface:s,lints:C(a)})}return c};return{surfaces:r,light:i(n,"light"),dark:i(o,"dark")}}function d(e,t){const n=Math.pow(10,t);return Math.round(e*n)/n}function E(e,t){return t===void 0?e:`${e}.${t}`}function M(e){const t={};for(const[n,o]of Object.entries(e)){if(typeof o=="string"){t[n]=o;continue}for(const[r,i]of Object.entries(o))t[r==="color"?n:E(n,r)]=i}return t}function j(e){const t=p(e.resolved),n=e.requested===void 0?e.resolved:v(e.requested),o={token:e.token,property:e.property,source:e.source,lightness:e.lightness,requested:n,resolved:e.resolved,adjusted:n!==e.resolved,sourceResolved:e.sourceResolved},r=x(e.source);if(r&&e.sourceResolved&&(o.anchor={reference:E(r.name,r.slot),color:v(e.base),deltaE:d(f(e.base,t),4)}),e.contrast){const i=e.contrast.background;o.apca={from:n,to:e.resolved,targetLc:e.contrast.targetLc,against:e.contrast.against,againstColor:v(i),fromLc:d(Math.abs(h(p(n),i)),1),toLc:d(Math.abs(h(t,i)),1)}}return o}function I(e){if(!e.apca)return"";const{targetLc:t,against:n,fromLc:o,toLc:r}=e.apca;return e.adjusted?`Lc ${t} vs ${n} \u2014 pushed from Lc ${o} to Lc ${r}`:`Lc ${t} vs ${n} \u2014 met at Lc ${o}`}function F(e){return e.anchor?`${e.anchor.reference} \u0394E ${e.anchor.deltaE}`:e.sourceResolved?"":"unresolved \u2014 seed used"}export{g as ROOT_SURFACE,D as printThemeFitReport,C as themeFitLints,_ as themeFitReport,V as themeFitReportSuite,w as themeFitRows};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module theme-swatches
|
|
3
|
+
*
|
|
4
|
+
* Brand book in, reviewable theme out (ESP0175).
|
|
5
|
+
*
|
|
6
|
+
* The lightness-ramps guide documents the fitting procedure by hand:
|
|
7
|
+
* convert every swatch to OKLCH, seat each on the ramp stop whose job
|
|
8
|
+
* matches its lightness (set the stop to the swatch's *exact* L so the
|
|
9
|
+
* anchor lands on its swatch instead of a relative of it), interpolate
|
|
10
|
+
* the unseated stops between their seated neighbors, and repeat against
|
|
11
|
+
* the dark scheme's inverted expectations. This module automates those
|
|
12
|
+
* mechanical steps — {@link deriveLightnessRamp} — and wraps them in
|
|
13
|
+
* {@link themeFromSwatches}, which returns a paired partial-theme
|
|
14
|
+
* **starter**: anchors, seated ramps, and the two role guesses that are
|
|
15
|
+
* safe to make from lightness alone. Everything else — the remaining
|
|
16
|
+
* roles, contexts, intents — is the design work the guide walks through,
|
|
17
|
+
* and the output is meant to be hand-tuned and reviewed, not shipped
|
|
18
|
+
* as-is. Verify the result the way the guide says to: a fit report per
|
|
19
|
+
* scheme (a seated anchor shows a near-zero ΔE), then `validateThemePair`.
|
|
20
|
+
*/
|
|
21
|
+
import { type LightnessMap, type PartialTheme } from "./theme.js";
|
|
22
|
+
/**
|
|
23
|
+
* Derive a full lightness ramp by seating brand swatches on the scheme's
|
|
24
|
+
* default ramp — the guide's procedure, steps 1–4, automated.
|
|
25
|
+
*
|
|
26
|
+
* The stops that carry a token identity — `surface` (the canvas) and
|
|
27
|
+
* `text` (body copy) — seat first, each taking the nearest swatch within
|
|
28
|
+
* tolerance: those are the two seats where "the anchor lands on its
|
|
29
|
+
* swatch" matters most, and a numerically closer neighboring stop must
|
|
30
|
+
* not steal their swatch (the guide seats cream on `surface`, not on
|
|
31
|
+
* the fractionally closer `raised1`). The remaining seating is greedy
|
|
32
|
+
* by closeness: the (stop, swatch) pair with the smallest lightness
|
|
33
|
+
* distance seats first, each stop and each swatch seat at most once, a
|
|
34
|
+
* pair further than the seating tolerance never seats, and a seat that
|
|
35
|
+
* would invert the ramp's ordering (relative to the stops already
|
|
36
|
+
* seated) is skipped. Unseated stops interpolate between their seated
|
|
37
|
+
* neighbors along the default ramp; stops outside the seated span shift
|
|
38
|
+
* parallel to their nearest seated neighbor, so the ramp keeps the
|
|
39
|
+
* default's shape wherever the brand is silent.
|
|
40
|
+
*
|
|
41
|
+
* @param swatches Brand swatches, name → CSS color (any accepted form).
|
|
42
|
+
* @param scheme Which scheme's default ramp to seat against.
|
|
43
|
+
*/
|
|
44
|
+
export declare function deriveLightnessRamp(swatches: Record<string, string>, scheme: "light" | "dark"): LightnessMap;
|
|
45
|
+
/** Options for {@link themeFromSwatches}. */
|
|
46
|
+
export interface ThemeFromSwatchesOptions {
|
|
47
|
+
/** Brand swatches, name → CSS color in any accepted form. */
|
|
48
|
+
swatches: Record<string, string>;
|
|
49
|
+
/** Seed override; defaults to the most chromatic swatch's color. */
|
|
50
|
+
seedColor?: string;
|
|
51
|
+
}
|
|
52
|
+
/** The paired starter {@link themeFromSwatches} returns. */
|
|
53
|
+
export interface ThemeFromSwatchesResult {
|
|
54
|
+
light: PartialTheme;
|
|
55
|
+
dark: PartialTheme;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Turn a brand book's swatches into a paired partial-theme starter.
|
|
59
|
+
*
|
|
60
|
+
* What it derives, and why only this much:
|
|
61
|
+
*
|
|
62
|
+
* - **anchors** — every swatch, keyed by its normalized slug: names are
|
|
63
|
+
* lowercased with non-alphanumeric runs collapsed to single hyphens
|
|
64
|
+
* ("Rose Gold" → `rose-gold`); unnormalizable or colliding names
|
|
65
|
+
* throw rather than silently renaming past each other.
|
|
66
|
+
* - **seedColor** — the most chromatic swatch unless overridden; the
|
|
67
|
+
* seed drives every derived family, and the brand's most saturated
|
|
68
|
+
* color is where that identity lives.
|
|
69
|
+
* - **lightness** — both schemes' ramps via {@link deriveLightnessRamp}.
|
|
70
|
+
* - **roles** — only the two guesses lightness alone justifies: a
|
|
71
|
+
* near-white swatch (L ≥ 0.9) becomes the light scheme's `canvas`, a
|
|
72
|
+
* near-black one (L ≤ 0.3) the dark scheme's; the most chromatic
|
|
73
|
+
* mid-band swatch (0.3 ≤ L ≤ 0.65) becomes `action` in both.
|
|
74
|
+
*
|
|
75
|
+
* Every other decision — ink, accent, contexts, intents — is design, not
|
|
76
|
+
* derivation: make them in the theming guide's order, and verify with a
|
|
77
|
+
* fit report per scheme.
|
|
78
|
+
*/
|
|
79
|
+
export declare function themeFromSwatches(options: ThemeFromSwatchesOptions): ThemeFromSwatchesResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{parseCssColor as M}from"./color-engine.js";import{isSafeKey as b}from"./unsafe-keys.js";import{ANCHOR_SLUG_PATTERN as L,COLOR_SOURCES as T,DEFAULT_DARK_THEME as F,DEFAULT_LIGHT_THEME as _,LIGHTNESS_KEYS as E}from"./theme.js";const $=.08;function x(i){return Object.entries(i).map(([c,n])=>{const s=M(n);if(!s)throw new Error(`themeFromSwatches: swatch "${c}" is not a parseable CSS color: "${n}".`);return{name:c,l:s.l,c:s.c}})}function O(i,c){const n=(c==="light"?_:F).lightness,s=x(i),o=[...E].sort((e,t)=>n[e]-n[t]),a=new Map,w=new Set,p=(e,t)=>{if(a.has(e)||w.has(t.name))return!1;const r=o.indexOf(e);for(const[l,h]of a){const d=o.indexOf(l);if(d<r&&h>t.l||d>r&&h<t.l)return!1}return a.set(e,t.l),w.add(t.name),!0};for(const e of["surface","text"]){const t=[...s].filter(r=>Math.abs(n[e]-r.l)<=$).sort((r,l)=>Math.abs(n[e]-r.l)-Math.abs(n[e]-l.l))[0];t&&p(e,t)}const S=o.flatMap(e=>s.map(t=>({stop:e,swatch:t,distance:Math.abs(n[e]-t.l)}))).filter(e=>e.distance<=$).sort((e,t)=>e.distance-t.distance);for(const{stop:e,swatch:t}of S)p(e,t);const f={};for(const e of E){const t=a.get(e);if(t!==void 0){f[e]=t;continue}const r=o.indexOf(e);let l,h;for(const[m]of a){const u=o.indexOf(m);u<r&&(l===void 0||u>o.indexOf(l))&&(l=m),u>r&&(h===void 0||u<o.indexOf(h))&&(h=m)}const d=n[e];if(l!==void 0&&h!==void 0){const m=n[l],u=n[h],g=a.get(l),C=a.get(h);f[e]=u===m?g:g+(d-m)/(u-m)*(C-g)}else l!==void 0?f[e]=R(d+(a.get(l)-n[l])):h!==void 0?f[e]=R(d+(a.get(h)-n[h])):f[e]=d}for(const e of E)f[e]=y(f[e]);return f}function R(i){return Math.min(1,Math.max(0,i))}function y(i){return Math.round(i*1e3)/1e3}function v(i){const c=new Map;for(const s of Object.keys(i)){const o=s.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"");if(!L.test(o))throw new Error(`themeFromSwatches: swatch name "${s}" cannot become an anchor slug (normalized to "${o}"; a slug is lowercase letters, digits, and hyphens, starting with a letter). Rename the swatch.`);if(T.includes(o))throw new Error(`themeFromSwatches: swatch name "${s}" normalizes to the reserved color source "${o}". Rename the swatch.`);if(!b(o))throw new Error(`themeFromSwatches: swatch name "${s}" normalizes to "${o}", a reserved JavaScript property name. Rename the swatch.`);const a=c.get(o);if(a!==void 0)throw new Error(`themeFromSwatches: swatch names "${a}" and "${s}" collide as "${o}". Rename one.`);c.set(o,s)}const n={};for(const[s,o]of c)n[s]=i[o];return n}function H(i){const c=v(i.swatches),n=x(c);if(n.length===0)throw new Error("themeFromSwatches: at least one swatch is required.");const s=[...n].sort((t,r)=>r.c-t.c)[0],o=i.seedColor??c[s.name],a={},w={},p=[...n].sort((t,r)=>r.l-t.l)[0];p.l>=.9&&(a.canvas=`anchor:${p.name}`);const S=[...n].sort((t,r)=>t.l-r.l)[0];S.l<=.3&&(w.canvas=`anchor:${S.name}`);const f=n.filter(t=>t.l>=.3&&t.l<=.65).sort((t,r)=>r.c-t.c)[0];f&&(a.action=`anchor:${f.name}`,w.action=`anchor:${f.name}`);const e={...c};return{light:{anchors:e,lightness:O(c,"light"),roles:a,seedColor:o},dark:{anchors:e,lightness:O(c,"dark"),roles:w,seedColor:o}}}export{O as deriveLightnessRamp,H as themeFromSwatches};
|