@taprootio/espalier 2.8.0 → 2.9.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.
@@ -63,6 +63,9 @@ import { EspalierElementBase } from "../shared/esp-element-base.js";
63
63
  * @cssprop --esp-button-incognito-padding - The padding for the incognito button style.
64
64
  * @attr incognito - Renders the button with a borderless, transparent treatment
65
65
  * for quiet icon or inline actions.
66
+ * @attr aria-pressed - Forwards a toggle's pressed state to the inner native
67
+ * button. Use `"true"` while the controlled content is active and `"false"`
68
+ * otherwise.
66
69
  * @docPageTitle Button
67
70
  * @docUrl /components/button
68
71
  * @menuGroup Interaction
@@ -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 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
1
+ var n=function(h,t,i,e){var s=arguments.length,r=s<3?t:e===null?e=Object.getOwnPropertyDescriptor(t,i):e,c;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")r=Reflect.decorate(h,t,i,e);else for(var d=h.length-1;d>=0;d--)(c=h[d])&&(r=(s<3?c(r):s>3?c(t,i,r):c(t,i))||r);return s>3&&r&&Object.defineProperty(t,i,r),r};import{css as f,html as p,nothing as l}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=p`<svg
2
2
  xmlns="http://www.w3.org/2000/svg"
3
3
  viewBox="0 0 24 24"
4
4
  fill="none"
@@ -10,33 +10,34 @@ 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 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`
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?p` <span>${N}</span> `:p`
14
14
  <span>
15
15
  <slot ${k(this.iconSlot.slotRef)} @slotchange=${this.iconSlot.handleSlotChange}></slot>
16
- ${!this.iconSlot.hasSlottedIcon(":scope > *")&&t?w(t):c}
16
+ ${!this.iconSlot.hasSlottedIcon(":scope > *")&&t?w(t):l}
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=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
- <div part="wrapper" class=${u(l)}>
18
+ `}static get observedAttributes(){return[...super.observedAttributes,"tabindex","aria-pressed"]}attributeChangedCallback(t,i,e){if(super.attributeChangedCallback(t,i,e),t==="aria-pressed"){this.requestUpdate();return}if(t!=="tabindex"||this.neutralizingTabindex)return;const s=e===null?null:Number(e),r=s===null||Number.isNaN(s)?null:s;r!==null&&r>=0?(this.innerTabindex=r,this.writeHostTabindex(null)):r===null&&e!==null?(this.innerTabindex=null,this.writeHostTabindex(null)):this.innerTabindex=r}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 i=this.closest("form");if(i){const e=document.createElement("button");e.type="submit",e.hidden=!0,this.formNoValidate&&(e.formNoValidate=!0),i.appendChild(e),i.requestSubmit(e),e.remove()}}else this.buttonType==="reset"&&this.closest("form")?.reset()}render(){const t=x(this.icon,this),i=this.loading||this.iconSlot.hasSlottedIcon(":scope > *")||t.length>0,{collapsed:e,iconOnly:s}=this,r=s?this.getAttribute("aria-label")||this.label||l:l,c=this.getAttribute("aria-pressed")||l,d={"esp-field":!0,"icon-only":s,incognito:this.incognito,"is-collapsed":e,"has-icon":i};return this.href.length?p`
19
+ <div part="wrapper" class=${u(d)}>
20
20
  <a
21
21
  ?disabled=${this.disabled}
22
22
  href=${this.href}
23
23
  target=${this.target}
24
- rel=${this.rel||c}
25
- tabindex=${this.innerTabindex??c}
26
- aria-label=${n}
24
+ rel=${this.rel||l}
25
+ tabindex=${this.innerTabindex??l}
26
+ aria-label=${r}
27
27
  >
28
28
  <span>${this.label??""}</span>
29
29
  ${this.buildIcon(t)}
30
30
  </a>
31
31
  </div>
32
- `:d`
33
- <div part="wrapper" class=${u(l)}>
32
+ `:p`
33
+ <div part="wrapper" class=${u(d)}>
34
34
  <button
35
35
  ?disabled=${this.disabled||this.loading}
36
36
  @click=${this.handleClick}
37
37
  type=${this.buttonType}
38
- tabindex=${this.innerTabindex??c}
39
- aria-label=${n}
38
+ tabindex=${this.innerTabindex??l}
39
+ aria-label=${r}
40
+ aria-pressed=${c}
40
41
  >
41
42
  <span>${this.label??""}</span>
42
43
  ${this.buildIcon(t)}
@@ -222,4 +223,4 @@ var i=function(p,t,r,e){var s=arguments.length,n=s<3?t:e===null?e=Object.getOwnP
222
223
  transform: rotate(360deg);
223
224
  }
224
225
  }
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};
226
+ `],n([y()],o.prototype,"innerTabindex",void 0),n([a({attribute:"button-type",type:String})],o.prototype,"buttonType",void 0),n([a({type:Boolean})],o.prototype,"collapsed",void 0),n([a({type:Boolean,reflect:!0})],o.prototype,"disabled",void 0),n([a({type:String})],o.prototype,"href",void 0),n([a({attribute:"icon-only",type:Boolean})],o.prototype,"iconOnly",void 0),n([a({type:Boolean,reflect:!0})],o.prototype,"incognito",void 0),n([a({type:String})],o.prototype,"icon",void 0),n([a({type:String})],o.prototype,"label",void 0),n([a({type:Boolean,reflect:!0})],o.prototype,"loading",void 0),n([a({type:String})],o.prototype,"target",void 0),n([a({type:String})],o.prototype,"rel",void 0),n([a({attribute:"formnovalidate",type:Boolean})],o.prototype,"formNoValidate",void 0),n([v({capture:!1,passive:!0})],o.prototype,"handleClick",null),o=n([m("esp-button")],o);export{o as EspalierButton};
@@ -86,8 +86,8 @@ import { LitElement } from "lit";
86
86
  * has moved in. Safe to initialize third-party widgets (e.g. Stripe
87
87
  * Elements) or measure layout at this point.
88
88
  *
89
- * @event {CustomEvent<{ reason: "escape" | "close-dialog" | "api" }>}
90
- * esp-dialog-closing - Fired before the dialog begins closing.
89
+ * @event {CustomEvent<{ reason: "escape" | "close-dialog" | "api" }>} esp-dialog-closing -
90
+ * Fired before the dialog begins closing.
91
91
  * Call `event.preventDefault()` to cancel the close (e.g. for
92
92
  * unsaved-changes confirmation). The `reason` indicates what
93
93
  * triggered the close: `"escape"` for the Escape key,
@@ -95,8 +95,8 @@ import { LitElement } from "lit";
95
95
  * (typically `<esp-form method="dialog">`), or `"api"` for a
96
96
  * direct `toggleOpen()` call.
97
97
  *
98
- * @event {CustomEvent<{ reason: "escape" | "close-dialog" | "api" }>}
99
- * esp-dialog-closed - Fired after the dialog has fully closed,
98
+ * @event {CustomEvent<{ reason: "escape" | "close-dialog" | "api" }>} esp-dialog-closed -
99
+ * Fired after the dialog has fully closed,
100
100
  * inert state has been restored, and focus has returned to the
101
101
  * previously focused element. Safe to tear down resources.
102
102
  *
@@ -1,6 +1,6 @@
1
1
  import { type PropertyValues } from "lit";
2
2
  import { EspalierElementBase } from "../shared/esp-element-base.js";
3
- import type { FlyoutCloseReason } from "../shared/flyout-events.js";
3
+ import { type FlyoutCloseReason } from "../shared/flyout-events.js";
4
4
  /**
5
5
  * A transient panel for help, more-info, and preview content that
6
6
  * claims designed spare width before ever covering the page.
@@ -169,14 +169,18 @@ import type { FlyoutCloseReason } from "../shared/flyout-events.js";
169
169
  * scroll flow. If the panel would cross the visible viewport's bottom,
170
170
  * it shifts up only far enough to fit and returns to its natural trigger
171
171
  * alignment as the page scrolls. Content taller than the viewport
172
- * scrolls inside the panel. Overlay drawers stay viewport-fixed.
172
+ * scrolls inside the panel. Pass `fullHeight: true` (or set the
173
+ * `full-height` attribute) when a reading surface should fill the visible
174
+ * block-size even when its content is short. Overlay drawers stay
175
+ * viewport-fixed.
173
176
  *
174
- * The bus is a global broadcast, so every listening flyout on the
175
- * page answers a `showFlyout()` call. The demos above are each driven
176
- * by their own toggle button, so they are marked `standalone` to opt
177
- * out of the bus leaving this one flyout as the page's shared
178
- * surface. In a real app you typically have a single flyout and need
179
- * no attribute at all.
177
+ * Bus requests anchored inside nested pages are presented by the
178
+ * outermost containing page's flyout by default. This keeps previews,
179
+ * embedded tools, and nested layouts from trapping application-level
180
+ * help or detail panels. Set `scope="nearest"` on a nested flyout when
181
+ * that page should explicitly own requests originating inside it.
182
+ * The demos above are each driven by their own toggle button, so they
183
+ * are marked `standalone` to opt out of the bus entirely.
180
184
  *
181
185
  * ```html
