@taprootio/espalier 2.6.0 → 2.7.1

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,6 +1,8 @@
1
1
  import { LitElement, type PropertyValues } from "lit";
2
2
  import { type SchemeEvents, type SeedColorRoot } from "../shared/bus-events.js";
3
3
  import { type EspalierTheme } from "../shared/theme.js";
4
+ import { type ImageTextureDefinition } from "../shared/image-texture-registry.js";
5
+ export { registerImageTexture, registeredImageTextures, BUILT_IN_IMAGE_TEXTURES, type ImageTextureDefinition, } from "../shared/image-texture-registry.js";
4
6
  export type GoogleFontLoadingPolicy = "auto" | "none";
5
7
  export type { SchemeEvents } from "../shared/bus-events.js";
6
8
  /**
@@ -52,6 +54,13 @@ export type { SchemeEvents } from "../shared/bus-events.js";
52
54
  * `theme-changed`, and `icon-sprite-url-changed`. Components derived from
53
55
  * `EspalierElementBase` use the same closest-root filter internally.
54
56
  *
57
+ * The root also owns the application's banner-texture vocabulary:
58
+ * `EspalierRoot.registerTexture(name, definition)` registers an
59
+ * image-backed texture preset that any
60
+ * [esp-image](/components/image) banner in the application selects with
61
+ * `texture="<name>"`, and `EspalierRoot.registeredTextures()` enumerates
62
+ * the presets for building texture pickers.
63
+ *
55
64
  * @customElement esp-root
56
65
  * @slot - Place Espalier components and content here.
57
66
  *
@@ -64,6 +73,17 @@ export type { SchemeEvents } from "../shared/bus-events.js";
64
73
  *
65
74
  */
