@taprootio/espalier 2.0.1 → 2.1.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.
@@ -17753,7 +17753,7 @@
17753
17753
  "text": "GridRow[]"
17754
17754
  },
17755
17755
  "privacy": "public",
17756
- "description": "If using a `data-url`, the items retrieved are available\nin the items property. An items array can be assigned\ninstead of using a `data-url`.",
17756
+ "description": "If using a `data-url`, the items retrieved are available\nin the items property. An items array can be assigned\ninstead of using a `data-url`.\n\nAssigning a new items array preserves the grid's current\nintrinsic state: an active search term is re-applied to the\nnew items, the active sort column re-sorts them, and the\ncurrent page is kept when it still exists (clamping to the\nnearest valid page otherwise). Infinite grids delegate search\nand sort to the server through `fetchPage` instead.",
17757
17757
  "attribute": "items"
17758
17758
  },
17759
17759
  {
@@ -18229,6 +18229,25 @@
18229
18229
  "name": "handleSearch",
18230
18230
  "privacy": "private"
18231
18231
  },
18232
+ {
18233
+ "kind": "method",
18234
+ "name": "applySearchTerm",
18235
+ "privacy": "private",
18236
+ "return": {
18237
+ "type": {
18238
+ "text": "GridRow[]"
18239
+ }
18240
+ },
18241
+ "parameters": [
18242
+ {
18243
+ "name": "items",
18244
+ "type": {
18245
+ "text": "GridRow[]"
18246
+ }
18247
+ }
18248
+ ],
18249
+ "description": "Filters `items` by the current intrinsic search term, matching\nagainst `searchFields` when configured (all fields otherwise).\nReturns the input array untouched when no search term is active."
18250
+ },
18232
18251
  {
18233
18252
  "kind": "method",
18234
18253
  "name": "loadDataUrl",
@@ -18888,7 +18907,7 @@
18888
18907
  "type": {
18889
18908
  "text": "GridRow[]"
18890
18909
  },
18891
- "description": "If using a `data-url`, the items retrieved are available\nin the items property. An items array can be assigned\ninstead of using a `data-url`.",
18910
+ "description": "If using a `data-url`, the items retrieved are available\nin the items property. An items array can be assigned\ninstead of using a `data-url`.\n\nAssigning a new items array preserves the grid's current\nintrinsic state: an active search term is re-applied to the\nnew items, the active sort column re-sorts them, and the\ncurrent page is kept when it still exists (clamping to the\nnearest valid page otherwise). Infinite grids delegate search\nand sort to the server through `fetchPage` instead.",
18892
18911
  "fieldName": "items"
18893
18912
  },
18894
18913
  {
@@ -19222,6 +19241,14 @@
19222
19241
  "description": "The z-index of the header host. Defaults to `20`.",
19223
19242
  "name": "--esp-header-z-index"
19224
19243
  },
19244
+ {
19245
+ "description": "Surface-alignment contract set by `esp-page`: the maximum width the bar content is constrained to (the bar background stays full-bleed). Defaults to `100%` (no constraint). `esp-page` sets this to its resolved content cap.",
19246
+ "name": "--esp-header-content-max-width"
19247
+ },
19248
+ {
19249
+ "description": "Surface-alignment contract set by `esp-page`: the fraction of the leftover width placed before the content, aligning it (`0` = start, `0.5` = center, `1` = end). Defaults to `0`.",
19250
+ "name": "--esp-header-content-lead"
19251
+ },
19225
19252
  {
19226
19253
  "description": "The height of compact scroll-reactive headers. Defaults to `max(44px, calc(3.5 * var(--esp-size-small)))`.",
19227
19254
  "name": "--esp-header-compact-height"
@@ -19638,6 +19665,18 @@
19638
19665
  "description": "Optional ID of an external drawer-capable menu controlled by the\nheader burger. When set, burger open/close events call\n`openDrawer()` / `closeDrawer()` on that element instead of the\nmenu slotted into the header.",
19639
19666
  "attribute": "drawer-target"
19640
19667
  },
19668
+ {
19669
+ "kind": "field",
19670
+ "name": "fullBleedContent",
19671
+ "type": {
19672
+ "text": "boolean"
19673
+ },
19674
+ "privacy": "public",
19675
+ "default": "false",
19676
+ "description": "Opt out of surface alignment. When an `esp-header` is slotted into an\n`esp-page`, it constrains its bar content to the page's content\nsurface by default (the bar background stays full-bleed). Set this to\nlet the content span the full width instead, as it did before the\npage grew alignment modes. Standalone headers (outside `esp-page`) are\nalready full-bleed and unaffected.",
19677
+ "attribute": "full-bleed-content",
19678
+ "reflects": true
19679
+ },
19641
19680
  {
19642
19681
  "kind": "field",
19643
19682
  "name": "menuContainer",
@@ -20555,6 +20594,15 @@
20555
20594
  "description": "Optional ID of an external drawer-capable menu controlled by the\nheader burger. When set, burger open/close events call\n`openDrawer()` / `closeDrawer()` on that element instead of the\nmenu slotted into the header.",
20556
20595
  "fieldName": "drawerTarget"
20557
20596
  },
