@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.
@@ -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 k,html as b,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 T}from"../shared/root-event-subscription.js";import{DEFAULT_LIGHT_THEME as g,DEFAULT_DARK_THEME as f,parseTheme as y,mergeTheme as v}from"../shared/theme.js";import{computeThemeProperties as x}from"./helpers/compute-theme-properties.js";import{registerImageTexture as R,registeredImageTextures as D}from"../shared/image-texture-registry.js";import{registerImageTexture as W,registeredImageTextures as _,BUILT_IN_IMAGE_TEXTURES as H}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={...g},this.resolvedDarkTheme={...f},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){R(e,t)}static registeredTextures(){return D()}subscribeScoped(e,t){return T(this,e,t)}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,S=u&&u!=="400"?`:wght@${u}`:"",m=document.createElement("link");m.rel="stylesheet",m.href=`https://fonts.googleapis.com/css2?family=${n}${S}&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?y(this.lightThemeAttr):null;this.resolvedLightTheme=e?v(g,e):{...g};const t=this.darkThemeAttr?y(this.darkThemeAttr):null;this.resolvedDarkTheme=t?v(f,t):{...f}}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=x(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 b`<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=`
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=k`
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{H as BUILT_IN_IMAGE_TEXTURES,i as EspalierRoot,W as registerImageTexture,_ as registeredImageTextures};
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 g,serializeOklch as l,gamutMapToSRGB as f}from"../../shared/color-engine.js";import{generateScaleProperties as k}from"../../shared/scale-engine.js";import{DATA_SERIES_KEYS as u,DEFAULT_DATA_PALETTE as v,generateDivergingRamp as B,generateSequentialRamp as R}from"../../shared/data-colors.js";import{resolveDataColorSource as w}from"../../shared/theme.js";import{lightnessRampProperties as O}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 j(o,b,m){const i=g(o.seedColor);if(!i)return{};const a={};a["--esp-scheme"]=b,a["--esp-seed-color"]=o.seedColor;const c=$(i,o);for(const[e,n]of Object.entries(c))a[`--esp-color-${e}`]=l(f(n));const p=new Set,t=(e,n)=>{const r=typeof e=="string"?w(e,o.anchors):null;if(r)return r;const s=typeof e=="string"?e:String(e);return p.has(s)||(p.add(s),console.warn(`Espalier theme: ${n} color source "${s}" cannot be resolved \u2014 falling back to the seed. Run validateTheme for the declared data colors.`)),o.seedColor},y=o.dataPalette??v;for(let e=0;e<u.length;e+=1){const n=u[e],r=g(t(y[n],`dataPalette.${n}`))??i;a[`--esp-color-series-${e+1}`]=l(f(r))}for(const[e,n]of Object.entries(o.dataRamps??{}))try{(n.type==="sequential"?R(t(n.source,`dataRamps.${e}.source`),{steps:n.steps,lightnessStart:n.lightnessStart,lightnessEnd:n.lightnessEnd}):B(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((s,S)=>{a[`--esp-color-ramp-${e}-${S+1}`]=s})}catch(r){console.warn(`Espalier theme: dataRamps.${e} cannot be generated \u2014 ${r instanceof Error?r.message:String(r)}`)}Object.assign(a,O(o));const d=new Set;return Object.assign(a,E(o,c,{trace:m,onUnresolvedSource:e=>{d.has(e)||(d.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,k(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{j as computeThemeProperties};
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 h=function(f,e,t,r){var o=arguments.length,s=o<3?e:r===null?r=Object.getOwnPropertyDescriptor(e,t):r,a;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")s=Reflect.decorate(f,e,t,r);else for(var i=f.length-1;i>=0;i--)(a=f[i])&&(s=(o<3?a(s):o>3?a(e,t,s):a(e,t))||s);return o>3&&s&&Object.defineProperty(e,t,s),s};import{css as R,LitElement as C}from"lit";import{property as p,state as S}from"lit/decorators.js";import{subscribeToRootEvent as u}from"./root-event-subscription.js";import{traverseToClosest as b}from"./utilities.js";import{parseCssColor as w,serializeOklch as x,gamutMapToSRGB as P}from"./color-engine.js";import{computeVariants as v}from"../root/helpers/compute-variants.js";import{computeSemanticProperties as m}from"../root/helpers/compute-semantic-properties.js";import{lightnessRampProperties as E}from"../root/helpers/lightness-ramp-properties.js";import{ROLE_NAMES as V,mergeTheme as U,semanticToCSS as A}from"./theme.js";import{alignAttributeTextInheritance as I,focusRing as O}from"./style-fragments.js";import{syncNormalizedAttribute as D}from"./attribute-helpers.js";import{INTENT_VARIANTS as y,normalizeIntentVariant as z}from"./intent-values.js";const k=new WeakMap;class n extends C{constructor(){super(...arguments),this.seedColorBacker="oklch(0.7 0.125 216)",this.espRoot=null,this.contextBacker="",this.scopedTokenOriginalValues=new Map,this.warnedUnknownContexts=new Set,this.pendingInitialThemeRoot=null,this.rootEventSubscriptionsActive=!1,this.subscribedRoot=null,this.rootEventUnsubscribers=[],this.intentBacker="neutral",this.intentEmitsTokens=!0,this.warnedUnknownIntents=new Set,this.correlationId=globalThis.crypto?.randomUUID?.()??Math.random().toString(36),this.scheme="light",this.handleSeedColorChanged=e=>{this.syncRootFromDom(!1)&&(this.seedColor=e.seedColor,this.applyScopedColorTokens())},this.handleSchemeChanged=e=>{!this.syncRootFromDom(!1)||this.scheme===e.scheme||(this.scheme=e.scheme,this.applyScopedColorTokens())},this.handleThemeChanged=()=>{this.syncRootFromDom(!1)&&this.applyScopedColorTokens()},this.handleIconSpriteUrlChanged=()=>{this.syncRootFromDom(!1)&&this.requestUpdate()}}get seedColor(){return this.seedColorBacker}set seedColor(e){this.seedColorBacker=e}focusResolvedElementAfterUpdate(e,t){const r=()=>{const o=e();return o?(o.focus(t),!0):!1};r()||this.updateComplete.then(()=>{r()})}focusShadowElementAfterUpdate(e,t){this.focusResolvedElementAfterUpdate(()=>this.shadowRoot?.querySelector(e),t)}emitValueChanged(e){this.dispatchEvent(new CustomEvent("value-changed",{detail:e,bubbles:!0,composed:!0}))}get intent(){return this.intentBacker}set intent(e){const t=typeof e=="string"?e.trim():"";t!==""&&!y.includes(t)&&!this.warnedUnknownIntents.has(t)&&(this.warnedUnknownIntents.add(t),console.warn(`Espalier intent: "${t}" is not an intent; expected one of ${y.join(", ")}. Treating it as "neutral".`));const r=this.intentBacker,o=z(e);D(this,"intent",o),o!==r&&(this.intentBacker=o,this.requestUpdate("intent",r),this.applyTokensWhenRootReady())}get context(){return this.contextBacker}set context(e){const t=e??"";t!==this.contextBacker&&(this.contextBacker=t,this.applyTokensWhenRootReady(),this.refreshDescendantIntentDerivations())}refreshDescendantIntentDerivations(){const e=new Set,t=o=>{for(const s of Array.from(o.children))r(s)},r=o=>{if(!e.has(o)){if(e.add(o),o instanceof n&&o.applyTokensWhenRootReady(),o.shadowRoot&&t(o.shadowRoot),o instanceof HTMLSlotElement)for(const s of o.assignedElements({flatten:!0}))r(s);t(o)}};t(this),this.shadowRoot&&t(this.shadowRoot)}static flattenedParent(e){return e instanceof Element&&e.assignedSlot?e.assignedSlot:e instanceof ShadowRoot?e.host:e.parentNode}resolveAncestorZoneTheme(e){let t=n.flattenedParent(this);for(;t;){if(t instanceof Element&&t.localName==="esp-root")return null;if(t instanceof n){const r=t.contextBacker.trim();if(r){const o=this.resolveNamedContextTheme(e,r);if(o)return o}}t=n.flattenedParent(t)}return null}connectedCallback(){super.connectedCallback();const e=this.syncRootFromDom(!1);e&&this.subscribeToRootEvents(e)}disconnectedCallback(){super.disconnectedCallback(),this.unsubscribeFromRootEvents(),this.clearScopedTokens(),this.espRoot=null}firstUpdated(e){this.syncRootFromDom(!0),this.applyTokensWhenRootReady()}syncRootFromDom(e){const t=b(this,"esp-root");if(!t){if(this.espRoot&&this.clearScopedTokens(),this.unsubscribeFromRootEvents(),this.espRoot=null,!e)return null;throw new Error("No esp-root ancestor found. Espalier components must be placed inside an <esp-root> element.")}const r=t!==this.espRoot,s=t.scheme==="dark"?"dark":"light",a=this.scheme!==s,i=this.seedColor!==t.seedColor;return this.espRoot=t,r&&this.isConnected&&this.subscribeToRootEvents(t),(r||a||i)&&(this.scheme=s,this.seedColor=t.seedColor,this.applyTokensWhenRootReady()),r&&this.requestUpdate(),t}applyTokensWhenRootReady(){const e=this.espRoot;if(e&&(!e.hasUpdated||e.isUpdatePending)){this.applyTokensAfterInitialRootUpdate(e);return}this.applyScopedColorTokens()}applyTokensAfterInitialRootUpdate(e){if(this.pendingInitialThemeRoot===e)return;this.pendingInitialThemeRoot=e;const t=e.updateComplete;if(!t){this.pendingInitialThemeRoot=null,this.applyScopedColorTokens();return}t.then(()=>{this.pendingInitialThemeRoot===e&&(this.pendingInitialThemeRoot=null),!(!this.isConnected||this.espRoot!==e)&&(this.scheme=e.scheme==="dark"?"dark":"light",this.seedColor=e.seedColor,this.applyScopedColorTokens())})}subscribeToRootEvents(e){this.rootEventSubscriptionsActive&&this.subscribedRoot===e||(this.unsubscribeFromRootEvents(),this.subscribedRoot=e,this.rootEventUnsubscribers=[u(e,"seed-color-changed",this.handleSeedColorChanged),u(e,"scheme-changed",this.handleSchemeChanged),u(e,"theme-changed",this.handleThemeChanged),u(e,"icon-sprite-url-changed",this.handleIconSpriteUrlChanged)],this.rootEventSubscriptionsActive=!0)}unsubscribeFromRootEvents(){if(this.rootEventSubscriptionsActive){for(const e of this.rootEventUnsubscribers)e();this.rootEventUnsubscribers=[],this.subscribedRoot=null,this.rootEventSubscriptionsActive=!1}}traverseToClosest(e){return b(this,e)}applyScopedColorTokens(){if(!this.espRoot)return;const e=this.intentEmitsTokens?n.intentColorSources[this.intentBacker]:"";if(!e&&!this.context.trim()){this.clearScopedTokens();return}const t=this.espRoot.activeTheme;if(!t){this.clearScopedTokens();return}const r=this.resolveContextTheme(t),o=r.theme,s=w(o.seedColor);if(!s)return;const a=v(s,o),i={};if(r.applied&&(Object.assign(i,m(o,a)),Object.assign(i,E(o))),e){let c=o;r.applied||(c=this.resolveAncestorZoneTheme(t)??o);const l=c===o?a:v(s,c),T=m(c,l,{effectiveSource:(g,d)=>n.semanticActionTokens.has(g)?e:d});i["--esp-color-primary"]=x(P(l[e]));for(const g of n.semanticActionTokens){const d=A(g);i[d]=T[d]}}this.applyScopedTokenProperties(i)}resolveContextTheme(e){const t=this.context.trim();if(!t)return{applied:!1,theme:e};const r=this.resolveNamedContextTheme(e,t);return r?{applied:!0,theme:r}:(this.warnedUnknownContexts.has(t)||(this.warnedUnknownContexts.add(t),console.warn(`Espalier context: "${t}" is not defined by the active theme; inheriting root tokens.`)),{applied:!1,theme:e})}resolveNamedContextTheme(e,t){const r=e.contexts&&Object.prototype.hasOwnProperty.call(e.contexts,t)?e.contexts[t]:void 0;if(!r)return null;let o=k.get(e);const s=o?.get(t);if(s)return s;const a={};for(const c of V){const l=r[c];l!==void 0&&(a[c]=l)}const i=U(e,{lightness:r.lightness,roles:a});return o||(o=new Map,k.set(e,o)),o.set(t,i),i}applyScopedTokenProperties(e){for(const t of this.scopedTokenOriginalValues.keys())t in e||this.restoreScopedTokenProperty(t);for(const[t,r]of Object.entries(e))this.setScopedTokenProperty(t,r)}setScopedTokenProperty(e,t){const r=this.scopedTokenOriginalValues.get(e);if(r){const o=this.style.getPropertyValue(e),s=this.style.getPropertyPriority(e);(o!==r.generatedValue||s!==r.generatedPriority)&&(r.originalValue=o.length?o:null,r.originalPriority=s),r.generatedValue=t,r.generatedPriority=""}else{const o=this.style.getPropertyValue(e);this.scopedTokenOriginalValues.set(e,{generatedPriority:"",generatedValue:t,originalPriority:this.style.getPropertyPriority(e),originalValue:o.length?o:null})}this.style.setProperty(e,t)}restoreScopedTokenProperty(e){const t=this.scopedTokenOriginalValues.get(e);if(!t)return;const r=this.style.getPropertyValue(e),o=this.style.getPropertyPriority(e);(r!==t.generatedValue||o!==t.generatedPriority)&&(t.originalValue=r.length?r:null,t.originalPriority=o),t.originalValue===null?this.style.removeProperty(e):this.style.setProperty(e,t.originalValue,t.originalPriority),this.scopedTokenOriginalValues.delete(e)}clearScopedTokens(){for(const e of[...this.scopedTokenOriginalValues.keys()])this.restoreScopedTokenProperty(e)}}n.intentColorSources={danger:"danger",info:"info",neutral:"",success:"success",warning:"warning"},n.semanticActionTokens=new Set(["actionBackground","actionText"]),n.styles=[O(".esp-field:focus-within","--esp-field-focus-shadow"),I,R`
1
+ var 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
- `],h([S()],n.prototype,"seedColor",null),h([p({attribute:!1})],n.prototype,"correlationId",void 0),h([p({type:String,reflect:!0})],n.prototype,"scheme",void 0),h([p({type:String,attribute:"intent",reflect:!0,useDefault:!0})],n.prototype,"intent",null),h([p({type:String,attribute:"context",reflect:!0})],n.prototype,"context",null);export{n as EspalierElementBase};
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 c=["tiny","small","normal","medium","big","large","huge"],T=2,$=1.125,d=1.3333333333333333,_=[["tiny","small"],["small","normal"],["normal","medium"],["medium","big"],["big","large"],["large","huge"]],g=.375,y={rootFontSize:16,typeRatio:1.25,spaceRatio:1.618,borderRadius:.2,viewportMin:320,viewportMax:1200};function l(o,t=4){const e=10**t;return Math.round(o*e)/e}function f(o,t,e,s,n){if(Math.abs(t-o)<1e-4)return`${l(o)}rem`;const m=(t-o)*e*100/(n-s),p=o-m*s/(100*e);return`clamp(${l(o)}rem, ${l(p)}rem + ${l(m)}vw, ${l(t)}rem)`}function A(o){const t={};for(let e=0;e<c.length;e++){const s=e-2,n=1.125*o.typeRatio**s;t[c[e]]={min:n,max:n*d}}return t}function M(o){const t={};for(let e=0;e<c.length;e++){const s=g*o.spaceRatio**e;t[c[e]]={min:s,max:s*d}}return t}function z(o){const t={},{rootFontSize:e,viewportMin:s,viewportMax:n,borderRadius:m}=o,p=16,a=e/p,E=A(o);for(const r of c){const{min:i,max:S}=E[r];t[`--esp-type-${r}`]=f(i*a,S*a,p,s,n)}const u=M(o);for(const r of c){const{min:i,max:S}=u[r];t[`--esp-size-${r}`]=f(i*a,S*a,p,s,n)}for(const[r,i]of _)t[`--esp-size-${r}-to-${i}`]=f(u[r].min*a,u[i].max*a,p,s,n);return t["--esp-size-font"]="var(--esp-type-normal)",t["--esp-size-padding"]="var(--esp-size-tiny-to-small)",t["--esp-size-padding-page"]="var(--esp-size-small-to-normal)",t["--esp-size-border-radius"]=`${l(m*a)}rem`,t}export{y as DEFAULT_SCALE_CONFIG,c as STEP_NAMES,z as generateScaleProperties};
1
+ const i=["tiny","small","normal","medium","big","large","huge"],w=2,z=1.125,E=1.3333333333333333,g=[["tiny","small"],["small","normal"],["normal","medium"],["medium","big"],["big","large"],["large","huge"]],h=.375,T={rootFontSize:16,typeRatio:1.25,spaceRatio:1.618,borderRadius:.2,viewportMin:320,viewportMax:1200};function l(o,e=4){const s=10**e;return Math.round(o*s)/s}function S(o,e,s,t,r){if(Math.abs(e-o)<1e-4)return`${l(o)}rem`;const m=(e-o)*s*100/(r-t),p=o-m*t/(100*s);return`clamp(${l(o)}rem, ${l(p)}rem + ${l(m)}vw, ${l(e)}rem)`}function x(o){const e={};for(let s=0;s<i.length;s++){const t=s-2,r=1.125*o.typeRatio**t;e[i[s]]={min:r,max:r*E}}return e}function _(o){const e={};for(let s=0;s<i.length;s++){const t=h*o.spaceRatio**s;e[i[s]]={min:t,max:t*E}}return e}const v={"--esp-shadow-1":"1px 1px 4px var(--esp-color-shadow)","--esp-shadow-2":"0 2px 8px var(--esp-color-shadow)","--esp-shadow-3":"0 6px 20px var(--esp-color-shadow)"};function A(o){const e={},{rootFontSize:s,viewportMin:t,viewportMax:r,borderRadius:m}=o,p=16,a=s/p,f=x(o);for(const n of i){const{min:c,max:d}=f[n];e[`--esp-type-${n}`]=S(c*a,d*a,p,t,r)}const u=_(o);for(const n of i){const{min:c,max:d}=u[n];e[`--esp-size-${n}`]=S(c*a,d*a,p,t,r)}for(const[n,c]of g)e[`--esp-size-${n}-to-${c}`]=S(u[n].min*a,u[c].max*a,p,t,r);return e["--esp-size-font"]="var(--esp-type-normal)",e["--esp-size-padding"]="var(--esp-size-tiny-to-small)",e["--esp-size-padding-page"]="var(--esp-size-small-to-normal)",e["--esp-size-border-radius"]=`${l(m*a)}rem`,e["--esp-measure"]="66ch",e["--esp-measure-wide"]="90ch",e["--esp-size-section"]="clamp(var(--esp-size-big), 7vw, var(--esp-size-huge))",Object.assign(e,v),e["--esp-card-min"]="16rem",e}export{T as DEFAULT_SCALE_CONFIG,v as ELEVATION_PROPERTIES,i as STEP_NAMES,A as generateScaleProperties};
@@ -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 A resolved theme (as `mergeTheme` returns).
146
- * @param scheme The scheme to compute.
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 d,deltaEOK as f,parseOklch as u,serializeOklch as n}from"./color-engine.js";import{parseAnchorSource as l}from"./theme.js";import{computeThemeProperties as h}from"../root/helpers/compute-theme-properties.js";function A(e,o){const s=[];h(e,o,s);const r=s.map(v);return{scheme:o,seedColor:e.seedColor,anchors:m(e.anchors),tokens:r,adjustedTokens:r.filter(t=>t.adjusted).map(t=>t.token)}}function p(e){return e.tokens.map(o=>({token:o.token,source:o.source,requested:o.requested,resolved:o.resolved,apca:$(o),anchor:g(o)}))}function R(e){const o=e.adjustedTokens.length>0?`APCA moved ${e.adjustedTokens.join(", ")}`:"APCA moved nothing";console.info(`Espalier theme fit \u2014 ${e.scheme} scheme, seed ${e.seedColor}. ${o}.`),console.table(p(e))}function a(e,o){const s=Math.pow(10,o);return Math.round(e*s)/s}function i(e,o){return o===void 0?e:`${e}.${o}`}function m(e){const o={};for(const[s,r]of Object.entries(e)){if(typeof r=="string"){o[s]=r;continue}for(const[t,c]of Object.entries(r))o[t==="color"?s:i(s,t)]=c}return o}function v(e){const o=u(e.resolved),s=e.requested===void 0?e.resolved:n(e.requested),r={token:e.token,property:e.property,source:e.source,lightness:e.lightness,requested:s,resolved:e.resolved,adjusted:s!==e.resolved,sourceResolved:e.sourceResolved},t=l(e.source);if(t&&e.sourceResolved&&(r.anchor={reference:i(t.name,t.slot),color:n(e.base),deltaE:a(f(e.base,o),4)}),e.contrast){const c=e.contrast.background;r.apca={from:s,to:e.resolved,targetLc:e.contrast.targetLc,against:e.contrast.against,againstColor:n(c),fromLc:a(Math.abs(d(u(s),c)),1),toLc:a(Math.abs(d(o,c)),1)}}return r}function $(e){if(!e.apca)return"";const{targetLc:o,against:s,fromLc:r,toLc:t}=e.apca;return e.adjusted?`Lc ${o} vs ${s} \u2014 pushed from Lc ${r} to Lc ${t}`:`Lc ${o} vs ${s} \u2014 met at Lc ${r}`}function g(e){return e.anchor?`${e.anchor.reference} \u0394E ${e.anchor.deltaE}`:e.sourceResolved?"":"unresolved \u2014 seed used"}export{R as printThemeFitReport,A as themeFitReport,p as themeFitRows};
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};