66
75
  export declare class EspalierRoot extends LitElement implements SeedColorRoot {
76
+ /**
77
+ * Register (or replace) a named, image-backed banner-texture preset.
78
+ * Any `esp-image` banner under a root selects it with `texture="<name>"`;
79
+ * banners already pointing at the name re-render, so registration at
80
+ * mount and markup order never race. Static delegate to
81
+ * `registerImageTexture` so script-only contexts (docs demos, published
82
+ * sites) can reach it from the root element without module imports.
83
+ */
84
+ static registerTexture(name: string, definition: ImageTextureDefinition): void;
85
+ /** Snapshot of the registered texture presets, for building pickers. */
86
+ static registeredTextures(): Map<string, Readonly<ImageTextureDefinition>>;
67
87
  /** Unique ID for correlating bus events across multiple roots. */
68
88
  correlationId: `${string}-${string}-${string}-${string}-${string}`;
69
89
  /**
@@ -1,4 +1,4 @@
1
- var l=function(c,e,t,s){var h=arguments.length,o=h<3?e:s===null?s=Object.getOwnPropertyDescriptor(e,t):s,r;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")o=Reflect.decorate(c,e,t,s);else for(var d=c.length-1;d>=0;d--)(r=c[d])&&(o=(h<3?r(o):h>3?r(e,t,o):r(e,t))||o);return h>3&&o&&Object.defineProperty(e,t,o),o},n;import{css as k,html as b,LitElement as C}from"lit";import{customElement as E,property as a}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 f,DEFAULT_DARK_THEME as g,parseTheme as y,mergeTheme as v}from"../shared/theme.js";import{computeThemeProperties as x}from"./helpers/compute-theme-properties.js";let i=n=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={...g},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)}}subscribeScoped(e,t){return T(this,e,t)}get scheme(){return this.schemeBacker}set scheme(e){const t=e==="dark"?"dark":e==="light"?"light":"",s=this.schemeBacker,h=this.schemeExplicitlySet;this.resolvingDefaultScheme||(this.schemeExplicitlySet=t.length>0);const o=t||this.resolveDefaultScheme();h!==this.schemeExplicitlySet&&this.isConnected&&this.syncSystemSchemeListener(),o!==s&&(this.schemeBacker=o,this.requestUpdate("scheme",s),this.themeEventsReady&&p().publish("scheme-changed",{scheme:o,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,s){const h="data-esp-font",o=`${this.correlationId}-${e}`,r=document.head.querySelector(`link[${h}="${o}"]`);if(this.googleFontLoading==="none"){r?.remove();return}if(r&&t&&r.dataset.espFamily===t&&r.dataset.espWeight===(s??"")||(r?.remove(),!t))return;const d=encodeURIComponent(t),u=s==="bold"?"700":s==="normal"?"400":s,S=u&&u!=="400"?`:wght@${u}`:"",m=document.createElement("link");m.rel="stylesheet",m.href=`https://fonts.googleapis.com/css2?family=${d}${S}&display=swap`,m.setAttribute(h,o),m.dataset.espFamily=t,m.dataset.espWeight=s??"",document.head.appendChild(m)}syncGoogleFontLinks(){this.googleFontLoading==="none"&&this.removeRuntimeGoogleFontLinks();const e=this.activeTheme;this.injectGoogleFontLink("body",n.extractFamily(e.fontBody),e.fontWeightBody),this.injectGoogleFontLink("headings",n.extractFamily(e.fontHeadings),e.fontWeightHeadings),this.injectGoogleFontLink("brand",n.extractFamily(e.fontBrand),e.fontWeightBrand),this.injectGoogleFontLink("monospace",n.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(f,e):{...f};const t=this.darkThemeAttr?y(this.darkThemeAttr):null;this.resolvedDarkTheme=t?v(g,t):{...g}}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(r=>e.has(r)))&&(this.resolveThemes(),this.themeEventsReady&&p().publish("theme-changed",{correlationId:this.correlationId}));const h=x(this.activeTheme,this.scheme==="dark"?"dark":"light");for(const[r,d]of Object.entries(h))this.style.setProperty(r,d);const o=this.activeTheme.seedColor;this.themeEventsReady&&this.lastSeedColor&&this.lastSeedColor!==o&&p().publish("seed-color-changed",{seedColor:o,correlationId:this.correlationId}),this.lastSeedColor=o,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 s=document.createElement("link");s.rel="stylesheet",s.href=t,document.head.appendChild(s)}if(!document.getElementById("esp-root-base-styles")){const t=document.createElement("style");t.id="esp-root-base-styles",t.textContent=n.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,n.connectedRootCount+=1,n.connectedRootCount===1&&document.addEventListener("click",n.handleDocumentClick))}unregisterDocumentClickBridge(){this.documentClickRegistrationActive&&(this.documentClickRegistrationActive=!1,n.connectedRootCount=Math.max(0,n.connectedRootCount-1),n.connectedRootCount===0&&document.removeEventListener("click",n.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=c=>{const e=c.composedPath().filter(t=>t instanceof HTMLElement&&t.tagName==="ESP-POPOVER");p().publish("close-popovers",{source:c.target??void 0,skipPopovers:e})},i.lightDomStyles=`
1
+ var l=function(c,e,t,s){var h=arguments.length,o=h<3?e:s===null?s=Object.getOwnPropertyDescriptor(e,t):s,r;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")o=Reflect.decorate(c,e,t,s);else for(var d=c.length-1;d>=0;d--)(r=c[d])&&(o=(h<3?r(o):h>3?r(e,t,o):r(e,t))||o);return h>3&&o&&Object.defineProperty(e,t,o),o},n;import{css as k,html as b,LitElement as C}from"lit";import{customElement as E,property as a}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 L}from"../shared/image-texture-registry.js";import{registerImageTexture as H,registeredImageTextures as M,BUILT_IN_IMAGE_TEXTURES as P}from"../shared/image-texture-registry.js";let i=n=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.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 L()}subscribeScoped(e,t){return T(this,e,t)}get scheme(){return this.schemeBacker}set scheme(e){const t=e==="dark"?"dark":e==="light"?"light":"",s=this.schemeBacker,h=this.schemeExplicitlySet;this.resolvingDefaultScheme||(this.schemeExplicitlySet=t.length>0);const o=t||this.resolveDefaultScheme();h!==this.schemeExplicitlySet&&this.isConnected&&this.syncSystemSchemeListener(),o!==s&&(this.schemeBacker=o,this.requestUpdate("scheme",s),this.themeEventsReady&&p().publish("scheme-changed",{scheme:o,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,s){const h="data-esp-font",o=`${this.correlationId}-${e}`,r=document.head.querySelector(`link[${h}="${o}"]`);if(this.googleFontLoading==="none"){r?.remove();return}if(r&&t&&r.dataset.espFamily===t&&r.dataset.espWeight===(s??"")||(r?.remove(),!t))return;const d=encodeURIComponent(t),u=s==="bold"?"700":s==="normal"?"400":s,S=u&&u!=="400"?`:wght@${u}`:"",m=document.createElement("link");m.rel="stylesheet",m.href=`https://fonts.googleapis.com/css2?family=${d}${S}&display=swap`,m.setAttribute(h,o),m.dataset.espFamily=t,m.dataset.espWeight=s??"",document.head.appendChild(m)}syncGoogleFontLinks(){this.googleFontLoading==="none"&&this.removeRuntimeGoogleFontLinks();const e=this.activeTheme;this.injectGoogleFontLink("body",n.extractFamily(e.fontBody),e.fontWeightBody),this.injectGoogleFontLink("headings",n.extractFamily(e.fontHeadings),e.fontWeightHeadings),this.injectGoogleFontLink("brand",n.extractFamily(e.fontBrand),e.fontWeightBrand),this.injectGoogleFontLink("monospace",n.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(r=>e.has(r)))&&(this.resolveThemes(),this.themeEventsReady&&p().publish("theme-changed",{correlationId:this.correlationId}));const h=x(this.activeTheme,this.scheme==="dark"?"dark":"light");for(const[r,d]of Object.entries(h))this.style.setProperty(r,d);const o=this.activeTheme.seedColor;this.themeEventsReady&&this.lastSeedColor&&this.lastSeedColor!==o&&p().publish("seed-color-changed",{seedColor:o,correlationId:this.correlationId}),this.lastSeedColor=o,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 s=document.createElement("link");s.rel="stylesheet",s.href=t,document.head.appendChild(s)}if(!document.getElementById("esp-root-base-styles")){const t=document.createElement("style");t.id="esp-root-base-styles",t.textContent=n.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,n.connectedRootCount+=1,n.connectedRootCount===1&&document.addEventListener("click",n.handleDocumentClick))}unregisterDocumentClickBridge(){this.documentClickRegistrationActive&&(this.documentClickRegistrationActive=!1,n.connectedRootCount=Math.max(0,n.connectedRootCount-1),n.connectedRootCount===0&&document.removeEventListener("click",n.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=c=>{const e=c.composedPath().filter(t=>t instanceof HTMLElement&&t.tagName==="ESP-POPOVER");p().publish("close-popovers",{source:c.target??void 0,skipPopovers:e})},i.lightDomStyles=`
2
2
 
3
3
  esp-root {
4
4
  font-family: var(--esp-font-body);
@@ -125,4 +125,4 @@ var l=function(c,e,t,s){var h=arguments.length,o=h<3?e:s===null?s=Object.getOwnP
125
125
  :host {
126
126
  display: block;
127
127
  }
128
- `,l([a({attribute:!1})],i.prototype,"correlationId",void 0),l([a({attribute:"light-theme"})],i.prototype,"lightThemeAttr",void 0),l([a({attribute:"dark-theme"})],i.prototype,"darkThemeAttr",void 0),l([a({attribute:"font-css-root",type:String})],i.prototype,"fontCSSRoot",void 0),l([a({attribute:"font-definitions-url",type:String})],i.prototype,"fontDefinitionsUrl",void 0),l([a({attribute:"google-font-loading",type:String})],i.prototype,"googleFontLoading",void 0),l([a({attribute:"icon-sprite-url",type:String})],i.prototype,"iconSpriteUrl",void 0),l([a({attribute:"default-scheme",type:String,reflect:!0})],i.prototype,"defaultScheme",void 0),l([a({attribute:"scheme",type:String,reflect:!0})],i.prototype,"scheme",null),i=n=l([E("esp-root")],i);export{i as EspalierRoot};
128
+ `,l([a({attribute:!1})],i.prototype,"correlationId",void 0),l([a({attribute:"light-theme"})],i.prototype,"lightThemeAttr",void 0),l([a({attribute:"dark-theme"})],i.prototype,"darkThemeAttr",void 0),l([a({attribute:"font-css-root",type:String})],i.prototype,"fontCSSRoot",void 0),l([a({attribute:"font-definitions-url",type:String})],i.prototype,"fontDefinitionsUrl",void 0),l([a({attribute:"google-font-loading",type:String})],i.prototype,"googleFontLoading",void 0),l([a({attribute:"icon-sprite-url",type:String})],i.prototype,"iconSpriteUrl",void 0),l([a({attribute:"default-scheme",type:String,reflect:!0})],i.prototype,"defaultScheme",void 0),l([a({attribute:"scheme",type:String,reflect:!0})],i.prototype,"scheme",null),i=n=l([E("esp-root")],i);export{P as BUILT_IN_IMAGE_TEXTURES,i as EspalierRoot,H as registerImageTexture,M as registeredImageTextures};
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Named banner-texture registry.
3
+ *
4
+ * The built-in procedural presets cover the asset-free styles. Image-backed
5
+ * looks previously required hand-wiring the five consumer texture tokens on
6
+ * every banner; registering the look once turns it into a reusable preset a
7
+ * banner selects with `texture="<name>"` — and gives hosts (e.g. an editor
8
+ * preset picker) one place to enumerate the available styles.
9
+ */
10
+ /** Immutable snapshot of the built-in texture names, for pickers and docs. */
11
+ export declare const BUILT_IN_IMAGE_TEXTURES: readonly string[];
12
+ /** Whether a name is one of the built-in procedural textures. */
13
+ export declare function isBuiltInImageTexture(name: string): boolean;
14
+ /** A registered, image-backed texture preset. */
15
+ export interface ImageTextureDefinition {
16
+ /** CSS `background-image` value (a `url(...)` or gradient). */
17
+ image: string;
18
+ /** CSS `background-size`. Defaults to `auto`. */
19
+ size?: string;
20
+ /** CSS `background-repeat`. Defaults to `repeat`. */
21
+ repeat?: string;
22
+ /** CSS `background-position`. Defaults to `0 0`. */
23
+ position?: string;
24
+ /** Layer opacity (0–1). Defaults to `1`. */
25
+ opacity?: number;
26
+ /** CSS `mix-blend-mode` against the photo. Defaults to `normal`. */
27
+ blendMode?: string;
28
+ }
29
+ /**
30
+ * Register (or replace) a named texture preset. Live `esp-image` banners
31
+ * already pointing at the name re-render, so registration at mount and
32
+ * markup order never race.
33
+ *
34
+ * @throws when the name is not lowercase kebab-case or shadows a built-in.
35
+ */
36
+ export declare function registerImageTexture(name: string, definition: ImageTextureDefinition): void;
37
+ /** Look up a registered texture preset. */
38
+ export declare function getImageTexture(name: string): Readonly<ImageTextureDefinition> | undefined;
39
+ /**
40
+ * A snapshot of every registered preset, for building texture pickers.
41
+ * Mutating the returned map does not affect the registry.
42
+ */
43
+ export declare function registeredImageTextures(): Map<string, Readonly<ImageTextureDefinition>>;
44
+ /** Subscribe to registry changes. Returns the unsubscribe function. */
45
+ export declare function subscribeToImageTextureRegistry(listener: () => void): () => void;
@@ -0,0 +1 @@
1
+ const n=new Set(["none","dots","halftone","paper","grain","grunge","scanlines","duotone"]),a=Object.freeze([...n]);function i(e){return n.has(e)}const u=/^[a-z][a-z0-9-]*$/,t=new Map,r=new Set;function c(e,o){if(!u.test(e))throw new Error(`Invalid texture name "${e}". Use lowercase kebab-case (e.g. "linen-paper").`);if(i(e))throw new Error(`"${e}" is a built-in esp-image texture and cannot be replaced.`);t.set(e,Object.freeze({...o}));for(const s of r)s()}function g(e){return t.get(e)}function p(){return new Map(t)}function x(e){return r.add(e),()=>r.delete(e)}export{a as BUILT_IN_IMAGE_TEXTURES,g as getImageTexture,i as isBuiltInImageTexture,c as registerImageTexture,p as registeredImageTextures,x as subscribeToImageTextureRegistry};
@@ -1 +1 @@
1
- import{getEspBus as n}from"./bus-events.js";class u{constructor(e){this.bus=n(),this.rafId=0,this.handleClosePopovers=s=>{if(!this.opts.isOpen())return;const t=this.opts.host;if(this.opts.closeStrategy==="source-identity"){if(!s.source||s.source===t||s.source instanceof Node&&(t.contains(s.source)||t.shadowRoot?.contains(s.source)))return}else if(s.skipPopovers?.includes(t))return;this.opts.onShouldClose()},this.onScrollOrResize=()=>{this.rafId||(this.rafId=requestAnimationFrame(()=>{if(this.rafId=0,!!this.opts.isOpen()){if(this.opts.shouldContinueTracking&&!this.opts.shouldContinueTracking()){this.opts.onShouldClose();return}this.opts.onPositionUpdate?.()}}))},this.onDocumentClick=s=>{const t=this.opts.host,o=s.composedPath();if(o.includes(t))return;const r=this.opts.getInsideElements?.()??[];for(const i of r)if(i&&o.includes(i))return;this.opts.isOpen()&&this.opts.onOutsideClick?.()},this.opts=e,e.host.addController(this)}hostConnected(){this.bus.subscribe("close-popovers",this.handleClosePopovers)}hostDisconnected(){this.bus.unsubscribe("close-popovers",this.handleClosePopovers),this.stopTracking(),this.stopOutsideClick()}startTracking(){this.opts.onPositionUpdate&&(window.addEventListener("scroll",this.onScrollOrResize,{capture:!0,passive:!0}),window.addEventListener("resize",this.onScrollOrResize,{passive:!0}))}stopTracking(){window.removeEventListener("scroll",this.onScrollOrResize,{capture:!0}),window.removeEventListener("resize",this.onScrollOrResize),this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=0)}startOutsideClick(){this.opts.onOutsideClick&&document.addEventListener("click",this.onDocumentClick,!0)}stopOutsideClick(){document.removeEventListener("click",this.onDocumentClick,!0)}publishCloseOthers(){this.bus.publish("close-popovers",{source:this.opts.host})}publishCloseExcept(e){this.bus.publish("close-popovers",{skipPopovers:e})}}export{u as PopoverController};
1
+ import{getEspBus as c}from"./bus-events.js";function u(r){const t=[],e=new Set;let s=r;for(;s&&!e.has(s);){e.add(s);const i=s["linked-popover"];if(i){s=i;continue}const o=s.closest("esp-popover");if(o){t.includes(o)||t.push(o),s=o.parentElement||o.getRootNode()?.host;continue}const n=s.getRootNode();s=n instanceof ShadowRoot?n.host:void 0}return t}class l{constructor(t){this.bus=c(),this.rafId=0,this.handleClosePopovers=e=>{if(!this.opts.isOpen())return;const s=this.opts.host;if(this.opts.closeStrategy==="source-identity"){if(!e.source||e.source===s||e.source instanceof Node&&(s.contains(e.source)||s.shadowRoot?.contains(e.source)))return}else if(e.skipPopovers?.includes(s))return;this.opts.onShouldClose()},this.onScrollOrResize=()=>{this.rafId||(this.rafId=requestAnimationFrame(()=>{if(this.rafId=0,!!this.opts.isOpen()){if(this.opts.shouldContinueTracking&&!this.opts.shouldContinueTracking()){this.opts.onShouldClose();return}this.opts.onPositionUpdate?.()}}))},this.onDocumentClick=e=>{const s=this.opts.host,i=e.composedPath();if(i.includes(s))return;const o=this.opts.getInsideElements?.()??[];for(const n of o)if(n&&i.includes(n))return;this.opts.isOpen()&&this.opts.onOutsideClick?.()},this.opts=t,t.host.addController(this)}hostConnected(){this.bus.subscribe("close-popovers",this.handleClosePopovers)}hostDisconnected(){this.bus.unsubscribe("close-popovers",this.handleClosePopovers),this.stopTracking(),this.stopOutsideClick()}startTracking(){this.opts.onPositionUpdate&&(window.addEventListener("scroll",this.onScrollOrResize,{capture:!0,passive:!0}),window.addEventListener("resize",this.onScrollOrResize,{passive:!0}))}stopTracking(){window.removeEventListener("scroll",this.onScrollOrResize,{capture:!0}),window.removeEventListener("resize",this.onScrollOrResize),this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=0)}startOutsideClick(){this.opts.onOutsideClick&&document.addEventListener("click",this.onDocumentClick,!0)}stopOutsideClick(){document.removeEventListener("click",this.onDocumentClick,!0)}publishCloseOthers(t=[]){this.bus.publish("close-popovers",{source:this.opts.host,...t.length>0?{skipPopovers:t}:{}})}publishCloseExcept(t){this.bus.publish("close-popovers",{source:this.opts.host,skipPopovers:t})}}export{l as PopoverController,u as findContainingPopovers};
@@ -0,0 +1,51 @@
1
+ import{css as r,html as o,nothing as a}from"lit";function i(e={}){return o`
2
+ <div
3
+ class="esp-visual-overlay ${e.className??""}"
4
+ part=${e.rootPart||a}
5
+ aria-hidden="true"
6
+ >
7
+ <div class="esp-visual-overlay__image" part=${e.imagePart||a}></div>
8
+ <div class="esp-visual-overlay__scrim" part=${e.scrimPart||a}></div>
9
+ <div class="esp-visual-overlay__texture" part=${e.texturePart||a}></div>
10
+ </div>
11
+ `}const v=r`
12
+ .esp-visual-overlay {
13
+ position: absolute;
14
+ z-index: var(--_esp-overlay-z-index, auto);
15
+ inset: 0;
16
+ overflow: hidden;
17
+ pointer-events: none;
18
+ background: var(--_esp-overlay-base-color, transparent);
19
+ }
20
+
21
+ .esp-visual-overlay__image,
22
+ .esp-visual-overlay__scrim,
23
+ .esp-visual-overlay__texture {
24
+ position: absolute;
25
+ inset: 0;
26
+ pointer-events: none;
27
+ }
28
+
29
+ .esp-visual-overlay__image {
30
+ background-image: var(--_esp-overlay-image, none);
31
+ background-repeat: var(--_esp-overlay-image-repeat, repeat);
32
+ background-position: var(--_esp-overlay-image-position, 0 0);
33
+ background-size: var(--_esp-overlay-image-size, auto);
34
+ opacity: var(--_esp-overlay-image-opacity, 1);
35
+ mix-blend-mode: var(--_esp-overlay-image-blend-mode, normal);
36
+ }
37
+
38
+ .esp-visual-overlay__scrim {
39
+ background: var(--_esp-overlay-scrim, none);
40
+ opacity: var(--_esp-overlay-scrim-opacity, 1);
41
+ }
42
+
43
+ .esp-visual-overlay__texture {
44
+ background: var(--_esp-overlay-texture, none);
45
+ background-size: var(--_esp-overlay-texture-size, auto);
46
+ opacity: var(--_esp-overlay-texture-opacity, 1);
47
+ mix-blend-mode: var(--_esp-overlay-texture-blend-mode, normal);
48
+ filter: var(--_esp-overlay-texture-filter, none);
49
+ mask: var(--_esp-overlay-texture-mask, none);
50
+ }
51
+ `;export{i as renderVisualOverlay,v as visualOverlayStyles};
@@ -496,6 +496,48 @@
496
496
  "component": "esp-flyout",