20597
+ {
20598
+ "name": "full-bleed-content",
20599
+ "type": {
20600
+ "text": "boolean"
20601
+ },
20602
+ "default": "false",
20603
+ "description": "Opt out of surface alignment. When an `esp-header` is slotted into an\n`esp-page`, it constrains its bar content to the page's content\nsurface by default (the bar background stays full-bleed). Set this to\nlet the content span the full width instead, as it did before the\npage grew alignment modes. Standalone headers (outside `esp-page`) are\nalready full-bleed and unaffected.",
20604
+ "fieldName": "fullBleedContent"
20605
+ },
20558
20606
  {
20559
20607
  "name": "scheme",
20560
20608
  "type": {
@@ -30496,11 +30544,11 @@
30496
30544
  "name": "EspalierPage",
30497
30545
  "cssProperties": [
30498
30546
  {
30499
- "description": "The background color of the page. Defaults to `var(--esp-color-background)`.",
30547
+ "description": "The background color of the page. Fills both the surface and the canvas gutters; the canvas tokens paint over it in the gutters only. Defaults to `var(--esp-color-background)`.",
30500
30548
  "name": "--esp-page-background"
30501
30549
  },
30502
30550
  {
30503
- "description": "The maximum width of the main content well. Set automatically by the `kind` attribute: `1536px` (wide), `768px` (narrow), or `none` (full).",
30551
+ "description": "The maximum width of the main content well. Set automatically by the `kind` attribute: `1536px` (wide), `768px` (narrow), or `none` (full). This cap now sizes the main grid track; surplus width beyond it becomes the canvas gutters.",
30504
30552
  "name": "--esp-page-max-width"
30505
30553
  },
30506
30554
  {
@@ -30511,6 +30559,26 @@
30511
30559
  "description": "The opacity of the background image layer. Defaults to `1`.",
30512
30560
  "name": "--esp-page-background-image-opacity"
30513
30561
  },
30562
+ {
30563
+ "description": "The background color of the canvas gutters (the outer regions revealed when the viewport exceeds the cap). Defaults to `transparent`, so gutters match the page until styled and narrow viewports are unaffected.",
30564
+ "name": "--esp-page-canvas-background"
30565
+ },
30566
+ {
30567
+ "description": "A background image for the canvas gutters, mirroring the page background-image knob. Defaults to `none`.",
30568
+ "name": "--esp-page-canvas-background-image"
30569
+ },
30570
+ {
30571
+ "description": "The opacity of the canvas gutter image layer. Defaults to `1`.",
30572
+ "name": "--esp-page-canvas-background-image-opacity"
30573
+ },
30574
+ {
30575
+ "description": "The box shadow drawn on the left and right edges of the content surface. On by default; casts into the gutters and is clipped away when the surface fills the viewport, so it only shows above the cap. Set to `none` to remove it.",
30576
+ "name": "--esp-page-surface-shadow"
30577
+ },
30578
+ {
30579
+ "description": "An optional border on the inline edges of the surface, for themes preferring a hairline over a shadow. Defaults to `none` (e.g. `1px solid var(--esp-color-border)`).",
30580
+ "name": "--esp-page-surface-border"
30581
+ },
30514
30582
  {
30515
30583
  "description": "Offset reserved for fixed headers. Defaults to `var(--esp-header-height)`.",
30516
30584
  "name": "--esp-page-fixed-header-offset"
@@ -30520,7 +30588,7 @@
30520
30588
  "name": "--esp-page-sticky-header-top"
30521
30589
  },
30522
30590
  {
30523
- "description": "Z-index for fixed/sticky header regions. Defaults to `20`. ```html <style> esp-page.with-background { --esp-page-background-image: url(/assets/espalier-paper-texture.png); --esp-page-background-image-opacity: .5; } </style> <esp-page class=\"docs with-background\"> <div slot=\"header\" style=\"background-color: red;\">header</div> <div slot=\"sidebar\" style=\"background-color: green;\">sidebar</div> <div slot=\"right\" style=\"background-color: purple;\">right</div> <div slot=\"footer\" style=\"background-color: blue;\">footer</div> <div style=\"overflow: hidden;\"> <h2>With background image</h2> </div> </esp-page> ```",
30591
+ "description": "Z-index for fixed/sticky header regions. Defaults to `20`.",
30524
30592
  "name": "--esp-page-header-z-index"
30525
30593
  },
30526
30594
  {
@@ -30584,6 +30652,14 @@
30584
30652
  {
30585
30653
  "description": "The page wrapper. It defaults to a grid with a height of 100vh. For the demo page, the height is set to 400px by styling the CSS part: ```html <style> esp-page.docs { &::part(wrapper) { min-height: 400px; height: 400px; } > div { height: 100%; color: yellow; padding: var(--esp-size-padding); } } .demo-wrapper { border: 2px solid var(--esp-color-border); } </style> <esp-page class=\"docs\"> <div slot=\"header\" style=\"background-color: red;\">header</div> <div slot=\"sidebar\" style=\"background-color: green;\">sidebar</div> <div slot=\"right\" style=\"background-color: purple;\">right</div> <div slot=\"footer\" style=\"background-color: blue;\">footer</div> <div style=\"background-color: black; overflow: hidden;\"> content </div> </esp-page> ```",
30586
30654
  "name": "wrapper"
30655
+ },
30656
+ {
30657
+ "description": "The two canvas gutter regions flanking the surface.",
30658
+ "name": "canvas"
30659
+ },
30660
+ {
30661
+ "description": "The surface backdrop carrying the edge shadow/border. ```html <style> esp-page.with-background { --esp-page-background-image: url(/assets/espalier-paper-texture.png); --esp-page-background-image-opacity: .5; } </style> <esp-page class=\"docs with-background\"> <div slot=\"header\" style=\"background-color: red;\">header</div> <div slot=\"sidebar\" style=\"background-color: green;\">sidebar</div> <div slot=\"right\" style=\"background-color: purple;\">right</div> <div slot=\"footer\" style=\"background-color: blue;\">footer</div> <div style=\"overflow: hidden;\"> <h2>With background image</h2> </div> </esp-page> ``` Use `align` to place the surface within the page once it hits its cap, and the canvas tokens to style the gutters. A low `--esp-page-max-width` is set here only so the gutters are visible inside the narrow demo frame; the surface edge shadow is on by default. ```html <style> esp-page.align-demo { --esp-page-max-width: 520px; --esp-page-canvas-background: var(--esp-color-layer-1); } </style> <esp-page class=\"docs align-demo\" align=\"center\"> <div slot=\"header\" style=\"background-color: red;\">header</div> <div slot=\"sidebar\" style=\"background-color: green;\">sidebar</div> <div slot=\"right\" style=\"background-color: purple;\">right</div> <div slot=\"footer\" style=\"background-color: blue;\">footer</div> <div style=\"background-color: black; overflow: hidden;\"> content </div> </esp-page> ```",
30662
+ "name": "surface"
30587
30663
  }
30588
30664
  ],
30589
30665
  "slots": [
@@ -30626,6 +30702,30 @@
30626
30702
  "attribute": "kind",
30627
30703
  "reflects": true
30628
30704
  },
30705
+ {
30706
+ "kind": "field",
30707
+ "name": "align",
30708
+ "type": {
30709
+ "text": "\"start\" | \"center\" | \"end\""
30710
+ },
30711
+ "privacy": "public",
30712
+ "default": "\"start\"",
30713
+ "description": "Horizontal alignment of the content **surface** (left aside + main\nwell + right aside) within the page once the viewport is wider than\nthe content cap. The surplus width becomes styleable **canvas**\ngutters on the outer edges, weighted by this attribute.\n\n- `start` (default) — the surface hugs the leading edge; all spare\n width collects in the trailing gutter. This reproduces today's\n layout.\n- `center` — spare width is split evenly into both gutters, centering\n the surface.\n- `end` — the surface hugs the trailing edge; spare width collects in\n the leading gutter.\n\nHas no effect when `kind=\"full\"` (no cap means no gutters) or on\nviewports narrower than the cap (the surface already fills the width).",
30714
+ "attribute": "align",
30715
+ "reflects": true
30716
+ },
30717
+ {
30718
+ "kind": "field",
30719
+ "name": "contained",
30720
+ "type": {
30721
+ "text": "boolean"
30722
+ },
30723
+ "privacy": "public",
30724
+ "default": "false",
30725
+ "description": "When set, the header and footer sit **on** the content surface —\nthe same width as the left aside + main well + right aside — instead\nof spanning the full viewport. Combined with a styled canvas this\nframes the whole page as one contained band floating on the canvas,\nwith a single continuous surface edge shadow running from the header\nthrough the content to the footer.\n\nBelow the cap there are no gutters, so a contained page renders\nidentically to the default full-bleed chrome. Designed for\n`header-position=\"normal\"` or `\"sticky\"`; a fixed header cannot be\nbanded to the surface.",
30726
+ "attribute": "contained",
30727
+ "reflects": true
30728
+ },
30629
30729
  {
30630
30730
  "kind": "field",
30631
30731
  "name": "headerPosition",
@@ -31134,6 +31234,24 @@
31134
31234
  "description": "The layout mode of the page.\n\n- `wide` (default) — constrains the main content well to 1536px.\n Optimized for high-density dashboards and complex data grids.\n- `narrow` — constrains the main content well to 768px and applies\n a `max-inline-size: 66ch` reading measure. Optimized for\n long-form reading content.\n- `full` — no max-width constraint; the content well spans the\n full available space. Optimized for immersive canvases like\n maps or design tools.",
31135
31235
  "fieldName": "kind"
31136
31236
  },
31237
+ {
31238
+ "name": "align",
31239
+ "type": {
31240
+ "text": "\"start\" | \"center\" | \"end\""
31241
+ },
31242
+ "default": "\"start\"",
31243
+ "description": "Horizontal alignment of the content **surface** (left aside + main\nwell + right aside) within the page once the viewport is wider than\nthe content cap. The surplus width becomes styleable **canvas**\ngutters on the outer edges, weighted by this attribute.\n\n- `start` (default) — the surface hugs the leading edge; all spare\n width collects in the trailing gutter. This reproduces today's\n layout.\n- `center` — spare width is split evenly into both gutters, centering\n the surface.\n- `end` — the surface hugs the trailing edge; spare width collects in\n the leading gutter.\n\nHas no effect when `kind=\"full\"` (no cap means no gutters) or on\nviewports narrower than the cap (the surface already fills the width).",
31244
+ "fieldName": "align"
31245
+ },
31246
+ {
31247
+ "name": "contained",
31248
+ "type": {
31249
+ "text": "boolean"
31250
+ },
31251
+ "default": "false",
31252
+ "description": "When set, the header and footer sit **on** the content surface —\nthe same width as the left aside + main well + right aside — instead\nof spanning the full viewport. Combined with a styled canvas this\nframes the whole page as one contained band floating on the canvas,\nwith a single continuous surface edge shadow running from the header\nthrough the content to the footer.\n\nBelow the cap there are no gutters, so a contained page renders\nidentically to the default full-bleed chrome. Designed for\n`header-position=\"normal\"` or `\"sticky\"`; a fixed header cannot be\nbanded to the surface.",
31253
+ "fieldName": "contained"
31254
+ },
31137
31255
  {
31138
31256
  "name": "header-position",
31139
31257
  "type": {
@@ -394,6 +394,13 @@ export declare class EspalierGrid extends EspalierElementBase {
394
394
  * If using a `data-url`, the items retrieved are available
395
395
  * in the items property. An items array can be assigned
396
396
  * instead of using a `data-url`.
397
+ *
398
+ * Assigning a new items array preserves the grid's current
399
+ * intrinsic state: an active search term is re-applied to the
400
+ * new items, the active sort column re-sorts them, and the
401
+ * current page is kept when it still exists (clamping to the
402
+ * nearest valid page otherwise). Infinite grids delegate search
403
+ * and sort to the server through `fetchPage` instead.
397
404
  */
398
405
  get items(): GridRow[];
399
406
  set items(newItems: GridRow[]);
@@ -1,4 +1,4 @@
1
- var h=function(I,e,t,i){var s=arguments.length,o=s<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,t):i,r;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")o=Reflect.decorate(I,e,t,i);else for(var n=I.length-1;n>=0;n--)(r=I[n])&&(o=(s<3?r(o):s>3?r(e,t,o):r(e,t))||o);return s>3&&o&&Object.defineProperty(e,t,o),o};import{css as E,html as c}from"lit";import{customElement as A,property as m,state as u}from"lit/decorators.js";import"../input/esp-input.js";import"../button/esp-button.js";import{EspalierElementBase as S}from"../shared/esp-element-base.js";import{ref as y,createRef as w}from"lit/directives/ref.js";import"./esp-grid-column.js";import{classMap as x}from"lit/directives/class-map.js";import{styleMap as P}from"lit/directives/style-map.js";import{leftArrow as N}from"../shared/svgs/left-arrow.js";import{rightArrow as T}from"../shared/svgs/right-arrow.js";import{filter as G}from"../shared/svgs/filter.js";import"../shared/virtualizer/lit-virtualizer.js";const L="esp-grid-load-start",M="esp-grid-load-success",z="esp-grid-load-error",j="esp-grid-items-changed";let d=class extends S{constructor(){super(...arguments),this.columnDefinitionsSlot=w(),this.headerButtonsSlot=w(),this.filteredItems=[],this.sortColumn=void 0,this.itemsBacker=[],this.virtualizerRef=w(),this.gridHeaderRowRef=w(),this.infiniteContainerRef=w(),this.pageLoadVersion=0,this.nextCursor=null,this.searchTerm="",this.allowNext=!1,this.allowPrevious=!1,this.columnDefinitions=new Array,this.displayedItems=[],this.pageNumber=1,this.footerMessage="",this.allLoadedItems=[],this.hasMoreData=!1,this.isLoadingFirstPage=!1,this.isLoadingPage=!1,this.effectiveGridHeight="",this.pendingHeightRetry=!1,this.wheelHandlerAttached=!1,this.dataField="",this.dataUrl="",this.pageSize=10,this.gridHeight="60vh",this.searchFields=[],this.fetchPageBacker=null,this.isAdjustingHeight=!1,this.heightRetryObserver=null,this.pinnedOffsets=new Map,this.handleGridClick=e=>{const t=e.target,i=t.hasAttribute("grid-event")?t:t.closest("[grid-event]");if(!i)return;const s=i.closest("tr")??i.closest(".grid-row"),o=Number(s?.dataset.rowIndex),r=this.isInfiniteMode?this.allLoadedItems:this.displayedItems,n=Number.isInteger(o)&&o>=0?r[o]:void 0;this.dispatchEvent(new CustomEvent("grid-event",{detail:{grid:this,target:i,event:i.getAttribute("grid-event")??"",data:n??{}},bubbles:!1,composed:!0})),e.stopPropagation()},this.syncHeaderScroll=()=>{const e=this.virtualizerRef.value,t=this.gridHeaderRowRef.value;e&&t&&(t.scrollLeft=e.scrollLeft)},this.handleVirtualizerWheel=e=>{const t=this.infiniteContainerRef.value;if(!t)return;const i=t.getBoundingClientRect(),s=window.innerHeight,o=i.top>=0&&i.bottom<=s,r=i.top<=0&&i.bottom>=s;!o&&!r&&(e.preventDefault(),window.scrollBy(0,e.deltaY))},this.handleSearch=e=>{const t=e.detail.toLowerCase();if(this.isInfiniteMode)this.searchDebounceTimer&&clearTimeout(this.searchDebounceTimer),this.searchDebounceTimer=setTimeout(async()=>{this.searchTerm=t,await this.resetAndReload().catch(()=>{})},300);else{this.searchTerm=t;const i=this.items.filter(s=>{let o;return this.searchFields.length>0?o=this.searchFields.map(r=>s[r]):o=Object.values(s),o.some(r=>String(r).toLowerCase().includes(t))});this.filteredItems=i,this.setPage(1),this.dispatchItemsChanged()}}}get items(){return this.itemsBacker}set items(e){this.applyPagedItems(e)}applyPagedItems(e,t={}){const i=t.emitItemsChanged??!0,s=t.pageNumber??this.pageNumber??1;this.itemsBacker=e,this.filteredItems=this.itemsBacker,this.sortColumn?this.sort({emitItemsChanged:!1,pageNumber:s}):this.setPage(s),i&&this.dispatchItemsChanged()}get fetchPage(){return this.fetchPageBacker}set fetchPage(e){this.fetchPageBacker=e,e&&this.hasUpdated&&this.loadPage(null).then(async()=>{await this.updateComplete,this.measurePinnedOffsets()}).catch(()=>{})}get isInfiniteMode(){return this.fetchPage!==null}get currentDataStateItems(){return this.isInfiniteMode?this.allLoadedItems:this.filteredItems}getCurrentSortField(){return this.sortColumn?this.sortColumn.sortField.length>0?this.sortColumn.sortField:this.sortColumn.fieldName:""}createDataStateDetail(e={}){const t=[...this.currentDataStateItems],i=e.cursor??null,s=e.isFirstPage??(this.isInfiniteMode?i===null:this.pageNumber<=1);return{items:t,itemCount:t.length,isFirstPage:s,cursor:i,nextCursor:this.nextCursor,hasRows:t.length>0,isEmpty:t.length===0,search:this.searchTerm,sortField:this.getCurrentSortField(),sortOrder:this.sortColumn?.sortOrder??""}}dispatchGridDataEvent(e,t){this.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0}))}dispatchItemsChanged(e={}){this.dispatchGridDataEvent(j,this.createDataStateDetail(e))}setPage(e){const t=Math.ceil(this.filteredItems.length/this.pageSize);e>t&&(e=t),this.pageNumber=e;const i=(this.pageNumber-1)*this.pageSize;switch(this.displayedItems=this.filteredItems.slice(i,i+this.pageSize),this.allowPrevious=this.pageNumber>1,this.allowNext=t>this.pageNumber,t){case 0:this.footerMessage="No matching items";break;case 1:this.footerMessage=this.filteredItems.length<this.items.length?"Showing all filtered items":"Showing all items";break;default:this.footerMessage=`Showing page ${this.pageNumber} of ${t} pages`}}async loadPage(e){const t=this.fetchPage;if(!t||e!==null&&this.isLoadingPage)return;const i=++this.pageLoadVersion,s=e===null;let o=!1;this.isLoadingPage=!0,this.isLoadingFirstPage=s,s&&(this.nextCursor=null,this.hasMoreData=!1);const r={cursor:e,limit:this.pageSize,search:this.searchTerm,sortField:this.getCurrentSortField(),sortOrder:this.sortColumn?.sortOrder??""};this.dispatchGridDataEvent(L,this.createDataStateDetail({cursor:e,isFirstPage:s}));try{const n=await t(r);if(i!==this.pageLoadVersion)return;e===null?this.allLoadedItems=[...n.items]:this.allLoadedItems=[...this.allLoadedItems,...n.items],this.nextCursor=n.nextCursor,this.hasMoreData=n.nextCursor!==null,o=!0,this.dispatchGridDataEvent(M,this.createDataStateDetail({cursor:e,isFirstPage:s})),this.dispatchItemsChanged({cursor:e,isFirstPage:s})}catch(n){throw i===this.pageLoadVersion&&this.dispatchGridDataEvent(z,{...this.createDataStateDetail({cursor:e,isFirstPage:s}),error:n}),n}finally{if(i===this.pageLoadVersion){if(this.isLoadingPage=!1,this.isLoadingFirstPage=!1,s&&o){const n=this.virtualizerRef.value;if(n&&n.scrollTop>0)try{n.scrollTo({top:0})}catch{n.scrollTop=0}}await this.adjustInfiniteHeight()}}}async resetAndReload(){await this.loadPage(null)}onRangeChanged(e){this.isLoadingFirstPage||e.last>=this.allLoadedItems.length-5&&this.hasMoreData&&!this.isLoadingPage&&this.loadPage(this.nextCursor).catch(()=>{})}async adjustInfiniteHeight(){if(!this.isAdjustingHeight){this.isAdjustingHeight=!0;try{const e=this.virtualizerRef.value;e&&(e.style.minHeight=""),this.effectiveGridHeight=this.gridHeight,await this.updateComplete;const t=this.infiniteContainerRef.value,i=this.virtualizerRef.value;if(!t||this.hasMoreData)return;const o=this.gridHeaderRowRef.value?.offsetHeight??0;if(!i){const p=t.querySelector(".no-results");let f=0;p?f=o+p.offsetHeight:t.querySelectorAll(":scope > *").forEach(R=>f+=R.offsetHeight),f<t.offsetHeight&&(this.effectiveGridHeight=`${f}px`);return}const r=i.querySelectorAll(".grid-row");let n=0,a=!1;if(r.forEach(p=>{const f=p.offsetHeight;f===0&&(a=!0),n+=f}),r.length===0||a||n===0){this.pendingHeightRetry||(this.pendingHeightRetry=!0,this.observeForHeightRetry(i));return}const l=i.querySelectorAll(".grid-row :defined"),g=[];l.forEach(p=>{"updateComplete"in p&&g.push(p.updateComplete)}),g.length>0&&(await Promise.all(g),n=0,r.forEach(p=>{n+=p.offsetHeight}));const v=n+o,b=t.offsetHeight;v<b&&r.length>=this.allLoadedItems.length&&(i.style.minHeight=`${n}px`,this.effectiveGridHeight=`${v}px`),!this.hasMoreData&&!this.pendingHeightRetry&&(this.pendingHeightRetry=!0,setTimeout(()=>{this.pendingHeightRetry=!1;const p=this.infiniteContainerRef.value,f=this.virtualizerRef.value;if(!p||!f||this.hasMoreData)return;const O=this.gridHeaderRowRef.value?.offsetHeight??0,k=f.querySelectorAll(".grid-row");let C=0,D=!1;if(k.forEach(F=>{const H=F.offsetHeight;H===0&&(D=!0),C+=H}),k.length===0||D||C===0)return;const $=C+O;$<p.offsetHeight&&k.length>=this.allLoadedItems.length&&(f.style.minHeight=`${C}px`,this.effectiveGridHeight=`${$}px`)},100))}finally{this.isAdjustingHeight=!1}}}observeForHeightRetry(e){this.heightRetryObserver?.disconnect(),this.heightRetryObserver=new IntersectionObserver(t=>{if(!t.some(r=>r.isIntersecting))return;this.heightRetryObserver?.disconnect(),this.heightRetryObserver=null;let s=0;const o=()=>{this.pendingHeightRetry=!1,this.adjustInfiniteHeight(),this.pendingHeightRetry&&++s<5&&setTimeout(o,100)};requestAnimationFrame(()=>setTimeout(o,50))},{threshold:0}),this.heightRetryObserver.observe(e)}async sortOn(e){this.sortColumn===e?this.sortColumn.sortOrder=this.sortColumn.sortOrder=="asc"?"desc":"asc":(this.sortColumn=e,this.sortColumn.sortOrder="asc"),this.isInfiniteMode?await this.resetAndReload():this.sort()}sort(e={}){if(!this.sortColumn)return;const t=this.getCurrentSortField(),i=this.sortColumn.sortOrder==="asc"?1:-1,s=this.sortColumn.sortType,o=this.filteredItems.map((r,n)=>{const a=r[t]??"",l=s==="number"?Number(a):a.toString().toLowerCase();return{item:r,index:n,sortValue:l}});o.sort((r,n)=>r.sortValue<n.sortValue?-1*i:r.sortValue>n.sortValue?1*i:r.index-n.index),this.filteredItems=o.map(({item:r})=>r),this.setPage(e.pageNumber??1),(e.emitItemsChanged??!0)&&this.dispatchItemsChanged()}delete(e){if(this.isInfiniteMode){const t=this.allLoadedItems.filter(i=>!e(i));if(t.length===this.allLoadedItems.length)return;this.allLoadedItems=t,this.dispatchItemsChanged(),this.adjustInfiniteHeight()}else{const t=this.items.filter(i=>!e(i));if(t.length===this.items.length)return;this.items=t}}addOrReplace(e,t){if(this.isInfiniteMode){const i=this.allLoadedItems.findIndex(e);if(i>=0){const s=[...this.allLoadedItems];s[i]={...s[i],...t},this.allLoadedItems=s}else this.allLoadedItems=[...this.allLoadedItems,t];this.dispatchItemsChanged(),this.adjustInfiniteHeight()}else this.items.find(e)?this.items=this.items.map(s=>e(s)?{...s,...t}:s):this.items=[...this.items,t]}addInOrder(e,t,i){if(this.isInfiniteMode){const a=this.allLoadedItems.findIndex(e),l=[...this.allLoadedItems];if(a>=0)return l[a]={...l[a],...t},this.allLoadedItems=l,this.dispatchItemsChanged(),a;let g=0,v=l.length;for(;g<v;){const b=g+v>>>1;i(l[b],t)<0?g=b+1:v=b}return l.splice(g,0,t),this.allLoadedItems=l,this.dispatchItemsChanged(),this.adjustInfiniteHeight(),g}const s=this.items.findIndex(e),o=[...this.items];if(s>=0)return o[s]={...o[s],...t},this.items=o,s;let r=0,n=o.length;for(;r<n;){const a=r+n>>>1;i(o[a],t)<0?r=a+1:n=a}return o.splice(r,0,t),this.items=o,r}async scrollToIndex(e,t="center"){if(this.isInfiniteMode){await this.updateComplete;const i=this.virtualizerRef.value;if(!i)return;await i.updateComplete,await new Promise(s=>requestAnimationFrame(s)),await i.layoutComplete,i.scrollToIndex(e,t)}else{const i=Math.floor(e/this.pageSize)+1;this.setPage(i)}}clear(){if(this.isInfiniteMode){if(this.allLoadedItems.length===0&&this.nextCursor===null&&!this.hasMoreData)return;this.allLoadedItems=[],this.nextCursor=null,this.hasMoreData=!1,this.dispatchItemsChanged(),this.adjustInfiniteHeight()}else{if(this.items.length===0)return;this.items=[]}}async reload(){this.isInfiniteMode&&await this.resetAndReload()}computePinnedStyles(){return this.pinnedOffsets}measurePinnedOffsets(){this.pinnedOffsets=new Map,this.isInfiniteMode?this.computePinnedOffsetsFromWidths():this.measurePinnedOffsetsFromCells(),this.requestUpdate()}computePinnedOffsetsFromWidths(){const e=this.columnDefinitions.filter(r=>r.pin==="left"),t=this.columnDefinitions.filter(r=>r.pin==="right"),i=[];for(let r=0;r<e.length;r++){const n=e[r],a=i.length===0?"0px":`calc(${i.join(" + ")})`;this.pinnedOffsets.set(n,{position:"sticky",left:a,zIndex:`${e.length-r+1}`}),i.push(n.width||"0px")}const s=[],o=[...t].reverse();for(let r=0;r<o.length;r++){const n=o[r],a=s.length===0?"0px":`calc(${s.join(" + ")})`;this.pinnedOffsets.set(n,{position:"sticky",right:a,zIndex:`${o.length-r+1}`}),s.push(n.width||"0px")}}measurePinnedOffsetsFromCells(){const e=this.shadowRoot?.querySelectorAll("thead th");if(!e)return;const t=this.columnDefinitions.filter(a=>a.pin==="left"),i=this.columnDefinitions.filter(a=>a.pin==="right"),s=new Map;this.columnDefinitions.forEach((a,l)=>{e[l]&&s.set(a,e[l])});let o=0;for(let a=0;a<t.length;a++){const l=t[a];this.pinnedOffsets.set(l,{position:"sticky",left:`${o}px`,zIndex:`${t.length-a+1}`});const g=s.get(l);g&&(o+=g.offsetWidth)}let r=0;const n=[...i].reverse();for(let a=0;a<n.length;a++){const l=n[a];this.pinnedOffsets.set(l,{position:"sticky",right:`${r}px`,zIndex:`${n.length-a+1}`});const g=s.get(l);g&&(r+=g.offsetWidth)}}computeGridTemplateColumns(){return this.columnDefinitions.map(e=>e.width?e.grow?`minmax(${e.width}, 1fr)`:e.width:e.grow?"minmax(0, 1fr)":"auto").join(" ")}get hasGrowableColumns(){return this.columnDefinitions.some(e=>e.grow)}computePinnedColumnFlags(){const e=new Map,t=this.columnDefinitions.filter(r=>r.pin==="left"),i=this.columnDefinitions.filter(r=>r.pin==="right"),s=t[t.length-1],o=i[0];return this.columnDefinitions.forEach((r,n)=>{e.set(r,{beforePinnedRight:this.columnDefinitions[n+1]?.pin==="right",firstPinnedRight:o===r,lastPinnedLeft:s===r})}),e}renderCell(e,t){if(!e.fieldName)return c`<span></span>`;const i=e.textAlign??"left";return c`<span style="text-align: ${i};"> ${t[e.fieldName]} </span>`}attachWheelHandler(){const e=this.virtualizerRef.value;e&&!this.wheelHandlerAttached?(e.addEventListener("wheel",this.handleVirtualizerWheel,{passive:!1}),this.wheelHandlerAttached=!0):!e&&this.wheelHandlerAttached&&(this.wheelHandlerAttached=!1)}async firstUpdated(e){super.firstUpdated(e),this.headerButtonsSlot.value.assignedElements().length===0&&this.headerButtonsSlot.value.remove();for(const i of this.columnDefinitionsSlot.value.assignedElements())i.localName==="esp-grid-column"&&this.columnDefinitions.push(i);const t=this.columnDefinitions.find(i=>i.sortOrder!=="");t&&(this.sortColumn=t),this.isInfiniteMode?await this.loadPage(null).catch(()=>{}):this.dataUrl.length&&await this.loadDataUrl().catch(()=>{}),await this.updateComplete,this.measurePinnedOffsets()}updated(e){super.updated(e),this.isInfiniteMode&&this.attachWheelHandler()}async loadDataUrl(){this.dispatchGridDataEvent(L,this.createDataStateDetail({cursor:null,isFirstPage:!0}));try{const t=await fetch(this.dataUrl).then(i=>i.json());this.applyPagedItems(this.dataField?t[this.dataField]:t,{emitItemsChanged:!1}),this.dispatchGridDataEvent(M,this.createDataStateDetail({cursor:null,isFirstPage:!0})),this.dispatchItemsChanged({cursor:null,isFirstPage:!0})}catch(t){throw this.dispatchGridDataEvent(z,{...this.createDataStateDetail({cursor:null,isFirstPage:!0}),error:t}),t}}disconnectedCallback(){super.disconnectedCallback();const e=this.virtualizerRef.value;e&&e.removeEventListener("wheel",this.handleVirtualizerWheel),this.wheelHandlerAttached=!1,this.heightRetryObserver?.disconnect(),this.heightRetryObserver=null}renderSortIcons(){return c`
1
+ var h=function(I,e,t,i){var s=arguments.length,o=s<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,t):i,r;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")o=Reflect.decorate(I,e,t,i);else for(var n=I.length-1;n>=0;n--)(r=I[n])&&(o=(s<3?r(o):s>3?r(e,t,o):r(e,t))||o);return s>3&&o&&Object.defineProperty(e,t,o),o};import{css as E,html as c}from"lit";import{customElement as A,property as m,state as u}from"lit/decorators.js";import"../input/esp-input.js";import"../button/esp-button.js";import{EspalierElementBase as S}from"../shared/esp-element-base.js";import{ref as y,createRef as w}from"lit/directives/ref.js";import"./esp-grid-column.js";import{classMap as x}from"lit/directives/class-map.js";import{styleMap as P}from"lit/directives/style-map.js";import{leftArrow as T}from"../shared/svgs/left-arrow.js";import{rightArrow as G}from"../shared/svgs/right-arrow.js";import{filter as N}from"../shared/svgs/filter.js";import"../shared/virtualizer/lit-virtualizer.js";const L="esp-grid-load-start",M="esp-grid-load-success",z="esp-grid-load-error",j="esp-grid-items-changed";let d=class extends S{constructor(){super(...arguments),this.columnDefinitionsSlot=w(),this.headerButtonsSlot=w(),this.filteredItems=[],this.sortColumn=void 0,this.itemsBacker=[],this.virtualizerRef=w(),this.gridHeaderRowRef=w(),this.infiniteContainerRef=w(),this.pageLoadVersion=0,this.nextCursor=null,this.searchTerm="",this.allowNext=!1,this.allowPrevious=!1,this.columnDefinitions=new Array,this.displayedItems=[],this.pageNumber=1,this.footerMessage="",this.allLoadedItems=[],this.hasMoreData=!1,this.isLoadingFirstPage=!1,this.isLoadingPage=!1,this.effectiveGridHeight="",this.pendingHeightRetry=!1,this.wheelHandlerAttached=!1,this.dataField="",this.dataUrl="",this.pageSize=10,this.gridHeight="60vh",this.searchFields=[],this.fetchPageBacker=null,this.isAdjustingHeight=!1,this.heightRetryObserver=null,this.pinnedOffsets=new Map,this.handleGridClick=e=>{const t=e.target,i=t.hasAttribute("grid-event")?t:t.closest("[grid-event]");if(!i)return;const s=i.closest("tr")??i.closest(".grid-row"),o=Number(s?.dataset.rowIndex),r=this.isInfiniteMode?this.allLoadedItems:this.displayedItems,n=Number.isInteger(o)&&o>=0?r[o]:void 0;this.dispatchEvent(new CustomEvent("grid-event",{detail:{grid:this,target:i,event:i.getAttribute("grid-event")??"",data:n??{}},bubbles:!1,composed:!0})),e.stopPropagation()},this.syncHeaderScroll=()=>{const e=this.virtualizerRef.value,t=this.gridHeaderRowRef.value;e&&t&&(t.scrollLeft=e.scrollLeft)},this.handleVirtualizerWheel=e=>{const t=this.infiniteContainerRef.value;if(!t)return;const i=t.getBoundingClientRect(),s=window.innerHeight,o=i.top>=0&&i.bottom<=s,r=i.top<=0&&i.bottom>=s;!o&&!r&&(e.preventDefault(),window.scrollBy(0,e.deltaY))},this.handleSearch=e=>{const t=e.detail.toLowerCase();this.isInfiniteMode?(this.searchDebounceTimer&&clearTimeout(this.searchDebounceTimer),this.searchDebounceTimer=setTimeout(async()=>{this.searchTerm=t,await this.resetAndReload().catch(()=>{})},300)):(this.searchTerm=t,this.filteredItems=this.applySearchTerm(this.items),this.sortColumn?this.sort({emitItemsChanged:!1,pageNumber:1}):this.setPage(1),this.dispatchItemsChanged())}}get items(){return this.itemsBacker}set items(e){this.applyPagedItems(e)}applyPagedItems(e,t={}){const i=t.emitItemsChanged??!0,s=t.pageNumber??this.pageNumber??1;this.itemsBacker=e,this.filteredItems=this.applySearchTerm(this.itemsBacker),this.sortColumn?this.sort({emitItemsChanged:!1,pageNumber:s}):this.setPage(s),i&&this.dispatchItemsChanged()}get fetchPage(){return this.fetchPageBacker}set fetchPage(e){this.fetchPageBacker=e,e&&this.hasUpdated&&this.loadPage(null).then(async()=>{await this.updateComplete,this.measurePinnedOffsets()}).catch(()=>{})}get isInfiniteMode(){return this.fetchPage!==null}get currentDataStateItems(){return this.isInfiniteMode?this.allLoadedItems:this.filteredItems}getCurrentSortField(){return this.sortColumn?this.sortColumn.sortField.length>0?this.sortColumn.sortField:this.sortColumn.fieldName:""}createDataStateDetail(e={}){const t=[...this.currentDataStateItems],i=e.cursor??null,s=e.isFirstPage??(this.isInfiniteMode?i===null:this.pageNumber<=1);return{items:t,itemCount:t.length,isFirstPage:s,cursor:i,nextCursor:this.nextCursor,hasRows:t.length>0,isEmpty:t.length===0,search:this.searchTerm,sortField:this.getCurrentSortField(),sortOrder:this.sortColumn?.sortOrder??""}}dispatchGridDataEvent(e,t){this.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0}))}dispatchItemsChanged(e={}){this.dispatchGridDataEvent(j,this.createDataStateDetail(e))}setPage(e){const t=Math.ceil(this.filteredItems.length/this.pageSize);e>t&&(e=t),e<1&&(e=1),this.pageNumber=e;const i=(this.pageNumber-1)*this.pageSize;switch(this.displayedItems=this.filteredItems.slice(i,i+this.pageSize),this.allowPrevious=this.pageNumber>1,this.allowNext=t>this.pageNumber,t){case 0:this.footerMessage="No matching items";break;case 1:this.footerMessage=this.filteredItems.length<this.items.length?"Showing all filtered items":"Showing all items";break;default:this.footerMessage=`Showing page ${this.pageNumber} of ${t} pages`}}async loadPage(e){const t=this.fetchPage;if(!t||e!==null&&this.isLoadingPage)return;const i=++this.pageLoadVersion,s=e===null;let o=!1;this.isLoadingPage=!0,this.isLoadingFirstPage=s,s&&(this.nextCursor=null,this.hasMoreData=!1);const r={cursor:e,limit:this.pageSize,search:this.searchTerm,sortField:this.getCurrentSortField(),sortOrder:this.sortColumn?.sortOrder??""};this.dispatchGridDataEvent(L,this.createDataStateDetail({cursor:e,isFirstPage:s}));try{const n=await t(r);if(i!==this.pageLoadVersion)return;e===null?this.allLoadedItems=[...n.items]:this.allLoadedItems=[...this.allLoadedItems,...n.items],this.nextCursor=n.nextCursor,this.hasMoreData=n.nextCursor!==null,o=!0,this.dispatchGridDataEvent(M,this.createDataStateDetail({cursor:e,isFirstPage:s})),this.dispatchItemsChanged({cursor:e,isFirstPage:s})}catch(n){throw i===this.pageLoadVersion&&this.dispatchGridDataEvent(z,{...this.createDataStateDetail({cursor:e,isFirstPage:s}),error:n}),n}finally{if(i===this.pageLoadVersion){if(this.isLoadingPage=!1,this.isLoadingFirstPage=!1,s&&o){const n=this.virtualizerRef.value;if(n&&n.scrollTop>0)try{n.scrollTo({top:0})}catch{n.scrollTop=0}}await this.adjustInfiniteHeight()}}}async resetAndReload(){await this.loadPage(null)}onRangeChanged(e){this.isLoadingFirstPage||e.last>=this.allLoadedItems.length-5&&this.hasMoreData&&!this.isLoadingPage&&this.loadPage(this.nextCursor).catch(()=>{})}async adjustInfiniteHeight(){if(!this.isAdjustingHeight){this.isAdjustingHeight=!0;try{const e=this.virtualizerRef.value;e&&(e.style.minHeight=""),this.effectiveGridHeight=this.gridHeight,await this.updateComplete;const t=this.infiniteContainerRef.value,i=this.virtualizerRef.value;if(!t||this.hasMoreData)return;const o=this.gridHeaderRowRef.value?.offsetHeight??0;if(!i){const p=t.querySelector(".no-results");let f=0;p?f=o+p.offsetHeight:t.querySelectorAll(":scope > *").forEach(R=>f+=R.offsetHeight),f<t.offsetHeight&&(this.effectiveGridHeight=`${f}px`);return}const r=i.querySelectorAll(".grid-row");let n=0,a=!1;if(r.forEach(p=>{const f=p.offsetHeight;f===0&&(a=!0),n+=f}),r.length===0||a||n===0){this.pendingHeightRetry||(this.pendingHeightRetry=!0,this.observeForHeightRetry(i));return}const l=i.querySelectorAll(".grid-row :defined"),g=[];l.forEach(p=>{"updateComplete"in p&&g.push(p.updateComplete)}),g.length>0&&(await Promise.all(g),n=0,r.forEach(p=>{n+=p.offsetHeight}));const v=n+o,b=t.offsetHeight;v<b&&r.length>=this.allLoadedItems.length&&(i.style.minHeight=`${n}px`,this.effectiveGridHeight=`${v}px`),!this.hasMoreData&&!this.pendingHeightRetry&&(this.pendingHeightRetry=!0,setTimeout(()=>{this.pendingHeightRetry=!1;const p=this.infiniteContainerRef.value,f=this.virtualizerRef.value;if(!p||!f||this.hasMoreData)return;const O=this.gridHeaderRowRef.value?.offsetHeight??0,k=f.querySelectorAll(".grid-row");let C=0,D=!1;if(k.forEach(F=>{const H=F.offsetHeight;H===0&&(D=!0),C+=H}),k.length===0||D||C===0)return;const $=C+O;$<p.offsetHeight&&k.length>=this.allLoadedItems.length&&(f.style.minHeight=`${C}px`,this.effectiveGridHeight=`${$}px`)},100))}finally{this.isAdjustingHeight=!1}}}observeForHeightRetry(e){this.heightRetryObserver?.disconnect(),this.heightRetryObserver=new IntersectionObserver(t=>{if(!t.some(r=>r.isIntersecting))return;this.heightRetryObserver?.disconnect(),this.heightRetryObserver=null;let s=0;const o=()=>{this.pendingHeightRetry=!1,this.adjustInfiniteHeight(),this.pendingHeightRetry&&++s<5&&setTimeout(o,100)};requestAnimationFrame(()=>setTimeout(o,50))},{threshold:0}),this.heightRetryObserver.observe(e)}async sortOn(e){this.sortColumn===e?this.sortColumn.sortOrder=this.sortColumn.sortOrder=="asc"?"desc":"asc":(this.sortColumn=e,this.sortColumn.sortOrder="asc"),this.isInfiniteMode?await this.resetAndReload():this.sort()}sort(e={}){if(!this.sortColumn)return;const t=this.getCurrentSortField(),i=this.sortColumn.sortOrder==="asc"?1:-1,s=this.sortColumn.sortType,o=this.filteredItems.map((r,n)=>{const a=r[t]??"",l=s==="number"?Number(a):a.toString().toLowerCase();return{item:r,index:n,sortValue:l}});o.sort((r,n)=>r.sortValue<n.sortValue?-1*i:r.sortValue>n.sortValue?1*i:r.index-n.index),this.filteredItems=o.map(({item:r})=>r),this.setPage(e.pageNumber??1),(e.emitItemsChanged??!0)&&this.dispatchItemsChanged()}delete(e){if(this.isInfiniteMode){const t=this.allLoadedItems.filter(i=>!e(i));if(t.length===this.allLoadedItems.length)return;this.allLoadedItems=t,this.dispatchItemsChanged(),this.adjustInfiniteHeight()}else{const t=this.items.filter(i=>!e(i));if(t.length===this.items.length)return;this.items=t}}addOrReplace(e,t){if(this.isInfiniteMode){const i=this.allLoadedItems.findIndex(e);if(i>=0){const s=[...this.allLoadedItems];s[i]={...s[i],...t},this.allLoadedItems=s}else this.allLoadedItems=[...this.allLoadedItems,t];this.dispatchItemsChanged(),this.adjustInfiniteHeight()}else this.items.find(e)?this.items=this.items.map(s=>e(s)?{...s,...t}:s):this.items=[...this.items,t]}addInOrder(e,t,i){if(this.isInfiniteMode){const a=this.allLoadedItems.findIndex(e),l=[...this.allLoadedItems];if(a>=0)return l[a]={...l[a],...t},this.allLoadedItems=l,this.dispatchItemsChanged(),a;let g=0,v=l.length;for(;g<v;){const b=g+v>>>1;i(l[b],t)<0?g=b+1:v=b}return l.splice(g,0,t),this.allLoadedItems=l,this.dispatchItemsChanged(),this.adjustInfiniteHeight(),g}const s=this.items.findIndex(e),o=[...this.items];if(s>=0)return o[s]={...o[s],...t},this.items=o,s;let r=0,n=o.length;for(;r<n;){const a=r+n>>>1;i(o[a],t)<0?r=a+1:n=a}return o.splice(r,0,t),this.items=o,r}async scrollToIndex(e,t="center"){if(this.isInfiniteMode){await this.updateComplete;const i=this.virtualizerRef.value;if(!i)return;await i.updateComplete,await new Promise(s=>requestAnimationFrame(s)),await i.layoutComplete,i.scrollToIndex(e,t)}else{const i=Math.floor(e/this.pageSize)+1;this.setPage(i)}}clear(){if(this.isInfiniteMode){if(this.allLoadedItems.length===0&&this.nextCursor===null&&!this.hasMoreData)return;this.allLoadedItems=[],this.nextCursor=null,this.hasMoreData=!1,this.dispatchItemsChanged(),this.adjustInfiniteHeight()}else{if(this.items.length===0)return;this.items=[]}}async reload(){this.isInfiniteMode&&await this.resetAndReload()}computePinnedStyles(){return this.pinnedOffsets}measurePinnedOffsets(){this.pinnedOffsets=new Map,this.isInfiniteMode?this.computePinnedOffsetsFromWidths():this.measurePinnedOffsetsFromCells(),this.requestUpdate()}computePinnedOffsetsFromWidths(){const e=this.columnDefinitions.filter(r=>r.pin==="left"),t=this.columnDefinitions.filter(r=>r.pin==="right"),i=[];for(let r=0;r<e.length;r++){const n=e[r],a=i.length===0?"0px":`calc(${i.join(" + ")})`;this.pinnedOffsets.set(n,{position:"sticky",left:a,zIndex:`${e.length-r+1}`}),i.push(n.width||"0px")}const s=[],o=[...t].reverse();for(let r=0;r<o.length;r++){const n=o[r],a=s.length===0?"0px":`calc(${s.join(" + ")})`;this.pinnedOffsets.set(n,{position:"sticky",right:a,zIndex:`${o.length-r+1}`}),s.push(n.width||"0px")}}measurePinnedOffsetsFromCells(){const e=this.shadowRoot?.querySelectorAll("thead th");if(!e)return;const t=this.columnDefinitions.filter(a=>a.pin==="left"),i=this.columnDefinitions.filter(a=>a.pin==="right"),s=new Map;this.columnDefinitions.forEach((a,l)=>{e[l]&&s.set(a,e[l])});let o=0;for(let a=0;a<t.length;a++){const l=t[a];this.pinnedOffsets.set(l,{position:"sticky",left:`${o}px`,zIndex:`${t.length-a+1}`});const g=s.get(l);g&&(o+=g.offsetWidth)}let r=0;const n=[...i].reverse();for(let a=0;a<n.length;a++){const l=n[a];this.pinnedOffsets.set(l,{position:"sticky",right:`${r}px`,zIndex:`${n.length-a+1}`});const g=s.get(l);g&&(r+=g.offsetWidth)}}computeGridTemplateColumns(){return this.columnDefinitions.map(e=>e.width?e.grow?`minmax(${e.width}, 1fr)`:e.width:e.grow?"minmax(0, 1fr)":"auto").join(" ")}get hasGrowableColumns(){return this.columnDefinitions.some(e=>e.grow)}computePinnedColumnFlags(){const e=new Map,t=this.columnDefinitions.filter(r=>r.pin==="left"),i=this.columnDefinitions.filter(r=>r.pin==="right"),s=t[t.length-1],o=i[0];return this.columnDefinitions.forEach((r,n)=>{e.set(r,{beforePinnedRight:this.columnDefinitions[n+1]?.pin==="right",firstPinnedRight:o===r,lastPinnedLeft:s===r})}),e}renderCell(e,t){if(!e.fieldName)return c`<span></span>`;const i=e.textAlign??"left";return c`<span style="text-align: ${i};"> ${t[e.fieldName]} </span>`}attachWheelHandler(){const e=this.virtualizerRef.value;e&&!this.wheelHandlerAttached?(e.addEventListener("wheel",this.handleVirtualizerWheel,{passive:!1}),this.wheelHandlerAttached=!0):!e&&this.wheelHandlerAttached&&(this.wheelHandlerAttached=!1)}applySearchTerm(e){return this.searchTerm.length===0?e:e.filter(t=>{let i;return this.searchFields.length>0?i=this.searchFields.map(s=>t[s]):i=Object.values(t),i.some(s=>String(s).toLowerCase().includes(this.searchTerm))})}async firstUpdated(e){super.firstUpdated(e),this.headerButtonsSlot.value.assignedElements().length===0&&this.headerButtonsSlot.value.remove();for(const i of this.columnDefinitionsSlot.value.assignedElements())i.localName==="esp-grid-column"&&this.columnDefinitions.push(i);const t=this.columnDefinitions.find(i=>i.sortOrder!=="");t&&(this.sortColumn=t),this.isInfiniteMode?await this.loadPage(null).catch(()=>{}):this.dataUrl.length&&await this.loadDataUrl().catch(()=>{}),await this.updateComplete,this.measurePinnedOffsets()}updated(e){super.updated(e),this.isInfiniteMode&&this.attachWheelHandler()}async loadDataUrl(){this.dispatchGridDataEvent(L,this.createDataStateDetail({cursor:null,isFirstPage:!0}));try{const t=await fetch(this.dataUrl).then(i=>i.json());this.applyPagedItems(this.dataField?t[this.dataField]:t,{emitItemsChanged:!1}),this.dispatchGridDataEvent(M,this.createDataStateDetail({cursor:null,isFirstPage:!0})),this.dispatchItemsChanged({cursor:null,isFirstPage:!0})}catch(t){throw this.dispatchGridDataEvent(z,{...this.createDataStateDetail({cursor:null,isFirstPage:!0}),error:t}),t}}disconnectedCallback(){super.disconnectedCallback();const e=this.virtualizerRef.value;e&&e.removeEventListener("wheel",this.handleVirtualizerWheel),this.wheelHandlerAttached=!1,this.heightRetryObserver?.disconnect(),this.heightRetryObserver=null}renderSortIcons(){return c`
2
2
  <svg
3
3
  xmlns="http://www.w3.org/2000/svg"
4
4
  viewBox="0 0 24 24"
@@ -37,7 +37,7 @@ var h=function(I,e,t,i){var s=arguments.length,o=s<3?e:i===null?i=Object.getOwnP
37
37
  <slot ${y(this.columnDefinitionsSlot)}></slot>
38
38
 
39
39
  <div class="header-controls">
40
- <esp-input fieldName="filter" @value-changed=${this.handleSearch}> ${G} </esp-input>
40
+ <esp-input fieldName="filter" @value-changed=${this.handleSearch}> ${N} </esp-input>
41
41
  <slot name="header-buttons" ${y(this.headerButtonsSlot)}></slot>
42
42
  </div>
43
43
 
@@ -79,7 +79,7 @@ var h=function(I,e,t,i){var s=arguments.length,o=s<3?e:i===null?i=Object.getOwnP
79
79
  .disabled=${!this.allowPrevious}
80
80
  @clicked=${i=>{this.setPage(this.pageNumber-1),i.stopPropagation()}}
81
81
  >
82
- ${N}
82
+ ${T}
83
83
  </esp-button>
84
84
  <h4>${this.footerMessage}</h4>
85
85
  <esp-button
@@ -87,7 +87,7 @@ var h=function(I,e,t,i){var s=arguments.length,o=s<3?e:i===null?i=Object.getOwnP
87
87
  .disabled=${!this.allowNext}
88
88
  @clicked=${i=>{this.setPage(this.pageNumber+1),i.stopPropagation()}}
89
89
  >
90
- ${T}
90
+ ${G}
91
91
  </esp-button>
92
92
  </div>
93
93
  `}renderInfiniteMode(e,t){const i=this.computeGridTemplateColumns(),s=this.hasGrowableColumns?"":"width: max-content; min-width: 100%;",o=(r,n)=>this.renderVirtualRow(r,n,e,t,i);return c`
@@ -144,6 +144,8 @@ type HeaderThemeToggle = "hidden" | "visible";
144
144
  * @cssprop --esp-header-height - The height of the header bar. Defaults to `calc(4.5 * var(--esp-size-small))`.
145
145
  * @cssprop --esp-header-color - The text/icon color inherited by header controls. Defaults to `var(--esp-color-text)`.
146
146
  * @cssprop --esp-header-z-index - The z-index of the header host. Defaults to `20`.
147
+ * @cssprop --esp-header-content-max-width - Surface-alignment contract set by `esp-page`: the maximum width the bar content is constrained to (the bar background stays full-bleed). Defaults to `100%` (no constraint). `esp-page` sets this to its resolved content cap.
148
+ * @cssprop --esp-header-content-lead - Surface-alignment contract set by `esp-page`: the fraction of the leftover width placed before the content, aligning it (`0` = start, `0.5` = center, `1` = end). Defaults to `0`.
147
149
  * @cssprop --esp-header-compact-height - The height of compact scroll-reactive headers. Defaults to `max(44px, calc(3.5 * var(--esp-size-small)))`.
148
150
  * @cssprop --esp-header-extended-menu-height - The second-row menu height for extended layouts. Defaults to `calc(3.5 * var(--esp-size-small))`.
149
151
  * @cssprop --esp-header-extended-menu-border - The border between the primary row and second-row menu in extended layouts. Defaults to `1px solid var(--esp-color-border)`.
@@ -306,6 +308,15 @@ export declare class EspalierHeader extends EspalierElementBase {
306
308
  * menu slotted into the header.
307
309
  */
308
310
  drawerTarget: string;
311
+ /**
312
+ * Opt out of surface alignment. When an `esp-header` is slotted into an
313
+ * `esp-page`, it constrains its bar content to the page's content
314
+ * surface by default (the bar background stays full-bleed). Set this to
315
+ * let the content span the full width instead, as it did before the
316
+ * page grew alignment modes. Standalone headers (outside `esp-page`) are
317
+ * already full-bleed and unaffected.
318
+ */
319
+ fullBleedContent: boolean;
309
320
  /**
310
321
  * The burger element inside the header. External code can use
311
322
  * this to programmatically reset the burger's visual state
@@ -1,7 +1,7 @@
1
- var n=function(p,e,t,o){var s=arguments.length,a=s<3?e:o===null?o=Object.getOwnPropertyDescriptor(e,t):o,i;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")a=Reflect.decorate(p,e,t,o);else for(var h=p.length-1;h>=0;h--)(i=p[h])&&(a=(s<3?i(a):s>3?i(e,t,a):i(e,t))||a);return s>3&&a&&Object.defineProperty(e,t,a),a};import{css as y,html as d,nothing as c}from"lit";import{customElement as x,property as l,state as u}from"lit/decorators.js";import{classMap as M}from"lit/directives/class-map.js";import{styleMap as S}from"lit/directives/style-map.js";import"../burger/esp-burger.js";import{createRef as b,ref as v}from"lit/directives/ref.js";import{EspalierMenu as T}from"./esp-menu.js";import{EspalierElementBase as f}from"../shared/esp-element-base.js";const D="(max-width: 36rem)";let r=class extends f{constructor(){super(...arguments),this.menuOpen=!1,this.menuTooLarge=!1,this.mobileMenu=!1,this.scrolled=!1,this.hiddenByScroll=!1,this.scrollProgress=0,this.showBurger=!1,this.layout="standard",this.brandText="",this.brandLogo="",this.brandHref="",this.brandAlt="",this.brandColor="",this.brandAlign="start",this.brandWrap="truncate",this.menuDisplay="auto",this.themeToggle="hidden",this.scrollBehavior="none",this.scrollThreshold=16,this.dockOffset=0,this.drawerTarget="",this.menuContainer=b(),this.menuButton=b(),this.menu=void 0,this.externalDrawerTarget=null,this.lastScrollY=0,this.lastInlineMenuWidth=0,this.mobileMenuQuery=null,this.menuDrawerListenersAttached=!1,this.observingMenuContainer=!1,this.scrollListenersAttached=!1,this.scrollRafId=null,this.resizeObserver=new ResizeObserver(()=>this.syncMenuLayout()),this.updateScrollState=()=>{this.scrollRafId===null&&(this.scrollRafId=requestAnimationFrame(()=>{this.scrollRafId=null,this.runScrollStateUpdate()}))},this.handleMobileMenuQueryChange=e=>{this.mobileMenu=e.matches,this.syncMenuLayout()},this.toggleTheme=()=>{const e=this.getThemeRoot();e&&(e.scheme=e.scheme==="dark"?"light":"dark")},this.handleMenuToggleClicked=()=>{if(this.menuOpen){this.closeNav();return}this.openNav()},this.onMenuDrawerOpened=()=>{this.menuOpen=!0,document.body.classList.add("menu-open"),this.burger&&(this.burger.menuOpen=!0)},this.onMenuDrawerClosed=()=>{this.menuOpen=!1,document.body.classList.remove("menu-open"),this.burger&&(this.burger.menuOpen=!1)}}syncMenuLayout(){if(!this.menuContainer.value||!this.menu||!this.menu.getWidth)return;const e=this.menuContainer.value.clientWidth,t=this.menu.getWidth();t>0&&(this.lastInlineMenuWidth=t);const o=this.lastInlineMenuWidth||t,s=this.getExternalDrawerTarget(),a=this.menuDisplay!=="inline",i=this.showBurger||this.menuDisplay==="drawer"||this.layout==="minimal";this.menuTooLarge=a&&this.menuDisplay==="auto"&&o>e,this.menu.sliding=a&&!s&&(this.menuTooLarge||i||this.mobileMenu)}getExternalDrawerTarget(){if(!this.drawerTarget)return null;const e=document.getElementById(this.drawerTarget);return e===this.menu?null:e&&"openDrawer"in e&&typeof e.openDrawer=="function"&&"closeDrawer"in e&&typeof e.closeDrawer=="function"?e:null}getBurgerFocusTarget(){const t=this.shadowRoot?.querySelector("esp-header-button.menu-toggle")?.shadowRoot?.querySelector("button"),o=this.menuButton.value?.shadowRoot?.querySelector("button");return t??o??null}getScrollTokens(){const e=this.scrollBehavior.split(/\s+/).map(t=>t.trim().toLowerCase()).filter(Boolean);return new Set(e.filter(t=>t!=="none"))}getEffectiveScrollThreshold(){return Number.isFinite(this.scrollThreshold)?Math.max(0,this.scrollThreshold):0}clearScrollState(){this.scrolled=!1,this.hiddenByScroll=!1,this.scrollProgress=0}runScrollStateUpdate(){const e=this.getScrollTokens();if(!e.size){this.clearScrollState();return}const t=Math.max(0,window.scrollY||document.documentElement.scrollTop||0),o=this.getEffectiveScrollThreshold(),s=Number.isFinite(this.dockOffset)?Math.max(0,this.dockOffset):o,a=t>(e.has("dock")?s:o),i=t>this.lastScrollY,h=e.has("reveal")&&i&&t>o&&!this.menuOpen;if(this.scrolled=a,this.hiddenByScroll=h,e.has("progress")){const m=document.documentElement,g=Math.max(1,m.scrollHeight-window.innerHeight);this.scrollProgress=Math.min(1,t/g)}else this.scrollProgress=0;this.lastScrollY=t}cancelScrollUpdate(){this.scrollRafId!==null&&(cancelAnimationFrame(this.scrollRafId),this.scrollRafId=null)}syncScrollListeners(){const e=this.getScrollTokens().size>0;if(e&&!this.scrollListenersAttached?(window.addEventListener("scroll",this.updateScrollState,{passive:!0}),window.addEventListener("resize",this.updateScrollState,{passive:!0}),this.scrollListenersAttached=!0):!e&&this.scrollListenersAttached&&this.removeScrollListeners(),e){this.updateScrollState();return}this.cancelScrollUpdate(),this.clearScrollState()}removeScrollListeners(){this.scrollListenersAttached&&(window.removeEventListener("scroll",this.updateScrollState),window.removeEventListener("resize",this.updateScrollState),this.scrollListenersAttached=!1)}addMenuDrawerListeners(){!this.menu||this.menuDrawerListenersAttached||(this.menu.addEventListener("drawer-opened",this.onMenuDrawerOpened),this.menu.addEventListener("drawer-closed",this.onMenuDrawerClosed),this.menuDrawerListenersAttached=!0)}removeMenuDrawerListeners(){!this.menu||!this.menuDrawerListenersAttached||(this.menu.removeEventListener("drawer-opened",this.onMenuDrawerOpened),this.menu.removeEventListener("drawer-closed",this.onMenuDrawerClosed),this.menuDrawerListenersAttached=!1)}observeMenuContainer(){!this.menuContainer.value||this.observingMenuContainer||(this.resizeObserver.observe(this.menuContainer.value),this.observingMenuContainer=!0)}disconnectResizeObserver(){this.observingMenuContainer&&(this.resizeObserver.disconnect(),this.observingMenuContainer=!1)}syncMobileMenuQuery(){if("matchMedia"in window){if(!this.mobileMenuQuery){this.mobileMenuQuery=window.matchMedia(D),this.mobileMenu=this.mobileMenuQuery.matches,this.mobileMenuQuery.addEventListener("change",this.handleMobileMenuQueryChange);return}this.mobileMenu=this.mobileMenuQuery.matches}}getThemeRoot(){return this.traverseToClosest("esp-root")}syncExternalDrawerTarget(){const e=this.getExternalDrawerTarget(),t=e===this.menu?null:e;t!==this.externalDrawerTarget&&(this.externalDrawerTarget?.removeEventListener("drawer-opened",this.onMenuDrawerOpened),this.externalDrawerTarget?.removeEventListener("drawer-closed",this.onMenuDrawerClosed),this.externalDrawerTarget=t,this.externalDrawerTarget?.addEventListener("drawer-opened",this.onMenuDrawerOpened),this.externalDrawerTarget?.addEventListener("drawer-closed",this.onMenuDrawerClosed))}openNav(){const e=this.getBurgerFocusTarget(),t=this.getExternalDrawerTarget();if(this.burger&&(this.burger.menuOpen=!0),t){this.menuOpen=!0,t.openDrawer(e),document.body.classList.add("menu-open");return}if(this.menu){this.menu.sliding=!0,this.menuOpen=!0,this.menu.openDrawer(e),document.body.classList.add("menu-open");return}this.burger&&(this.burger.menuOpen=!1)}closeNav(){this.menuOpen=!1,this.burger&&(this.burger.menuOpen=!1);const e=this.getExternalDrawerTarget();if(e){e.closeDrawer(),document.body.classList.remove("menu-open");return}this.menu&&(this.menu.closeDrawer(),document.body.classList.remove("menu-open"))}get burger(){return this.menuButton.value??void 0}firstUpdated(e){if(super.firstUpdated(e),!this.menuContainer.value)return;const s=(this.menuContainer.value.firstElementChild?.assignedElements()??[])[0];s instanceof T?(this.menu=s,this.addMenuDrawerListeners()):s&&console.warn("<esp-header>: the menu slot expects an <esp-menu> element, received",s),this.observeMenuContainer(),this.syncExternalDrawerTarget(),this.syncMobileMenuQuery(),this.syncScrollListeners(),this.syncMenuLayout()}updated(e){super.updated(e),(e.has("showBurger")||e.has("drawerTarget")||e.has("menuDisplay")||e.has("layout"))&&(this.syncExternalDrawerTarget(),this.syncMenuLayout()),e.has("scrollBehavior")?this.syncScrollListeners():(e.has("scrollThreshold")||e.has("dockOffset"))&&this.updateScrollState()}connectedCallback(){super.connectedCallback(),this.syncScrollListeners(),this.syncMobileMenuQuery(),this.addMenuDrawerListeners(),this.syncExternalDrawerTarget(),this.observeMenuContainer()}disconnectedCallback(){this.removeMenuDrawerListeners(),this.externalDrawerTarget?.removeEventListener("drawer-opened",this.onMenuDrawerOpened),this.externalDrawerTarget?.removeEventListener("drawer-closed",this.onMenuDrawerClosed),this.externalDrawerTarget=null,this.mobileMenuQuery?.removeEventListener("change",this.handleMobileMenuQueryChange),this.mobileMenuQuery=null,this.removeScrollListeners(),this.cancelScrollUpdate(),this.disconnectResizeObserver(),super.disconnectedCallback()}renderConfiguredBrand(){if(!this.brandLogo&&!this.brandText)return c;const e=this.brandAlt||(this.brandText?"":"Site logo"),t=d`
1
+ var n=function(p,e,t,a){var s=arguments.length,i=s<3?e:a===null?a=Object.getOwnPropertyDescriptor(e,t):a,l;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")i=Reflect.decorate(p,e,t,a);else for(var h=p.length-1;h>=0;h--)(l=p[h])&&(i=(s<3?l(i):s>3?l(e,t,i):l(e,t))||i);return s>3&&i&&Object.defineProperty(e,t,i),i};import{css as y,html as d,nothing as c}from"lit";import{customElement as x,property as o,state as u}from"lit/decorators.js";import{classMap as M}from"lit/directives/class-map.js";import{styleMap as S}from"lit/directives/style-map.js";import"../burger/esp-burger.js";import{createRef as b,ref as f}from"lit/directives/ref.js";import{EspalierMenu as T}from"./esp-menu.js";import{EspalierElementBase as v}from"../shared/esp-element-base.js";const D="(max-width: 36rem)";let r=class extends v{constructor(){super(...arguments),this.menuOpen=!1,this.menuTooLarge=!1,this.mobileMenu=!1,this.scrolled=!1,this.hiddenByScroll=!1,this.scrollProgress=0,this.showBurger=!1,this.layout="standard",this.brandText="",this.brandLogo="",this.brandHref="",this.brandAlt="",this.brandColor="",this.brandAlign="start",this.brandWrap="truncate",this.menuDisplay="auto",this.themeToggle="hidden",this.scrollBehavior="none",this.scrollThreshold=16,this.dockOffset=0,this.drawerTarget="",this.fullBleedContent=!1,this.menuContainer=b(),this.menuButton=b(),this.menu=void 0,this.externalDrawerTarget=null,this.lastScrollY=0,this.lastInlineMenuWidth=0,this.mobileMenuQuery=null,this.menuDrawerListenersAttached=!1,this.observingMenuContainer=!1,this.scrollListenersAttached=!1,this.scrollRafId=null,this.resizeObserver=new ResizeObserver(()=>this.syncMenuLayout()),this.updateScrollState=()=>{this.scrollRafId===null&&(this.scrollRafId=requestAnimationFrame(()=>{this.scrollRafId=null,this.runScrollStateUpdate()}))},this.handleMobileMenuQueryChange=e=>{this.mobileMenu=e.matches,this.syncMenuLayout()},this.toggleTheme=()=>{const e=this.getThemeRoot();e&&(e.scheme=e.scheme==="dark"?"light":"dark")},this.handleMenuToggleClicked=()=>{if(this.menuOpen){this.closeNav();return}this.openNav()},this.onMenuDrawerOpened=()=>{this.menuOpen=!0,document.body.classList.add("menu-open"),this.burger&&(this.burger.menuOpen=!0)},this.onMenuDrawerClosed=()=>{this.menuOpen=!1,document.body.classList.remove("menu-open"),this.burger&&(this.burger.menuOpen=!1)}}syncMenuLayout(){if(!this.menuContainer.value||!this.menu||!this.menu.getWidth)return;const e=this.menuContainer.value.clientWidth,t=this.menu.getWidth();t>0&&(this.lastInlineMenuWidth=t);const a=this.lastInlineMenuWidth||t,s=this.getExternalDrawerTarget(),i=this.menuDisplay!=="inline",l=this.showBurger||this.menuDisplay==="drawer"||this.layout==="minimal";this.menuTooLarge=i&&this.menuDisplay==="auto"&&a>e,this.menu.sliding=i&&!s&&(this.menuTooLarge||l||this.mobileMenu)}getExternalDrawerTarget(){if(!this.drawerTarget)return null;const e=document.getElementById(this.drawerTarget);return e===this.menu?null:e&&"openDrawer"in e&&typeof e.openDrawer=="function"&&"closeDrawer"in e&&typeof e.closeDrawer=="function"?e:null}getBurgerFocusTarget(){const t=this.shadowRoot?.querySelector("esp-header-button.menu-toggle")?.shadowRoot?.querySelector("button"),a=this.menuButton.value?.shadowRoot?.querySelector("button");return t??a??null}getScrollTokens(){const e=this.scrollBehavior.split(/\s+/).map(t=>t.trim().toLowerCase()).filter(Boolean);return new Set(e.filter(t=>t!=="none"))}getEffectiveScrollThreshold(){return Number.isFinite(this.scrollThreshold)?Math.max(0,this.scrollThreshold):0}clearScrollState(){this.scrolled=!1,this.hiddenByScroll=!1,this.scrollProgress=0}runScrollStateUpdate(){const e=this.getScrollTokens();if(!e.size){this.clearScrollState();return}const t=Math.max(0,window.scrollY||document.documentElement.scrollTop||0),a=this.getEffectiveScrollThreshold(),s=Number.isFinite(this.dockOffset)?Math.max(0,this.dockOffset):a,i=t>(e.has("dock")?s:a),l=t>this.lastScrollY,h=e.has("reveal")&&l&&t>a&&!this.menuOpen;if(this.scrolled=i,this.hiddenByScroll=h,e.has("progress")){const m=document.documentElement,g=Math.max(1,m.scrollHeight-window.innerHeight);this.scrollProgress=Math.min(1,t/g)}else this.scrollProgress=0;this.lastScrollY=t}cancelScrollUpdate(){this.scrollRafId!==null&&(cancelAnimationFrame(this.scrollRafId),this.scrollRafId=null)}syncScrollListeners(){const e=this.getScrollTokens().size>0;if(e&&!this.scrollListenersAttached?(window.addEventListener("scroll",this.updateScrollState,{passive:!0}),window.addEventListener("resize",this.updateScrollState,{passive:!0}),this.scrollListenersAttached=!0):!e&&this.scrollListenersAttached&&this.removeScrollListeners(),e){this.updateScrollState();return}this.cancelScrollUpdate(),this.clearScrollState()}removeScrollListeners(){this.scrollListenersAttached&&(window.removeEventListener("scroll",this.updateScrollState),window.removeEventListener("resize",this.updateScrollState),this.scrollListenersAttached=!1)}addMenuDrawerListeners(){!this.menu||this.menuDrawerListenersAttached||(this.menu.addEventListener("drawer-opened",this.onMenuDrawerOpened),this.menu.addEventListener("drawer-closed",this.onMenuDrawerClosed),this.menuDrawerListenersAttached=!0)}removeMenuDrawerListeners(){!this.menu||!this.menuDrawerListenersAttached||(this.menu.removeEventListener("drawer-opened",this.onMenuDrawerOpened),this.menu.removeEventListener("drawer-closed",this.onMenuDrawerClosed),this.menuDrawerListenersAttached=!1)}observeMenuContainer(){!this.menuContainer.value||this.observingMenuContainer||(this.resizeObserver.observe(this.menuContainer.value),this.observingMenuContainer=!0)}disconnectResizeObserver(){this.observingMenuContainer&&(this.resizeObserver.disconnect(),this.observingMenuContainer=!1)}syncMobileMenuQuery(){if("matchMedia"in window){if(!this.mobileMenuQuery){this.mobileMenuQuery=window.matchMedia(D),this.mobileMenu=this.mobileMenuQuery.matches,this.mobileMenuQuery.addEventListener("change",this.handleMobileMenuQueryChange);return}this.mobileMenu=this.mobileMenuQuery.matches}}getThemeRoot(){return this.traverseToClosest("esp-root")}syncExternalDrawerTarget(){const e=this.getExternalDrawerTarget(),t=e===this.menu?null:e;t!==this.externalDrawerTarget&&(this.externalDrawerTarget?.removeEventListener("drawer-opened",this.onMenuDrawerOpened),this.externalDrawerTarget?.removeEventListener("drawer-closed",this.onMenuDrawerClosed),this.externalDrawerTarget=t,this.externalDrawerTarget?.addEventListener("drawer-opened",this.onMenuDrawerOpened),this.externalDrawerTarget?.addEventListener("drawer-closed",this.onMenuDrawerClosed))}openNav(){const e=this.getBurgerFocusTarget(),t=this.getExternalDrawerTarget();if(this.burger&&(this.burger.menuOpen=!0),t){this.menuOpen=!0,t.openDrawer(e),document.body.classList.add("menu-open");return}if(this.menu){this.menu.sliding=!0,this.menuOpen=!0,this.menu.openDrawer(e),document.body.classList.add("menu-open");return}this.burger&&(this.burger.menuOpen=!1)}closeNav(){this.menuOpen=!1,this.burger&&(this.burger.menuOpen=!1);const e=this.getExternalDrawerTarget();if(e){e.closeDrawer(),document.body.classList.remove("menu-open");return}this.menu&&(this.menu.closeDrawer(),document.body.classList.remove("menu-open"))}get burger(){return this.menuButton.value??void 0}firstUpdated(e){if(super.firstUpdated(e),!this.menuContainer.value)return;const s=(this.menuContainer.value.firstElementChild?.assignedElements()??[])[0];s instanceof T?(this.menu=s,this.addMenuDrawerListeners()):s&&console.warn("<esp-header>: the menu slot expects an <esp-menu> element, received",s),this.observeMenuContainer(),this.syncExternalDrawerTarget(),this.syncMobileMenuQuery(),this.syncScrollListeners(),this.syncMenuLayout()}updated(e){super.updated(e),(e.has("showBurger")||e.has("drawerTarget")||e.has("menuDisplay")||e.has("layout"))&&(this.syncExternalDrawerTarget(),this.syncMenuLayout()),e.has("scrollBehavior")?this.syncScrollListeners():(e.has("scrollThreshold")||e.has("dockOffset"))&&this.updateScrollState()}connectedCallback(){super.connectedCallback(),this.syncScrollListeners(),this.syncMobileMenuQuery(),this.addMenuDrawerListeners(),this.syncExternalDrawerTarget(),this.observeMenuContainer()}disconnectedCallback(){this.removeMenuDrawerListeners(),this.externalDrawerTarget?.removeEventListener("drawer-opened",this.onMenuDrawerOpened),this.externalDrawerTarget?.removeEventListener("drawer-closed",this.onMenuDrawerClosed),this.externalDrawerTarget=null,this.mobileMenuQuery?.removeEventListener("change",this.handleMobileMenuQueryChange),this.mobileMenuQuery=null,this.removeScrollListeners(),this.cancelScrollUpdate(),this.disconnectResizeObserver(),super.disconnectedCallback()}renderConfiguredBrand(){if(!this.brandLogo&&!this.brandText)return c;const e=this.brandAlt||(this.brandText?"":"Site logo"),t=d`
2
2
  ${this.brandLogo?d`<img class="brand-logo" src=${this.brandLogo} alt=${e} />`:c}
3
3
  ${this.brandText?d`<span class="brand-text">${this.brandText}</span>`:c}
4
- `;return this.brandHref?d`<a class="configured-brand" href=${this.brandHref}>${t}</a>`:d`<span class="configured-brand">${t}</span>`}render(){const{menuOpen:e,menuTooLarge:t,mobileMenu:o}=this,s=this.menuDisplay!=="inline",a=s&&(this.showBurger||this.menuDisplay==="drawer"||this.layout==="minimal"),i=this.getScrollTokens(),h=i.has("progress"),m={"--_esp-header-brand-color":this.brandColor||null,"--_esp-header-scroll-progress":String(this.scrollProgress)},g={"menu-open":e,"too-wide":s&&(t||o||a),[`layout-${this.layout}`]:!0,"is-scrolled":this.scrolled,"hide-on-scroll":this.hiddenByScroll,"scroll-compact":i.has("compact"),"scroll-elevate":i.has("elevate"),"scroll-transparent":i.has("transparent")||i.has("transparent-to-solid"),"scroll-collapse-topbar":i.has("collapse-topbar")},w=this.scheme==="dark"?"light":"dark";return d`
4
+ `;return this.brandHref?d`<a class="configured-brand" href=${this.brandHref}>${t}</a>`:d`<span class="configured-brand">${t}</span>`}render(){const{menuOpen:e,menuTooLarge:t,mobileMenu:a}=this,s=this.menuDisplay!=="inline",i=s&&(this.showBurger||this.menuDisplay==="drawer"||this.layout==="minimal"),l=this.getScrollTokens(),h=l.has("progress"),m={"--_esp-header-brand-color":this.brandColor||null,"--_esp-header-scroll-progress":String(this.scrollProgress)},g={"menu-open":e,"too-wide":s&&(t||a||i),[`layout-${this.layout}`]:!0,"is-scrolled":this.scrolled,"hide-on-scroll":this.hiddenByScroll,"scroll-compact":l.has("compact"),"scroll-elevate":l.has("elevate"),"scroll-transparent":l.has("transparent")||l.has("transparent-to-solid"),"scroll-collapse-topbar":l.has("collapse-topbar")},w=this.scheme==="dark"?"light":"dark";return d`
5
5
  <header class=${M(g)} style=${S(m)}>
6
6
  <section class="topbar">
7
7
  <slot name="topbar"></slot>
@@ -11,7 +11,7 @@ var n=function(p,e,t,o){var s=arguments.length,a=s<3?e:o===null?o=Object.getOwnP
11
11
  <slot name="brand">${this.renderConfiguredBrand()}</slot>
12
12
  </section>
13
13
 
14
- <section ${v(this.menuContainer)} class="menu-container">
14
+ <section ${f(this.menuContainer)} class="menu-container">
15
15
  <slot name="menu"></slot>
16
16
  </section>
17
17
 
@@ -31,12 +31,12 @@ var n=function(p,e,t,o){var s=arguments.length,a=s<3?e:o===null?o=Object.getOwnP
31
31
  aria-expanded=${e?"true":"false"}
32
32
  @clicked=${this.handleMenuToggleClicked}
33
33
  >
34
- <esp-burger ${v(this.menuButton)} presentation-only></esp-burger>
34
+ <esp-burger ${f(this.menuButton)} presentation-only></esp-burger>
35
35
  </esp-header-button>
36
36
  </section>
37
37
  ${h?d`<div class="scroll-progress" aria-hidden="true"></div>`:c}
38
38
  </header>
39
- `}};r.styles=[...f.styles,y`
39
+ `}};r.styles=[...v.styles,y`
40
40
  :host {
41
41
  --_esp-header-current-height: var(--esp-header-height, calc(4.5 * var(--esp-size-small)));
42
42
 
@@ -51,6 +51,14 @@ var n=function(p,e,t,o){var s=arguments.length,a=s<3?e:o===null?o=Object.getOwnP
51
51
  var(--esp-header-brand-color, var(--esp-color-headings))
52
52
  );
53
53
 
54
+
55
+ --_esp-header-content-surplus: max(
56
+ 0px,
57
+ calc(100% - var(--esp-header-content-max-width, 100%))
58
+ );
59
+ padding-inline: calc(var(--_esp-header-content-surplus) * var(--esp-header-content-lead, 0))
60
+ calc(var(--_esp-header-content-surplus) * (1 - var(--esp-header-content-lead, 0)));
61
+
54
62
  display: grid;
55
63
  grid-template-columns: minmax(0, max-content) minmax(0, 1fr) max-content;
56
64
  grid-template-rows: min-content var(--_esp-header-current-height);
@@ -349,6 +357,11 @@ var n=function(p,e,t,o){var s=arguments.length,a=s<3?e:o===null?o=Object.getOwnP
349
357
  }
350
358
  }
351
359
 
360
+
361
+ :host([full-bleed-content]) header {
362
+ padding-inline: 0;
363
+ }
364
+
352
365
  :host([brand-align="center"]) header section.brand {
353
366
  justify-content: center;
354
367
  text-align: center;
@@ -418,4 +431,4 @@ var n=function(p,e,t,o){var s=arguments.length,a=s<3?e:o===null?o=Object.getOwnP
418
431
  transition: none;
419
432
  }
420
433
  }
421
- `],n([u()],r.prototype,"menuOpen",void 0),n([u()],r.prototype,"menuTooLarge",void 0),n([u()],r.prototype,"mobileMenu",void 0),n([u()],r.prototype,"scrolled",void 0),n([u()],r.prototype,"hiddenByScroll",void 0),n([u()],r.prototype,"scrollProgress",void 0),n([l({attribute:"show-burger",type:Boolean})],r.prototype,"showBurger",void 0),n([l({type:String,reflect:!0})],r.prototype,"layout",void 0),n([l({attribute:"brand-text",type:String})],r.prototype,"brandText",void 0),n([l({attribute:"brand-logo",type:String})],r.prototype,"brandLogo",void 0),n([l({attribute:"brand-href",type:String})],r.prototype,"brandHref",void 0),n([l({attribute:"brand-alt",type:String})],r.prototype,"brandAlt",void 0),n([l({attribute:"brand-color",type:String})],r.prototype,"brandColor",void 0),n([l({attribute:"brand-align",type:String,reflect:!0})],r.prototype,"brandAlign",void 0),n([l({attribute:"brand-wrap",type:String,reflect:!0})],r.prototype,"brandWrap",void 0),n([l({attribute:"menu-display",type:String,reflect:!0})],r.prototype,"menuDisplay",void 0),n([l({attribute:"theme-toggle",type:String,reflect:!0})],r.prototype,"themeToggle",void 0),n([l({attribute:"scroll-behavior",type:String})],r.prototype,"scrollBehavior",void 0),n([l({attribute:"scroll-threshold",type:Number})],r.prototype,"scrollThreshold",void 0),n([l({attribute:"dock-offset",type:Number})],r.prototype,"dockOffset",void 0),n([l({attribute:"drawer-target",type:String})],r.prototype,"drawerTarget",void 0),r=n([x("esp-header")],r);export{r as EspalierHeader};
434
+ `],n([u()],r.prototype,"menuOpen",void 0),n([u()],r.prototype,"menuTooLarge",void 0),n([u()],r.prototype,"mobileMenu",void 0),n([u()],r.prototype,"scrolled",void 0),n([u()],r.prototype,"hiddenByScroll",void 0),n([u()],r.prototype,"scrollProgress",void 0),n([o({attribute:"show-burger",type:Boolean})],r.prototype,"showBurger",void 0),n([o({type:String,reflect:!0})],r.prototype,"layout",void 0),n([o({attribute:"brand-text",type:String})],r.prototype,"brandText",void 0),n([o({attribute:"brand-logo",type:String})],r.prototype,"brandLogo",void 0),n([o({attribute:"brand-href",type:String})],r.prototype,"brandHref",void 0),n([o({attribute:"brand-alt",type:String})],r.prototype,"brandAlt",void 0),n([o({attribute:"brand-color",type:String})],r.prototype,"brandColor",void 0),n([o({attribute:"brand-align",type:String,reflect:!0})],r.prototype,"brandAlign",void 0),n([o({attribute:"brand-wrap",type:String,reflect:!0})],r.prototype,"brandWrap",void 0),n([o({attribute:"menu-display",type:String,reflect:!0})],r.prototype,"menuDisplay",void 0),n([o({attribute:"theme-toggle",type:String,reflect:!0})],r.prototype,"themeToggle",void 0),n([o({attribute:"scroll-behavior",type:String})],r.prototype,"scrollBehavior",void 0),n([o({attribute:"scroll-threshold",type:Number})],r.prototype,"scrollThreshold",void 0),n([o({attribute:"dock-offset",type:Number})],r.prototype,"dockOffset",void 0),n([o({attribute:"drawer-target",type:String})],r.prototype,"drawerTarget",void 0),n([o({attribute:"full-bleed-content",type:Boolean,reflect:!0})],r.prototype,"fullBleedContent",void 0),r=n([x("esp-header")],r);export{r as EspalierHeader};
@@ -96,15 +96,33 @@ type HeaderPosition = "normal" | "sticky" | "fixed";
96
96
  * </esp-page>
97
97
  * ```
98
98
  *
99
- * @cssprop --esp-page-background - The background color of the page.
100
- * Defaults to `var(--esp-color-background)`.
99
+ * @cssprop --esp-page-background - The background color of the page. Fills
100
+ * both the surface and the canvas gutters; the canvas tokens paint over it
101
+ * in the gutters only. Defaults to `var(--esp-color-background)`.
101
102
  * @cssprop --esp-page-max-width - The maximum width of the main content
102
103
  * well. Set automatically by the `kind` attribute: `1536px` (wide),
103
- * `768px` (narrow), or `none` (full).
104
+ * `768px` (narrow), or `none` (full). This cap now sizes the main grid
105
+ * track; surplus width beyond it becomes the canvas gutters.
104
106
  * @cssprop --esp-page-background-image - The background image to
105
107
  * display behind page content. Defaults to `none`.
106
108
  * @cssprop --esp-page-background-image-opacity - The opacity of the
107
109
  * background image layer. Defaults to `1`.
110
+ * @cssprop --esp-page-canvas-background - The background color of the
111
+ * canvas gutters (the outer regions revealed when the viewport exceeds
112
+ * the cap). Defaults to `transparent`, so gutters match the page until
113
+ * styled and narrow viewports are unaffected.
114
+ * @cssprop --esp-page-canvas-background-image - A background image for the
115
+ * canvas gutters, mirroring the page background-image knob. Defaults to
116
+ * `none`.
117
+ * @cssprop --esp-page-canvas-background-image-opacity - The opacity of the
118
+ * canvas gutter image layer. Defaults to `1`.
119
+ * @cssprop --esp-page-surface-shadow - The box shadow drawn on the left
120
+ * and right edges of the content surface. On by default; casts into the
121
+ * gutters and is clipped away when the surface fills the viewport, so it
122
+ * only shows above the cap. Set to `none` to remove it.
123
+ * @cssprop --esp-page-surface-border - An optional border on the inline
124
+ * edges of the surface, for themes preferring a hairline over a shadow.
125
+ * Defaults to `none` (e.g. `1px solid var(--esp-color-border)`).
108
126
  * @cssprop --esp-page-fixed-header-offset - Offset reserved for fixed
109
127
  * headers. Defaults to `var(--esp-header-height)`.
110
128
  * @cssprop --esp-page-sticky-header-top - Top inset for sticky headers.
@@ -112,6 +130,9 @@ type HeaderPosition = "normal" | "sticky" | "fixed";
112
130
  * @cssprop --esp-page-header-z-index - Z-index for fixed/sticky header
113
131
  * regions. Defaults to `20`.
114
132
  *
133
+ * @csspart canvas - The two canvas gutter regions flanking the surface.
134
+ * @csspart surface - The surface backdrop carrying the edge shadow/border.
135
+ *
115
136
  * ```html
116
137
  * <style>
117
138
  * esp-page.with-background {
@@ -130,6 +151,29 @@ type HeaderPosition = "normal" | "sticky" | "fixed";
130
151
  * </esp-page>
131
152
  * ```
132
153
  *
154
+ * Use `align` to place the surface within the page once it hits its cap,
155
+ * and the canvas tokens to style the gutters. A low `--esp-page-max-width`
156
+ * is set here only so the gutters are visible inside the narrow demo
157
+ * frame; the surface edge shadow is on by default.
158
+ *
159
+ * ```html
160
+ * <style>
161
+ * esp-page.align-demo {
162
+ * --esp-page-max-width: 520px;
163
+ * --esp-page-canvas-background: var(--esp-color-layer-1);
164
+ * }
165
+ * </style>
166
+ * <esp-page class="docs align-demo" align="center">
167
+ * <div slot="header" style="background-color: red;">header</div>
168
+ * <div slot="sidebar" style="background-color: green;">sidebar</div>
169
+ * <div slot="right" style="background-color: purple;">right</div>
170
+ * <div slot="footer" style="background-color: blue;">footer</div>
171
+ * <div style="background-color: black; overflow: hidden;">
172
+ * content
173
+ * </div>
174
+ * </esp-page>
175
+ * ```
176
+ *
133
177
  * @docPageTitle Page
134
178
  * @docUrl /components/page
135
179
  * @menuGroup Structure
@@ -153,6 +197,38 @@ export declare class EspalierPage extends EspalierElementBase {
153
197
  * maps or design tools.
154
198
  */
155
199
  kind: "wide" | "narrow" | "full";
200
+ /**
201
+ * Horizontal alignment of the content **surface** (left aside + main
202
+ * well + right aside) within the page once the viewport is wider than
203
+ * the content cap. The surplus width becomes styleable **canvas**
204
+ * gutters on the outer edges, weighted by this attribute.
205
+ *
206
+ * - `start` (default) — the surface hugs the leading edge; all spare
207
+ * width collects in the trailing gutter. This reproduces today's
208
+ * layout.
209
+ * - `center` — spare width is split evenly into both gutters, centering
210
+ * the surface.
211
+ * - `end` — the surface hugs the trailing edge; spare width collects in
212
+ * the leading gutter.
213
+ *
214
+ * Has no effect when `kind="full"` (no cap means no gutters) or on
215
+ * viewports narrower than the cap (the surface already fills the width).
216
+ */
217
+ align: "start" | "center" | "end";
218
+ /**
219
+ * When set, the header and footer sit **on** the content surface —
220
+ * the same width as the left aside + main well + right aside — instead
221
+ * of spanning the full viewport. Combined with a styled canvas this
222
+ * frames the whole page as one contained band floating on the canvas,
223
+ * with a single continuous surface edge shadow running from the header
224
+ * through the content to the footer.
225
+ *
226
+ * Below the cap there are no gutters, so a contained page renders
227
+ * identically to the default full-bleed chrome. Designed for
228
+ * `header-position="normal"` or `"sticky"`; a fixed header cannot be
229
+ * banded to the surface.
230
+ */
231
+ contained: boolean;
156
232
  /**
157
233
  * Header positioning behavior for the `header` slot.
158
234
  *
@@ -1,5 +1,8 @@
1
- var n=function(s,e,i,o){var r=arguments.length,t=r<3?e:o===null?o=Object.getOwnPropertyDescriptor(e,i):o,p;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")t=Reflect.decorate(s,e,i,o);else for(var d=s.length-1;d>=0;d--)(p=s[d])&&(t=(r<3?p(t):r>3?p(e,i,t):p(e,i))||t);return r>3&&t&&Object.defineProperty(e,i,t),t};import{css as c,html as f}from"lit";import{customElement as m,property as l}from"lit/decorators.js";import{classMap as u}from"lit/directives/class-map.js";import{createRef as v,ref as x}from"lit/directives/ref.js";import{EspalierElementBase as g}from"../shared/esp-element-base.js";import{BiDirectionalStickyController as h}from"./bi-directional-sticky-controller.js";import{getEspBus as y}from"../shared/bus-events.js";import"../toaster/esp-toaster.js";let a=class extends g{constructor(){super(),this.dialogZone=v(),this.kind="wide",this.headerPosition="normal",this.fixedMenus=!1,new h(this,".esp-page-left > .sticky-wrapper"),new h(this,".esp-page-right > .sticky-wrapper")}AddDialog(e){this.dialogZone.value?.appendChild(e)}updated(e){super.updated(e),(e.has("fixedMenus")||e.has("headerPosition"))&&y().publish("fixed-menus-changed",{fixed:this.fixedMenus||this.headerPosition==="fixed"})}render(){const e=this.fixedMenus||this.headerPosition==="fixed",i=!e&&this.headerPosition==="sticky";return f`
2
- <div part="wrapper" class="esp-page ${u({"fixed-menus":e,"fixed-header":e,"sticky-header":i})}">
1
+ var o=function(n,e,s,r){var d=arguments.length,a=d<3?e:r===null?r=Object.getOwnPropertyDescriptor(e,s):r,p;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")a=Reflect.decorate(n,e,s,r);else for(var l=n.length-1;l>=0;l--)(p=n[l])&&(a=(d<3?p(a):d>3?p(e,s,a):p(e,s))||a);return d>3&&a&&Object.defineProperty(e,s,a),a};import{css as g,html as f}from"lit";import{customElement as u,property as i}from"lit/decorators.js";import{classMap as m}from"lit/directives/class-map.js";import{createRef as v,ref as w}from"lit/directives/ref.js";import{EspalierElementBase as h}from"../shared/esp-element-base.js";import{BiDirectionalStickyController as c}from"./bi-directional-sticky-controller.js";import{getEspBus as b}from"../shared/bus-events.js";import"../toaster/esp-toaster.js";let t=class extends h{constructor(){super(),this.dialogZone=v(),this.kind="wide",this.align="start",this.contained=!1,this.headerPosition="normal",this.fixedMenus=!1,new c(this,".esp-page-left > .sticky-wrapper"),new c(this,".esp-page-right > .sticky-wrapper")}AddDialog(e){this.dialogZone.value?.appendChild(e)}updated(e){super.updated(e),(e.has("fixedMenus")||e.has("headerPosition"))&&b().publish("fixed-menus-changed",{fixed:this.fixedMenus||this.headerPosition==="fixed"})}render(){const e=this.fixedMenus||this.headerPosition==="fixed",s=!e&&this.headerPosition==="sticky";return f`
2
+ <div part="wrapper" class="esp-page ${m({"fixed-menus":e,"fixed-header":e,"sticky-header":s})}">
3
+ <div part="canvas" class="esp-page-canvas esp-page-canvas--left" aria-hidden="true"></div>
4
+ <div part="canvas" class="esp-page-canvas esp-page-canvas--right" aria-hidden="true"></div>
5
+ <div part="surface" class="esp-page-surface" aria-hidden="true"></div>
3
6
  <div class="esp-page-top">
4
7
  <slot name="header"></slot>
5
8
  </div>
@@ -19,12 +22,17 @@ var n=function(s,e,i,o){var r=arguments.length,t=r<3?e:o===null?o=Object.getOwnP
19
22
  <footer>
20
23
  <slot name="footer"></slot>
21
24
  </footer>
22
- <div id="dialog-drop-zone" ${x(this.dialogZone)}></div>
25
+ <div id="dialog-drop-zone" ${w(this.dialogZone)}></div>
23
26
  <esp-toaster></esp-toaster>
24
27
  </div>
25
- `}};a.styles=[...g.styles,c`
28
+ `}};t.styles=[...h.styles,g`
26
29
  :host {
27
30
  --_esp-page-resolved-max-width: var(--esp-page-max-width, 1536px);
31
+
32
+ --_esp-page-main-track: minmax(0, var(--_esp-page-resolved-max-width));
33
+
34
+ --_esp-page-gutter-left: 0;
35
+ --_esp-page-gutter-right: 1fr;
28
36
  --_esp-page-fixed-header-offset: var(
29
37
  --esp-page-fixed-header-offset,
30
38
  var(--esp-header-height, calc(4.5 * var(--esp-size-small)))
@@ -37,8 +45,53 @@ var n=function(s,e,i,o){var r=arguments.length,t=r<3?e:o===null?o=Object.getOwnP
37
45
  --_esp-page-resolved-max-width: var(--esp-page-max-width, 768px);
38
46
  }
39
47
 
48
+ :host([align="center"]) {
49
+ --_esp-page-gutter-left: 1fr;
50
+ --_esp-page-gutter-right: 1fr;
51
+ }
52
+
53
+ :host([align="end"]) {
54
+ --_esp-page-gutter-left: 1fr;
55
+ --_esp-page-gutter-right: 0;
56
+ }
57
+
58
+
40
59
  :host([kind="full"]) {
41
60
  --_esp-page-resolved-max-width: var(--esp-page-max-width, none);
61
+ --_esp-page-main-track: 1fr;
62
+ --_esp-page-gutter-left: 0;
63
+ --_esp-page-gutter-right: 0;
64
+ }
65
+
66
+
67
+ slot[name="header"]::slotted(esp-header) {
68
+ --esp-header-content-max-width: var(--_esp-page-resolved-max-width);
69
+ --esp-header-content-lead: 0;
70
+ }
71
+
72
+ :host([align="center"]) slot[name="header"]::slotted(esp-header) {
73
+ --esp-header-content-lead: 0.5;
74
+ }
75
+
76
+ :host([align="end"]) slot[name="header"]::slotted(esp-header) {
77
+ --esp-header-content-lead: 1;
78
+ }
79
+
80
+
81
+ :host([kind="full"]) slot[name="header"]::slotted(esp-header) {
82
+ --esp-header-content-max-width: 100%;
83
+ }
84
+
85
+
86
+ :host([contained]) .esp-page > div.esp-page-top,
87
+ :host([contained]) .esp-page > footer {
88
+ grid-column: surface;
89
+ }
90
+
91
+
92
+ :host([contained]) slot[name="header"]::slotted(esp-header) {
93
+ --esp-header-content-max-width: 100%;
94
+ --esp-header-shadow: none;
42
95
  }
43
96
 
44
97
  :host([kind="narrow"]) .esp-page > div.esp-page-main {
@@ -57,10 +110,12 @@ var n=function(s,e,i,o){var r=arguments.length,t=r<3?e:o===null?o=Object.getOwnP
57
110
  min-height: 100vh;
58
111
  display: grid;
59
112
  grid-template-columns:
60
- [full-start left-start] min-content
61
- [left-end main-start] 1fr
113
+ [full-start canvas-left-start] var(--_esp-page-gutter-left)
114
+ [canvas-left-end surface-start left-start] min-content
115
+ [left-end main-start] var(--_esp-page-main-track)
62
116
  [main-end right-start] min-content
63
- [right-end full-end];
117
+ [right-end surface-end canvas-right-start] var(--_esp-page-gutter-right)
118
+ [canvas-right-end full-end];
64
119
  grid-template-rows:
65
120
  [top-start] min-content
66
121
  [top-end content-start] 1fr
@@ -113,7 +168,7 @@ var n=function(s,e,i,o){var r=arguments.length,t=r<3?e:o===null?o=Object.getOwnP
113
168
  grid-row: content;
114
169
  position: relative;
115
170
  overflow: hidden;
116
- max-width: var(--_esp-page-resolved-max-width);
171
+
117
172
  contain: inline-size;
118
173
  }
119
174
 
@@ -122,6 +177,48 @@ var n=function(s,e,i,o){var r=arguments.length,t=r<3?e:o===null?o=Object.getOwnP
122
177
  }
123
178
 
124
179
 
180
+ > .esp-page-canvas {
181
+
182
+ grid-row: top-start / footer-end;
183
+ z-index: 1;
184
+ position: relative;
185
+ pointer-events: none;
186
+ background-color: var(--esp-page-canvas-background, transparent);
187
+
188
+
189
+ &::before {
190
+ content: "";
191
+ position: absolute;
192
+ inset: 0;
193
+ background-image: var(--esp-page-canvas-background-image, none);
194
+ opacity: var(--esp-page-canvas-background-image-opacity, 1);
195
+ }
196
+ }
197
+
198
+ > .esp-page-canvas--left {
199
+ grid-column: canvas-left;
200
+ }
201
+
202
+ > .esp-page-canvas--right {
203
+ grid-column: canvas-right;
204
+ }
205
+
206
+
207
+ > .esp-page-surface {
208
+ grid-column: surface;
209
+
210
+ grid-row: top-start / footer-end;
211
+ z-index: 2;
212
+ pointer-events: none;
213
+ box-shadow: var(
214
+ --esp-page-surface-shadow,
215
+ -0.75rem 0 1.5rem -0.75rem var(--esp-color-shadow),
216
+ 0.75rem 0 1.5rem -0.75rem var(--esp-color-shadow)
217
+ );
218
+ border-inline: var(--esp-page-surface-border, none);
219
+ }
220
+
221
+
125
222
  .sticky-wrapper {
126
223
  will-change: top;
127
224
  display: block;
@@ -166,7 +263,9 @@ var n=function(s,e,i,o){var r=arguments.length,t=r<3?e:o===null?o=Object.getOwnP
166
263
 
167
264
  > div.esp-page-main,
168
265
  > aside.esp-page-left,
169
- > aside.esp-page-right {
266
+ > aside.esp-page-right,
267
+ > .esp-page-surface,
268
+ > .esp-page-canvas {
170
269
  margin-top: var(--_esp-page-fixed-header-offset);
171
270
  }
172
271
  }
@@ -194,4 +293,4 @@ var n=function(s,e,i,o){var r=arguments.length,t=r<3?e:o===null?o=Object.getOwnP
194
293
  .esp-page:has(esp-dialog[is-open="true"]) {
195
294
  overflow: hidden;
196
295
  }
197
- `],n([l({reflect:!0})],a.prototype,"kind",void 0),n([l({attribute:"header-position",reflect:!0})],a.prototype,"headerPosition",void 0),n([l({attribute:"fixed-menus",type:Boolean,reflect:!0})],a.prototype,"fixedMenus",void 0),a=n([m("esp-page")],a);export{a as EspalierPage};
296
+ `],o([i({reflect:!0})],t.prototype,"kind",void 0),o([i({reflect:!0})],t.prototype,"align",void 0),o([i({type:Boolean,reflect:!0})],t.prototype,"contained",void 0),o([i({attribute:"header-position",reflect:!0})],t.prototype,"headerPosition",void 0),o([i({attribute:"fixed-menus",type:Boolean,reflect:!0})],t.prototype,"fixedMenus",void 0),t=o([u("esp-page")],t);export{t as EspalierPage};
@@ -842,6 +842,18 @@
842
842
  "component": "esp-header",
843
843
  "description": "The height of compact scroll-reactive headers. Defaults to `max(44px, calc(3.5 * var(--esp-size-small)))`."
844
844
  },
845
+ {
846
+ "name": "--esp-header-content-lead",
847
+ "category": "component",
848
+ "component": "esp-header",
849
+ "description": "Surface-alignment contract set by `esp-page`: the fraction of the leftover width placed before the content, aligning it (`0` = start, `0.5` = center, `1` = end). Defaults to `0`."
850
+ },
851
+ {
852
+ "name": "--esp-header-content-max-width",
853
+ "category": "component",
854
+ "component": "esp-header",
855
+ "description": "Surface-alignment contract set by `esp-page`: the maximum width the bar content is constrained to (the bar background stays full-bleed). Defaults to `100%` (no constraint). `esp-page` sets this to its resolved content cap."
856
+ },
845
857
  {
846
858
  "name": "--esp-header-extended-menu-border",
847
859
  "category": "component",
@@ -1219,7 +1231,7 @@
1219
1231
  "name": "--esp-page-background",
1220
1232
  "category": "component",
1221
1233
  "component": "esp-page",
1222
- "description": "The background color of the page. Defaults to `var(--esp-color-background)`."
1234
+ "description": "The background color of the page. Fills both the surface and the canvas gutters; the canvas tokens paint over it in the gutters only. Defaults to `var(--esp-color-background)`."
1223
1235
  },
1224
1236
  {
1225
1237
  "name": "--esp-page-background-image",
@@ -1231,6 +1243,24 @@
1231
1243
  "category": "root-theme",
1232
1244
  "description": "The opacity of the background image layer. Defaults to `1`."
1233
1245
  },
1246
+ {
1247
+ "name": "--esp-page-canvas-background",
1248
+ "category": "component",
1249
+ "component": "esp-page",
1250
+ "description": "The background color of the canvas gutters (the outer regions revealed when the viewport exceeds the cap). Defaults to `transparent`, so gutters match the page until styled and narrow viewports are unaffected."
1251
+ },
1252
+ {
1253
+ "name": "--esp-page-canvas-background-image",
1254
+ "category": "component",
1255
+ "component": "esp-page",
1256
+ "description": "A background image for the canvas gutters, mirroring the page background-image knob. Defaults to `none`."
1257
+ },
1258
+ {
1259
+ "name": "--esp-page-canvas-background-image-opacity",
1260
+ "category": "component",
1261
+ "component": "esp-page",
1262
+ "description": "The opacity of the canvas gutter image layer. Defaults to `1`."
1263
+ },
1234
1264
  {
1235
1265
  "name": "--esp-page-fixed-header-offset",
1236
1266
  "category": "component",
@@ -1247,7 +1277,7 @@
1247
1277
  "name": "--esp-page-max-width",
1248
1278
  "category": "component",
1249
1279
  "component": "esp-page",
1250
- "description": "The maximum width of the main content well. Set automatically by the `kind` attribute: `1536px` (wide), `768px` (narrow), or `none` (full)."
1280
+ "description": "The maximum width of the main content well. Set automatically by the `kind` attribute: `1536px` (wide), `768px` (narrow), or `none` (full). This cap now sizes the main grid track; surplus width beyond it becomes the canvas gutters."
1251
1281
  },
1252
1282
  {
1253
1283
  "name": "--esp-page-sticky-header-top",
@@ -1255,6 +1285,18 @@
1255
1285
  "component": "esp-page",
1256
1286
  "description": "Top inset for sticky headers. Defaults to `0`."
1257
1287
  },
1288
+ {
1289
+ "name": "--esp-page-surface-border",
1290
+ "category": "component",
1291
+ "component": "esp-page",
1292
+ "description": "An optional border on the inline edges of the surface, for themes preferring a hairline over a shadow. Defaults to `none` (e.g. `1px solid var(--esp-color-border)`)."
1293
+ },
1294
+ {
1295
+ "name": "--esp-page-surface-shadow",
1296
+ "category": "component",
1297
+ "component": "esp-page",
1298
+ "description": "The box shadow drawn on the left and right edges of the content surface. On by default; casts into the gutters and is clipped away when the surface fills the viewport, so it only shows above the cap. Set to `none` to remove it."
1299
+ },
1258
1300
  {
1259
1301
  "name": "--esp-progress-border-color",
1260
1302
  "category": "component",
@@ -14,6 +14,8 @@ removing, or replacing non-code assets.
14
14
  | `dist/shared/virtualizer/**` | Vendored Lit Virtualizer runtime source compiled into Espalier's package. | `licenses/THIRD_PARTY_NOTICES.md` |
15
15
  | Brand favicons/logos | Taproot-owned Espalier brand assets. | None |
16
16
  | Demo textures | Taproot-owned generated PNG textures created for this repository. | None |
17
+ | `assets/canvas-bg.jpg` | Docs-only light canvas texture — Envato Elements item "Seamless Pattern Design With Colorful Flowers" by yusufdemirci; certificate `licenses/license_certificate_A2BVZ6Y9DN.pdf`; attribution not required; kept out of the published `@taprootio/espalier` package. | None |
18
+ | `assets/canvas-bg-dark.jpg` | Docs-only dark canvas texture — Envato Elements item "Nocturnal Garden Pattern" by Webvilla; certificate `licenses/license_certificate_HF5ZVPRNC2.pdf`; attribution not required; kept out of the published package. | None |
17
19
  | Demo JSON data | Taproot-authored fictitious samples or standards-derived factual color data. | None |
18
20
 
19
21
  ## Texture Replacement
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "updatedAt": "2026-06-30",
3
+ "updatedAt": "2026-07-11",
4
4
  "entries": [
5
5
  {
6
6
  "id": "google-font-preview-assets",
@@ -211,6 +211,44 @@
211
211
  "noticeRequirement": "none",
212
212
  "noticePaths": [],
213
213
  "reviewStatus": "documented"
214
+ },
215
+ {
216
+ "id": "envato-canvas-background-texture",
217
+ "paths": [
218
+ "assets/canvas-bg.jpg"
219
+ ],
220
+ "assetType": "decorative floral background texture (JPEG, 400x400 seamless tile)",
221
+ "classification": "third-party licensed",
222
+ "owner": "Envato Elements author \"yusufdemirci\" retains ownership; licensed to Jereme Evans / Taproot under an Envato Elements subscription",
223
+ "source": "Envato Elements item \"Seamless Pattern Design With Colorful Flowers\" (item id ab0dc3dd-7d36-44be-9898-d148173189da, https://app.envato.com/item/ab0dc3dd-7d36-44be-9898-d148173189da), downloaded 2026-07-11 under an active subscription. License certificate A2BVZ6Y9DN on file at licenses/license_certificate_A2BVZ6Y9DN.pdf.",
224
+ "license": "Envato Elements subscription license (certificate A2BVZ6Y9DN): broad commercial use permitted and attribution is not required for this use. The author retains ownership; the item (or any product using it) may not be trademarked or claimed as Taproot-owned, and it must not be redistributed so that others can extract it. Valid only while the Envato subscription remains active.",
225
+ "transformations": [
226
+ "None; used as-is as a repeating docs-site canvas gutter background at 50% image-layer opacity"
227
+ ],
228
+ "packageInclusion": "not included in npm package",
229
+ "docsInclusion": "docs-site only — referenced as --esp-page-canvas-background-image in css/docs.css (which the public @taprootio/espalier package excludes) and served from the Eleventy /assets passthrough",
230
+ "noticeRequirement": "none",
231
+ "noticePaths": [],
232
+ "reviewStatus": "documented"
233
+ },
234
+ {
235
+ "id": "envato-canvas-background-texture-dark",
236
+ "paths": [
237
+ "assets/canvas-bg-dark.jpg"
238
+ ],
239
+ "assetType": "decorative dark botanical background texture (JPEG, 500x500 seamless tile)",
240
+ "classification": "third-party licensed",
241
+ "owner": "Envato Elements author \"Webvilla\" retains ownership; licensed to Jereme Evans / Taproot under an Envato Elements subscription",
242
+ "source": "Envato Elements item \"Nocturnal Garden Pattern\" (item id df520f04-5dfb-41c3-aa87-2ce3c03c8c34, https://app.envato.com/item/df520f04-5dfb-41c3-aa87-2ce3c03c8c34), downloaded 2026-07-11 under an active subscription as the dark-mode counterpart to canvas-bg.jpg. License certificate HF5ZVPRNC2 on file at licenses/license_certificate_HF5ZVPRNC2.pdf.",
243
+ "license": "Envato Elements subscription license (certificate HF5ZVPRNC2), same terms as canvas-bg.jpg: broad commercial use permitted and attribution not required; author retains ownership, no trademark/ownership claims, no redistribution for extraction, valid only while the subscription is active.",
244
+ "transformations": [
245
+ "None; used as-is as a repeating docs-site dark-mode canvas gutter background at 50% image-layer opacity"
246
+ ],
247
+ "packageInclusion": "not included in npm package",
248
+ "docsInclusion": "docs-site only — referenced as --esp-page-canvas-background-image under esp-root[scheme=\"dark\"] in css/docs.css (excluded from the public @taprootio/espalier package) and served from the Eleventy /assets passthrough",
249
+ "noticeRequirement": "none",
250
+ "noticePaths": [],
251
+ "reviewStatus": "documented"
214
252
  }
215
253
  ]
216
254
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taprootio/espalier",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Espalier — a themeable, accessible, framework-agnostic, enterprise-grade design system built on web standards and love.",
5
5
  "customElements": "custom-elements.json",
6
6
  "type": "module",