182
186
  * <style>
@@ -322,13 +326,17 @@ export declare class EspalierFlyout extends EspalierElementBase {
322
326
  mode: "auto" | "overlay";
323
327
  /**
324
328
  * Triggering element whose block-start edge the in-grid panel should
325
- * align with. Anchored panels stay in normal page flow, shift upward
326
- * only while needed to fit the visible scrollport, and return to their
327
- * natural alignment as the page scrolls. Oversized content scrolls
328
- * inside the panel. Overlay drawers ignore this geometry and remain
329
- * fixed to the viewport.
329
+ * align with. Anchored panels size to their content when it fits, shift
330
+ * only enough to remain visible when it does not, and keep an edge
331
+ * marker aligned with the trigger. Overlay drawers ignore this geometry
332
+ * and remain fixed to the viewport.
330
333
  */
331
334
  anchor: HTMLElement | undefined;
335
+ /**
336
+ * Fill the visible block-size in gutter and docked modes. The content
337
+ * region scrolls independently. Overlay drawers already fill the viewport.
338
+ */
339
+ fullHeight: boolean;
332
340
  /**
333
341
  * When set on a flyout slotted into an `esp-page`, the panel lifts
334
342
  * its **trailing** edge with the page's surface edge shadow, so it
@@ -347,13 +355,27 @@ export declare class EspalierFlyout extends EspalierElementBase {
347
355
  *
348
356
  * By default a flyout services the bus, so a single flyout in a
349
357
  * page "just works" as the target of `showFlyout()` — the same
350
- * zero-config model as `<esp-toaster>` and `showToast()`. Because
351
- * the bus is a global broadcast, **every** listening flyout on the
352
- * page answers a `showFlyout()` call; set `standalone` on any
353
- * flyout you drive directly so only your one designated help
354
- * surface responds.
358
+ * zero-config model as `<esp-toaster>` and `showToast()`. Anchored
359
+ * requests resolve to one page scope according to `scope`; set
360
+ * `standalone` on flyouts that should never answer the bus.
355
361
  */
356
362
  standalone: boolean;
363
+ /**
364
+ * Which containing page owns anchored bus requests.
365
+ *
366
+ * - `"outermost"` (default) routes to the flyout in the outermost
367
+ * `esp-page` containing the request trigger.
368
+ * - `"nearest"` explicitly gives this flyout precedence for requests
369
+ * originating in its own page or nested descendants.
370
+ *
371
+ * A `scope="nearest"` flyout must be a light-DOM descendant of the
372
+ * `esp-page` whose requests it owns so that page can discover it.
373
+ *
374
+ * Requests without an `anchor` or `returnFocusTo` have no local context,
375
+ * so only outermost page flyouts service them. Use the instance API for
376
+ * a directly controlled nested flyout.
377
+ */
378
+ scope: "outermost" | "nearest";
357
379
  /**
358
380
  * Element to return focus to when the flyout closes. In the in-grid
359
381
  * modes it is focused only if focus is inside the flyout at close;
@@ -1,4 +1,4 @@
1
- var h=function(c,e,t,i){var o=arguments.length,s=o<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,t):i,n;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")s=Reflect.decorate(c,e,t,i);else for(var a=c.length-1;a>=0;a--)(n=c[a])&&(s=(o<3?n(s):o>3?n(e,t,s):n(e,t))||s);return o>3&&s&&Object.defineProperty(e,t,s),s};import{css as p,html as y,nothing as R}from"lit";import{customElement as k,property as l}from"lit/decorators.js";import{createRef as x,ref as C}from"lit/directives/ref.js";import{EspalierElementBase as v}from"../shared/esp-element-base.js";import{getEspBus as f}from"../shared/bus-events.js";import{OverlayController as T}from"../shared/overlay-controller.js";import{cancelSVG as E}from"../shared/svgs/cancel.js";import{traverseToClosest as b}from"../shared/utilities.js";const P="(max-width: 50em)",O="Flyout",m=p`
1
+ var h=function(d,e,t,i){var o=arguments.length,s=o<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,t):i,a;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")s=Reflect.decorate(d,e,t,i);else for(var r=d.length-1;r>=0;r--)(a=d[r])&&(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 p,html as w,nothing as S}from"lit";import{customElement as F,property as l}from"lit/decorators.js";import{classMap as L}from"lit/directives/class-map.js";import{createRef as M,ref as O}from"lit/directives/ref.js";import{EspalierElementBase as A}from"../shared/esp-element-base.js";import{getEspBus as f}from"../shared/bus-events.js";import{FLYOUT_FULL_HEIGHT_REQUEST_EVENT as k,markFlyoutRequestServiced as _}from"../shared/flyout-events.js";import{OverlayController as G}from"../shared/overlay-controller.js";import{cancelSVG as B}from"../shared/svgs/cancel.js";import{traverseToClosest as y}from"../shared/utilities.js";const H="(max-width: 50em)",q="Flyout",R=p`
2
2
  position: fixed;
3
3
  inset: 0;
4
4
  margin-block-start: 0;
@@ -7,16 +7,18 @@ var h=function(c,e,t,i){var o=arguments.length,s=o<3?e:i===null?i=Object.getOwnP
7
7
  height: 100vh;
8
8
  height: 100dvh;
9
9
  z-index: var(--esp-flyout-z-index, 3000);
10
- `,w=p`
10
+ `,x=p`
11
11
  display: block;
12
12
  position: absolute;
13
13
  inset: 0;
14
14
  background-color: var(--esp-vellum-background, var(--esp-color-layer-3));
15
15
  opacity: var(--esp-vellum-opacity, 0.85);
16
- `,g=p`
16
+ `,P=p`
17
17
  position: absolute;
18
18
  inset-block: 0;
19
19
  inset-inline-end: 0;
20
+ block-size: auto;
21
+ max-block-size: none;
20
22
  width: min(var(--esp-page-flyout-width, 20rem), 85vw);
21
23
  overflow-y: auto;
22
24
 
@@ -29,21 +31,25 @@ var h=function(c,e,t,i){var o=arguments.length,s=o<3?e:i===null?i=Object.getOwnP
29
31
  @starting-style {
30
32
  translate: 100% 0;
31
33
  }
32
- `;let r=class extends v{constructor(){super(...arguments),this.open=!1,this.heading="",this.mode="auto",this.matchSurface=!1,this.standalone=!1,this.lastGeneratedAriaLabel=null,this.boundKeydown=e=>this.handleKeydown(e),this.boundShowRequest=e=>this.handleShowRequest(e),this.boundCloseRequest=()=>this.close(),this.boundOverlayMediaChange=()=>{this.syncOverlayModal(),this.syncAnchorTracking(),this.syncAnchorGeometry()},this.boundAnchorViewportChange=()=>this.scheduleAnchorGeometrySync(),this.anchorPositionRaf=0,this.trackingAnchorPosition=!1,this.busSubscribed=!1,this.panelRef=x(),this.overlay=new T({host:this,getFocusTrapContainer:()=>this.panelRef.value??null,getFocusFallback:()=>this.panelRef.value??null,promote:!1}),this.overlayActive=!1,this.baseRole="complementary"}get effectiveOverlay(){return this.mode==="overlay"||(this.overlayMediaQuery?.matches??!1)}connectedCallback(){super.connectedCallback(),this.hasAttribute("role")||this.setAttribute("role","complementary"),this.baseRole=this.getAttribute("role")??"complementary",!this.overlayMediaQuery&&typeof window<"u"&&"matchMedia"in window&&(this.overlayMediaQuery=window.matchMedia(P)),this.overlayMediaQuery?.addEventListener("change",this.boundOverlayMediaChange),this.syncBusSubscription(),document.addEventListener("keydown",this.boundKeydown),this.syncOverlayModal(),this.syncAnchorTracking(),this.syncAnchorGeometry()}disconnectedCallback(){super.disconnectedCallback(),this.unsubscribeBus(),this.overlayMediaQuery?.removeEventListener("change",this.boundOverlayMediaChange),document.removeEventListener("keydown",this.boundKeydown),this.stopAnchorTracking(),this.overlayActive&&(this.overlayActive=!1,this.setAttribute("role",this.baseRole),this.removeAttribute("aria-modal"),this.updateGeneratedAriaLabel()),this.overlayReturnFocusTo=void 0}syncBusSubscription(){if(this.standalone||!this.isConnected){this.unsubscribeBus();return}this.busSubscribed||(f().subscribe("show-flyout",this.boundShowRequest),f().subscribe("close-flyout",this.boundCloseRequest),this.busSubscribed=!0)}unsubscribeBus(){this.busSubscribed&&(f().unsubscribe("show-flyout",this.boundShowRequest),f().unsubscribe("close-flyout",this.boundCloseRequest),this.busSubscribed=!1)}show(){this.open||(this.open=!0,this.dispatchEvent(new CustomEvent("flyout-opened",{detail:{},bubbles:!0,composed:!0})))}close(e="programmatic"){if(this.open){if(this.open=!1,this.overlayActive)this.overlayReturnFocusTo=this.returnFocusTo;else{const t=this.focusIsInside();this.overlay.close(),t&&this.returnFocusTo?.focus()}this.returnFocusTo=void 0,this.dispatchEvent(new CustomEvent("flyout-closed",{detail:{reason:e},bubbles:!0,composed:!0}))}}toggle(){this.open?this.close():this.show()}handleShowRequest(e){e.heading!==void 0&&(this.heading=e.heading),e.content!==void 0&&this.replaceChildren(typeof e.content=="string"?document.createTextNode(e.content):e.content),this.anchor=e.anchor,this.returnFocusTo=e.returnFocusTo,this.show()}handleKeydown(e){if(this.open){if(this.overlayActive&&e.key==="Tab"){this.overlay.trapFocus(e);return}e.key!=="Escape"||e.defaultPrevented||(e.preventDefault(),this.close("escape"))}}focusIsInside(){const e=document.activeElement;return e&&this.contains(e)?!0:this.shadowRoot?.activeElement!=null}updated(e){super.updated(e),e.has("standalone")&&this.syncBusSubscription(),e.has("heading")&&this.updateGeneratedAriaLabel(),(e.has("anchor")||e.has("open")||e.has("mode"))&&(this.syncAnchorTracking(),this.syncAnchorGeometry()),(e.has("open")||e.has("mode"))&&this.syncOverlayModal(),(e.has("open")||e.has("mode")||e.has("anchor"))&&this.dispatchEvent(new CustomEvent("flyout-state-changed",{detail:{},bubbles:!0,composed:!0}))}syncAnchorGeometry(){const e=b(this,"esp-page"),t=this.anchor?b(this.anchor,"esp-page"):null,i=e?.shadowRoot?.querySelector(".esp-page-main"),o=this.panelRef.value;if(!this.open||this.effectiveOverlay||!this.anchor||!e||t!==e||!i||!o){this.clearAnchorGeometry();return}const s=this.anchor.getBoundingClientRect(),n=Math.max(0,s.top-i.getBoundingClientRect().top),a=this.getVisibleBlockBounds(),d=Math.max(0,a.bottom-a.top);this.setGeometryProperty("--_esp-flyout-anchor-offset",`${n}px`),this.setGeometryProperty("--_esp-flyout-max-block-size",`${d}px`);const u=o.getBoundingClientRect().height,A=Math.max(0,s.top+u-a.bottom);this.setGeometryProperty("--_esp-flyout-viewport-shift",`${A}px`)}getVisibleBlockBounds(){const e=window.visualViewport;let t=e?.offsetTop??0,i=t+(e?.height??window.innerHeight);for(let o=this.composedParent(this);o;o=this.composedParent(o)){const s=getComputedStyle(o).overflowY;if(s!=="auto"&&s!=="scroll")continue;const n=o.getBoundingClientRect(),a=o instanceof HTMLElement?o.clientTop:0,d=o instanceof HTMLElement?o.clientHeight:n.height;if(d<=0)continue;const u=n.top+a;t=Math.max(t,u),i=Math.min(i,u+d)}return{top:t,bottom:Math.max(t,i)}}composedParent(e){if(e.parentElement)return e.parentElement;const t=e.getRootNode();return t instanceof ShadowRoot?t.host:null}setGeometryProperty(e,t){this.style.getPropertyValue(e)!==t&&this.style.setProperty(e,t)}clearAnchorGeometry(){this.style.removeProperty("--_esp-flyout-anchor-offset"),this.style.removeProperty("--_esp-flyout-viewport-shift"),this.style.removeProperty("--_esp-flyout-max-block-size")}scheduleAnchorGeometrySync(){!this.isConnected||this.anchorPositionRaf||(this.anchorPositionRaf=requestAnimationFrame(()=>{this.anchorPositionRaf=0,this.syncAnchorGeometry()}))}syncAnchorTracking(){if(!(this.isConnected&&this.open&&!!this.anchor&&!this.effectiveOverlay)){this.stopAnchorTracking();return}this.trackingAnchorPosition||(this.trackingAnchorPosition=!0,window.addEventListener("scroll",this.boundAnchorViewportChange,{capture:!0,passive:!0}),window.addEventListener("resize",this.boundAnchorViewportChange,{passive:!0}));const t=this.panelRef.value;t&&t!==this.observedAnchorPanel&&typeof ResizeObserver<"u"&&(this.anchorResizeObserver??=new ResizeObserver(this.boundAnchorViewportChange),this.anchorResizeObserver.disconnect(),this.anchorResizeObserver.observe(t),this.observedAnchorPanel=t)}stopAnchorTracking(){this.trackingAnchorPosition&&(window.removeEventListener("scroll",this.boundAnchorViewportChange,{capture:!0}),window.removeEventListener("resize",this.boundAnchorViewportChange),this.trackingAnchorPosition=!1),this.anchorPositionRaf&&(cancelAnimationFrame(this.anchorPositionRaf),this.anchorPositionRaf=0),this.anchorResizeObserver?.disconnect(),this.observedAnchorPanel=void 0}syncOverlayModal(){const e=this.isConnected&&this.open&&this.effectiveOverlay;if(e===this.overlayActive){!e&&!this.open&&this.overlay.close();return}e?(this.overlayActive=!0,this.setAttribute("role","dialog"),this.setAttribute("aria-modal","true"),this.updateGeneratedAriaLabel(),this.overlay.open(),this.updateComplete.then(()=>{this.overlayActive&&this.overlay.moveFocusInto()})):(this.overlayActive=!1,this.overlay.close(),this.overlayReturnFocusTo&&(this.overlayReturnFocusTo.focus(),this.overlayReturnFocusTo=void 0),this.setAttribute("role",this.baseRole),this.removeAttribute("aria-modal"),this.updateGeneratedAriaLabel())}updateGeneratedAriaLabel(){const e=this.getAttribute("aria-label");if(e!==null&&e!==this.lastGeneratedAriaLabel)return;const t=this.heading||(this.overlayActive?O:"");t?(this.setAttribute("aria-label",t),this.lastGeneratedAriaLabel=t):(this.removeAttribute("aria-label"),this.lastGeneratedAriaLabel=null)}render(){return y`
34
+ `;let n=class extends A{constructor(){super(...arguments),this.open=!1,this.heading="",this.mode="auto",this.fullHeight=!1,this.matchSurface=!1,this.standalone=!1,this.scope="outermost",this.lastGeneratedAriaLabel=null,this.boundKeydown=e=>this.handleKeydown(e),this.boundShowRequest=e=>this.handleShowRequest(e),this.boundCloseRequest=()=>this.close(),this.boundOverlayMediaChange=()=>{this.syncOverlayModal(),this.syncAnchorTracking(),this.syncAnchorGeometry()},this.boundAnchorViewportChange=()=>this.scheduleAnchorGeometrySync(),this.boundFullHeightRequest=e=>{e.stopPropagation(),this.fullHeight=!0},this.anchorPositionRaf=0,this.trackingAnchorPosition=!1,this.busSubscribed=!1,this.panelRef=M(),this.overlay=new G({host:this,getFocusTrapContainer:()=>this.panelRef.value??null,getFocusFallback:()=>this.panelRef.value??null,promote:!1}),this.overlayActive=!1,this.baseRole="complementary"}get effectiveOverlay(){return this.mode==="overlay"||(this.overlayMediaQuery?.matches??!1)}connectedCallback(){super.connectedCallback(),this.hasAttribute("role")||this.setAttribute("role","complementary"),this.baseRole=this.getAttribute("role")??"complementary",!this.overlayMediaQuery&&typeof window<"u"&&"matchMedia"in window&&(this.overlayMediaQuery=window.matchMedia(H)),this.overlayMediaQuery?.addEventListener("change",this.boundOverlayMediaChange),this.syncBusSubscription(),document.addEventListener("keydown",this.boundKeydown),this.addEventListener(k,this.boundFullHeightRequest),this.syncOverlayModal(),this.syncAnchorTracking(),this.syncAnchorGeometry()}disconnectedCallback(){super.disconnectedCallback(),this.unsubscribeBus(),this.overlayMediaQuery?.removeEventListener("change",this.boundOverlayMediaChange),document.removeEventListener("keydown",this.boundKeydown),this.removeEventListener(k,this.boundFullHeightRequest),this.stopAnchorTracking(),this.overlayActive&&(this.overlayActive=!1,this.setAttribute("role",this.baseRole),this.removeAttribute("aria-modal"),this.updateGeneratedAriaLabel()),this.overlayReturnFocusTo=void 0}syncBusSubscription(){if(this.standalone||!this.isConnected){this.unsubscribeBus();return}this.busSubscribed||(f().subscribe("show-flyout",this.boundShowRequest),f().subscribe("close-flyout",this.boundCloseRequest),this.busSubscribed=!0)}unsubscribeBus(){this.busSubscribed&&(f().unsubscribe("show-flyout",this.boundShowRequest),f().unsubscribe("close-flyout",this.boundCloseRequest),this.busSubscribed=!1)}show(){this.open||(this.open=!0,this.dispatchEvent(new CustomEvent("flyout-opened",{detail:{},bubbles:!0,composed:!0})))}close(e="programmatic"){if(this.open){if(this.open=!1,this.overlayActive)this.overlayReturnFocusTo=this.returnFocusTo;else{const t=this.focusIsInside();this.overlay.close(),t&&this.returnFocusTo?.focus()}this.returnFocusTo=void 0,this.dispatchEvent(new CustomEvent("flyout-closed",{detail:{reason:e},bubbles:!0,composed:!0}))}}toggle(){this.open?this.close():this.show()}handleShowRequest(e){this.shouldHandleShowRequest(e)&&(_(e),e.heading!==void 0&&(this.heading=e.heading),e.content!==void 0&&this.replaceChildren(typeof e.content=="string"?document.createTextNode(e.content):e.content),this.anchor=e.anchor,this.fullHeight=e.fullHeight??!1,this.returnFocusTo=e.returnFocusTo,this.show())}shouldHandleShowRequest(e){const t=y(this,"esp-page"),i=e.anchor??e.returnFocusTo;if(!i){if(!t)return!0;const r=this.getPageChain(t);return this.scope==="outermost"&&r[r.length-1]===t}const o=this.getPageChain(i);if(o.length===0)return t===null;const s=o.find(r=>this.hasNearestScopeFlyout(r)),a=s??o[o.length-1];return t!==a?!1:s?this.scope==="nearest":this.scope==="outermost"}getPageChain(e){const t=[];let i=e;for(;i;){const o=y(i,"esp-page");if(!o||t.includes(o))break;t.push(o),i=this.composedParent(o)}return t}hasNearestScopeFlyout(e){return Array.from(e.querySelectorAll("esp-flyout")).some(t=>!t.standalone&&t.scope==="nearest"&&y(t,"esp-page")===e)}handleKeydown(e){if(this.open){if(this.overlayActive&&e.key==="Tab"){this.overlay.trapFocus(e);return}e.key!=="Escape"||e.defaultPrevented||(e.preventDefault(),this.close("escape"))}}focusIsInside(){const e=document.activeElement;return e&&this.contains(e)?!0:this.shadowRoot?.activeElement!=null}updated(e){super.updated(e),e.has("standalone")&&this.syncBusSubscription(),e.has("heading")&&this.updateGeneratedAriaLabel(),(e.has("anchor")||e.has("fullHeight")||e.has("open")||e.has("mode"))&&(this.syncAnchorTracking(),this.syncAnchorGeometry()),(e.has("open")||e.has("mode"))&&this.syncOverlayModal(),(e.has("open")||e.has("mode")||e.has("anchor"))&&this.dispatchEvent(new CustomEvent("flyout-state-changed",{detail:{},bubbles:!0,composed:!0}))}syncAnchorGeometry(){const e=y(this,"esp-page"),t=this.anchor?this.getPageChain(this.anchor):[],i=e?.shadowRoot?.querySelector(".esp-page-main"),o=this.panelRef.value;if(!this.open||this.effectiveOverlay||!this.anchor||!e||!t.includes(e)||!i||!o){this.clearAnchorGeometry();return}const s=this.anchor.getBoundingClientRect(),a=Math.max(0,s.top-i.getBoundingClientRect().top),r=this.getVisibleBlockBounds(),u=Math.max(0,r.bottom-r.top),c=Number.isFinite(s.height)?s.height:0;this.setGeometryProperty("--_esp-flyout-anchor-offset",`${a}px`),this.setGeometryProperty("--_esp-flyout-max-block-size",`${u}px`),this.setGeometryProperty("--_esp-flyout-anchor-block-size",`${c}px`);const v=o.getBoundingClientRect().height||u,b=Math.max(0,s.top+v-r.bottom),C=Math.max(0,v-c),g=this.fullHeight?b:Math.min(b,C);this.setGeometryProperty("--_esp-flyout-viewport-shift",`${g}px`);const z=s.top-g,m=8,E=s.top+c/2,T=Math.min(Math.max(m,v-m),Math.max(m,E-z));this.setGeometryProperty("--_esp-flyout-anchor-marker-offset",`${T}px`)}getVisibleBlockBounds(){const e=window.visualViewport;let t=e?.offsetTop??0,i=t+(e?.height??window.innerHeight);for(let o=this.composedParent(this);o;o=this.composedParent(o)){const s=getComputedStyle(o).overflowY;if(s!=="auto"&&s!=="scroll")continue;const a=o.getBoundingClientRect(),r=o instanceof HTMLElement?o.clientTop:0,u=o instanceof HTMLElement?o.clientHeight:a.height;if(u<=0)continue;const c=a.top+r;t=Math.max(t,c),i=Math.min(i,c+u)}return{top:t,bottom:Math.max(t,i)}}composedParent(e){if(e.parentElement)return e.parentElement;const t=e.getRootNode();return t instanceof ShadowRoot?t.host:null}setGeometryProperty(e,t){this.style.getPropertyValue(e)!==t&&this.style.setProperty(e,t)}clearAnchorGeometry(){this.style.removeProperty("--_esp-flyout-anchor-offset"),this.style.removeProperty("--_esp-flyout-viewport-shift"),this.style.removeProperty("--_esp-flyout-max-block-size"),this.style.removeProperty("--_esp-flyout-anchor-block-size"),this.style.removeProperty("--_esp-flyout-anchor-marker-offset")}scheduleAnchorGeometrySync(){!this.isConnected||this.anchorPositionRaf||(this.anchorPositionRaf=requestAnimationFrame(()=>{this.anchorPositionRaf=0,this.syncAnchorGeometry()}))}syncAnchorTracking(){if(!(this.isConnected&&this.open&&!!this.anchor&&!this.effectiveOverlay)){this.stopAnchorTracking();return}this.trackingAnchorPosition||(this.trackingAnchorPosition=!0,window.addEventListener("scroll",this.boundAnchorViewportChange,{capture:!0,passive:!0}),window.addEventListener("resize",this.boundAnchorViewportChange,{passive:!0}));const t=this.panelRef.value;t&&t!==this.observedAnchorPanel&&typeof ResizeObserver<"u"&&(this.anchorResizeObserver??=new ResizeObserver(this.boundAnchorViewportChange),this.anchorResizeObserver.disconnect(),this.anchorResizeObserver.observe(t),this.observedAnchorPanel=t)}stopAnchorTracking(){this.trackingAnchorPosition&&(window.removeEventListener("scroll",this.boundAnchorViewportChange,{capture:!0}),window.removeEventListener("resize",this.boundAnchorViewportChange),this.trackingAnchorPosition=!1),this.anchorPositionRaf&&(cancelAnimationFrame(this.anchorPositionRaf),this.anchorPositionRaf=0),this.anchorResizeObserver?.disconnect(),this.observedAnchorPanel=void 0}syncOverlayModal(){const e=this.isConnected&&this.open&&this.effectiveOverlay;if(e===this.overlayActive){!e&&!this.open&&this.overlay.close();return}e?(this.overlayActive=!0,this.setAttribute("role","dialog"),this.setAttribute("aria-modal","true"),this.updateGeneratedAriaLabel(),this.overlay.open(),this.updateComplete.then(()=>{this.overlayActive&&this.overlay.moveFocusInto()})):(this.overlayActive=!1,this.overlay.close(),this.overlayReturnFocusTo&&(this.overlayReturnFocusTo.focus(),this.overlayReturnFocusTo=void 0),this.setAttribute("role",this.baseRole),this.removeAttribute("aria-modal"),this.updateGeneratedAriaLabel())}updateGeneratedAriaLabel(){const e=this.getAttribute("aria-label");if(e!==null&&e!==this.lastGeneratedAriaLabel)return;const t=this.heading||(this.overlayActive?q:"");t?(this.setAttribute("aria-label",t),this.lastGeneratedAriaLabel=t):(this.removeAttribute("aria-label"),this.lastGeneratedAriaLabel=null)}render(){return w`
33
35
  <div class="vellum" aria-hidden="true" @click=${()=>this.close("vellum")}></div>
34
- <div class="panel" part="panel" ${C(this.panelRef)}>
36
+ <div
37
+ class=${L({panel:!0,anchored:!!this.anchor,"full-height":this.fullHeight})}
38
+ part="panel"
39
+ ${O(this.panelRef)}
40
+ >
35
41
  <header part="header">
36
- ${this.heading?y`<h2>${this.heading}</h2>`:R}
42
+ ${this.heading?w`<h2>${this.heading}</h2>`:S}
37
43
 
38
44
  <button class="close" aria-label="Close" @click=${()=>this.close("button")}>
39
- ${E}
45
+ ${B}
40
46
  </button>
41
47
  </header>
42
48
  <div class="content" part="content">
43
49
  <slot @slotchange=${this.boundAnchorViewportChange}></slot>
44
50
  </div>
45
51
  </div>
46
- `}};r.styles=[...v.styles,p`
52
+ `}};n.styles=[...A.styles,p`
47
53
  :host {
48
54
  display: none;
49
55
  }
@@ -67,8 +73,8 @@ var h=function(c,e,t,i){var o=arguments.length,s=o<3?e:i===null?i=Object.getOwnP
67
73
  width: var(--esp-page-flyout-width, 20rem);
68
74
  display: flex;
69
75
  flex-direction: column;
70
- max-block-size: var(--_esp-flyout-max-block-size, none);
71
- overflow: hidden;
76
+ position: relative;
77
+ overflow: visible;
72
78
 
73
79
  border-inline-start: var(--esp-flyout-border, 1px dotted var(--esp-color-border));
74
80
  border-start-end-radius: var(--esp-flyout-radius, var(--esp-size-border-radius));
@@ -77,6 +83,39 @@ var h=function(c,e,t,i){var o=arguments.length,s=o<3?e:i===null?i=Object.getOwnP
77
83
  font-family: var(--esp-font-body);
78
84
  color: var(--esp-color-text);
79
85
 
86
+ &.anchored {
87
+ max-block-size: var(--_esp-flyout-max-block-size, 100dvh);
88
+
89
+ &:not(.full-height) {
90
+ min-block-size: min(
91
+ var(--_esp-flyout-anchor-block-size, 0px),
92
+ var(--_esp-flyout-max-block-size, 100dvh)
93
+ );
94
+ }
95
+
96
+ &.full-height {
97
+ block-size: var(--_esp-flyout-max-block-size, 100dvh);
98
+
99
+ &::before {
100
+ display: none;
101
+ }
102
+ }
103
+
104
+ &::before {
105
+ content: "";
106
+ position: absolute;
107
+ z-index: 1;
108
+ inset-block-start: var(--_esp-flyout-anchor-marker-offset, 50%);
109
+ inset-inline-start: calc(-1 * var(--esp-size-small));
110
+ inline-size: var(--esp-size-small);
111
+ block-size: var(--esp-size-medium);
112
+ translate: 0 -50%;
113
+ clip-path: polygon(0 50%, 100% 0, 100% 100%);
114
+ background: var(--esp-color-border);
115
+ pointer-events: none;
116
+ }
117
+ }
118
+
80
119
  > header {
81
120
  display: flex;
82
121
  align-items: center;
@@ -110,6 +149,7 @@ var h=function(c,e,t,i){var o=arguments.length,s=o<3?e:i===null?i=Object.getOwnP
110
149
  }
111
150
 
112
151
  > .content {
152
+ flex: 1;
113
153
  min-block-size: 0;
114
154
  overflow-y: auto;
115
155
  overscroll-behavior-block: contain;
@@ -119,29 +159,37 @@ var h=function(c,e,t,i){var o=arguments.length,s=o<3?e:i===null?i=Object.getOwnP
119
159
 
120
160
 
121
161
  :host([open][mode="overlay"]) {
122
- ${m}
162
+ ${R}
123
163
  }
124
164
 
125
165
  :host([open][mode="overlay"]) .vellum {
126
- ${w}
166
+ ${x}
127
167
  }
128
168
 
129
169
  :host([open][mode="overlay"]) .panel {
130
- ${g}
170
+ ${P}
171
+ }
172
+
173
+ :host([open][mode="overlay"]) .panel.anchored::before {
174
+ display: none;
131
175
  }
132
176
 
133
177
 
134
178
  @media (max-width: 50em) {
135
179
  :host([open]) {
136
- ${m}
180
+ ${R}
137
181
  }
138
182
 
139
183
  .vellum {
140
- ${w}
184
+ ${x}
141
185
  }
142
186
 
143
187
  .panel {
144
- ${g}
188
+ ${P}
189
+ }
190
+
191
+ .panel.anchored::before {
192
+ display: none;
145
193
  }
146
194
  }
147
195
 
@@ -150,4 +198,4 @@ var h=function(c,e,t,i){var o=arguments.length,s=o<3?e:i===null?i=Object.getOwnP
150
198
  transition: none;
151
199
  }
152
200
  }
153
- `],h([l({type:Boolean,reflect:!0})],r.prototype,"open",void 0),h([l({type:String})],r.prototype,"heading",void 0),h([l({reflect:!0})],r.prototype,"mode",void 0),h([l({attribute:!1})],r.prototype,"anchor",void 0),h([l({type:Boolean,reflect:!0,attribute:"match-surface"})],r.prototype,"matchSurface",void 0),h([l({type:Boolean,reflect:!0})],r.prototype,"standalone",void 0),r=h([k("esp-flyout")],r);export{r as EspalierFlyout};
201
+ `],h([l({type:Boolean,reflect:!0})],n.prototype,"open",void 0),h([l({type:String})],n.prototype,"heading",void 0),h([l({reflect:!0})],n.prototype,"mode",void 0),h([l({attribute:!1})],n.prototype,"anchor",void 0),h([l({type:Boolean,reflect:!0,attribute:"full-height"})],n.prototype,"fullHeight",void 0),h([l({type:Boolean,reflect:!0,attribute:"match-surface"})],n.prototype,"matchSurface",void 0),h([l({type:Boolean,reflect:!0})],n.prototype,"standalone",void 0),h([l({type:String,reflect:!0})],n.prototype,"scope",void 0),n=h([F("esp-flyout")],n);export{n as EspalierFlyout};
@@ -1,5 +1,6 @@
1
1
  import { LitElement, type PropertyValues } from "lit";
2
2
  import { type ValidationError } from "../shared/validation.js";
3
+ import "../help/esp-help-button.js";
3
4
  export type EspalierFormField = {
4
5
  focus: (options?: FocusOptions) => void;
5
6
  validate?: () => void;
@@ -150,6 +151,27 @@ export declare class EspalierFormItem extends LitElement {
150
151
  * field, so there is no need to set `name` separately.
151
152
  */
152
153
  fieldName: string;
154
+ /**
155
+ * Per-field help document override. A URL fragment selects the topic;
156
+ * otherwise `field-name` supplies the anchor. When omitted, the nearest
157
+ * composed-tree ancestor's `help-src` is used.
158
+ *
159
+ * ```html
160
+ * <esp-form-item
161
+ * label="Email address"
162
+ * field-name="email"
163
+ * help-url="/help/account.html#contact-email">
164
+ * <esp-input type="email"></esp-input>
165
+ * </esp-form-item>
166
+ * ```
167
+ */
168
+ helpUrl: string;
169
+ /**
170
+ * Chooses the flyout title for form-context help. The default, `field`,
171
+ * uses this form item's visible `label`. Set `document` to adopt the text
172
+ * of the matched heading in the help document instead.
173
+ */
174
+ helpTitleSource: "field" | "document";
153
175
  /**
154
176
  * Bind to an array of `ValidationError` to automatically filter
155
177
  * and show errors whose `fieldName` matches this item's
@@ -1,16 +1,28 @@
1
- var n=function(l,e,t,r){var o=arguments.length,i=o<3?e:r===null?r=Object.getOwnPropertyDescriptor(e,t):r,h;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")i=Reflect.decorate(l,e,t,r);else for(var d=l.length-1;d>=0;d--)(h=l[d])&&(i=(o<3?h(i):o>3?h(e,t,i):h(e,t))||i);return o>3&&i&&Object.defineProperty(e,t,i),i};import{LitElement as f,css as u,html as m,nothing as c}from"lit";import{customElement as g,property as a}from"lit/decorators.js";import{classMap as v}from"lit/directives/class-map.js";import{createRef as b,ref as y}from"lit/directives/ref.js";import{VALIDITY_CHANGED_EVENT as w}from"../shared/validation.js";let p=0,s=class extends f{constructor(){super(...arguments),this.fieldSlot=b(),this.theField={focus:()=>{}},this._errorSource="manual",this.label="",this.autofocus=!1,this.hint="",this.hintPlacement="below",this.hintId=`esp-form-item-hint-${++p}`,this.managedHintEl=null,this.appliedHintTokens=[],this.hintContentObserver=new MutationObserver(()=>{this.syncFieldDescribedBy()}),this._error="",this._warning="",this.fieldName="",this._errorPool=[],this.handleHintSlotChange=()=>{this.syncManagedHint(),this.observeConsumerHintContent(),this.requestUpdate(),this.syncFieldDescribedBy()}}get hasHint(){return!!this.hint||this.consumerHintElements().length>0}consumerHintElements(){return[...this.children].filter(e=>e.slot==="hint"&&!e.hasAttribute("data-esp-managed-hint"))}get error(){return this._error}set error(e){this._setError(e,"manual")}_setError(e,t){const r=this._error;this._error=e,this._errorSource=t,this.requestUpdate("error",r)}get warning(){return this._warning}set warning(e){const t=this._warning;this._warning=e,this.requestUpdate("warning",t)}get errorPool(){return this._errorPool}set errorPool(e){const t=this._errorPool;this._errorPool=e,this.requestUpdate("errorPool",t);const r=e.filter(o=>o.fieldName===this.fieldName);if(r.length===0){this._errorSource==="pool"&&this._setError("","pool");return}this._setError(r[0].errorMessage,"pool")}firstUpdated(){if(this.fieldSlot.value?.assignedElements().length!=1)throw new Error("esp-form-item requires exactly one slotted element.");this.theField=this.fieldSlot.value?.assignedElements()[0],this.fieldName&&this.theField.setAttribute("name",this.fieldName),this.syncFieldDescribedBy(),this.autofocus&&this.focus(),this.addEventListener(w,t=>{const r=t.detail;this._errorSource==="pool"&&this._error.length>0||(r.valid?this._errorSource==="native"&&this._setError("","manual"):this._setError(r.message,"native"))});const e=this.theField;e.addEventListener("invalid",t=>{t.preventDefault(),!(this._errorSource==="pool"&&this._error.length>0)&&"validationMessage"in e&&this._setError(e.validationMessage,"native")})}updated(e){if(super.updated(e),(e.has("hint")||e.has("hintPlacement"))&&(this.syncManagedHint(),this.syncFieldDescribedBy()),!e.has("fieldName"))return;const t=this.theField;t instanceof Element&&(this.fieldName?t.setAttribute("name",this.fieldName):t.removeAttribute("name"))}connectedCallback(){super.connectedCallback(),this.observeConsumerHintContent()}disconnectedCallback(){this.hintContentObserver.disconnect(),super.disconnectedCallback()}observeConsumerHintContent(){this.hintContentObserver.disconnect();for(const e of this.consumerHintElements())this.hintContentObserver.observe(e,{attributes:!0,attributeFilter:["id"],characterData:!0,childList:!0,subtree:!0})}syncManagedHint(){if(!(!!this.hint&&this.consumerHintElements().length===0)){this.managedHintEl?.remove(),this.managedHintEl=null;return}if(!this.managedHintEl){const t=document.createElement("span");t.slot="hint",t.id=this.hintId,t.toggleAttribute("data-esp-managed-hint",!0),this.managedHintEl=t,this.appendChild(t)}this.managedHintEl.textContent=this.hint}hintDescriptionIds(){return this.managedHintEl?[this.managedHintEl.id]:this.consumerHintElements().map(e=>(e.id||(e.id=`esp-form-item-hint-${++p}`),e.id))}hintDescriptionText(){if(!this.hasHint)return null;if(this.managedHintEl)return this.managedHintEl.textContent??"";const e=this.consumerHintElements();return e.length===0?this.hint:e.map(t=>t.textContent?.trim()??"").filter(Boolean).join(" ")}syncFieldDescribedBy(){const e=this.fieldSlot.value?.assignedElements()[0];if(!e)return;const t=this.hasHint?this.hintDescriptionIds():[],r=(e.getAttribute("aria-describedby")??"").split(/\s+/).filter(i=>i&&!this.appliedHintTokens.includes(i)&&!t.includes(i));this.appliedHintTokens=t;const o=[...r,...t];o.length>0?e.setAttribute("aria-describedby",o.join(" ")):e.removeAttribute("aria-describedby"),e.setFormItemDescription?.(this.hintDescriptionText())}focus(e){(this.fieldSlot.value?.assignedElements()[0]??this.theField).focus(e)}renderHint(){return m`
2
- <div class="hint" aria-hidden=${this.hintPlacement==="above"?"true":c}>
1
+ var n=function(a,e,t,r){var i=arguments.length,s=i<3?e:r===null?r=Object.getOwnPropertyDescriptor(e,t):r,h;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")s=Reflect.decorate(a,e,t,r);else for(var c=a.length-1;c>=0;c--)(h=a[c])&&(s=(i<3?h(s):i>3?h(e,t,s):h(e,t))||s);return i>3&&s&&Object.defineProperty(e,t,s),s};import{LitElement as f,css as u,html as p,nothing as d}from"lit";import{customElement as g,property as l}from"lit/decorators.js";import{classMap as v}from"lit/directives/class-map.js";import{createRef as b,ref as y}from"lit/directives/ref.js";import{VALIDITY_CHANGED_EVENT as w}from"../shared/validation.js";import"../help/esp-help-button.js";import{resolveHelpTarget as E}from"../help/help-document.js";let m=0,o=class extends f{constructor(){super(...arguments),this.fieldSlot=b(),this.theField={focus:()=>{}},this._errorSource="manual",this.label="",this.autofocus=!1,this.hint="",this.hintPlacement="below",this.hintId=`esp-form-item-hint-${++m}`,this.managedHintEl=null,this.appliedHintTokens=[],this.hintContentObserver=new MutationObserver(()=>{this.syncFieldDescribedBy()}),this._error="",this._warning="",this.fieldName="",this.helpUrl="",this.helpTitleSource="field",this._errorPool=[],this.handleHintSlotChange=()=>{this.syncManagedHint(),this.observeConsumerHintContent(),this.requestUpdate(),this.syncFieldDescribedBy()}}get hasHint(){return!!this.hint||this.consumerHintElements().length>0}consumerHintElements(){return[...this.children].filter(e=>e.slot==="hint"&&!e.hasAttribute("data-esp-managed-hint"))}get error(){return this._error}set error(e){this._setError(e,"manual")}_setError(e,t){const r=this._error;this._error=e,this._errorSource=t,this.requestUpdate("error",r)}get warning(){return this._warning}set warning(e){const t=this._warning;this._warning=e,this.requestUpdate("warning",t)}get errorPool(){return this._errorPool}set errorPool(e){const t=this._errorPool;this._errorPool=e,this.requestUpdate("errorPool",t);const r=e.filter(i=>i.fieldName===this.fieldName);if(r.length===0){this._errorSource==="pool"&&this._setError("","pool");return}this._setError(r[0].errorMessage,"pool")}firstUpdated(){if(this.fieldSlot.value?.assignedElements().length!=1)throw new Error("esp-form-item requires exactly one slotted element.");const e=this.fieldSlot.value?.assignedElements()[0];this.fieldElement=e instanceof HTMLElement?e:void 0,this.theField=e,this.fieldName&&this.theField.setAttribute("name",this.fieldName),this.syncFieldDescribedBy(),this.autofocus&&this.focus();const t=this.shadowRoot?.querySelector("esp-help-button");t&&(t.placementTarget=this.fieldElement),this.addEventListener(w,i=>{const s=i.detail;this._errorSource==="pool"&&this._error.length>0||(s.valid?this._errorSource==="native"&&this._setError("","manual"):this._setError(s.message,"native"))});const r=this.theField;r.addEventListener("invalid",i=>{i.preventDefault(),!(this._errorSource==="pool"&&this._error.length>0)&&"validationMessage"in r&&this._setError(r.validationMessage,"native")})}updated(e){if(super.updated(e),(e.has("hint")||e.has("hintPlacement"))&&(this.syncManagedHint(),this.syncFieldDescribedBy()),!e.has("fieldName"))return;const t=this.theField;t instanceof Element&&(this.fieldName?t.setAttribute("name",this.fieldName):t.removeAttribute("name"))}connectedCallback(){super.connectedCallback(),this.observeConsumerHintContent()}disconnectedCallback(){this.hintContentObserver.disconnect(),super.disconnectedCallback()}observeConsumerHintContent(){this.hintContentObserver.disconnect();for(const e of this.consumerHintElements())this.hintContentObserver.observe(e,{attributes:!0,attributeFilter:["id"],characterData:!0,childList:!0,subtree:!0})}syncManagedHint(){if(!(!!this.hint&&this.consumerHintElements().length===0)){this.managedHintEl?.remove(),this.managedHintEl=null;return}if(!this.managedHintEl){const t=document.createElement("span");t.slot="hint",t.id=this.hintId,t.toggleAttribute("data-esp-managed-hint",!0),this.managedHintEl=t,this.appendChild(t)}this.managedHintEl.textContent=this.hint}hintDescriptionIds(){return this.managedHintEl?[this.managedHintEl.id]:this.consumerHintElements().map(e=>(e.id||(e.id=`esp-form-item-hint-${++m}`),e.id))}hintDescriptionText(){if(!this.hasHint)return null;if(this.managedHintEl)return this.managedHintEl.textContent??"";const e=this.consumerHintElements();return e.length===0?this.hint:e.map(t=>t.textContent?.trim()??"").filter(Boolean).join(" ")}syncFieldDescribedBy(){const e=this.fieldSlot.value?.assignedElements()[0];if(!e)return;const t=this.hasHint?this.hintDescriptionIds():[],r=(e.getAttribute("aria-describedby")??"").split(/\s+/).filter(s=>s&&!this.appliedHintTokens.includes(s)&&!t.includes(s));this.appliedHintTokens=t;const i=[...r,...t];i.length>0?e.setAttribute("aria-describedby",i.join(" ")):e.removeAttribute("aria-describedby"),e.setFormItemDescription?.(this.hintDescriptionText())}focus(e){(this.fieldSlot.value?.assignedElements()[0]??this.theField).focus(e)}renderHint(){return p`
2
+ <div class="hint" aria-hidden=${this.hintPlacement==="above"?"true":d}>
3
3
  <slot name="hint" @slotchange=${this.handleHintSlotChange}></slot>
4
4
  </div>
5
- `}render(){const e={"form-item":!0,"has-hint":this.hasHint,"has-error":this.error.length>0,"has-warning":this.warning.length>0&&this.error.length===0};return m`
6
- <div class=${v(e)}>
7
- <label
8
- @click=${t=>{this.focus(),t.stopPropagation()}}
9
- >
10
- ${this.label} ${this.hintPlacement==="above"?this.renderHint():c}
11
- <slot ${y(this.fieldSlot)}></slot>
12
- </label>
13
- ${this.hintPlacement!=="above"?this.renderHint():c}
5
+ `}render(){const e=E(this,{helpUrl:this.helpUrl,fallbackAnchor:this.fieldName}),t=!!e?.anchor,r={"form-item":!0,"has-hint":this.hasHint,"has-help":t,"hint-above":this.hasHint&&this.hintPlacement==="above","has-error":this.error.length>0,"has-warning":this.warning.length>0&&this.error.length===0};return p`
6
+ <div class=${v(r)}>
7
+ <div class="field-shell">
8
+ <label
9
+ @click=${i=>{this.focus(),i.stopPropagation()}}
10
+ >
11
+ <span class="field-label">${this.label}</span>
12
+ ${this.hintPlacement==="above"?this.renderHint():d}
13
+ <slot ${y(this.fieldSlot)}></slot>
14
+ </label>
15
+ ${e?.anchor?p`
16
+ <esp-help-button
17
+ class="field-help"
18
+ help-url=${e.href}
19
+ label=${this.label?`Help for ${this.label}`:"Help for this field"}
20
+ .helpTitle=${this.helpTitleSource==="document"?"":this.label.trim()}
21
+ .placementTarget=${this.fieldElement}
22
+ ></esp-help-button>
23
+ `:d}
24
+ </div>
25
+ ${this.hintPlacement!=="above"?this.renderHint():d}
14
26
  <div class="error-message">
15
27
  <span>${this.error}</span>
16
28
  </div>
@@ -18,18 +30,52 @@ var n=function(l,e,t,r){var o=arguments.length,i=o<3?e:r===null?r=Object.getOwnP
18
30
  <span>${this.warning}</span>
19
31
  </div>
20
32
  </div>
21
- `}};s.styles=u`
33
+ `}};o.styles=u`
22
34
  .form-item {
23
35
  font-family: var(--esp-form-item-font, var(--esp-font-body));
24
36
  font-size: var(--esp-form-item-font-size, var(--esp-size-font));
25
37
  display: grid;
26
38
  grid-auto-rows: min-content;
27
39
 
28
- > label {
40
+ > .field-shell {
41
+ align-items: center;
42
+ column-gap: var(--esp-size-tiny);
43
+ display: grid;
44
+ grid-template-columns: max-content min-content minmax(0, 1fr);
45
+ }
46
+
47
+ > .field-shell > label {
48
+ display: contents;
49
+ }
50
+
51
+ > .field-shell > label > .field-label {
52
+ grid-column: 1;
53
+ grid-row: 1;
29
54
  font-weight: bold;
30
55
  color: var(--esp-form-item-label-color, var(--esp-color-text));
31
56
  }
32
57
 
58
+ > .field-shell > label > slot,
59
+ > .field-shell > label > slot::slotted(*) {
60
+ grid-column: 1 / -1;
61
+ grid-row: 2;
62
+ }
63
+
64
+ .field-help {
65
+ grid-column: 2;
66
+ grid-row: 1;
67
+ }
68
+
69
+ &.hint-above > .field-shell > label > .hint {
70
+ grid-column: 1 / -1;
71
+ grid-row: 2;
72
+ }
73
+
74
+ &.hint-above > .field-shell > label > slot,
75
+ &.hint-above > .field-shell > label > slot::slotted(*) {
76
+ grid-row: 3;
77
+ }
78
+
33
79
  .hint {
34
80
  display: none;
35
81
  font-size: var(--esp-form-item-hint-font-size, var(--esp-type-tiny));
@@ -146,4 +192,4 @@ var n=function(l,e,t,r){var o=arguments.length,i=o<3?e:r===null?r=Object.getOwnP
146
192
  }
147
193
  }
148
194
  }
149
- `,n([a({type:String})],s.prototype,"label",void 0),n([a({type:Boolean,reflect:!0})],s.prototype,"autofocus",void 0),n([a({type:String})],s.prototype,"hint",void 0),n([a({attribute:"hint-placement",type:String})],s.prototype,"hintPlacement",void 0),n([a({type:String})],s.prototype,"error",null),n([a({type:String})],s.prototype,"warning",null),n([a({attribute:"field-name",type:String})],s.prototype,"fieldName",void 0),n([a({attribute:"error-pool",type:Array})],s.prototype,"errorPool",null),s=n([g("esp-form-item")],s);export{s as EspalierFormItem};
195
+ `,n([l({type:String})],o.prototype,"label",void 0),n([l({type:Boolean,reflect:!0})],o.prototype,"autofocus",void 0),n([l({type:String})],o.prototype,"hint",void 0),n([l({attribute:"hint-placement",type:String})],o.prototype,"hintPlacement",void 0),n([l({type:String})],o.prototype,"error",null),n([l({type:String})],o.prototype,"warning",null),n([l({attribute:"field-name",type:String})],o.prototype,"fieldName",void 0),n([l({attribute:"help-url",type:String})],o.prototype,"helpUrl",void 0),n([l({attribute:"help-title-source",type:String})],o.prototype,"helpTitleSource",void 0),n([l({attribute:"error-pool",type:Array})],o.prototype,"errorPool",null),o=n([g("esp-form-item")],o);export{o as EspalierFormItem};
@@ -0,0 +1,61 @@
1
+ import { nothing, type PropertyValues } from "lit";
2
+ import { EspalierElementBase } from "../shared/esp-element-base.js";
3
+ /**
4
+ * An icon-only trigger for URL-addressable help. Set `help-url` directly or
5
+ * place the button beneath an ancestor carrying `help-src`; `anchor` selects
6
+ * a stable heading id inside that document. The provider keeps the button's
7
+ * pressed state synchronized while its topic is showing.
8
+ *
9
+ * ```html
10
+ * <section help-src="/help/billing.html">
11
+ * <h2>
12
+ * Billing
13
+ * <esp-help-button anchor="billing" label="Help for billing"></esp-help-button>
14
+ * </h2>
15
+ * </section>
16
+ *
17
+ * <esp-help-button
18
+ * help-url="/help/exporting.html#csv"
19
+ * label="Help with CSV exports"
20
+ * title="Export help">
21
+ * </esp-help-button>
22
+ * ```
23
+ *
24
+ * @docPageTitle Help Button
25
+ * @docUrl /components/help/button
26
+ * @menuGroup Interaction
27
+ * @menuLabel Help Button
28
+ * @menuIcon info
29
+ */
30
+ export declare class EspalierHelpButton extends EspalierElementBase {
31
+ /**
32
+ * Per-trigger help document override. May include a topic fragment. When
33
+ * omitted, the nearest composed-tree ancestor's `help-src` is used.
34
+ */
35
+ helpUrl: string;
36
+ /** Stable heading id inside the resolved help document. */
37
+ anchor: string;
38
+ /** Accessible label for the icon-only button. */
39
+ label: string;
40
+ /** Optional flyout heading. Topic/document headings are used when omitted. */
41
+ helpTitle: string;
42
+ /**
43
+ * Element the contextual flyout should point at. Form items set this to
44
+ * their field control while the help button remains the focus-return target.
45
+ */
46
+ placementTarget: HTMLElement | undefined;
47
+ /** Whether this button's exact help topic is currently showing. */
48
+ active: boolean;
49
+ connectedCallback(): void;
50
+ disconnectedCallback(): void;
51
+ /** Forward focus to the composed native control. */
52
+ focus(options?: FocusOptions): void;
53
+ protected updated(changed: PropertyValues): void;
54
+ protected render(): import("lit-html").TemplateResult<1> | typeof nothing;
55
+ static styles: import("lit").CSSResult[];
56
+ }
57
+ declare global {
58
+ interface HTMLElementTagNameMap {
59
+ "esp-help-button": EspalierHelpButton;
60
+ }
61
+ }