497
497
  "description": "Stack order of the overlay-drawer mode. Defaults to `3000` — above page chrome, below the dialog drop zone (4000). Set it on the `esp-page` (or an ancestor), not on the flyout itself: the page reads the same token to hoist the drawer's aside above its header."
498
498
  },
499
+ {
500
+ "name": "--esp-focus-picker-focus-outline",
501
+ "category": "component",
502
+ "component": "esp-focus-picker",
503
+ "description": "Marker focus-visible outline."
504
+ },
505
+ {
506
+ "name": "--esp-focus-picker-gap",
507
+ "category": "component",
508
+ "component": "esp-focus-picker",
509
+ "description": "Gap between the stage and previews."
510
+ },
511
+ {
512
+ "name": "--esp-focus-picker-marker-border",
513
+ "category": "component",
514
+ "component": "esp-focus-picker",
515
+ "description": "Focal marker border."
516
+ },
517
+ {
518
+ "name": "--esp-focus-picker-marker-color",
519
+ "category": "component",
520
+ "component": "esp-focus-picker",
521
+ "description": "Focal marker fill."
522
+ },
523
+ {
524
+ "name": "--esp-focus-picker-marker-shadow",
525
+ "category": "component",
526
+ "component": "esp-focus-picker",
527
+ "description": "Focal marker shadow."
528
+ },
529
+ {
530
+ "name": "--esp-focus-picker-marker-size",
531
+ "category": "component",
532
+ "component": "esp-focus-picker",
533
+ "description": "Focal marker diameter."
534
+ },
535
+ {
536
+ "name": "--esp-focus-picker-stage-background",
537
+ "category": "component",
538
+ "component": "esp-focus-picker",
539
+ "description": "Empty/loading stage color."
540
+ },
499
541
  {
500
542
  "name": "--esp-font-body",
501
543
  "category": "root-theme",
@@ -1122,7 +1164,55 @@
1122
1164
  "name": "--esp-image-border",
1123
1165
  "category": "component",
1124
1166
  "component": "esp-image",
1125
- "description": "Set the border property of the image."
1167
+ "description": "Border; defaults to `2px solid var(--esp-color-border)` or none in banner mode."
1168
+ },
1169
+ {
1170
+ "name": "--esp-image-border-radius",
1171
+ "category": "component",
1172
+ "component": "esp-image",
1173
+ "description": "Corner radius; defaults to `var(--esp-size-border-radius)` or zero in banner mode."
1174
+ },
1175
+ {
1176
+ "name": "--esp-image-compact-width",
1177
+ "category": "component",
1178
+ "component": "esp-image",
1179
+ "description": "Component-width threshold; defaults to `40rem`."
1180
+ },
1181
+ {
1182
+ "name": "--esp-image-duotone-highlight-color",
1183
+ "category": "component",
1184
+ "component": "esp-image",
1185
+ "description": "Duotone highlight stop; defaults to a light stop derived from the theme complementary."
1186
+ },
1187
+ {
1188
+ "name": "--esp-image-duotone-shadow-color",
1189
+ "category": "component",
1190
+ "component": "esp-image",
1191
+ "description": "Duotone shadow stop; defaults to a dark stop derived from the theme primary."
1192
+ },
1193
+ {
1194
+ "name": "--esp-image-object-position",
1195
+ "category": "component",
1196
+ "component": "esp-image",
1197
+ "description": "Final object-position override."
1198
+ },
1199
+ {
1200
+ "name": "--esp-image-overlay-color",
1201
+ "category": "component",
1202
+ "component": "esp-image",
1203
+ "description": "Overlay text color; defaults to the theme heading color when the banner polarity matches the scheme."
1204
+ },
1205
+ {
1206
+ "name": "--esp-image-overlay-padding",
1207
+ "category": "component",
1208
+ "component": "esp-image",
1209
+ "description": "Overlay content padding."
1210
+ },
1211
+ {
1212
+ "name": "--esp-image-overlay-text-shadow",
1213
+ "category": "component",
1214
+ "component": "esp-image",
1215
+ "description": "Overlay text shadow; defaults to a soft polarity-matched halo. Set to `none` to disable."
1126
1216
  },
