@taprootio/espalier 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,47 @@
1
+ import { EspalierElementBase } from "../shared/esp-element-base.js";
2
+ /**
3
+ * A layout-only column for creator-controlled link-group stacking inside
4
+ * [`<esp-footer>`](/components/footer/).
5
+ *
6
+ * Use bare `esp-footer-link-group` children when groups should auto-flow
7
+ * independently through the footer grid. Wrap ordered groups in
8
+ * `esp-footer-column` when they must remain together as one grid item:
9
+ *
10
+ * ```html
11
+ * <esp-footer columns="3">
12
+ * <esp-footer-column>
13
+ * <esp-footer-link-group heading="Product">
14
+ * <a href="/features">Features</a>
15
+ * <a href="/pricing">Pricing</a>
16
+ * </esp-footer-link-group>
17
+ * <esp-footer-link-group heading="Company">
18
+ * <a href="/about">About</a>
19
+ * <a href="/careers">Careers</a>
20
+ * </esp-footer-link-group>
21
+ * </esp-footer-column>
22
+ * </esp-footer>
23
+ * ```
24
+ *
25
+ * The component adds no heading, landmark, or chrome. It preserves the
26
+ * slotted order of its child groups and uses `--esp-footer-row-gap`, defaulting
27
+ * to `var(--esp-size-big)`, for its vertical rhythm.
28
+ *
29
+ * @customElement esp-footer-column
30
+ * @slot - Ordered `esp-footer-link-group` children belonging to this column.
31
+ * @cssprop --esp-footer-row-gap - Vertical gap between footer grid rows and
32
+ * link groups stacked in `esp-footer-column`. Defaults to `var(--esp-size-big)`.
33
+ * @docPageTitle Footer Column
34
+ * @docUrl /components/footer/column
35
+ * @menuGroup Structure
36
+ * @menuLabel Footer Column
37
+ * @menuIcon columns
38
+ */
39
+ export declare class EspalierFooterColumn extends EspalierElementBase {
40
+ protected render(): import("lit-html").TemplateResult<1>;
41
+ static styles: import("lit").CSSResult[];
42
+ }
43
+ declare global {
44
+ interface HTMLElementTagNameMap {
45
+ "esp-footer-column": EspalierFooterColumn;
46
+ }
47
+ }
@@ -0,0 +1,8 @@
1
+ var m=function(r,o,t,l){var n=arguments.length,e=n<3?o:l===null?l=Object.getOwnPropertyDescriptor(o,t):l,s;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")e=Reflect.decorate(r,o,t,l);else for(var f=r.length-1;f>=0;f--)(s=r[f])&&(e=(n<3?s(e):n>3?s(o,t,e):s(o,t))||e);return n>3&&e&&Object.defineProperty(o,t,e),e};import{css as c,html as a}from"lit";import{customElement as u}from"lit/decorators.js";import{EspalierElementBase as p}from"../shared/esp-element-base.js";let i=class extends p{render(){return a`<slot></slot>`}};i.styles=[...p.styles,c`
2
+ :host {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: var(--esp-footer-row-gap, var(--esp-size-big));
6
+ min-inline-size: 0;
7
+ }
8
+ `],i=m([u("esp-footer-column")],i);export{i as EspalierFooterColumn};
@@ -1,5 +1,6 @@
1
1
  import { type PropertyValues } from "lit";
2
2
  import { EspalierElementBase } from "../shared/esp-element-base.js";
3
+ import "./esp-footer-column.js";
3
4
  import "./esp-footer-link-group.js";
4
5
  export type FooterColumns = "auto" | "1" | "2" | "3" | "4" | "5" | "6";
5
6
  /**
@@ -8,9 +9,9 @@ export type FooterColumns = "auto" | "1" | "2" | "3" | "4" | "5" | "6";
8
9
  * Its background remains full-bleed while its content automatically aligns
9
10
  * to the surface of a containing [`<esp-page>`](/components/page/).
10
11
  *
11
- * Use [`<esp-footer-link-group>`](/components/footer/link-group/) children for
12
- * ordinary navigation columns. `columns` is a maximum: the grid always drops
13
- * columns when they no longer fit.
12
+ * Use bare [`<esp-footer-link-group>`](/components/footer/link-group/) children
13
+ * when navigation groups should auto-flow independently through the grid.
14
+ * `columns` is a maximum: the grid always drops columns when they no longer fit.
14
15
  *
15
16
  * ```html
16
17
  * <esp-footer
@@ -35,6 +36,33 @@ export type FooterColumns = "auto" | "1" | "2" | "3" | "4" | "5" | "6";
35
36
  * </esp-footer>
36
37
  * ```
37
38
  *
39
+ * For creator-controlled columns, wrap each ordered stack of groups in
40
+ * [`<esp-footer-column>`](/components/footer/column/). Each wrapper is one grid
41
+ * item at wide sizes; below the footer's compact container breakpoint, the
42
+ * columns become full-width rows in their slotted order. `columns` remains the
43
+ * maximum number of grid items per row:
44
+ *
45
+ * ```html
46
+ * <esp-footer columns="3">
47
+ * <esp-footer-column>
48
+ * <esp-footer-link-group heading="Product">
49
+ * <a href="/features">Features</a>
50
+ * <a href="/pricing">Pricing</a>
51
+ * </esp-footer-link-group>
52
+ * <esp-footer-link-group heading="Company">
53
+ * <a href="/about">About</a>
54
+ * <a href="/careers">Careers</a>
55
+ * </esp-footer-link-group>
56
+ * </esp-footer-column>
57
+ * <esp-footer-column>
58
+ * <esp-footer-link-group heading="Resources">
59
+ * <a href="/docs">Documentation</a>
60
+ * <a href="/support">Support</a>
61
+ * </esp-footer-link-group>
62
+ * </esp-footer-column>
63
+ * </esp-footer>
64
+ * ```
65
+ *
38
66
  * A slotted brand replaces configured branding, while the media slot accepts
39
67
  * a responsive `picture`, native image, or `esp-image`. Media spans the
40
68
  * surface-aligned content well; use the decorative background layer for
@@ -112,7 +140,8 @@ export type FooterColumns = "auto" | "1" | "2" | "3" | "4" | "5" | "6";
112
140
  * preferably responsive `<picture>` markup or an `esp-image` with appropriate
113
141
  * alternative text.
114
142
  * @slot brand - Custom brand identity. Overrides configured brand properties.
115
- * @slot - `esp-footer-link-group` navigation columns.
143
+ * @slot - Bare `esp-footer-link-group` children for automatic flow, or
144
+ * `esp-footer-column` children for creator-controlled group stacks.
116
145
  * @slot aside - Supporting content such as a newsletter, call to action,
117
146
  * contact details, social links, or locale controls.
118
147
  * @slot bottom - Copyright, legal/utility links, attribution, accessibility
@@ -137,7 +166,8 @@ export type FooterColumns = "auto" | "1" | "2" | "3" | "4" | "5" | "6";
137
166
  * @cssprop --esp-footer-padding-inline - Content inline padding. Defaults to `var(--esp-size-padding-page)`.
138
167
  * @cssprop --esp-footer-section-gap - Gap between media, primary, and bottom regions. Defaults to `var(--esp-size-big)`.
139
168
  * @cssprop --esp-footer-column-gap - Gap between link-group columns and major primary regions. Defaults to `var(--esp-size-padding-page)`.
140
- * @cssprop --esp-footer-row-gap - Compact-layout row gap. Defaults to `var(--esp-size-big)`.
169
+ * @cssprop --esp-footer-row-gap - Vertical gap between footer grid rows and
170
+ * link groups stacked in `esp-footer-column`. Defaults to `var(--esp-size-big)`.
141
171
  * @cssprop --esp-footer-link-group-min-width - Intrinsic minimum link-group column width. Defaults to `10rem`.
142
172
  * @cssprop --esp-footer-link-group-gap - Space between a link-group heading and its links. Defaults to `var(--esp-size-small)`.
143
173
  * @cssprop --esp-footer-brand-logo-size - Configured logo block size. Defaults to `3rem`.
@@ -1,4 +1,4 @@
1
- var t=function(i,o,e,a){var s=arguments.length,d=s<3?o:a===null?a=Object.getOwnPropertyDescriptor(o,e):a,l;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")d=Reflect.decorate(i,o,e,a);else for(var p=i.length-1;p>=0;p--)(l=i[p])&&(d=(s<3?l(d):s>3?l(o,e,d):l(o,e))||d);return s>3&&d&&Object.defineProperty(o,e,d),d};import{css as f,html as v,nothing as y}from"lit";import{customElement as x,property as n,state as C}from"lit/decorators.js";import{classMap as k}from"lit/directives/class-map.js";import{styleMap as _}from"lit/directives/style-map.js";import{renderConfiguredBrand as z,resolveConfiguredBrand as B}from"../shared/configured-brand.js";import{EspalierElementBase as g}from"../shared/esp-element-base.js";import{slotHasContent as h}from"../shared/slot-content.js";import"./esp-footer-link-group.js";const m=new Set(["auto","1","2","3","4","5","6"]),c={media:!1,brand:!1,groups:!1,aside:!1,bottom:!1},S={fromAttribute(i){return i&&m.has(i)?i:"auto"},toAttribute(i){return m.has(i)?i:"auto"}};let r=class extends g{constructor(){super(...arguments),this.slotContent={...c},this.brandText="",this.brandLogo="",this.lightBrandLogo="",this.darkBrandLogo="",this.brandHref="",this.brandAlt="",this.brandColor="",this.lightBrandColor="",this.darkBrandColor="",this.columns="auto",this.fullBleedContent=!1,this.landmarkLabel="",this.handleSlotChange=o=>{if(!(o.target instanceof HTMLSlotElement))return;const e=this.slotRegion(o.target);if(!e)return;const a=h(o.target);this.slotContent[e]!==a&&(this.slotContent={...this.slotContent,[e]:a})}}willUpdate(o){super.willUpdate(o),o.has("columns")&&!m.has(this.columns)&&(this.columns="auto")}slotRegion(o){return o.name?["media","brand","aside","bottom"].includes(o.name)?o.name:null:"groups"}firstUpdated(o){super.firstUpdated(o);const e={...c};for(const a of this.shadowRoot?.querySelectorAll("slot")??[]){const s=this.slotRegion(a);s&&(e[s]=h(a))}Object.entries(e).some(([a,s])=>this.slotContent[a]!==s)&&(this.slotContent=e)}render(){const{media:o,brand:e,groups:a,aside:s,bottom:d}=this.slotContent,l=B({scheme:this.scheme,brandLogo:this.brandLogo,brandColor:this.brandColor,lightBrandLogo:this.lightBrandLogo,darkBrandLogo:this.darkBrandLogo,lightBrandColor:this.lightBrandColor,darkBrandColor:this.darkBrandColor}),p=e||!!(l.brandLogo||this.brandText),u=p||a||s,b={primary:!0,"has-brand":p,"has-groups":a,"has-aside":s};return v`
1
+ var t=function(i,o,e,a){var s=arguments.length,d=s<3?o:a===null?a=Object.getOwnPropertyDescriptor(o,e):a,l;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")d=Reflect.decorate(i,o,e,a);else for(var p=i.length-1;p>=0;p--)(l=i[p])&&(d=(s<3?l(d):s>3?l(o,e,d):l(o,e))||d);return s>3&&d&&Object.defineProperty(o,e,d),d};import{css as f,html as v,nothing as y}from"lit";import{customElement as x,property as n,state as C}from"lit/decorators.js";import{classMap as k}from"lit/directives/class-map.js";import{styleMap as _}from"lit/directives/style-map.js";import{renderConfiguredBrand as z,resolveConfiguredBrand as B}from"../shared/configured-brand.js";import{EspalierElementBase as g}from"../shared/esp-element-base.js";import{slotHasContent as c}from"../shared/slot-content.js";import"./esp-footer-column.js";import"./esp-footer-link-group.js";const m=new Set(["auto","1","2","3","4","5","6"]),h={media:!1,brand:!1,groups:!1,aside:!1,bottom:!1},S={fromAttribute(i){return i&&m.has(i)?i:"auto"},toAttribute(i){return m.has(i)?i:"auto"}};let r=class extends g{constructor(){super(...arguments),this.slotContent={...h},this.brandText="",this.brandLogo="",this.lightBrandLogo="",this.darkBrandLogo="",this.brandHref="",this.brandAlt="",this.brandColor="",this.lightBrandColor="",this.darkBrandColor="",this.columns="auto",this.fullBleedContent=!1,this.landmarkLabel="",this.handleSlotChange=o=>{if(!(o.target instanceof HTMLSlotElement))return;const e=this.slotRegion(o.target);if(!e)return;const a=c(o.target);this.slotContent[e]!==a&&(this.slotContent={...this.slotContent,[e]:a})}}willUpdate(o){super.willUpdate(o),o.has("columns")&&!m.has(this.columns)&&(this.columns="auto")}slotRegion(o){return o.name?["media","brand","aside","bottom"].includes(o.name)?o.name:null:"groups"}firstUpdated(o){super.firstUpdated(o);const e={...h};for(const a of this.shadowRoot?.querySelectorAll("slot")??[]){const s=this.slotRegion(a);s&&(e[s]=c(a))}Object.entries(e).some(([a,s])=>this.slotContent[a]!==s)&&(this.slotContent=e)}render(){const{media:o,brand:e,groups:a,aside:s,bottom:d}=this.slotContent,l=B({scheme:this.scheme,brandLogo:this.brandLogo,brandColor:this.brandColor,lightBrandLogo:this.lightBrandLogo,darkBrandLogo:this.darkBrandLogo,lightBrandColor:this.lightBrandColor,darkBrandColor:this.darkBrandColor}),p=e||!!(l.brandLogo||this.brandText),u=p||a||s,b={primary:!0,"has-brand":p,"has-groups":a,"has-aside":s};return v`
2
2
  <footer
3
3
  part="footer"
4
4
  aria-label=${this.landmarkLabel||y}
@@ -257,6 +257,10 @@ var t=function(i,o,e,a){var s=arguments.length,d=s<3?o:a===null?a=Object.getOwnP
257
257
  }
258
258
 
259
259
  @container esp-footer (max-width: 48rem) {
260
+ .groups ::slotted(esp-footer-column) {
261
+ grid-column: 1 / -1;
262
+ }
263
+
260
264
  .primary,
261
265
  .primary.has-brand.has-groups:not(.has-aside),
262
266
  .primary.has-groups.has-aside:not(.has-brand),
@@ -1,2 +1,3 @@
1
+ export * from "./esp-footer-column.js";
1
2
  export * from "./esp-footer-link-group.js";
2
3
  export * from "./esp-footer.js";
@@ -1 +1 @@
1
- export*from"./esp-footer-link-group.js";export*from"./esp-footer.js";
1
+ export*from"./esp-footer-column.js";export*from"./esp-footer-link-group.js";export*from"./esp-footer.js";
@@ -1,7 +1,8 @@
1
1
  import { LitElement, type PropertyValues } from "lit";
2
- import { type SeedColorRoot } from "../shared/bus-events.js";
2
+ import { type SchemeEvents, type SeedColorRoot } from "../shared/bus-events.js";
3
3
  import { type EspalierTheme } from "../shared/theme.js";
4
4
  export type GoogleFontLoadingPolicy = "auto" | "none";
5
+ export type { SchemeEvents } from "../shared/bus-events.js";
5
6
  /**
6
7
  * Root container for the Espalier design system.
7
8
  *
@@ -26,6 +27,31 @@ export type GoogleFontLoadingPolicy = "auto" | "none";
26
27
  * </esp-root>
27
28
  * ```
28
29
  *
30
+ * ### Nested theme previews and scoped events
31
+ *
32
+ * A nested root creates a scoped theme for its descendants. It does not create
33
+ * a separate event bus: every root publishes theme changes through the same
34
+ * Espalier singleton. Subscribe through the root whose changes you own so a
35
+ * nested preview cannot be mistaken for an application-level theme change.
36
+ *
37
+ * ```ts
38
+ * const applicationRoot = document.querySelector<EspalierRoot>("#application-theme")!;
39
+ *
40
+ * const unsubscribe = applicationRoot.subscribeScoped("scheme-changed", ({ scheme }) => {
41
+ * localStorage.setItem("scheme", scheme);
42
+ * });
43
+ *
44
+ * // Initial state is read directly; scoped events report later changes only.
45
+ * renderScheme(applicationRoot.scheme);
46
+ *
47
+ * // Call when the owning integration is disposed.
48
+ * unsubscribe();
49
+ * ```
50
+ *
51
+ * This contract covers `scheme-changed`, `seed-color-changed`,
52
+ * `theme-changed`, and `icon-sprite-url-changed`. Components derived from
53
+ * `EspalierElementBase` use the same closest-root filter internally.
54
+ *
29
55
  * @customElement esp-root
30
56
  * @slot - Place Espalier components and content here.
31
57
  *
@@ -40,6 +66,17 @@ export type GoogleFontLoadingPolicy = "auto" | "none";
40
66
  export declare class EspalierRoot extends LitElement implements SeedColorRoot {
41
67
  /** Unique ID for correlating bus events across multiple roots. */
42
68
  correlationId: `${string}-${string}-${string}-${string}-${string}`;
69
+ /**
70
+ * Subscribe to a theme event published by this root only.
71
+ *
72
+ * All roots share Espalier's global bus. This method filters foreign and
73
+ * nested-root payloads by `correlationId` and returns a cleanup function.
74
+ * It reports changes after mount; read initial state from this root.
75
+ *
76
+ * @param event Root-scoped theme event to observe.
77
+ * @param handler Callback invoked with that event's typed payload.
78
+ */
79
+ subscribeScoped<K extends keyof SchemeEvents>(event: K, handler: (payload: SchemeEvents[K]) => void): () => void;
43
80
  /**
44
81
  * Base64-encoded JSON partial theme for the **light** scheme.
45
82
  *
@@ -178,6 +215,7 @@ export declare class EspalierRoot extends LitElement implements SeedColorRoot {
178
215
  get seedColor(): string;
179
216
  /** Returns the resolved theme for the active scheme. */
180
217
  get activeTheme(): EspalierTheme;
218
+ connectedCallback(): void;
181
219
  protected willUpdate(changed: PropertyValues): void;
182
220
  protected firstUpdated(): void;
183
221
  protected updated(changedProperties: PropertyValues): void;
@@ -1,4 +1,4 @@
1
- var h=function(m,e,t,o){var n=arguments.length,s=n<3?e:o===null?o=Object.getOwnPropertyDescriptor(e,t):o,r;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")s=Reflect.decorate(m,e,t,o);else for(var a=m.length-1;a>=0;a--)(r=m[a])&&(s=(n<3?r(s):n>3?r(e,t,s):r(e,t))||s);return n>3&&s&&Object.defineProperty(e,t,s),s},c;import{css as k,html as b,LitElement as T}from"lit";import{customElement as w,property as l}from"lit/decorators.js";import{getEspBus as p}from"../shared/bus-events.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 E}from"./helpers/compute-theme-properties.js";let i=c=class extends T{constructor(){super(...arguments),this.schemeBacker="",this.schemeExplicitlySet=!1,this.resolvingDefaultScheme=!1,this.systemSchemeQuery=null,this.lastSeedColor="",this.resolvedLightTheme={...f},this.resolvedDarkTheme={...g},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)}}get scheme(){return this.schemeBacker}set scheme(e){const t=e==="dark"?"dark":e==="light"?"light":"",o=this.schemeBacker;this.resolvingDefaultScheme||(this.schemeExplicitlySet=t.length>0),t!==o&&(this.schemeBacker=t,this.requestUpdate("scheme",o),t&&p().publish("scheme-changed",{scheme:t,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 n="data-esp-font",s=`${this.correlationId}-${e}`,r=document.head.querySelector(`link[${n}="${s}"]`);if(this.googleFontLoading==="none"){r?.remove();return}if(r&&t&&r.dataset.espFamily===t&&r.dataset.espWeight===(o??"")||(r?.remove(),!t))return;const a=encodeURIComponent(t),u=o==="bold"?"700":o==="normal"?"400":o,S=u&&u!=="400"?`:wght@${u}`:"",d=document.createElement("link");d.rel="stylesheet",d.href=`https://fonts.googleapis.com/css2?family=${a}${S}&display=swap`,d.setAttribute(n,s),d.dataset.espFamily=t,d.dataset.espWeight=o??"",document.head.appendChild(d)}syncGoogleFontLinks(){this.googleFontLoading==="none"&&this.removeRuntimeGoogleFontLinks();const e=this.activeTheme;this.injectGoogleFontLink("body",c.extractFamily(e.fontBody),e.fontWeightBody),this.injectGoogleFontLink("headings",c.extractFamily(e.fontHeadings),e.fontWeightHeadings),this.injectGoogleFontLink("brand",c.extractFamily(e.fontBrand),e.fontWeightBrand),this.injectGoogleFontLink("monospace",c.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}}willUpdate(e){const t=["lightThemeAttr","darkThemeAttr"];(!this.lastSeedColor||t.some(r=>e.has(r)))&&(this.resolveThemes(),p().publish("theme-changed",{correlationId:this.correlationId}));const n=E(this.activeTheme,this.scheme==="dark"?"dark":"light");for(const[r,a]of Object.entries(n))this.style.setProperty(r,a);const s=this.activeTheme.seedColor;this.lastSeedColor&&this.lastSeedColor!==s&&p().publish("seed-color-changed",{seedColor:s,correlationId:this.correlationId}),this.lastSeedColor=s,e.has("iconSpriteUrl")&&p().publish("icon-sprite-url-changed",{iconSpriteUrl:this.iconSpriteUrl,correlationId:this.correlationId}),this.syncGoogleFontLinks()}firstUpdated(){document.addEventListener("click",t=>{const o=t.composedPath().filter(n=>n instanceof HTMLElement&&n.tagName==="ESP-POPOVER");p().publish("close-popovers",{source:t.target??void 0,skipPopovers:o})}),this.resizeObserver.observe(this);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=c.lightDomStyles,document.head.appendChild(t)}this.applyDefaultScheme()}updated(e){(e.has("defaultScheme")||e.has("scheme"))&&!this.schemeExplicitlySet&&this.applyDefaultScheme()}disconnectedCallback(){this.systemSchemeQuery?.removeEventListener("change",this.handleSystemSchemeChange),this.systemSchemeQuery=null,this.resizeObserver.disconnect(),super.disconnectedCallback()}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.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.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 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=`
2
2
 
3
3
  esp-root {
4
4
  font-family: var(--esp-font-body);
@@ -125,4 +125,4 @@ var h=function(m,e,t,o){var n=arguments.length,s=n<3?e:o===null?o=Object.getOwnP
125
125
  :host {
126
126
  display: block;
127
127
  }
128
- `,h([l({attribute:!1})],i.prototype,"correlationId",void 0),h([l({attribute:"light-theme"})],i.prototype,"lightThemeAttr",void 0),h([l({attribute:"dark-theme"})],i.prototype,"darkThemeAttr",void 0),h([l({attribute:"font-css-root",type:String})],i.prototype,"fontCSSRoot",void 0),h([l({attribute:"font-definitions-url",type:String})],i.prototype,"fontDefinitionsUrl",void 0),h([l({attribute:"google-font-loading",type:String})],i.prototype,"googleFontLoading",void 0),h([l({attribute:"icon-sprite-url",type:String})],i.prototype,"iconSpriteUrl",void 0),h([l({attribute:"default-scheme",type:String,reflect:!0})],i.prototype,"defaultScheme",void 0),h([l({attribute:"scheme",type:String,reflect:!0})],i.prototype,"scheme",null),i=c=h([w("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{i as EspalierRoot};
@@ -9,11 +9,15 @@
9
9
  * ```ts
10
10
  * import { getEspBus } from "@taprootio/espalier";
11
11
  *
12
- * // Use built-in Espalier events out of the box:
12
+ * // Use the global bus for application-global Espalier events:
13
13
  * const bus = getEspBus();
14
- * bus.subscribe("scheme-changed", (payload) => { … });
15
14
  * bus.publish("show-toast", { message: "Done!" });
16
15
  *
16
+ * // Theme events are root-scoped. Subscribe through the owning root so
17
+ * // nested preview roots cannot trigger an application-level handler:
18
+ * const root = document.querySelector("esp-root")!;
19
+ * const unsubscribe = root.subscribeScoped("scheme-changed", ({ scheme }) => { … });
20
+ *
17
21
  * // Or extend with your own application events:
18
22
  * type MyAppEvents = EspBusEventMap & {
19
23
  * "sse-notification": { type: string; payload: unknown };
@@ -41,9 +45,12 @@ export interface SeedColorRoot {
41
45
  /**
42
46
  * Theme coordination events.
43
47
  *
44
- * Published by `<esp-root>` and subscribed to by every
45
- * `EspalierElementBase` descendant so that scheme, seed,
46
- * and full-theme changes propagate to variant-aware children.
48
+ * Published by `<esp-root>` after initial mount and subscribed to by every
49
+ * `EspalierElementBase` descendant so that scheme, seed, and full-theme changes
50
+ * propagate to variant-aware children. All roots share one global bus, so
51
+ * consumers should use `EspalierRoot.subscribeScoped()` instead of subscribing
52
+ * to these events directly. Read initial values from the root; these events are
53
+ * change-only and are not replayed for late subscribers.
47
54
  *
48
55
  * @docUrl /api/scheme-events
49
56
  * @menuGroup Bus Events
@@ -1,4 +1,4 @@
1
- var p=function(g,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(g,e,t,r);else for(var c=g.length-1;c>=0;c--)(a=g[c])&&(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 x,LitElement as E}from"lit";import{property as m,state as I}from"lit/decorators.js";import{getEspBus as V}from"./bus-events.js";import{traverseToClosest as S}from"./utilities.js";import{parseOklch as A,serializeOklch as y,gamutMapToSRGB as O,deriveSemantic as L,deriveSemanticWithContrast as U}from"./color-engine.js";import{computeVariants as T}from"../root/helpers/compute-variants.js";import{SEMANTIC_COLOR_NAMES as R,semanticToCSS as P}from"./theme.js";import{alignAttributeTextInheritance as _,focusRing as z}from"./style-fragments.js";class n extends E{constructor(){super(...arguments),this.seedColorBacker="oklch(0.7 0.125 216)",this.espRoot=null,this.variantTokenOriginalValues=new Map,this.rootEventSubscriptionsActive=!1,this.variantBacker="primary",this.correlationId=globalThis.crypto?.randomUUID?.()??Math.random().toString(36),this.scheme="light",this.handleSeedColorChanged=e=>{const t=this.syncRootFromDom(!1);!t||e.correlationId!==t.correlationId||(this.seedColor=e.seedColor,this.applyVariantTokens())},this.handleSchemeChanged=e=>{const t=this.syncRootFromDom(!1);!t||e.correlationId!==t.correlationId||this.scheme===e.scheme||(this.scheme=e.scheme,this.applyVariantTokens())},this.handleThemeChanged=e=>{const t=this.syncRootFromDom(!1);!t||e.correlationId!==t.correlationId||this.applyVariantTokens()},this.handleIconSpriteUrlChanged=e=>{const t=this.syncRootFromDom(!1);!t||e.correlationId!==t.correlationId||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 variant(){return this.variantBacker}set variant(e){this.variantBacker=e,this.applyVariantTokens()}connectedCallback(){super.connectedCallback(),this.subscribeToRootEvents(),this.hasUpdated&&this.syncRootFromDom(!1)}disconnectedCallback(){super.disconnectedCallback(),this.unsubscribeFromRootEvents()}firstUpdated(e){this.syncRootFromDom(!0)}syncRootFromDom(e){const t=S(this,"esp-root");if(!t){if(this.espRoot&&this.clearVariantTokens(),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,c=this.seedColor!==t.seedColor;return this.espRoot=t,(r||a||c)&&(this.scheme=s,this.seedColor=t.seedColor,this.applyVariantTokens()),r&&this.requestUpdate(),t}subscribeToRootEvents(){if(this.rootEventSubscriptionsActive)return;const e=V();e.subscribe("seed-color-changed",this.handleSeedColorChanged),e.subscribe("scheme-changed",this.handleSchemeChanged),e.subscribe("theme-changed",this.handleThemeChanged),e.subscribe("icon-sprite-url-changed",this.handleIconSpriteUrlChanged),this.rootEventSubscriptionsActive=!0}unsubscribeFromRootEvents(){if(!this.rootEventSubscriptionsActive)return;const e=V();e.unsubscribe("seed-color-changed",this.handleSeedColorChanged),e.unsubscribe("scheme-changed",this.handleSchemeChanged),e.unsubscribe("theme-changed",this.handleThemeChanged),e.unsubscribe("icon-sprite-url-changed",this.handleIconSpriteUrlChanged),this.rootEventSubscriptionsActive=!1}traverseToClosest(e){return S(this,e)}applyVariantTokens(){if(!this.espRoot)return;const e=this.getVariantColorSource();if(!e){this.clearVariantTokens();return}const t=this.espRoot.activeTheme,r=A(t.seedColor);if(!r)return;const s=T(r,t)[e];if(!s){this.clearVariantTokens();return}this.setVariantTokenProperty("--esp-color-primary",y(O(s)));const a=T(s,t),c=n.statusVariantSources.has(e),{textContrast:k}=n,f={};for(const i of R){if(k[i])continue;const l=t.semanticMappings[i],h=t.chroma[i],d=this.effectiveVariantTokenSource(i,l.source,c),b=a[d],v=t.lightness[l.lightness],u=L(b,v,h.min,h.max);f[i]=u,this.setVariantTokenProperty(P(i),y(u))}for(const i of R){const l=k[i];if(!l)continue;const h=t.semanticMappings[i],d=t.chroma[i],b=this.effectiveVariantTokenSource(i,h.source,c),v=a[b],u=t.lightness[h.lightness],w=f[l.bg],C=U(v,u,d.min,d.max,w,l.targetLc);f[i]=C,this.setVariantTokenProperty(P(i),y(C))}}effectiveVariantTokenSource(e,t,r){return r&&n.semanticActionTokens.has(e)?"primary":t}getVariantColorSource(){switch(this.variant){case"":case"primary":case"neutral":return"";case"info":return"complementary";default:return this.variant}}setVariantTokenProperty(e,t){const r=this.variantTokenOriginalValues.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.variantTokenOriginalValues.set(e,{generatedPriority:"",generatedValue:t,originalPriority:this.style.getPropertyPriority(e),originalValue:o.length?o:null})}this.style.setProperty(e,t)}clearVariantTokens(){for(const[e,t]of this.variantTokenOriginalValues){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.variantTokenOriginalValues.clear()}}n.textContrast={text:{bg:"background",targetLc:75},dangerText:{bg:"background",targetLc:75},headings:{bg:"background",targetLc:60},headingsHover:{bg:"background",targetLc:60},link:{bg:"background",targetLc:75},linkHover:{bg:"background",targetLc:75},actionText:{bg:"actionBackground",targetLc:75},inputCaret:{bg:"layer2",targetLc:60},inputSelection:{bg:"inputSelectionBg",targetLc:60}},n.statusVariantSources=new Set(["danger","success","warning"]),n.semanticActionTokens=new Set(["actionBackground","actionText"]),n.styles=[z(".esp-field:focus-within","--esp-field-focus-shadow"),_,x`
1
+ var p=function(f,e,t,o){var r=arguments.length,s=r<3?e:o===null?o=Object.getOwnPropertyDescriptor(e,t):o,a;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")s=Reflect.decorate(f,e,t,o);else for(var c=f.length-1;c>=0;c--)(a=f[c])&&(s=(r<3?a(s):r>3?a(e,t,s):a(e,t))||s);return r>3&&s&&Object.defineProperty(e,t,s),s};import{css as E,LitElement as x}from"lit";import{property as y,state as U}from"lit/decorators.js";import{subscribeToRootEvent as g}from"./root-event-subscription.js";import{traverseToClosest as V}from"./utilities.js";import{parseOklch as A,serializeOklch as k,gamutMapToSRGB as O,deriveSemantic as L,deriveSemanticWithContrast as F}from"./color-engine.js";import{computeVariants as T}from"../root/helpers/compute-variants.js";import{SEMANTIC_COLOR_NAMES as S,semanticToCSS as P}from"./theme.js";import{alignAttributeTextInheritance as _,focusRing as z}from"./style-fragments.js";class n extends x{constructor(){super(...arguments),this.seedColorBacker="oklch(0.7 0.125 216)",this.espRoot=null,this.variantTokenOriginalValues=new Map,this.rootEventSubscriptionsActive=!1,this.subscribedRoot=null,this.rootEventUnsubscribers=[],this.variantBacker="primary",this.correlationId=globalThis.crypto?.randomUUID?.()??Math.random().toString(36),this.scheme="light",this.handleSeedColorChanged=e=>{this.syncRootFromDom(!1)&&(this.seedColor=e.seedColor,this.applyVariantTokens())},this.handleSchemeChanged=e=>{!this.syncRootFromDom(!1)||this.scheme===e.scheme||(this.scheme=e.scheme,this.applyVariantTokens())},this.handleThemeChanged=()=>{this.syncRootFromDom(!1)&&this.applyVariantTokens()},this.handleIconSpriteUrlChanged=()=>{this.syncRootFromDom(!1)&&this.requestUpdate()}}get seedColor(){return this.seedColorBacker}set seedColor(e){this.seedColorBacker=e}focusResolvedElementAfterUpdate(e,t){const o=()=>{const r=e();return r?(r.focus(t),!0):!1};o()||this.updateComplete.then(()=>{o()})}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 variant(){return this.variantBacker}set variant(e){this.variantBacker=e,this.applyVariantTokens()}connectedCallback(){super.connectedCallback();const e=this.syncRootFromDom(!1);e&&this.subscribeToRootEvents(e)}disconnectedCallback(){super.disconnectedCallback(),this.unsubscribeFromRootEvents()}firstUpdated(e){this.syncRootFromDom(!0)}syncRootFromDom(e){const t=V(this,"esp-root");if(!t){if(this.espRoot&&this.clearVariantTokens(),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 o=t!==this.espRoot,s=t.scheme==="dark"?"dark":"light",a=this.scheme!==s,c=this.seedColor!==t.seedColor;return this.espRoot=t,o&&this.isConnected&&this.subscribeToRootEvents(t),(o||a||c)&&(this.scheme=s,this.seedColor=t.seedColor,this.applyVariantTokens()),o&&this.requestUpdate(),t}subscribeToRootEvents(e){this.rootEventSubscriptionsActive&&this.subscribedRoot===e||(this.unsubscribeFromRootEvents(),this.subscribedRoot=e,this.rootEventUnsubscribers=[g(e,"seed-color-changed",this.handleSeedColorChanged),g(e,"scheme-changed",this.handleSchemeChanged),g(e,"theme-changed",this.handleThemeChanged),g(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 V(this,e)}applyVariantTokens(){if(!this.espRoot)return;const e=this.getVariantColorSource();if(!e){this.clearVariantTokens();return}const t=this.espRoot.activeTheme,o=A(t.seedColor);if(!o)return;const s=T(o,t)[e];if(!s){this.clearVariantTokens();return}this.setVariantTokenProperty("--esp-color-primary",k(O(s)));const a=T(s,t),c=n.statusVariantSources.has(e),{textContrast:C}=n,b={};for(const i of S){if(C[i])continue;const l=t.semanticMappings[i],h=t.chroma[i],d=this.effectiveVariantTokenSource(i,l.source,c),v=a[d],m=t.lightness[l.lightness],u=L(v,m,h.min,h.max);b[i]=u,this.setVariantTokenProperty(P(i),k(u))}for(const i of S){const l=C[i];if(!l)continue;const h=t.semanticMappings[i],d=t.chroma[i],v=this.effectiveVariantTokenSource(i,h.source,c),m=a[v],u=t.lightness[h.lightness],w=b[l.bg],R=F(m,u,d.min,d.max,w,l.targetLc);b[i]=R,this.setVariantTokenProperty(P(i),k(R))}}effectiveVariantTokenSource(e,t,o){return o&&n.semanticActionTokens.has(e)?"primary":t}getVariantColorSource(){switch(this.variant){case"":case"primary":case"neutral":return"";case"info":return"complementary";default:return this.variant}}setVariantTokenProperty(e,t){const o=this.variantTokenOriginalValues.get(e);if(o){const r=this.style.getPropertyValue(e),s=this.style.getPropertyPriority(e);(r!==o.generatedValue||s!==o.generatedPriority)&&(o.originalValue=r.length?r:null,o.originalPriority=s),o.generatedValue=t,o.generatedPriority=""}else{const r=this.style.getPropertyValue(e);this.variantTokenOriginalValues.set(e,{generatedPriority:"",generatedValue:t,originalPriority:this.style.getPropertyPriority(e),originalValue:r.length?r:null})}this.style.setProperty(e,t)}clearVariantTokens(){for(const[e,t]of this.variantTokenOriginalValues){const o=this.style.getPropertyValue(e),r=this.style.getPropertyPriority(e);(o!==t.generatedValue||r!==t.generatedPriority)&&(t.originalValue=o.length?o:null,t.originalPriority=r),t.originalValue===null?this.style.removeProperty(e):this.style.setProperty(e,t.originalValue,t.originalPriority)}this.variantTokenOriginalValues.clear()}}n.textContrast={text:{bg:"background",targetLc:75},dangerText:{bg:"background",targetLc:75},headings:{bg:"background",targetLc:60},headingsHover:{bg:"background",targetLc:60},link:{bg:"background",targetLc:75},linkHover:{bg:"background",targetLc:75},actionText:{bg:"actionBackground",targetLc:75},inputCaret:{bg:"layer2",targetLc:60},inputSelection:{bg:"inputSelectionBg",targetLc:60}},n.statusVariantSources=new Set(["danger","success","warning"]),n.semanticActionTokens=new Set(["actionBackground","actionText"]),n.styles=[z(".esp-field:focus-within","--esp-field-focus-shadow"),_,E`
2
2
  :host {
3
3
 
4
4
  --_esp-field-resolved-hover-bg: var(
@@ -122,4 +122,4 @@ var p=function(g,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
- `],p([I()],n.prototype,"seedColor",null),p([m({attribute:!1})],n.prototype,"correlationId",void 0),p([m({type:String,reflect:!0})],n.prototype,"scheme",void 0),p([m({type:String,attribute:"variant",reflect:!0})],n.prototype,"variant",null);export{n as EspalierElementBase};
125
+ `],p([U()],n.prototype,"seedColor",null),p([y({attribute:!1})],n.prototype,"correlationId",void 0),p([y({type:String,reflect:!0})],n.prototype,"scheme",void 0),p([y({type:String,attribute:"variant",reflect:!0})],n.prototype,"variant",null);export{n as EspalierElementBase};
@@ -0,0 +1 @@
1
+ import{getEspBus as n}from"./bus-events.js";function u(c,o,e){const r=t=>{t.correlationId===c.correlationId&&e(t)},s=n();return s.subscribe(o,r),()=>s.unsubscribe(o,r)}export{u as subscribeToRootEvent};
@@ -672,7 +672,7 @@
672
672
  "name": "--esp-footer-row-gap",
673
673
  "category": "component",
674
674
  "component": "esp-footer",
675
- "description": "Compact-layout row gap. Defaults to `var(--esp-size-big)`."
675
+ "description": "Vertical gap between footer grid rows and link groups stacked in `esp-footer-column`. Defaults to `var(--esp-size-big)`."
676
676
  },
677
677
  {
678
678
  "name": "--esp-footer-section-gap",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taprootio/espalier",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
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",
@@ -102,6 +102,10 @@
102
102
  "types": "./dist/footer/index.d.ts",
103
103
  "import": "./dist/footer/index.js"
104
104
  },
105
+ "./footer/column": {
106
+ "types": "./dist/footer/esp-footer-column.d.ts",
107
+ "import": "./dist/footer/esp-footer-column.js"
108
+ },
105
109
  "./footer/link-group": {
106
110
  "types": "./dist/footer/esp-footer-link-group.d.ts",
107
111
  "import": "./dist/footer/esp-footer-link-group.js"