@taprootio/espalier 2.3.0 → 2.4.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.
@@ -35,7 +35,7 @@ export * from "./esp-breadcrumb.js";
35
35
  * @customElement esp-breadcrumbs
36
36
  * @slot - `<esp-breadcrumb>` items to display.
37
37
  * @cssprop --esp-breadcrumb-separator-color - Set the color of the separator. It defaults
38
- * to a bright complementary color.
38
+ * to the semantic heading color so navigation structure stays visible in light and dark themes.
39
39
  *
40
40
  * ```html
41
41
  * <style>
@@ -1,4 +1,4 @@
1
- var c=function(o,s,e,t){var r=arguments.length,l=r<3?s:t===null?t=Object.getOwnPropertyDescriptor(s,e):t,a;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")l=Reflect.decorate(o,s,e,t);else for(var i=o.length-1;i>=0;i--)(a=o[i])&&(l=(r<3?a(l):r>3?a(s,e,l):a(s,e))||l);return r>3&&l&&Object.defineProperty(s,e,l),l};import{css as p,html as f}from"lit";import{customElement as v}from"lit/decorators.js";import{EspalierElementBase as m}from"../shared/esp-element-base.js";import{createRef as h,ref as u}from"lit/directives/ref.js";export*from"./esp-breadcrumb.js";const d=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
1
+ var c=function(o,s,e,t){var r=arguments.length,l=r<3?s:t===null?t=Object.getOwnPropertyDescriptor(s,e):t,n;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")l=Reflect.decorate(o,s,e,t);else for(var i=o.length-1;i>=0;i--)(n=o[i])&&(l=(r<3?n(l):r>3?n(s,e,l):n(s,e))||l);return r>3&&l&&Object.defineProperty(s,e,l),l};import{css as p,html as f}from"lit";import{customElement as u}from"lit/decorators.js";import{EspalierElementBase as m}from"../shared/esp-element-base.js";import{createRef as v,ref as d}from"lit/directives/ref.js";export*from"./esp-breadcrumb.js";const h=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
2
2
  <path stroke="none" d="M0 0h24v24H0z" fill="none" />
3
3
  <path
4
4
  d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" />
@@ -7,19 +7,16 @@ var c=function(o,s,e,t){var r=arguments.length,l=r<3?s:t===null?t=Object.getOwnP
7
7
  <path
8
8
  d="M10 2l-.15 .005a2 2 0 0 0 -1.85 1.995v6.999l-2.586 .001a2 2 0 0 0 -1.414 3.414l6.586 6.586a2 2 0 0 0 2.828 0l6.586 -6.586a2 2 0 0 0 .434 -2.18l-.068 -.145a2 2 0 0 0 -1.78 -1.089l-2.586 -.001v-6.999a2 2 0 0 0 -2 -2h-4z"
9
9
  />
10
- </svg>`;let n=class extends m{constructor(){super(...arguments),this.itemsSlot=h()}firstUpdated(s){if(super.firstUpdated(s),!this.itemsSlot.value){console.warn("No items slot found for breadcrumbs.");return}const e=this.itemsSlot.value.assignedElements();for(let t=0;t<e.length;t++){const r=e[t];r.separator.length>0||(r.isLastElement=t===e.length-1,r.separator=r.isLastElement?g:d)}}render(){return f`
10
+ </svg>`;let a=class extends m{constructor(){super(...arguments),this.itemsSlot=v()}firstUpdated(s){if(super.firstUpdated(s),!this.itemsSlot.value){console.warn("No items slot found for breadcrumbs.");return}const e=this.itemsSlot.value.assignedElements();for(let t=0;t<e.length;t++){const r=e[t];r.separator.length>0||(r.isLastElement=t===e.length-1,r.separator=r.isLastElement?g:h)}}render(){return f`
11
11
  <div class="crumbs">
12
- <slot ${u(this.itemsSlot)}></slot>
12
+ <slot ${d(this.itemsSlot)}></slot>
13
13
  </div>
14
- `}};n.styles=[...m.styles,p`
14
+ `}};a.styles=[...m.styles,p`
15
15
  div.crumbs {
16
16
  display: flex;
17
17
  align-items: center;
18
18
  gap: var(--esp-size-tiny-to-small);
19
19
  font-size: var(--esp-size-small-to-normal);
20
- color: var(
21
- --esp-breadcrumb-separator-color,
22
- oklch(from var(--esp-color-complementary) var(--esp-l-accent) c h)
23
- );
20
+ color: var(--esp-breadcrumb-separator-color, var(--esp-color-headings));
24
21
  }
25
- `],n=c([v("esp-breadcrumbs")],n);export{n as EspalierBreadcrumbs};
22
+ `],a=c([u("esp-breadcrumbs")],a);export{a as EspalierBreadcrumbs};
@@ -0,0 +1 @@
1
+ const T=.9,N=.88,_=.8;export{_ as BUTTON_ICON_INTERACTION_LIGHTNESS_FACTOR,T as BUTTON_ICON_SPLIT_LIGHTNESS_FACTOR,N as BUTTON_INTERACTION_LIGHTNESS_FACTOR};
@@ -1,4 +1,4 @@
1
- var i=function(p,t,r,e){var s=arguments.length,n=s<3?t:e===null?e=Object.getOwnPropertyDescriptor(t,r):e,l;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")n=Reflect.decorate(p,t,r,e);else for(var h=p.length-1;h>=0;h--)(l=p[h])&&(n=(s<3?l(n):s>3?l(t,r,n):l(t,r))||n);return s>3&&n&&Object.defineProperty(t,r,n),n};import{css as g,html as d,nothing as c}from"lit";import{customElement as f,eventOptions as m,property as a,state as v}from"lit/decorators.js";import{classMap as u}from"lit/directives/class-map.js";import{EspalierElementBase as b}from"../shared/esp-element-base.js";import{ref as y}from"lit/directives/ref.js";import{getIconHrefForHost as k}from"../shared/intent-values.js";import{SlottedIconController as x}from"../shared/slotted-icon-controller.js";import{renderSpriteIcon as w}from"../shared/svgs/render-sprite-icon.js";const T=d`<svg
1
+ var i=function(p,t,r,e){var s=arguments.length,n=s<3?t:e===null?e=Object.getOwnPropertyDescriptor(t,r):e,l;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")n=Reflect.decorate(p,t,r,e);else for(var h=p.length-1;h>=0;h--)(l=p[h])&&(n=(s<3?l(n):s>3?l(t,r,n):l(t,r))||n);return s>3&&n&&Object.defineProperty(t,r,n),n};import{css as f,html as d,nothing as c}from"lit";import{customElement as m,eventOptions as v,property as a,state as y}from"lit/decorators.js";import{classMap as u}from"lit/directives/class-map.js";import{EspalierElementBase as b}from"../shared/esp-element-base.js";import{ref as k}from"lit/directives/ref.js";import{getIconHrefForHost as x}from"../shared/intent-values.js";import{SlottedIconController as T}from"../shared/slotted-icon-controller.js";import{renderSpriteIcon as w}from"../shared/svgs/render-sprite-icon.js";import{BUTTON_ICON_INTERACTION_LIGHTNESS_FACTOR as S,BUTTON_ICON_SPLIT_LIGHTNESS_FACTOR as g,BUTTON_INTERACTION_LIGHTNESS_FACTOR as $}from"./action-state-lightness.js";const N=d`<svg
2
2
  xmlns="http://www.w3.org/2000/svg"
3
3
  viewBox="0 0 24 24"
4
4
  fill="none"
@@ -10,12 +10,12 @@ var i=function(p,t,r,e){var s=arguments.length,n=s<3?t:e===null?e=Object.getOwnP
10
10
  >
11
11
  <path stroke="none" d="M0 0h24v24H0z" fill="none" />
12
12
  <path d="M4.05 11a8 8 0 1 1 .5 4m-.5 5v-5h5" />
13
- </svg>`;let o=class extends b{constructor(){super(...arguments),this.iconSlot=new x(this),this.innerTabindex=null,this.neutralizingTabindex=!1,this.buttonType="button",this.collapsed=!1,this.disabled=!1,this.href="",this.iconOnly=!1,this.incognito=!1,this.icon="",this.label="",this.loading=!1,this.target="",this.rel="",this.formNoValidate=!1,this.buildIcon=t=>this.loading?d` <span>${T}</span> `:d`
13
+ </svg>`;let o=class extends b{constructor(){super(...arguments),this.iconSlot=new T(this),this.innerTabindex=null,this.neutralizingTabindex=!1,this.buttonType="button",this.collapsed=!1,this.disabled=!1,this.href="",this.iconOnly=!1,this.incognito=!1,this.icon="",this.label="",this.loading=!1,this.target="",this.rel="",this.formNoValidate=!1,this.buildIcon=t=>this.loading?d` <span>${N}</span> `:d`
14
14
  <span>
15
- <slot ${y(this.iconSlot.slotRef)} @slotchange=${this.iconSlot.handleSlotChange}></slot>
15
+ <slot ${k(this.iconSlot.slotRef)} @slotchange=${this.iconSlot.handleSlotChange}></slot>
16
16
  ${!this.iconSlot.hasSlottedIcon(":scope > *")&&t?w(t):c}
17
17
  </span>
18
- `}static get observedAttributes(){return[...super.observedAttributes,"tabindex"]}attributeChangedCallback(t,r,e){if(super.attributeChangedCallback(t,r,e),t!=="tabindex"||this.neutralizingTabindex)return;const s=e===null?null:Number(e),n=s===null||Number.isNaN(s)?null:s;n!==null&&n>=0?(this.innerTabindex=n,this.writeHostTabindex(null)):n===null&&e!==null?(this.innerTabindex=null,this.writeHostTabindex(null)):this.innerTabindex=n}writeHostTabindex(t){this.getAttribute("tabindex")!==t&&(this.neutralizingTabindex=!0,t===null?this.removeAttribute("tabindex"):this.setAttribute("tabindex",t),this.neutralizingTabindex=!1)}removeAttribute(t){super.removeAttribute(t),t==="tabindex"&&!this.neutralizingTabindex&&(this.innerTabindex=null)}focus(t){this.focusShadowElementAfterUpdate("button, a",t)}handleClick(t){if(this.dispatchEvent(new CustomEvent("clicked",{detail:{},bubbles:!0,composed:!0})),this.buttonType==="submit"){const r=this.closest("form");if(r){const e=document.createElement("button");e.type="submit",e.hidden=!0,this.formNoValidate&&(e.formNoValidate=!0),r.appendChild(e),r.requestSubmit(e),e.remove()}}else this.buttonType==="reset"&&this.closest("form")?.reset()}render(){const t=k(this.icon,this),r=this.loading||this.iconSlot.hasSlottedIcon(":scope > *")||t.length>0,{collapsed:e,iconOnly:s}=this,n=s?this.getAttribute("aria-label")||this.label||c:c,l={"esp-field":!0,"icon-only":s,incognito:this.incognito,"is-collapsed":e,"has-icon":r};return this.href.length?d`
18
+ `}static get observedAttributes(){return[...super.observedAttributes,"tabindex"]}attributeChangedCallback(t,r,e){if(super.attributeChangedCallback(t,r,e),t!=="tabindex"||this.neutralizingTabindex)return;const s=e===null?null:Number(e),n=s===null||Number.isNaN(s)?null:s;n!==null&&n>=0?(this.innerTabindex=n,this.writeHostTabindex(null)):n===null&&e!==null?(this.innerTabindex=null,this.writeHostTabindex(null)):this.innerTabindex=n}writeHostTabindex(t){this.getAttribute("tabindex")!==t&&(this.neutralizingTabindex=!0,t===null?this.removeAttribute("tabindex"):this.setAttribute("tabindex",t),this.neutralizingTabindex=!1)}removeAttribute(t){super.removeAttribute(t),t==="tabindex"&&!this.neutralizingTabindex&&(this.innerTabindex=null)}focus(t){this.focusShadowElementAfterUpdate("button, a",t)}handleClick(t){if(this.dispatchEvent(new CustomEvent("clicked",{detail:{},bubbles:!0,composed:!0})),this.buttonType==="submit"){const r=this.closest("form");if(r){const e=document.createElement("button");e.type="submit",e.hidden=!0,this.formNoValidate&&(e.formNoValidate=!0),r.appendChild(e),r.requestSubmit(e),e.remove()}}else this.buttonType==="reset"&&this.closest("form")?.reset()}render(){const t=x(this.icon,this),r=this.loading||this.iconSlot.hasSlottedIcon(":scope > *")||t.length>0,{collapsed:e,iconOnly:s}=this,n=s?this.getAttribute("aria-label")||this.label||c:c,l={"esp-field":!0,"icon-only":s,incognito:this.incognito,"is-collapsed":e,"has-icon":r};return this.href.length?d`
19
19
  <div part="wrapper" class=${u(l)}>
20
20
  <a
21
21
  ?disabled=${this.disabled}
@@ -42,7 +42,7 @@ var i=function(p,t,r,e){var s=arguments.length,n=s<3?t:e===null?e=Object.getOwnP
42
42
  ${this.buildIcon(t)}
43
43
  </button>
44
44
  </div>
45
- `}};o.styles=[...b.styles,g`
45
+ `}};o.styles=[...b.styles,f`
46
46
  :host {
47
47
  --_esp-button-radius: var(--esp-size-border-radius);
48
48
  }
@@ -80,7 +80,10 @@ var i=function(p,t,r,e){var s=arguments.length,n=s<3?t:e===null?e=Object.getOwnP
80
80
  }
81
81
 
82
82
  > span:nth-child(2) {
83
- background-color: oklch(from var(--esp-color-action-background) calc(l * 0.9) c h);
83
+ background-color: oklch(
84
+ from var(--esp-color-action-background)
85
+ calc(l * ${g}) c h
86
+ );
84
87
  display: none;
85
88
  place-content: center;
86
89
  border-left: 1px dotted var(--esp-color-border);
@@ -131,12 +134,18 @@ var i=function(p,t,r,e){var s=arguments.length,n=s<3?t:e===null?e=Object.getOwnP
131
134
 
132
135
  &:hover,
133
136
  &:focus-within {
134
- background-color: oklch(from var(--esp-color-action-background) calc(l * 0.88) c h);
137
+ background-color: oklch(
138
+ from var(--esp-color-action-background) calc(l * ${$})
139
+ c h
140
+ );
135
141
 
136
142
  button,
137
143
  a {
138
144
  > span:nth-child(2) {
139
- background-color: oklch(from var(--esp-color-action-background) calc(l * 0.8) c h);
145
+ background-color: oklch(
146
+ from var(--esp-color-action-background)
147
+ calc(l * ${S}) c h
148
+ );
140
149
  border-color: oklch(from var(--esp-color-border) calc(l * 1.2) c h);
141
150
  }
142
151
  }
@@ -187,7 +196,10 @@ var i=function(p,t,r,e){var s=arguments.length,n=s<3?t:e===null?e=Object.getOwnP
187
196
  button,
188
197
  a {
189
198
  > span:nth-child(2) {
190
- background-color: oklch(from var(--esp-color-action-background) calc(l * 0.9) c h);
199
+ background-color: oklch(
200
+ from var(--esp-color-action-background)
201
+ calc(l * ${g}) c h
202
+ );
191
203
  border-color: var(--esp-color-border);
192
204
  }
193
205
  }
@@ -210,4 +222,4 @@ var i=function(p,t,r,e){var s=arguments.length,n=s<3?t:e===null?e=Object.getOwnP
210
222
  transform: rotate(360deg);
211
223
  }
212
224
  }
213
- `],i([v()],o.prototype,"innerTabindex",void 0),i([a({attribute:"button-type",type:String})],o.prototype,"buttonType",void 0),i([a({type:Boolean})],o.prototype,"collapsed",void 0),i([a({type:Boolean,reflect:!0})],o.prototype,"disabled",void 0),i([a({type:String})],o.prototype,"href",void 0),i([a({attribute:"icon-only",type:Boolean})],o.prototype,"iconOnly",void 0),i([a({type:Boolean,reflect:!0})],o.prototype,"incognito",void 0),i([a({type:String})],o.prototype,"icon",void 0),i([a({type:String})],o.prototype,"label",void 0),i([a({type:Boolean,reflect:!0})],o.prototype,"loading",void 0),i([a({type:String})],o.prototype,"target",void 0),i([a({type:String})],o.prototype,"rel",void 0),i([a({attribute:"formnovalidate",type:Boolean})],o.prototype,"formNoValidate",void 0),i([m({capture:!1,passive:!0})],o.prototype,"handleClick",null),o=i([f("esp-button")],o);export{o as EspalierButton};
225
+ `],i([y()],o.prototype,"innerTabindex",void 0),i([a({attribute:"button-type",type:String})],o.prototype,"buttonType",void 0),i([a({type:Boolean})],o.prototype,"collapsed",void 0),i([a({type:Boolean,reflect:!0})],o.prototype,"disabled",void 0),i([a({type:String})],o.prototype,"href",void 0),i([a({attribute:"icon-only",type:Boolean})],o.prototype,"iconOnly",void 0),i([a({type:Boolean,reflect:!0})],o.prototype,"incognito",void 0),i([a({type:String})],o.prototype,"icon",void 0),i([a({type:String})],o.prototype,"label",void 0),i([a({type:Boolean,reflect:!0})],o.prototype,"loading",void 0),i([a({type:String})],o.prototype,"target",void 0),i([a({type:String})],o.prototype,"rel",void 0),i([a({attribute:"formnovalidate",type:Boolean})],o.prototype,"formNoValidate",void 0),i([v({capture:!1,passive:!0})],o.prototype,"handleClick",null),o=i([m("esp-button")],o);export{o as EspalierButton};
@@ -1,4 +1,4 @@
1
- var c=function(n,e,s,a){var r=arguments.length,t=r<3?e:a===null?a=Object.getOwnPropertyDescriptor(e,s):a,l;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")t=Reflect.decorate(n,e,s,a);else for(var i=n.length-1;i>=0;i--)(l=n[i])&&(t=(r<3?l(t):r>3?l(e,s,t):l(e,s))||t);return r>3&&t&&Object.defineProperty(e,s,t),t};import{css as h,html as m}from"lit";import{customElement as y,property as f,state as u}from"lit/decorators.js";import{classMap as g}from"lit/directives/class-map.js";import{createRef as S,ref as p}from"lit/directives/ref.js";import{EspalierElementBase as d}from"../shared/esp-element-base.js";import{SlottedIconController as v}from"../shared/slotted-icon-controller.js";let o=class extends d{constructor(){super(...arguments),this.iconSlot=new v(this),this.secondarySlot=S(),this.hasSecondary=!1,this.truncate=!1,this.handleSecondarySlotChange=()=>{this.hasSecondary=this.secondarySlot.value?.assignedNodes({flatten:!0}).some(e=>e.nodeType===Node.TEXT_NODE?(e.textContent??"").trim().length>0:!0)??!1}}firstUpdated(e){super.firstUpdated(e),this.iconSlot.handleSlotChange(),this.handleSecondarySlotChange()}render(){const e={cell:!0,"has-icon":this.iconSlot.hasAssignedSlotContent,"has-secondary":this.hasSecondary,truncate:this.truncate};return m`
1
+ var c=function(a,e,s,n){var r=arguments.length,t=r<3?e:n===null?n=Object.getOwnPropertyDescriptor(e,s):n,l;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")t=Reflect.decorate(a,e,s,n);else for(var i=a.length-1;i>=0;i--)(l=a[i])&&(t=(r<3?l(t):r>3?l(e,s,t):l(e,s))||t);return r>3&&t&&Object.defineProperty(e,s,t),t};import{css as h,html as m}from"lit";import{customElement as y,property as f,state as u}from"lit/decorators.js";import{classMap as g}from"lit/directives/class-map.js";import{createRef as S,ref as p}from"lit/directives/ref.js";import{EspalierElementBase as d}from"../shared/esp-element-base.js";import{SlottedIconController as v}from"../shared/slotted-icon-controller.js";import{slotHasContent as w}from"../shared/slot-content.js";let o=class extends d{constructor(){super(...arguments),this.iconSlot=new v(this),this.secondarySlot=S(),this.hasSecondary=!1,this.truncate=!1,this.handleSecondarySlotChange=()=>{this.hasSecondary=w(this.secondarySlot.value)}}firstUpdated(e){super.firstUpdated(e),this.iconSlot.handleSlotChange(),this.handleSecondarySlotChange()}render(){const e={cell:!0,"has-icon":this.iconSlot.hasAssignedSlotContent,"has-secondary":this.hasSecondary,truncate:this.truncate};return m`
2
2
  <span part="cell" class=${g(e)}>
3
3
  <span part="icon" class="icon">
4
4
  <slot
@@ -0,0 +1,40 @@
1
+ import { EspalierElementBase } from "../shared/esp-element-base.js";
2
+ /**
3
+ * A labelled group of related links inside [`<esp-footer>`](/components/footer/).
4
+ * Supply ordinary anchors in the default slot; the component provides the
5
+ * compact vertical rhythm and an accessible navigation landmark.
6
+ *
7
+ * ```html
8
+ * <esp-footer-link-group heading="Explore">
9
+ * <a href="/components">Components</a>
10
+ * <a href="/guides">Guides</a>
11
+ * <a href="/api">API</a>
12
+ * </esp-footer-link-group>
13
+ * ```
14
+ *
15
+ * @customElement esp-footer-link-group
16
+ * @slot - Links belonging to the group. Native anchors are preferred because
17
+ * footer links do not need application-menu disclosure or drawer behavior.
18
+ * @csspart nav - The group's navigation landmark.
19
+ * @csspart heading - The visible heading used to label the navigation landmark.
20
+ * @csspart links - The link-list container.
21
+ * @docPageTitle Footer Link Group
22
+ * @docUrl /components/footer/link-group
23
+ * @menuGroup Navigation
24
+ * @menuIcon list-details
25
+ */
26
+ export declare class EspalierFooterLinkGroup extends EspalierElementBase {
27
+ /**
28
+ * Visible heading for the link group. It also labels the navigation
29
+ * landmark. When omitted, the landmark receives the fallback label
30
+ * “Footer links” without rendering an empty heading.
31
+ */
32
+ heading: string;
33
+ protected render(): import("lit-html").TemplateResult<1>;
34
+ static styles: import("lit").CSSResult[];
35
+ }
36
+ declare global {
37
+ interface HTMLElementTagNameMap {
38
+ "esp-footer-link-group": EspalierFooterLinkGroup;
39
+ }
40
+ }
@@ -0,0 +1,57 @@
1
+ var d=function(t,o,r,n){var s=arguments.length,e=s<3?o:n===null?n=Object.getOwnPropertyDescriptor(o,r):n,l;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")e=Reflect.decorate(t,o,r,n);else for(var a=t.length-1;a>=0;a--)(l=t[a])&&(e=(s<3?l(e):s>3?l(o,r,e):l(o,r))||e);return s>3&&e&&Object.defineProperty(o,r,e),e};import{css as v,html as f,nothing as p}from"lit";import{customElement as g,property as u}from"lit/decorators.js";import{EspalierElementBase as c}from"../shared/esp-element-base.js";const h="esp-footer-link-group-heading";let i=class extends c{constructor(){super(...arguments),this.heading=""}render(){return f`
2
+ <nav
3
+ part="nav"
4
+ aria-labelledby=${this.heading?h:p}
5
+ aria-label=${this.heading?p:"Footer links"}
6
+ >
7
+ ${this.heading?f`<h2 part="heading" id=${h}>${this.heading}</h2>`:p}
8
+ <div part="links" class="links"><slot></slot></div>
9
+ </nav>
10
+ `}};i.styles=[...c.styles,v`
11
+ :host {
12
+ display: block;
13
+ min-inline-size: 0;
14
+ }
15
+
16
+ nav {
17
+ display: grid;
18
+ gap: var(--esp-footer-link-group-gap, var(--esp-size-small));
19
+ min-inline-size: 0;
20
+ color: var(--esp-footer-color, var(--esp-color-text));
21
+ }
22
+
23
+ h2 {
24
+ margin: 0;
25
+ color: var(--esp-footer-heading-color, var(--esp-color-headings));
26
+ font-family: var(--esp-font-headings);
27
+ font-size: var(--esp-type-normal);
28
+ font-weight: var(--esp-font-weight-headings);
29
+ line-height: 1.25;
30
+ }
31
+
32
+ .links {
33
+ display: grid;
34
+ gap: var(--esp-size-tiny);
35
+ min-inline-size: 0;
36
+ }
37
+
38
+ ::slotted(a) {
39
+ inline-size: fit-content;
40
+ max-inline-size: 100%;
41
+ color: var(--esp-footer-link-color, var(--esp-color-link));
42
+ overflow-wrap: anywhere;
43
+ text-decoration: none;
44
+ text-underline-offset: 0.18em;
45
+ }
46
+
47
+ ::slotted(a:hover) {
48
+ color: var(--esp-footer-link-hover-color, var(--esp-color-link-hover));
49
+ text-decoration: underline;
50
+ }
51
+
52
+ ::slotted(a:focus-visible) {
53
+ border-radius: var(--esp-size-border-radius);
54
+ outline: var(--esp-footer-focus-outline, 2px solid var(--esp-color-link));
55
+ outline-offset: 3px;
56
+ }
57
+ `],d([u({type:String})],i.prototype,"heading",void 0),i=d([g("esp-footer-link-group")],i);export{i as EspalierFooterLinkGroup};
@@ -0,0 +1,186 @@
1
+ import { type PropertyValues } from "lit";
2
+ import { EspalierElementBase } from "../shared/esp-element-base.js";
3
+ import "./esp-footer-link-group.js";
4
+ export type FooterColumns = "auto" | "1" | "2" | "3" | "4" | "5" | "6";
5
+ /**
6
+ * A responsive site footer with structured spaces for branding, feature
7
+ * artwork, link groups, supporting actions, and legal or utility content.
8
+ * Its background remains full-bleed while its content automatically aligns
9
+ * to the surface of a containing [`<esp-page>`](/components/page/).
10
+ *
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.
14
+ *
15
+ * ```html
16
+ * <esp-footer
17
+ * brand-logo="/assets/favicon.svg"
18
+ * brand-text="Espalier"
19
+ * brand-href="/"
20
+ * brand-alt="Espalier logo"
21
+ * columns="3">
22
+ * <esp-footer-link-group heading="Explore">
23
+ * <a href="/components">Components</a>
24
+ * <a href="/api">API</a>
25
+ * </esp-footer-link-group>
26
+ * <esp-footer-link-group heading="Learn">
27
+ * <a href="/guides/getting-started">Getting started</a>
28
+ * <a href="/guides/styling">Styling</a>
29
+ * </esp-footer-link-group>
30
+ * <p slot="aside">Themeable web components built on web standards.</p>
31
+ * <div slot="bottom">Copyright 2026 Example Organization</div>
32
+ * </esp-footer>
33
+ * ```
34
+ *
35
+ * A slotted brand replaces configured branding, while the media slot accepts
36
+ * a responsive `picture`, native image, or `esp-image`. Media spans the
37
+ * surface-aligned content well; use the decorative background layer for
38
+ * edge-to-edge artwork:
39
+ *
40
+ * ```html
41
+ * <esp-footer columns="4">
42
+ * <picture slot="media">
43
+ * <source media="(prefers-color-scheme: dark)" srcset="/assets/canvas-bg-dark.jpg" />
44
+ * <img
45
+ * src="/assets/canvas-bg.jpg"
46
+ * alt="A colorful floral pattern"
47
+ * style="display: block; width: 100%; height: auto;" />
48
+ * </picture>
49
+ * <a slot="brand" href="/" aria-label="Espalier home">
50
+ * <img src="/assets/espalier-logo.svg" alt="Espalier" />
51
+ * </a>
52
+ * <esp-footer-link-group heading="Product">
53
+ * <a href="/components">Components</a>
54
+ * </esp-footer-link-group>
55
+ * </esp-footer>
56
+ * ```
57
+ *
58
+ * Decorative art uses an image layer independent of the base color. The same
59
+ * API supports a repeating landscape band or one large cover image:
60
+ *
61
+ * ```html
62
+ * <style>
63
+ * esp-footer.mountains {
64
+ * --esp-footer-background: oklch(0.48 0.12 145);
65
+ * --esp-footer-background-image-opacity: 0.55;
66
+ * --esp-footer-background-image:
67
+ * linear-gradient(to bottom, transparent 8rem, oklch(0.48 0.12 145)),
68
+ * url("/assets/espalier-geometric-texture.png");
69
+ * --esp-footer-background-repeat: no-repeat, repeat-x;
70
+ * --esp-footer-background-position: 0 0, top;
71
+ * --esp-footer-background-size: 100% 100%, auto 10rem;
72
+ * --esp-footer-padding-block: 6rem;
73
+ * }
74
+ *
75
+ * esp-footer.photo {
76
+ * --esp-footer-background: oklch(0.2 0.04 216);
77
+ * --esp-footer-heading-color: white;
78
+ * --esp-footer-background-image:
79
+ * linear-gradient(oklch(0.1 0 0 / 0.55), oklch(0.1 0 0 / 0.55)),
80
+ * url("/assets/canvas-bg.jpg");
81
+ * --esp-footer-background-repeat: no-repeat;
82
+ * --esp-footer-background-position: center;
83
+ * --esp-footer-background-size: cover;
84
+ * }
85
+ * </style>
86
+ * <esp-footer class="mountains" brand-text="Mountain Studio"></esp-footer>
87
+ * <esp-footer class="photo" brand-text="Photo Studio"></esp-footer>
88
+ * ```
89
+ *
90
+ * Surface alignment is automatic inside `esp-page`; opt out only when the
91
+ * footer's content should also span the entire bar:
92
+ *
93
+ * ```html
94
+ * <esp-page align="center" style="--esp-page-max-width: 36rem;">
95
+ * <main>Surface-aligned page content</main>
96
+ * <esp-footer slot="footer" brand-text="Aligned Studio" columns="3">
97
+ * <esp-footer-link-group heading="Explore">
98
+ * <a href="/work">Work</a>
99
+ * <a href="/about">About</a>
100
+ * </esp-footer-link-group>
101
+ * </esp-footer>
102
+ * </esp-page>
103
+ *
104
+ * <esp-footer full-bleed-content brand-text="Full-width Studio"></esp-footer>
105
+ * ```
106
+ *
107
+ * @customElement esp-footer
108
+ * @slot media - Meaningful artwork spanning the surface-aligned content well,
109
+ * preferably responsive `<picture>` markup or an `esp-image` with appropriate
110
+ * alternative text.
111
+ * @slot brand - Custom brand identity. Overrides configured brand properties.
112
+ * @slot - `esp-footer-link-group` navigation columns.
113
+ * @slot aside - Supporting content such as a newsletter, call to action,
114
+ * contact details, social links, or locale controls.
115
+ * @slot bottom - Copyright, legal/utility links, attribution, accessibility
116
+ * controls, site credit, or a final wordmark.
117
+ * @csspart footer - The native footer landmark and full-bleed base surface.
118
+ * @csspart background - The independent decorative image layer.
119
+ * @csspart content - The surface-aligned content wrapper.
120
+ * @csspart media - The media region spanning the aligned content well.
121
+ * @csspart primary - The grid containing brand, link groups, and aside content.
122
+ * @csspart brand - The brand region.
123
+ * @csspart groups - The link-group grid.
124
+ * @csspart aside - The supporting-content region.
125
+ * @csspart bottom - The final legal/utility region.
126
+ * @cssprop --esp-footer-background - Base footer color. Defaults to `var(--esp-color-layer-2)`.
127
+ * @cssprop --esp-footer-color - Body text color. Defaults to `var(--esp-color-text)`.
128
+ * @cssprop --esp-footer-heading-color - Heading and configured-brand color. Defaults to `var(--esp-color-headings)`.
129
+ * @cssprop --esp-footer-link-color - Link color. Defaults to `var(--esp-color-link)`.
130
+ * @cssprop --esp-footer-link-hover-color - Hovered link color. Defaults to `var(--esp-color-link-hover)`.
131
+ * @cssprop --esp-footer-focus-outline - Focus-visible outline. Defaults to `2px solid var(--esp-color-link)`.
132
+ * @cssprop --esp-footer-border - Block-start footer border. Defaults to `1px solid var(--esp-color-border)`.
133
+ * @cssprop --esp-footer-padding-block - Content block padding. Defaults to `var(--esp-size-padding-page)`.
134
+ * @cssprop --esp-footer-padding-inline - Content inline padding. Defaults to `var(--esp-size-padding-page)`.
135
+ * @cssprop --esp-footer-section-gap - Gap between media, primary, and bottom regions. Defaults to `var(--esp-size-big)`.
136
+ * @cssprop --esp-footer-column-gap - Gap between link-group columns and major primary regions. Defaults to `var(--esp-size-padding-page)`.
137
+ * @cssprop --esp-footer-row-gap - Compact-layout row gap. Defaults to `var(--esp-size-big)`.
138
+ * @cssprop --esp-footer-link-group-min-width - Intrinsic minimum link-group column width. Defaults to `10rem`.
139
+ * @cssprop --esp-footer-link-group-gap - Space between a link-group heading and its links. Defaults to `var(--esp-size-small)`.
140
+ * @cssprop --esp-footer-brand-logo-size - Configured logo block size. Defaults to `3rem`.
141
+ * @cssprop --esp-footer-brand-logo-max-width - Configured logo maximum inline size. Defaults to `12rem`.
142
+ * @cssprop --esp-footer-background-image - Decorative image or gradient layer. Defaults to `none`.
143
+ * @cssprop --esp-footer-background-image-opacity - Decorative layer opacity. Defaults to `1`.
144
+ * @cssprop --esp-footer-background-repeat - Decorative layer repeat behavior. Defaults to `repeat`.
145
+ * @cssprop --esp-footer-background-position - Decorative layer position. Defaults to `0 0`.
146
+ * @cssprop --esp-footer-background-size - Decorative layer size. Defaults to `auto`.
147
+ * @cssprop --esp-footer-background-blend-mode - Decorative layer blend mode. Defaults to `normal`.
148
+ * @cssprop --esp-footer-content-max-width - Surface-alignment cap published by `esp-page`. Defaults to `100%`.
149
+ * @cssprop --esp-footer-content-lead - Fraction of surplus placed before content (`0`, `0.5`, or `1`). Defaults to `0`.
150
+ * @docPageTitle Footer
151
+ * @docUrl /components/footer
152
+ * @menuGroup Structure
153
+ * @menuLabel Footer
154
+ * @menuIcon layout
155
+ */
156
+ export declare class EspalierFooter extends EspalierElementBase {
157
+ /** Configured brand text used when the `brand` slot is empty. */
158
+ brandText: string;
159
+ /** Configured brand logo URL used when the `brand` slot is empty. */
160
+ brandLogo: string;
161
+ /** Optional configured-brand link target. */
162
+ brandHref: string;
163
+ /** Accessible text for a configured logo. */
164
+ brandAlt: string;
165
+ /**
166
+ * Maximum number of link-group columns. Numeric values cap the wide layout;
167
+ * the intrinsic grid still renders fewer columns when space is limited.
168
+ */
169
+ columns: FooterColumns;
170
+ /**
171
+ * Opt out of the `esp-page` surface-alignment contract and allow footer
172
+ * content to use the full bar width. The background is always full-bleed.
173
+ */
174
+ fullBleedContent: boolean;
175
+ /** Optional accessible label for pages containing multiple footer landmarks. */
176
+ landmarkLabel: string;
177
+ protected willUpdate(changedProperties: PropertyValues): void;
178
+ protected firstUpdated(changedProperties: PropertyValues): void;
179
+ protected render(): import("lit-html").TemplateResult<1>;
180
+ static styles: import("lit").CSSResult[];
181
+ }
182
+ declare global {
183
+ interface HTMLElementTagNameMap {
184
+ "esp-footer": EspalierFooter;
185
+ }
186
+ }