1127
1217
  {
1128
1218
  "name": "--esp-image-preview-border-color",
@@ -1142,6 +1232,72 @@
1142
1232
  "component": "esp-image-preview",
1143
1233
  "description": "The text color of the remove button."
1144
1234
  },
1235
+ {
1236
+ "name": "--esp-image-scrim-color",
1237
+ "category": "component",
1238
+ "component": "esp-image",
1239
+ "description": "Scrim ink; defaults to the theme background when the banner polarity matches the scheme, else a polarity-pinned derivation."
1240
+ },
1241
+ {
1242
+ "name": "--esp-image-scrim-opacity",
1243
+ "category": "component",
1244
+ "component": "esp-image",
1245
+ "description": "Scrim-strength opacity override."
1246
+ },
1247
+ {
1248
+ "name": "--esp-image-texture-blend-mode",
1249
+ "category": "component",
1250
+ "component": "esp-image",
1251
+ "description": "Texture blend-mode override."
1252
+ },
1253
+ {
1254
+ "name": "--esp-image-texture-color",
1255
+ "category": "component",
1256
+ "component": "esp-image",
1257
+ "description": "Procedural texture ink; defaults to a theme-derived color following `banner-scheme`."
1258
+ },
1259
+ {
1260
+ "name": "--esp-image-texture-image",
1261
+ "category": "component",
1262
+ "component": "esp-image",
1263
+ "description": "Consumer-supplied texture background image."
1264
+ },
1265
+ {
1266
+ "name": "--esp-image-texture-image-opacity",
1267
+ "category": "component",
1268
+ "component": "esp-image",
1269
+ "description": "Consumer texture opacity."
1270
+ },
1271
+ {
1272
+ "name": "--esp-image-texture-opacity",
1273
+ "category": "component",
1274
+ "component": "esp-image",
1275
+ "description": "Procedural texture opacity override."
1276
+ },
1277
+ {
1278
+ "name": "--esp-image-texture-position",
1279
+ "category": "component",
1280
+ "component": "esp-image",
1281
+ "description": "Consumer texture position."
1282
+ },
1283
+ {
1284
+ "name": "--esp-image-texture-repeat",
1285
+ "category": "component",
1286
+ "component": "esp-image",
1287
+ "description": "Consumer texture repeat."
1288
+ },
1289
+ {
1290
+ "name": "--esp-image-texture-scale",
1291
+ "category": "component",
1292
+ "component": "esp-image",
1293
+ "description": "CSS-gradient texture pitch override."
1294
+ },
1295
+ {
1296
+ "name": "--esp-image-texture-size",
1297
+ "category": "component",
1298
+ "component": "esp-image",
1299
+ "description": "Consumer texture size."
1300
+ },
1145
1301
  {
1146
1302
  "name": "--esp-image-upload-preview-background",
1147
1303
  "category": "component",
@@ -14,6 +14,9 @@ removing, or replacing non-code assets.
14
14
  | `dist/shared/virtualizer/**` | Vendored Lit Virtualizer runtime source compiled into Espalier's package. | `licenses/THIRD_PARTY_NOTICES.md` |
15
15
  | Brand favicons/logos | Taproot-owned Espalier brand assets. | None |
16
16
  | Demo textures | Taproot-owned generated PNG textures created for this repository. | None |
17
+ | `css/textures/*.png` | Deterministic 256×256 first-party tiles from `node scripts/generate-banner-textures.js`; byte-for-byte verification uses `--check`. | None |
18
+ | Banner preset reference sheets | Taproot-authored browser captures of the locked visual recipes over the provenance-cleared bright and dark canvas images. | None |
19
+ | `assets/focus-picker-unsplash.jpg` | Docs-only landscape photograph by Sasha Freemind, downloaded from Unsplash and used to make focal-point movement easy to see. | Attribution is included in the focus-picker demos. |
17
20
  | `assets/canvas-bg.jpg` | Docs-only light canvas texture — Envato Elements item "Seamless Pattern Design With Colorful Flowers" by yusufdemirci; certificate `licenses/license_certificate_A2BVZ6Y9DN.pdf`; attribution not required; kept out of the published `@taprootio/espalier` package. | None |
18
21
  | `assets/canvas-bg-dark.jpg` | Docs-only dark canvas texture — Envato Elements item "Nocturnal Garden Pattern" by Webvilla; certificate `licenses/license_certificate_HF5ZVPRNC2.pdf`; attribution not required; kept out of the published package. | None |
19
22
  | Demo JSON data | Taproot-authored fictitious samples or standards-derived factual color data. | None |
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "updatedAt": "2026-07-11",
3
+ "updatedAt": "2026-07-21",
4
4
  "entries": [
5
5
  {
6
6
  "id": "google-font-preview-assets",
@@ -155,6 +155,73 @@
155
155
  "noticePaths": [],
156
156
  "reviewStatus": "documented"
157
157
  },
158
+ {
159
+ "id": "generated-banner-textures",
160
+ "paths": [
161
+ "css/textures/espalier-paper.png",
162
+ "css/textures/espalier-grain.png",
163
+ "css/textures/espalier-grunge.png"
164
+ ],
165
+ "assetType": "deterministic seamless banner texture tiles (PNG, 256x256)",
166
+ "classification": "generated Taproot-owned",
167
+ "owner": "Taproot",
168
+ "source": "First-party procedural generator committed at scripts/generate-banner-textures.js",
169
+ "license": "Taproot-owned generated assets distributed with Espalier",
170
+ "transformations": [
171
+ "Generate periodic paper fibers, deterministic film grain, and toroidal distressed marks",
172
+ "Encode RGBA pixels as reproducible PNG bytes with Node built-ins",
173
+ "Regenerate with: node scripts/generate-banner-textures.js",
174
+ "Verify byte-for-byte with: node scripts/generate-banner-textures.js --check"
175
+ ],
176
+ "dimensions": "256x256 each",
177
+ "packageInclusion": "included in private and public npm packages under css/textures/",
178
+ "docsInclusion": "available to esp-image examples through css/textures/banner-textures.css",
179
+ "noticeRequirement": "none",
180
+ "noticePaths": [],
181
+ "reviewStatus": "manifest-gated"
182
+ },
183
+ {
184
+ "id": "banner-preset-reference-sheets",
185
+ "paths": [
186
+ "assets/esp-image-banners/reference-bright.jpg",
187
+ "assets/esp-image-banners/reference-dark.jpg"
188
+ ],
189
+ "assetType": "browser-captured banner preset reference sheets (JPEG, 1120x1768)",
190
+ "classification": "Taproot-authored derived documentation assets",
191
+ "owner": "Taproot",
192
+ "source": "Captured from __tests__/image/esp-image-banner-reference.browser.html in the Codex in-app browser on 2026-07-20, using the provenance-cleared canvas-bg.jpg and canvas-bg-dark.jpg source images",
193
+ "license": "Taproot-owned reference composition; underlying source-image ownership and license remain recorded in their own manifest entries",
194
+ "transformations": [
195
+ "Rendered the locked procedural scrim and texture recipes over the bright and dark source images",
196
+ "Captured one 1120x1768 browser viewport per scheme"
197
+ ],
198
+ "packageInclusion": "not included in npm package",
199
+ "docsInclusion": "copied to public docs assets and embedded by docs/guides/esp-image-banners.md",
200
+ "noticeRequirement": "none",
201
+ "noticePaths": [],
202
+ "reviewStatus": "documented"
203
+ },
204
+ {
205
+ "id": "unsplash-focus-picker-demo",
206
+ "paths": [
207
+ "assets/focus-picker-unsplash.jpg"
208
+ ],
209
+ "assetType": "docs-only landscape photograph (JPEG, 1800x1200)",
210
+ "classification": "third-party licensed",
211
+ "owner": "Sasha Freemind",
212
+ "source": "https://unsplash.com/photos/woman-standing-on-grass-field-frq5Q6Ne9k4",
213
+ "license": "Unsplash License (https://unsplash.com/license)",
214
+ "transformations": [
215
+ "Downloaded through the Unsplash image CDN at 1800px width with JPEG quality 85",
216
+ "No local crop or retouching"
217
+ ],
218
+ "dimensions": "1800x1200",
219
+ "packageInclusion": "not included in npm package",
220
+ "docsInclusion": "copied to public docs assets and used by esp-focus-picker examples",
221
+ "noticeRequirement": "none",
222
+ "noticePaths": [],
223
+ "reviewStatus": "documented"
224
+ },
158
225
  {
159
226
  "id": "demo-grid-data",
160
227
  "paths": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taprootio/espalier",
3
- "version": "2.6.0",
3
+ "version": "2.7.1",
4
4
  "packageManager": "bun@1.3.12",
5
5
  "description": "Espalier — a themeable, accessible, framework-agnostic, enterprise-grade design system built on web standards and love.",
6
6
  "customElements": "custom-elements.json",
@@ -98,6 +98,10 @@
98
98
  "types": "./dist/font-picker/esp-font-picker.d.ts",
99
99
  "import": "./dist/font-picker/esp-font-picker.js"
100
100
  },
101
+ "./focus-picker": {
102
+ "types": "./dist/focus-picker/esp-focus-picker.d.ts",
103
+ "import": "./dist/focus-picker/esp-focus-picker.js"
104
+ },
101
105
  "./footer": {
102
106
  "types": "./dist/footer/index.d.ts",
103
107
  "import": "./dist/footer/index.js"
@@ -275,6 +279,10 @@
275
279
  "import": "./dist/shared/bus-events.js"
276
280
  },
277
281
  "./espalier.token-manifest.json": "./espalier.token-manifest.json",
282
+ "./textures/banner-textures.css": "./css/textures/banner-textures.css",
283
+ "./textures/espalier-paper.png": "./css/textures/espalier-paper.png",
284
+ "./textures/espalier-grain.png": "./css/textures/espalier-grain.png",
285
+ "./textures/espalier-grunge.png": "./css/textures/espalier-grunge.png",
278
286
  "./custom-elements.json": "./custom-elements.json",
279
287
  "./espalier.css-data.json": "./espalier.css-data.json",
280
288
  "./css/esp-reset.css": "./css/esp-reset.css",
@@ -295,6 +303,8 @@
295
303
  "css/esp-reset.css",
296
304
  "css/fonts/**/*.css",
297
305
  "css/fonts/font-definitions.json",
306
+ "css/textures/banner-textures.css",
307
+ "css/textures/*.png",
298
308
  "custom-elements.json",
299
309
  "espalier.css-data.json",
300
310
  "espalier.token-manifest.json",