@taprootio/espalier 4.20.2 → 6.0.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/custom-elements.json +2904 -741
  3. package/dist/checkbox/esp-checkbox-group.d.ts +6 -0
  4. package/dist/checkbox/esp-checkbox-group.js +10 -5
  5. package/dist/checkbox/esp-checkbox.d.ts +24 -0
  6. package/dist/checkbox/esp-checkbox.js +3 -3
  7. package/dist/cli/theme-check.js +2 -2
  8. package/dist/date-picker/esp-date-picker.js +6 -6
  9. package/dist/flyout/esp-flyout.d.ts +1 -1
  10. package/dist/flyout/esp-flyout.js +3 -9
  11. package/dist/font-picker/esp-font-picker.js +3 -3
  12. package/dist/footer/esp-footer.d.ts +1 -1
  13. package/dist/form-item/esp-form-item.d.ts +35 -0
  14. package/dist/form-item/esp-form-item.js +16 -11
  15. package/dist/image/esp-image.d.ts +2 -2
  16. package/dist/image/esp-image.js +4 -4
  17. package/dist/image/image-ratio.d.ts +5 -0
  18. package/dist/image/image-ratio.js +1 -0
  19. package/dist/image-picker/esp-image-picker.js +2 -2
  20. package/dist/index.d.ts +2 -1
  21. package/dist/index.js +1 -1
  22. package/dist/input/esp-input.d.ts +29 -3
  23. package/dist/input/esp-input.js +14 -14
  24. package/dist/menu/esp-menu.d.ts +0 -5
  25. package/dist/menu/esp-menu.js +4 -4
  26. package/dist/page/esp-page.d.ts +2 -17
  27. package/dist/page/esp-page.js +2 -2
  28. package/dist/page/esp-page.styles.js +8 -32
  29. package/dist/pickers/esp-pick-one.d.ts +4 -0
  30. package/dist/pickers/esp-pick-one.js +11 -11
  31. package/dist/pickers/esp-pick-some.js +8 -8
  32. package/dist/pickers/esp-picker-base.js +5 -5
  33. package/dist/pickers/esp-picker-item.js +5 -5
  34. package/dist/pickers/esp-picker-menu.js +2 -2
  35. package/dist/pickers/typeahead-controller.js +1 -1
  36. package/dist/radio-button/esp-radio-button-group.d.ts +6 -0
  37. package/dist/radio-button/esp-radio-button-group.js +5 -4
  38. package/dist/radio-button/esp-radio-button.d.ts +22 -0
  39. package/dist/radio-button/esp-radio-button.js +2 -2
  40. package/dist/root/esp-root.js +2 -2
  41. package/dist/shared/enum-converter.js +1 -0
  42. package/dist/shared/events.d.ts +11 -0
  43. package/dist/shared/events.js +1 -1
  44. package/dist/shared/font-helpers.d.ts +0 -2
  45. package/dist/shared/font-helpers.js +1 -1
  46. package/dist/shared/form-field-description-controller.js +1 -1
  47. package/dist/shared/form-item-relay.js +1 -0
  48. package/dist/shared/justified-layout.d.ts +5 -31
  49. package/dist/shared/justified-layout.js +1 -1
  50. package/dist/slider/esp-slider.js +1 -1
  51. package/dist/switch/esp-switch.js +3 -3
  52. package/dist/textarea/esp-textarea.js +2 -2
  53. package/dist/video/esp-video.d.ts +231 -0
  54. package/dist/video/esp-video.js +253 -0
  55. package/espalier.css-data.json +40 -16
  56. package/espalier.token-manifest.json +60 -24
  57. package/licenses/ASSET_PROVENANCE.md +1 -0
  58. package/licenses/asset-provenance.json +22 -0
  59. package/package.json +5 -1
@@ -311,6 +311,14 @@
311
311
  "module": "dist/image/image-focus.js"
312
312
  }
313
313
  },
314
+ {
315
+ "kind": "js",
316
+ "name": "*",
317
+ "declaration": {
318
+ "name": "*",
319
+ "module": "dist/video/esp-video.js"
320
+ }
321
+ },
314
322
  {
315
323
  "kind": "js",
316
324
  "name": "*",
@@ -887,14 +895,6 @@
887
895
  "module": "./shared/justified-layout.js"
888
896
  }
889
897
  },
890
- {
891
- "kind": "js",
892
- "name": "calculatePhotoLayout",
893
- "declaration": {
894
- "name": "calculatePhotoLayout",
895
- "module": "./shared/justified-layout.js"
896
- }
897
- },
898
898
  {
899
899
  "kind": "js",
900
900
  "name": "DEFAULT_ALBUM_MAX_ROW_HEIGHT_VH",
@@ -951,14 +951,6 @@
951
951
  "module": "./shared/justified-layout.js"
952
952
  }
953
953
  },
954
- {
955
- "kind": "js",
956
- "name": "PhotoRow",
957
- "declaration": {
958
- "name": "PhotoRow",
959
- "module": "./shared/justified-layout.js"
960
- }
961
- },
962
954
  {
963
955
  "kind": "js",
964
956
  "name": "TypeaheadFetchItems",
@@ -11288,7 +11280,7 @@
11288
11280
  "declarations": [
11289
11281
  {
11290
11282
  "kind": "class",
11291
- "description": "Groups multiple [esp-checkbox](/components/checkbox)\ncomponents and manages their collective state. Works with\n[esp-form-item](/components/form-item) for label and\nerror message integration.\n\n```html\n<esp-box>\n <esp-form-item label=\"Select toppings\">\n <esp-checkbox-group>\n <esp-checkbox value=\"cheese\">Cheese</esp-checkbox>\n <esp-checkbox value=\"pepperoni\">Pepperoni</esp-checkbox>\n <esp-checkbox value=\"mushrooms\">Mushrooms</esp-checkbox>\n </esp-checkbox-group>\n </esp-form-item>\n</esp-box>\n```\n\n```html\n<esp-box>\n <esp-form-item label=\"Disabled group\">\n <esp-checkbox-group disabled>\n <esp-checkbox value=\"a\">Option A</esp-checkbox>\n <esp-checkbox value=\"b\" checked>Option B</esp-checkbox>\n <esp-checkbox value=\"c\">Option C</esp-checkbox>\n </esp-checkbox-group>\n </esp-form-item>\n</esp-box>\n```",
11283
+ "description": "Groups multiple [esp-checkbox](/components/checkbox)\ncomponents and manages their collective state. Works with\n[esp-form-item](/components/form-item) for label and\nerror message integration.\n\n```html\n<esp-box>\n <esp-form-item label=\"Select toppings\">\n <esp-checkbox-group>\n <esp-checkbox value=\"cheese\">Cheese</esp-checkbox>\n <esp-checkbox value=\"pepperoni\">Pepperoni</esp-checkbox>\n <esp-checkbox value=\"mushrooms\">Mushrooms</esp-checkbox>\n </esp-checkbox-group>\n </esp-form-item>\n</esp-box>\n```\n\n```html\n<esp-box>\n <esp-form-item label=\"Disabled group\">\n <esp-checkbox-group disabled>\n <esp-checkbox value=\"a\">Option A</esp-checkbox>\n <esp-checkbox value=\"b\" checked>Option B</esp-checkbox>\n <esp-checkbox value=\"c\">Option C</esp-checkbox>\n </esp-checkbox-group>\n </esp-form-item>\n</esp-box>\n```\n\nInside an `esp-form-item`, the group's `group` element takes the form\nitem's `label` as its accessible name and its hint as its description.\nWhile the form item shows an error, that element is `aria-invalid=\"true\"`\nand the error joins its description after the hint. Each option keeps its\nown label and is not described separately.",
11292
11284
  "name": "EspalierCheckboxGroup",
11293
11285
  "slots": [
11294
11286
  {
@@ -11324,18 +11316,15 @@
11324
11316
  },
11325
11317
  {
11326
11318
  "kind": "field",
11327
- "name": "formItemDescription",
11328
- "type": {
11329
- "text": "string | null"
11330
- },
11331
- "privacy": "private",
11332
- "default": "null"
11319
+ "name": "groupElement",
11320
+ "privacy": "private"
11333
11321
  },
11334
11322
  {
11335
11323
  "kind": "field",
11336
- "name": "describedCheckboxes",
11324
+ "name": "formItemDescription",
11337
11325
  "privacy": "private",
11338
- "default": "new Set<EspalierCheckbox>()"
11326
+ "default": "new FormFieldDescriptionController({ host: this, getTarget: () => this.groupElement.value, })",
11327
+ "description": "The wrapping form item names, describes, and marks invalid the\n`group` element itself, so its label, hint, and error are read\nonce for the group rather than once per option (ESP0232)."
11339
11328
  },
11340
11329
  {
11341
11330
  "kind": "field",
@@ -11456,16 +11445,6 @@
11456
11445
  }
11457
11446
  }
11458
11447
  },
11459
- {
11460
- "kind": "method",
11461
- "name": "applyFormItemDescription",
11462
- "privacy": "private",
11463
- "return": {
11464
- "type": {
11465
- "text": "void"
11466
- }
11467
- }
11468
- },
11469
11448
  {
11470
11449
  "kind": "field",
11471
11450
  "name": "handleItemsSlotChange",
@@ -12454,7 +12433,7 @@
12454
12433
  "declarations": [
12455
12434
  {
12456
12435
  "kind": "class",
12457
- "description": "A checkbox control for toggling boolean values. Uses Tabler\nSVG icons for checked, unchecked, and indeterminate states.\nWorks standalone or inside an\n[esp-checkbox-group](/components/checkbox/group) for multi-select.\n\n```html\n<esp-box>\n <esp-form-item label=\"Terms and conditions\">\n <esp-checkbox value=\"agree\">\n I agree to the terms\n </esp-checkbox>\n </esp-form-item>\n</esp-box>\n```\n\n```html\n<esp-box>\n <esp-checkbox checked value=\"newsletter\">\n Subscribe to newsletter\n </esp-checkbox>\n <esp-checkbox disabled value=\"disabled-opt\">\n Disabled option\n </esp-checkbox>\n <esp-checkbox indeterminate value=\"partial\">\n Indeterminate state\n </esp-checkbox>\n</esp-box>\n```",
12436
+ "description": "A checkbox control for toggling boolean values. Uses Tabler\nSVG icons for checked, unchecked, and indeterminate states.\nWorks standalone or inside an\n[esp-checkbox-group](/components/checkbox/group) for multi-select.\n\n```html\n<esp-box>\n <esp-form-item label=\"Terms and conditions\">\n <esp-checkbox value=\"agree\">\n I agree to the terms\n </esp-checkbox>\n </esp-form-item>\n</esp-box>\n```\n\n```html\n<esp-box>\n <esp-checkbox checked value=\"newsletter\">\n Subscribe to newsletter\n </esp-checkbox>\n <esp-checkbox disabled value=\"disabled-opt\">\n Disabled option\n </esp-checkbox>\n <esp-checkbox indeterminate value=\"partial\">\n Indeterminate state\n </esp-checkbox>\n</esp-box>\n```\n\n### Errors\n\nInside an `esp-form-item`, a checkbox that shows an error is\n`aria-invalid=\"true\"`, the error joins its description after any hint,\nand the error is announced once. In an `esp-checkbox-group`, the group\ncarries all of this instead, and takes the form item's `label` as its\naccessible name. Checking or unchecking clears an error set through the\nform item's `error` or `errorPool`; a `required` checkbox or group raises\nits own error again when it is left empty.\n\n```html\n<esp-box>\n <esp-form-item label=\"Terms\" error=\"Accept the terms to continue.\">\n <esp-checkbox value=\"agree\">I agree to the terms</esp-checkbox>\n </esp-form-item>\n <esp-form-item label=\"Pizza toppings\" hint=\"Pick up to three.\" error=\"Choose at least one topping.\">\n <esp-checkbox-group required>\n <esp-checkbox value=\"cheese\">Cheese</esp-checkbox>\n <esp-checkbox value=\"basil\">Basil</esp-checkbox>\n </esp-checkbox-group>\n </esp-form-item>\n</esp-box>\n```",
12458
12437
  "name": "EspalierCheckbox",
12459
12438
  "cssProperties": [
12460
12439
  {
@@ -21428,7 +21407,7 @@
21428
21407
  "declarations": [
21429
21408
  {
21430
21409
  "kind": "class",
21431
- "description": "A transient panel for help and more-info content that claims designed spare\nwidth before ever covering the page.\n\nPlace one `esp-flyout` in an `esp-page`'s `flyout` slot. The panel\nlives on the canvas after any persistent preview, outside the content\nsurface. It negotiates between `--esp-page-flyout-min-width` and\n`--esp-page-flyout-max-width`; preview receives flexible room first. The page\ndecides where help goes, from widest viewport to narrowest:\n\n1. **Gutter** — the panel occupies the right canvas gutter. When the\n gutter already has room, it claims existing spare canvas and the\n main content does not move at all, on any alignment.\n2. **Shift** — when the gutter is not quite wide enough, the surface\n slides toward `start` by exactly the shortfall (no more) to free\n the difference.\n3. **Docked sidebar** — once the gutter is gone, the panel competes\n for width with the main well; content narrows but is never\n covered.\n4. **Overlay drawer** — when the complete sidebar + main + help\n minimums do not fit, or below `COMPACT_VIEWPORT_MEDIA_QUERY` (the\n same breakpoint the `esp-menu` drawer uses), the panel becomes a fixed\n drawer over a vellum backdrop. Set `mode=\"overlay\"` to get this\n presentation at every width.\n\nThe panel is styled as a tear-off piece: a dotted perforation on\nits leading edge, rounded trailing corners, and no shadow — all\noverridable through the `--esp-flyout-*` tokens below.\nAn anchored panel shown with preview keeps its caret at the main/preview\nedge and receives a non-interactive dotted bridge across preview from the\npage. A short, thick terminus on the preview/help seam completes that\nconnection; no consumer positioning is required.\n\nA11y follows the presentation. In the in-grid modes (1–3) the\nflyout is a non-modal `complementary` landmark named by its\n`heading`, opening it never steals focus, and boundary scrolling chains\nback to the main document. In the overlay-drawer mode (4) it is a true\nmodal: `role=\"dialog\"` + `aria-modal`, the\nbackground goes inert, scroll is locked, focus moves into the drawer\nand is trapped, and focus is restored on close. `Escape` closes it\nin every mode.\n\n```html\n<style>\nesp-page.flyout-demo {\n --esp-page-main-max-width: 420px;\n --esp-page-canvas-background: var(--esp-color-layer-1);\n &::part(wrapper) {\n min-height: 320px;\n height: 320px;\n }\n > div {\n padding: var(--esp-size-padding);\n }\n}\n</style>\n<esp-page class=\"flyout-demo\" align=\"center\">\n <esp-flyout slot=\"flyout\" heading=\"More info\" id=\"demo-flyout\" standalone>\n <p>Spare width claimed; the well shifted only enough to make room.</p>\n </esp-flyout>\n <div>\n <esp-button id=\"toggle-flyout\" label=\"Toggle flyout\" collapsed></esp-button>\n </div>\n</esp-page>\n<script>\n const flyout = findById(\"demo-flyout\");\n findById(\"toggle-flyout\").addEventListener(\"esp-clicked\", () => flyout.toggle());\n</script>\n```\n\nBy default the panel is a tear-off — a dotted perforation on its\nleading edge, rounded trailing corners, no shadow. Add\n`match-surface` to lift the trailing edge with the page's surface\nedge shadow, so the flyout reads as a raised peer of the content it\nflies out from while staying attached at the leading perforation:\n\n```html\n<style>\nesp-page.flyout-match-demo {\n --esp-page-main-max-width: 420px;\n &::part(wrapper) {\n min-height: 320px;\n height: 320px;\n }\n > div {\n padding: var(--esp-size-padding);\n }\n}\n</style>\n<esp-page class=\"flyout-match-demo\" align=\"center\">\n <esp-flyout slot=\"flyout\" heading=\"More info\" id=\"match-flyout\" match-surface standalone>\n <p>Attached at the perforation, trailing edge raised like the surface.</p>\n </esp-flyout>\n <div>\n <esp-button id=\"toggle-match\" label=\"Toggle flyout\" collapsed></esp-button>\n </div>\n</esp-page>\n<script>\n const matchFlyout = findById(\"match-flyout\");\n findById(\"toggle-match\").addEventListener(\"esp-clicked\", () => matchFlyout.toggle());\n</script>\n```\n\nWhen the page has no spare width to claim — a `kind=\"full\"` page,\nor a capped page on a viewport at the cap — the open flyout docks\nas a right sidebar and the content makes room:\n\n```html\n<style>\nesp-page.flyout-docked-demo {\n &::part(wrapper) {\n min-height: 320px;\n height: 320px;\n }\n > div {\n padding: var(--esp-size-padding);\n }\n}\n</style>\n<esp-page class=\"flyout-docked-demo\" kind=\"full\">\n <esp-flyout slot=\"flyout\" heading=\"Docked\" id=\"docked-flyout\" standalone>\n <p>No gutter to claim, so the content narrows instead.</p>\n </esp-flyout>\n <div>\n <p>A <code>kind=\"full\"</code> page has no canvas gutters at any\n width — the flyout squeezes the main well and never covers it.</p>\n <esp-button id=\"toggle-docked\" label=\"Toggle flyout\" collapsed></esp-button>\n </div>\n</esp-page>\n<script>\n const dockedFlyout = findById(\"docked-flyout\");\n findById(\"toggle-docked\").addEventListener(\"esp-clicked\", () => dockedFlyout.toggle());\n</script>\n```\n\nBelow `COMPACT_VIEWPORT_MEDIA_QUERY` every flyout becomes a fixed\noverlay drawer over a vellum — there is no room for a side-by-side\nsplit on a phone. `mode=\"overlay\"` forces that presentation at any\nwidth (try it here; `Escape` or the vellum closes it):\n\n```html\n<style>\nesp-page.flyout-overlay-demo {\n &::part(wrapper) {\n min-height: 160px;\n height: 160px;\n }\n > div {\n padding: var(--esp-size-padding);\n }\n}\n</style>\n<esp-page class=\"flyout-overlay-demo\">\n <esp-flyout slot=\"flyout\" heading=\"Overlay drawer\" mode=\"overlay\" id=\"overlay-flyout\" standalone>\n <p>A fixed drawer over a vellum — what every flyout becomes on\n small viewports.</p>\n </esp-flyout>\n <div>\n <esp-button id=\"open-overlay\" label=\"Open overlay flyout\" collapsed></esp-button>\n </div>\n</esp-page>\n<script>\n const overlayFlyout = findById(\"overlay-flyout\");\n findById(\"open-overlay\").addEventListener(\"esp-clicked\", () => overlayFlyout.toggle());\n</script>\n```\n\nComponents that cannot know where the flyout lives can request it\nover the bus with `showFlyout()` — the same pattern `showToast()`\nuses. A second request swaps the content in place; it never\nstacks. Content passed as a string renders as plain text; pass a\n`Node` for rich content. Pass the triggering element as `anchor` to\nalign the in-grid panel with that control and keep both in the same\nscroll flow. If the panel would cross the visible viewport's bottom,\nit shifts up only far enough to fit and returns to its natural trigger\nalignment as the page scrolls. Content taller than the viewport\nscrolls inside the panel. Pass `fullHeight: true` (or set the\n`full-height` attribute) when a reading surface should fill the visible\nblock-size even when its content is short. Overlay drawers stay\nviewport-fixed.\n\nBus requests anchored inside nested pages are presented by the\noutermost containing page's flyout by default. This keeps previews,\nembedded tools, and nested layouts from trapping application-level\nhelp or detail panels. Set `scope=\"nearest\"` on a nested flyout when\nthat page should explicitly own requests originating inside it.\nThe demos above are each driven by their own toggle button, so they\nare marked `standalone` to opt out of the bus entirely.\n\n```html\n<style>\nesp-page.flyout-bus-demo {\n --esp-page-max-width: 420px;\n &::part(wrapper) {\n min-height: 320px;\n }\n > div {\n padding: var(--esp-size-padding);\n }\n .flyout-demo-copy {\n max-inline-size: 42ch;\n }\n}\n</style>\n<esp-page class=\"flyout-bus-demo\">\n <esp-flyout slot=\"flyout\"></esp-flyout>\n <div>\n <esp-button id=\"show-apples\" label=\"Apples\" collapsed></esp-button>\n <div class=\"flyout-demo-copy\">\n <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod\n tempor incididunt ut labore et dolore magna aliqua.</p>\n <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi\n ut aliquip ex ea commodo consequat.</p>\n <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum\n dolore eu fugiat nulla pariatur.</p>\n </div>\n <esp-button id=\"show-pears\" label=\"Pears\" collapsed></esp-button>\n <template id=\"pears-description\">\n <p>Ripen pears at room temperature until the neck yields gently to\n pressure, then refrigerate them to slow further ripening.</p>\n <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer\n nec odio. Praesent libero. Sed cursus ante dapibus diam.</p>\n <p>Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis\n sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper\n porta.</p>\n <p>Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent\n taciti sociosqu ad litora torquent per conubia nostra.</p>\n <p>Curabitur sodales ligula in libero. Sed dignissim lacinia nunc.\n Curabitur tortor. Pellentesque nibh. Aenean quam.</p>\n <p>In scelerisque sem at dolor. Maecenas mattis. Sed convallis\n tristique sem. Proin ut ligula vel nunc egestas porttitor.</p>\n <p>Morbi lectus risus, iaculis vel, suscipit quis, luctus non,\n massa. Fusce ac turpis quis ligula lacinia aliquet.</p>\n <p>Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed,\n euismod in, nibh. Quisque volutpat condimentum velit.</p>\n </template>\n <div class=\"flyout-demo-copy\">\n <p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia\n deserunt mollit anim id est laborum.</p>\n <p>Curabitur pretium tincidunt lacus. Nulla gravida orci a odio, nullam\n varius, turpis et commodo pharetra.</p>\n </div>\n </div>\n</esp-page>\n<script>\n const applesButton = findById(\"show-apples\");\n applesButton.addEventListener(\"esp-clicked\", () => {\n showFlyout({ heading: \"Apples\", content: \"Crisp. Store cold.\", anchor: applesButton });\n });\n const pearsButton = findById(\"show-pears\");\n const pearsDescription = findById(\"pears-description\");\n pearsButton.addEventListener(\"esp-clicked\", () => {\n showFlyout({\n heading: \"Pears\",\n content: pearsDescription.content.cloneNode(true),\n anchor: pearsButton,\n });\n });\n</script>\n```",
21410
+ "description": "A transient panel for help and more-info content that claims designed spare\nwidth before ever covering the page.\n\nPlace one `esp-flyout` in an `esp-page`'s `flyout` slot. The panel\nlives on the canvas after any persistent preview, outside the content\nsurface. It negotiates between `--esp-page-flyout-min-width` and\n`--esp-page-flyout-max-width`; preview receives flexible room first. The page\ndecides where help goes, from widest viewport to narrowest:\n\n1. **Gutter** — the panel occupies the right canvas gutter. When the\n gutter already has room, it claims existing spare canvas and the\n main content does not move at all, on any alignment.\n2. **Shift** — when the gutter is not quite wide enough, the surface\n slides toward `start` by exactly the shortfall (no more) to free\n the difference.\n3. **Docked sidebar** — once the gutter is gone, the panel competes\n for width with the main well; content narrows but is never\n covered.\n4. **Overlay drawer** — when the complete sidebar + main + help\n minimums do not fit, or below `COMPACT_VIEWPORT_MEDIA_QUERY` (the\n same breakpoint the `esp-menu` drawer uses), the panel becomes a fixed\n drawer over a vellum backdrop. Set `mode=\"overlay\"` to get this\n presentation at every width.\n\nThe panel is styled as a tear-off piece: a dotted perforation on\nits leading edge, rounded trailing corners, and no shadow — all\noverridable through the `--esp-flyout-*` tokens below.\nAn anchored panel shown with preview keeps its caret at the main/preview\nedge and receives a non-interactive dotted bridge across preview from the\npage. A short, thick terminus on the preview/help seam completes that\nconnection; no consumer positioning is required.\n\nA11y follows the presentation. In the in-grid modes (1–3) the\nflyout is a non-modal `complementary` landmark named by its\n`heading`, opening it never steals focus, and boundary scrolling chains\nback to the main document. In the overlay-drawer mode (4) it is a true\nmodal: `role=\"dialog\"` + `aria-modal`, the\nbackground goes inert, scroll is locked, focus moves into the drawer\nand is trapped, and focus is restored on close. `Escape` closes it\nin every mode.\n\n```html\n<style>\nesp-page.flyout-demo {\n --esp-page-main-max-width: 420px;\n --esp-page-canvas-background: var(--esp-color-layer-1);\n &::part(wrapper) {\n min-height: 320px;\n height: 320px;\n }\n > div {\n padding: var(--esp-size-padding);\n }\n}\n</style>\n<esp-page class=\"flyout-demo\" align=\"center\">\n <esp-flyout slot=\"flyout\" heading=\"More info\" id=\"demo-flyout\" standalone>\n <p>Spare width claimed; the well shifted only enough to make room.</p>\n </esp-flyout>\n <div>\n <esp-button id=\"toggle-flyout\" label=\"Toggle flyout\" collapsed></esp-button>\n </div>\n</esp-page>\n<script>\n const flyout = findById(\"demo-flyout\");\n findById(\"toggle-flyout\").addEventListener(\"esp-clicked\", () => flyout.toggle());\n</script>\n```\n\nBy default the panel is a tear-off — a dotted perforation on its\nleading edge, rounded trailing corners, no shadow. Add\n`match-surface` to lift the trailing edge with the page's surface\nedge shadow, so the flyout reads as a raised peer of the content it\nflies out from while staying attached at the leading perforation:\n\n```html\n<style>\nesp-page.flyout-match-demo {\n --esp-page-main-max-width: 420px;\n &::part(wrapper) {\n min-height: 320px;\n height: 320px;\n }\n > div {\n padding: var(--esp-size-padding);\n }\n}\n</style>\n<esp-page class=\"flyout-match-demo\" align=\"center\">\n <esp-flyout slot=\"flyout\" heading=\"More info\" id=\"match-flyout\" match-surface standalone>\n <p>Attached at the perforation, trailing edge raised like the surface.</p>\n </esp-flyout>\n <div>\n <esp-button id=\"toggle-match\" label=\"Toggle flyout\" collapsed></esp-button>\n </div>\n</esp-page>\n<script>\n const matchFlyout = findById(\"match-flyout\");\n findById(\"toggle-match\").addEventListener(\"esp-clicked\", () => matchFlyout.toggle());\n</script>\n```\n\nWhen the page has no spare width to claim — a `kind=\"full\"` page,\nor a capped page on a viewport at the cap — the open flyout docks\nas a right sidebar and the content makes room:\n\n```html\n<style>\nesp-page.flyout-docked-demo {\n &::part(wrapper) {\n min-height: 320px;\n height: 320px;\n }\n > div {\n padding: var(--esp-size-padding);\n }\n}\n</style>\n<esp-page class=\"flyout-docked-demo\" kind=\"full\">\n <esp-flyout slot=\"flyout\" heading=\"Docked\" id=\"docked-flyout\" standalone>\n <p>No gutter to claim, so the content narrows instead.</p>\n </esp-flyout>\n <div>\n <p>A <code>kind=\"full\"</code> page has no canvas gutters at any\n width — the flyout squeezes the main well and never covers it.</p>\n <esp-button id=\"toggle-docked\" label=\"Toggle flyout\" collapsed></esp-button>\n </div>\n</esp-page>\n<script>\n const dockedFlyout = findById(\"docked-flyout\");\n findById(\"toggle-docked\").addEventListener(\"esp-clicked\", () => dockedFlyout.toggle());\n</script>\n```\n\nBelow `COMPACT_VIEWPORT_MEDIA_QUERY` every flyout becomes a fixed\noverlay drawer over a vellum — there is no room for a side-by-side\nsplit on a phone. `mode=\"overlay\"` forces that presentation at any\nwidth (try it here; `Escape` or the vellum closes it):\n\n```html\n<style>\nesp-page.flyout-overlay-demo {\n &::part(wrapper) {\n min-height: 160px;\n height: 160px;\n }\n > div {\n padding: var(--esp-size-padding);\n }\n}\n</style>\n<esp-page class=\"flyout-overlay-demo\">\n <esp-flyout slot=\"flyout\" heading=\"Overlay drawer\" mode=\"overlay\" id=\"overlay-flyout\" standalone>\n <p>A fixed drawer over a vellum — what every flyout becomes on\n small viewports.</p>\n </esp-flyout>\n <div>\n <esp-button id=\"open-overlay\" label=\"Open overlay flyout\" collapsed></esp-button>\n </div>\n</esp-page>\n<script>\n const overlayFlyout = findById(\"overlay-flyout\");\n findById(\"open-overlay\").addEventListener(\"esp-clicked\", () => overlayFlyout.toggle());\n</script>\n```\n\nComponents that cannot know where the flyout lives can request it\nover the bus with `showFlyout()` — the same pattern `showToast()`\nuses. A second request swaps the content in place; it never\nstacks. Content passed as a string renders as plain text; pass a\n`Node` for rich content. Pass the triggering element as `anchor` to\nalign the in-grid panel with that control and keep both in the same\nscroll flow. If the panel would cross the visible viewport's bottom,\nit shifts up only far enough to fit and returns to its natural trigger\nalignment as the page scrolls. Content taller than the viewport\nscrolls inside the panel. Pass `fullHeight: true` (or set the\n`full-height` attribute) when a reading surface should fill the visible\nblock-size even when its content is short. Overlay drawers stay\nviewport-fixed.\n\nBus requests anchored inside nested pages are presented by the\noutermost containing page's flyout by default. This keeps previews,\nembedded tools, and nested layouts from trapping application-level\nhelp or detail panels. Set `scope=\"nearest\"` on a nested flyout when\nthat page should explicitly own requests originating inside it.\nThe demos above are each driven by their own toggle button, so they\nare marked `standalone` to opt out of the bus entirely.\n\n```html\n<style>\nesp-page.flyout-bus-demo {\n --esp-page-main-max-width: 420px;\n &::part(wrapper) {\n min-height: 320px;\n }\n > div {\n padding: var(--esp-size-padding);\n }\n .flyout-demo-copy {\n max-inline-size: 42ch;\n }\n}\n</style>\n<esp-page class=\"flyout-bus-demo\">\n <esp-flyout slot=\"flyout\"></esp-flyout>\n <div>\n <esp-button id=\"show-apples\" label=\"Apples\" collapsed></esp-button>\n <div class=\"flyout-demo-copy\">\n <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod\n tempor incididunt ut labore et dolore magna aliqua.</p>\n <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi\n ut aliquip ex ea commodo consequat.</p>\n <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum\n dolore eu fugiat nulla pariatur.</p>\n </div>\n <esp-button id=\"show-pears\" label=\"Pears\" collapsed></esp-button>\n <template id=\"pears-description\">\n <p>Ripen pears at room temperature until the neck yields gently to\n pressure, then refrigerate them to slow further ripening.</p>\n <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer\n nec odio. Praesent libero. Sed cursus ante dapibus diam.</p>\n <p>Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis\n sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper\n porta.</p>\n <p>Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent\n taciti sociosqu ad litora torquent per conubia nostra.</p>\n <p>Curabitur sodales ligula in libero. Sed dignissim lacinia nunc.\n Curabitur tortor. Pellentesque nibh. Aenean quam.</p>\n <p>In scelerisque sem at dolor. Maecenas mattis. Sed convallis\n tristique sem. Proin ut ligula vel nunc egestas porttitor.</p>\n <p>Morbi lectus risus, iaculis vel, suscipit quis, luctus non,\n massa. Fusce ac turpis quis ligula lacinia aliquet.</p>\n <p>Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed,\n euismod in, nibh. Quisque volutpat condimentum velit.</p>\n </template>\n <div class=\"flyout-demo-copy\">\n <p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia\n deserunt mollit anim id est laborum.</p>\n <p>Curabitur pretium tincidunt lacus. Nulla gravida orci a odio, nullam\n varius, turpis et commodo pharetra.</p>\n </div>\n </div>\n</esp-page>\n<script>\n const applesButton = findById(\"show-apples\");\n applesButton.addEventListener(\"esp-clicked\", () => {\n showFlyout({ heading: \"Apples\", content: \"Crisp. Store cold.\", anchor: applesButton });\n });\n const pearsButton = findById(\"show-pears\");\n const pearsDescription = findById(\"pears-description\");\n pearsButton.addEventListener(\"esp-clicked\", () => {\n showFlyout({\n heading: \"Pears\",\n content: pearsDescription.content.cloneNode(true),\n anchor: pearsButton,\n });\n });\n</script>\n```",
21432
21411
  "name": "EspalierFlyout",
21433
21412
  "cssProperties": [
21434
21413
  {
@@ -24157,21 +24136,9 @@
24157
24136
  },
24158
24137
  {
24159
24138
  "kind": "field",
24160
- "name": "formItemDescription",
24161
- "type": {
24162
- "text": "string | null"
24163
- },
24164
- "privacy": "private",
24165
- "default": "null"
24166
- },
24167
- {
24168
- "kind": "field",
24169
- "name": "formItemLabel",
24170
- "type": {
24171
- "text": "string | null"
24172
- },
24139
+ "name": "formItemRelay",
24173
24140
  "privacy": "private",
24174
- "default": "null"
24141
+ "default": "new FormItemRelay(() => this.shadowRoot?.querySelector(\"esp-pick-one\") as EspalierFormField | undefined)"
24175
24142
  },
24176
24143
  {
24177
24144
  "kind": "field",
@@ -24265,16 +24232,6 @@
24265
24232
  },
24266
24233
  "description": "Focus the picker input."
24267
24234
  },
24268
- {
24269
- "kind": "method",
24270
- "name": "syncFormItemDescription",
24271
- "privacy": "private",
24272
- "return": {
24273
- "type": {
24274
- "text": "void"
24275
- }
24276
- }
24277
- },
24278
24235
  {
24279
24236
  "kind": "method",
24280
24237
  "name": "getUpdateComplete",
@@ -27311,7 +27268,7 @@
27311
27268
  "declarations": [
27312
27269
  {
27313
27270
  "kind": "class",
27314
- "description": "A responsive site footer with structured spaces for branding, feature\nartwork, link groups, supporting actions, and legal or utility content.\nIts background remains full-bleed while its content automatically aligns\nto the surface of a containing [`<esp-page>`](/components/page/).\nThe decorative background uses the same internal visual-layer primitive as\n`esp-image` banners; the footer's public background tokens are unchanged.\n\nUse bare [`<esp-footer-link-group>`](/components/footer/link-group/) children\nwhen navigation groups should auto-flow independently through the grid.\n`columns` is a maximum: the grid always drops columns when they no longer fit.\n\n```html\n<esp-footer\n light-brand-logo=\"/assets/logo-light.svg\"\n dark-brand-logo=\"/assets/logo-dark.svg\"\n brand-text=\"Espalier\"\n brand-href=\"/\"\n brand-alt=\"Espalier logo\"\n light-brand-color=\"oklch(0.35 0.12 216)\"\n dark-brand-color=\"oklch(0.9 0.08 216)\"\n columns=\"3\">\n <esp-footer-link-group heading=\"Explore\">\n <a href=\"/components\">Components</a>\n <a href=\"/api\">API</a>\n </esp-footer-link-group>\n <esp-footer-link-group heading=\"Learn\">\n <a href=\"/guides/getting-started\">Getting started</a>\n <a href=\"/guides/styling\">Styling</a>\n </esp-footer-link-group>\n <p slot=\"aside\">Themeable web components built on web standards.</p>\n <div slot=\"bottom\">Copyright 2026 Example Organization</div>\n</esp-footer>\n```\n\nFor creator-controlled columns, wrap each ordered stack of groups in\n[`<esp-footer-column>`](/components/footer/column/). Each wrapper is one grid\nitem at wide sizes; below the footer's compact container breakpoint, the\ncolumns become full-width rows in their slotted order. `columns` remains the\nmaximum number of grid items per row:\n\n```html\n<esp-footer columns=\"3\">\n <esp-footer-column>\n <esp-footer-link-group heading=\"Product\">\n <a href=\"/features\">Features</a>\n <a href=\"/pricing\">Pricing</a>\n </esp-footer-link-group>\n <esp-footer-link-group heading=\"Company\">\n <a href=\"/about\">About</a>\n <a href=\"/careers\">Careers</a>\n </esp-footer-link-group>\n </esp-footer-column>\n <esp-footer-column>\n <esp-footer-link-group heading=\"Resources\">\n <a href=\"/docs\">Documentation</a>\n <a href=\"/support\">Support</a>\n </esp-footer-link-group>\n </esp-footer-column>\n</esp-footer>\n```\n\nA slotted brand replaces configured branding, while the media slot accepts\na responsive `picture`, native image, or `esp-image`. Media spans the\nsurface-aligned content well; use the decorative background layer for\nedge-to-edge artwork:\n\n```html\n<esp-footer columns=\"4\">\n <picture slot=\"media\">\n <source media=\"(prefers-color-scheme: dark)\" srcset=\"/assets/canvas-bg-dark.jpg\" />\n <img\n src=\"/assets/canvas-bg.jpg\"\n alt=\"A colorful floral pattern\"\n style=\"display: block; width: 100%; height: auto;\" />\n </picture>\n <a slot=\"brand\" href=\"/\" aria-label=\"Espalier home\">\n <img src=\"/assets/espalier-logo.svg\" alt=\"Espalier\" />\n </a>\n <esp-footer-link-group heading=\"Product\">\n <a href=\"/components\">Components</a>\n </esp-footer-link-group>\n</esp-footer>\n```\n\nDecorative art uses an image layer independent of the base color. The same\nAPI supports a repeating landscape band or one large cover image:\n\n```html\n<style>\n esp-footer.mountains {\n --esp-footer-background: oklch(0.48 0.12 145);\n --esp-footer-background-image-opacity: 0.55;\n --esp-footer-background-image:\n linear-gradient(to bottom, transparent 8rem, oklch(0.48 0.12 145)),\n url(\"/assets/espalier-geometric-texture.png\");\n --esp-footer-background-repeat: no-repeat, repeat-x;\n --esp-footer-background-position: 0 0, top;\n --esp-footer-background-size: 100% 100%, auto 10rem;\n --esp-footer-padding-block: 6rem;\n }\n\n esp-footer.photo {\n --esp-footer-background: oklch(0.2 0.04 216);\n --esp-footer-heading-color: white;\n --esp-footer-background-image:\n linear-gradient(oklch(0.1 0 0 / 0.55), oklch(0.1 0 0 / 0.55)),\n url(\"/assets/canvas-bg.jpg\");\n --esp-footer-background-repeat: no-repeat;\n --esp-footer-background-position: center;\n --esp-footer-background-size: cover;\n }\n</style>\n<esp-footer class=\"mountains\" brand-text=\"Mountain Studio\"></esp-footer>\n<esp-footer class=\"photo\" brand-text=\"Photo Studio\"></esp-footer>\n```\n\nSurface alignment is automatic inside `esp-page`; opt out only when the\nfooter's content should also span the entire bar:\n\n```html\n<esp-page align=\"center\" style=\"--esp-page-max-width: 36rem;\">\n <main>Surface-aligned page content</main>\n <esp-footer slot=\"footer\" brand-text=\"Aligned Studio\" columns=\"3\">\n <esp-footer-link-group heading=\"Explore\">\n <a href=\"/work\">Work</a>\n <a href=\"/about\">About</a>\n </esp-footer-link-group>\n </esp-footer>\n</esp-page>\n\n<esp-footer full-bleed-content brand-text=\"Full-width Studio\"></esp-footer>\n```",
27271
+ "description": "A responsive site footer with structured spaces for branding, feature\nartwork, link groups, supporting actions, and legal or utility content.\nIts background remains full-bleed while its content automatically aligns\nto the surface of a containing [`<esp-page>`](/components/page/).\nThe decorative background uses the same internal visual-layer primitive as\n`esp-image` banners; the footer's public background tokens are unchanged.\n\nUse bare [`<esp-footer-link-group>`](/components/footer/link-group/) children\nwhen navigation groups should auto-flow independently through the grid.\n`columns` is a maximum: the grid always drops columns when they no longer fit.\n\n```html\n<esp-footer\n light-brand-logo=\"/assets/logo-light.svg\"\n dark-brand-logo=\"/assets/logo-dark.svg\"\n brand-text=\"Espalier\"\n brand-href=\"/\"\n brand-alt=\"Espalier logo\"\n light-brand-color=\"oklch(0.35 0.12 216)\"\n dark-brand-color=\"oklch(0.9 0.08 216)\"\n columns=\"3\">\n <esp-footer-link-group heading=\"Explore\">\n <a href=\"/components\">Components</a>\n <a href=\"/api\">API</a>\n </esp-footer-link-group>\n <esp-footer-link-group heading=\"Learn\">\n <a href=\"/guides/getting-started\">Getting started</a>\n <a href=\"/guides/styling\">Styling</a>\n </esp-footer-link-group>\n <p slot=\"aside\">Themeable web components built on web standards.</p>\n <div slot=\"bottom\">Copyright 2026 Example Organization</div>\n</esp-footer>\n```\n\nFor creator-controlled columns, wrap each ordered stack of groups in\n[`<esp-footer-column>`](/components/footer/column/). Each wrapper is one grid\nitem at wide sizes; below the footer's compact container breakpoint, the\ncolumns become full-width rows in their slotted order. `columns` remains the\nmaximum number of grid items per row:\n\n```html\n<esp-footer columns=\"3\">\n <esp-footer-column>\n <esp-footer-link-group heading=\"Product\">\n <a href=\"/features\">Features</a>\n <a href=\"/pricing\">Pricing</a>\n </esp-footer-link-group>\n <esp-footer-link-group heading=\"Company\">\n <a href=\"/about\">About</a>\n <a href=\"/careers\">Careers</a>\n </esp-footer-link-group>\n </esp-footer-column>\n <esp-footer-column>\n <esp-footer-link-group heading=\"Resources\">\n <a href=\"/docs\">Documentation</a>\n <a href=\"/support\">Support</a>\n </esp-footer-link-group>\n </esp-footer-column>\n</esp-footer>\n```\n\nA slotted brand replaces configured branding, while the media slot accepts\na responsive `picture`, native image, or `esp-image`. Media spans the\nsurface-aligned content well; use the decorative background layer for\nedge-to-edge artwork:\n\n```html\n<esp-footer columns=\"4\">\n <picture slot=\"media\">\n <source media=\"(prefers-color-scheme: dark)\" srcset=\"/assets/canvas-bg-dark.jpg\" />\n <img\n src=\"/assets/canvas-bg.jpg\"\n alt=\"A colorful floral pattern\"\n style=\"display: block; width: 100%; height: auto;\" />\n </picture>\n <a slot=\"brand\" href=\"/\" aria-label=\"Espalier home\">\n <img src=\"/assets/espalier-logo.svg\" alt=\"Espalier\" />\n </a>\n <esp-footer-link-group heading=\"Product\">\n <a href=\"/components\">Components</a>\n </esp-footer-link-group>\n</esp-footer>\n```\n\nDecorative art uses an image layer independent of the base color. The same\nAPI supports a repeating landscape band or one large cover image:\n\n```html\n<style>\n esp-footer.mountains {\n --esp-footer-background: oklch(0.48 0.12 145);\n --esp-footer-background-image-opacity: 0.55;\n --esp-footer-background-image:\n linear-gradient(to bottom, transparent 8rem, oklch(0.48 0.12 145)),\n url(\"/assets/espalier-geometric-texture.png\");\n --esp-footer-background-repeat: no-repeat, repeat-x;\n --esp-footer-background-position: 0 0, top;\n --esp-footer-background-size: 100% 100%, auto 10rem;\n --esp-footer-padding-block: 6rem;\n }\n\n esp-footer.photo {\n --esp-footer-background: oklch(0.2 0.04 216);\n --esp-footer-heading-color: white;\n --esp-footer-background-image:\n linear-gradient(oklch(0.1 0 0 / 0.55), oklch(0.1 0 0 / 0.55)),\n url(\"/assets/canvas-bg.jpg\");\n --esp-footer-background-repeat: no-repeat;\n --esp-footer-background-position: center;\n --esp-footer-background-size: cover;\n }\n</style>\n<esp-footer class=\"mountains\" brand-text=\"Mountain Studio\"></esp-footer>\n<esp-footer class=\"photo\" brand-text=\"Photo Studio\"></esp-footer>\n```\n\nSurface alignment is automatic inside `esp-page`; opt out only when the\nfooter's content should also span the entire bar:\n\n```html\n<esp-page align=\"center\" style=\"--esp-page-main-max-width: 36rem;\">\n <main>Surface-aligned page content</main>\n <esp-footer slot=\"footer\" brand-text=\"Aligned Studio\" columns=\"3\">\n <esp-footer-link-group heading=\"Explore\">\n <a href=\"/work\">Work</a>\n <a href=\"/about\">About</a>\n </esp-footer-link-group>\n </esp-footer>\n</esp-page>\n\n<esp-footer full-bleed-content brand-text=\"Full-width Studio\"></esp-footer>\n```",
27315
27272
  "name": "EspalierFooter",
27316
27273
  "cssProperties": [
27317
27274
  {
@@ -29017,7 +28974,7 @@
29017
28974
  "declarations": [
29018
28975
  {
29019
28976
  "kind": "class",
29020
- "description": "Wraps fields with label and error message functionality. For more\ninformation about forms, read our\n[guide for working with forms](/espalier-guides/#forms).\n\nExactly one element belongs in the default slot. That control may be\nreplaced or moved to another `esp-form-item` after render: the old binding\nis removed before the new one is applied, including form-item-owned name,\ndescription, accessible-label, validation-listener, and help-target state.\nConsumer-authored name and description values are preserved. An empty or\nmulti-control default slot is reported asynchronously from the queued\nslotchange reconciliation, after synchronous DOM moves have settled; the\nerror cannot be caught around the mutation itself. The same invalid initial\nassignment is reported synchronously from `firstUpdated` and rejects the\ncomponent's first `updateComplete` promise.\n\n```html\n<style>\nesp-box.demo-form::part(box) {\n display: grid;\n gap: var(--esp-size-tiny-to-small);\n}\n</style>\n<esp-box class=\"demo-form\">\n <esp-form-item label=\"Form item\" autofocus>\n <esp-input></esp-input>\n </esp-form-item>\n <esp-button label=\"Form Things\"></esp-button>\n</esp-box>\n```",
28977
+ "description": "Wraps fields with label and error message functionality. For more\ninformation about forms, read our\n[guide for working with forms](/espalier-guides/#forms).\n\nExactly one element belongs in the default slot. That control may be\nreplaced or moved to another `esp-form-item` after render: the old binding\nis removed before the new one is applied, including form-item-owned name,\ndescription, accessible-label, validation-listener, and help-target state.\nConsumer-authored name and description values are preserved. An empty or\nmulti-control default slot is reported asynchronously from the queued\nslotchange reconciliation, after synchronous DOM moves have settled; the\nerror cannot be caught around the mutation itself. The same invalid initial\nassignment is reported synchronously from `firstUpdated` and rejects the\ncomponent's first `updateComplete` promise.\n\n```html\n<style>\nesp-box.demo-form::part(box) {\n display: grid;\n gap: var(--esp-size-tiny-to-small);\n}\n</style>\n<esp-box class=\"demo-form\">\n <esp-form-item label=\"Form item\" autofocus>\n <esp-input></esp-input>\n </esp-form-item>\n <esp-button label=\"Form Things\"></esp-button>\n</esp-box>\n```\n\n### Errors and assistive technology\n\nWhile an error shows — from `error`, the bound `errorPool`, or the\ncontrol's own validation — the control's real focus target carries\n`aria-invalid=\"true\"` and the error joins its accessible description,\nafter the hint. A new or changed error is announced once through a polite\nstatus message; re-renders and clearing the error announce nothing. A\nwarning joins the description the same way, without `aria-invalid` or an\nannouncement. None of this changes how the error looks, and the required\nmarker is unchanged.\n\nChoice controls (the radio-button and checkbox groups, `esp-checkbox`,\n`esp-switch`, and the pickers) clear an error from `error` or `errorPool`\nas soon as the visitor changes them, because the change answers the\nquestion the error asked. Text controls, the date picker, and the slider\nkeep it until its owner changes it.\n\n```html\n<esp-box class=\"demo-form\">\n <esp-form-item\n label=\"Employer ID\"\n hint=\"Use the format XX-XXXXXXX.\"\n error=\"Enter the ID in the format XX-XXXXXXX.\">\n <esp-input value=\"12345\"></esp-input>\n </esp-form-item>\n</esp-box>\n```\n\nA screen reader on that field hears its name, \"Employer ID\", that it is\ninvalid, and the description \"Use the format XX-XXXXXXX. Enter the ID in\nthe format XX-XXXXXXX.\" Plain native fields get the same treatment\nthrough IDREFs in their own tree; Espalier controls get it on the element\ninside their shadow root that takes focus.",
29021
28978
  "name": "EspalierFormItem",
29022
28979
  "cssProperties": [
29023
28980
  {
@@ -29146,6 +29103,39 @@
29146
29103
  "privacy": "private",
29147
29104
  "readonly": true
29148
29105
  },
29106
+ {
29107
+ "kind": "field",
29108
+ "name": "handleFieldValueChanged",
29109
+ "privacy": "private",
29110
+ "readonly": true,
29111
+ "description": "A visitor's change to a choice control answers the question an error\nfrom `error` or `errorPool` asked, and a radio choice can never be taken\nback, so the error clears at once. A native error is left alone: the\ncontrol revalidated before announcing the change, so it is already\ncurrent (an emptied required checkbox group raises its error again)."
29112
+ },
29113
+ {
29114
+ "kind": "field",
29115
+ "name": "errorAssignments",
29116
+ "type": {
29117
+ "text": "number"
29118
+ },
29119
+ "privacy": "private",
29120
+ "default": "0",
29121
+ "description": "Consumer writes to `error` and `errorPool` that leave an error, counted."
29122
+ },
29123
+ {
29124
+ "kind": "field",
29125
+ "name": "errorAssignmentsAtChange",
29126
+ "type": {
29127
+ "text": "number"
29128
+ },
29129
+ "privacy": "private",
29130
+ "default": "-1",
29131
+ "description": "The count when the field's change event reached this item, capturing."
29132
+ },
29133
+ {
29134
+ "kind": "field",
29135
+ "name": "captureFieldValueChanged",
29136
+ "privacy": "private",
29137
+ "readonly": true
29138
+ },
29149
29139
  {
29150
29140
  "kind": "field",
29151
29141
  "name": "handleFieldInvalid",
@@ -29253,6 +29243,53 @@
29253
29243
  "default": "[]",
29254
29244
  "description": "The describedby tokens this component added on the last sync."
29255
29245
  },
29246
+ {
29247
+ "kind": "field",
29248
+ "name": "messageId",
29249
+ "privacy": "private",
29250
+ "readonly": true,
29251
+ "default": "`esp-form-item-message-${++messageIdCounter}`",
29252
+ "description": "A hidden light-DOM span carrying the visible error or warning text, so a\nplain native field can reference it the way it references the hint. It\nis assigned to a visually hidden slot so it stays in the flat tree, and\nis `hidden` itself, so it never paints or reads twice. Espalier controls\nreceive the same text through `setFormItemDescription` instead."
29253
+ },
29254
+ {
29255
+ "kind": "field",
29256
+ "name": "managedMessageEl",
29257
+ "type": {
29258
+ "text": "HTMLSpanElement | null"
29259
+ },
29260
+ "privacy": "private",
29261
+ "default": "null"
29262
+ },
29263
+ {
29264
+ "kind": "field",
29265
+ "name": "invalidField",
29266
+ "type": {
29267
+ "text": "HTMLElement | null"
29268
+ },
29269
+ "privacy": "private",
29270
+ "default": "null",
29271
+ "description": "The plain field this item set `aria-invalid` on, if any."
29272
+ },
29273
+ {
29274
+ "kind": "field",
29275
+ "name": "announcement",
29276
+ "type": {
29277
+ "text": "string"
29278
+ },
29279
+ "privacy": "private",
29280
+ "default": "\"\"",
29281
+ "description": "Text of the polite status region. Set when an error appears or changes,\nemptied when it clears, and untouched by unrelated re-renders, so each\nnew error is announced exactly once."
29282
+ },
29283
+ {
29284
+ "kind": "field",
29285
+ "name": "silentRestoreMessage",
29286
+ "type": {
29287
+ "text": "string | null"
29288
+ },
29289
+ "privacy": "private",
29290
+ "default": "null",
29291
+ "description": "The stashed native message being restored after a same-task move. The\nstash clears the error and the restore sets it again, which would\notherwise read as a new error and be announced a second time. Keyed on\nthe message, not the path: a control's own revalidation may report it\nback before the stash restore runs."
29292
+ },
29256
29293
  {
29257
29294
  "kind": "field",
29258
29295
  "name": "hintContentObserver",
@@ -29565,6 +29602,68 @@
29565
29602
  },
29566
29603
  "description": "Mirror the hint onto the slotted field as its accessible\ndescription, preserving any consumer-set `aria-describedby`\ntokens (the same merge `esp-tooltip` performs on its trigger)."
29567
29604
  },
29605
+ {
29606
+ "kind": "field",
29607
+ "name": "message",
29608
+ "type": {
29609
+ "text": "string"
29610
+ },
29611
+ "privacy": "private",
29612
+ "description": "The visible message: the error, or the warning when there is no error.",
29613
+ "readonly": true
29614
+ },
29615
+ {
29616
+ "kind": "method",
29617
+ "name": "fieldDescriptionText",
29618
+ "privacy": "private",
29619
+ "return": {
29620
+ "type": {
29621
+ "text": "string | null"
29622
+ }
29623
+ },
29624
+ "description": "The hint followed by the visible error or warning, for a control's own mirror."
29625
+ },
29626
+ {
29627
+ "kind": "method",
29628
+ "name": "syncManagedMessage",
29629
+ "privacy": "private",
29630
+ "return": {
29631
+ "type": {
29632
+ "text": "string[]"
29633
+ }
29634
+ },
29635
+ "description": "Keep the hidden light-DOM message span current and return its id while\nthere is a message to describe."
29636
+ },
29637
+ {
29638
+ "kind": "method",
29639
+ "name": "syncFieldInvalid",
29640
+ "privacy": "private",
29641
+ "return": {
29642
+ "type": {
29643
+ "text": "void"
29644
+ }
29645
+ },
29646
+ "description": "Mark the field invalid while an error shows. An Espalier control applies\nit to its own focus target through the handshake; a plain field gets the\nattribute directly, unless its author already set one."
29647
+ },
29648
+ {
29649
+ "kind": "method",
29650
+ "name": "removeFieldInvalid",
29651
+ "privacy": "private",
29652
+ "return": {
29653
+ "type": {
29654
+ "text": "void"
29655
+ }
29656
+ },
29657
+ "parameters": [
29658
+ {
29659
+ "name": "field",
29660
+ "type": {
29661
+ "text": "HTMLElement"
29662
+ }
29663
+ }
29664
+ ],
29665
+ "description": "Remove only the invalid state this form item applied."
29666
+ },
29568
29667
  {
29569
29668
  "kind": "method",
29570
29669
  "name": "removeFieldDescription",
@@ -36630,24 +36729,6 @@
36630
36729
  "kind": "javascript-module",
36631
36730
  "path": "dist/image/esp-image.js",
36632
36731
  "declarations": [
36633
- {
36634
- "kind": "function",
36635
- "name": "parseImageRatio",
36636
- "return": {
36637
- "type": {
36638
- "text": "string | null"
36639
- }
36640
- },
36641
- "parameters": [
36642
- {
36643
- "name": "value",
36644
- "type": {
36645
- "text": "string"
36646
- }
36647
- }
36648
- ],
36649
- "description": "Resolve a supported CSS aspect-ratio string, or null when it is invalid."
36650
- },
36651
36732
  {
36652
36733
  "kind": "class",
36653
36734
  "description": "A responsive image with a native `<picture>` and an optional\nfocal-point-aware banner mode.\n\n`esp-image` accepts two authoring shapes:\n\n1. **Project a light-DOM `<picture>`** — the recommended form for published /\n server-rendered markup. Provide a real `<picture>` (or a bare `<img>`) as\n a child and `esp-image` projects it through a `<slot>`, adding only the\n layout box, border, and lightbox wiring. Because the image lives in the\n served HTML it is discoverable by the preload scanner and visible to\n crawlers, and it is fetched exactly once.\n2. **Declare [esp-image-option](/components/image/image-option) children** —\n the editor / fallback form. `esp-image` builds a shadow `<picture>` and\n `srcset` from them. Use `local-image` when an editor needs a blob preview.\n\nSource precedence is `local-image` → a projected `<picture>` / `<img>` →\n`esp-image-option` children. In every mode the host reserves its layout box\nup front from `original-width` / `original-height`, when supplied.\n\n```html\n<!-- Projected native <picture> — published markup, crawlable + preloadable -->\n<esp-image original-width=\"1200\" original-height=\"800\" caption=\"A scenic mountain lake\">\n <picture>\n <source\n srcset=\"https://picsum.photos/600/400 400w, https://picsum.photos/900/600 800w, https://picsum.photos/1200/800 1200w\"\n sizes=\"100vw\"\n />\n <img src=\"https://picsum.photos/1200/800\" alt=\"A scenic mountain lake\" />\n </picture>\n</esp-image>\n```\n\n```html\n<!-- esp-image-option children — editor / fallback form -->\n<esp-image\n original-width=\"1200\"\n original-height=\"800\"\n low-res=\"https://picsum.photos/300/200\"\n caption=\"A scenic mountain lake\"\n sizes=\"100vw\"\n>\n <esp-image-option width=\"400\" url=\"https://picsum.photos/600/400\"></esp-image-option>\n <esp-image-option width=\"800\" url=\"https://picsum.photos/900/600\"></esp-image-option>\n <esp-image-option width=\"1200\" url=\"https://picsum.photos/1200/800\"></esp-image-option>\n</esp-image>\n```\n\nOnce upgraded, the component measures its own width and updates `sizes` so a\ncard or gallery column does not over-fetch. The `sizes` attribute covers the\nserver-rendered initial fetch and the brief window before measurement:\n\n```html\n<esp-image\n original-width=\"1200\"\n original-height=\"800\"\n low-res=\"https://picsum.photos/300/200\"\n sizes=\"(max-width: 600px) 100vw, 320px\"\n style=\"max-width: 320px;\"\n>\n <esp-image-option width=\"320\" url=\"https://picsum.photos/640/427\"></esp-image-option>\n <esp-image-option width=\"640\" url=\"https://picsum.photos/960/640\"></esp-image-option>\n</esp-image>\n```\n\nFor a long projected lazy-image list, `defer-offscreen` keeps the served\n`sizes` hint until an image approaches the viewport. This avoids replacing a\nsmall fallback selection with a larger candidate before the image can be\nseen. Eager projected images and every non-projected source keep the normal\nimmediate measured-size behavior.\n\nBanner mode gives all three source modes the same focal position, scrim,\ntexture, and semantic overlay stack. `ratio` applies at wide widths and\n`compact-ratio` below the component-relative compact threshold.\n\n```html\n<esp-image\n banner\n ratio=\"3/1\"\n compact-ratio=\"3/2\"\n focus=\"0.65 0.4\"\n scrim=\"auto\"\n scrim-strength=\"medium\"\n content-position=\"bottom-start\"\n>\n <img src=\"/assets/focus-picker-unsplash.jpg\" alt=\"A woman standing in a sunlit field with her back to the camera\" />\n <h2 slot=\"overlay\">Grow somewhere wonderful</h2>\n</esp-image>\n```\n\n`banner-scheme` sets the banner's polarity and defaults to the active\nscheme: a light page lightens the image behind dark text, a dark page\ndarkens it behind light text. Fix either polarity explicitly when a banner\nmust not follow the page scheme:\n\n```html\n<esp-image banner ratio=\"3/1\" banner-scheme=\"light\" focus=\"0.65 0.4\">\n <img src=\"/assets/focus-picker-unsplash.jpg\" alt=\"A woman standing in a sunlit field with her back to the camera\" />\n <h2 slot=\"overlay\">Light banner: dark ink on a lifted image</h2>\n</esp-image>\n<esp-image banner ratio=\"3/1\" banner-scheme=\"dark\" focus=\"0.65 0.4\">\n <img src=\"/assets/focus-picker-unsplash.jpg\" alt=\"A woman standing in a sunlit field with her back to the camera\" />\n <h2 slot=\"overlay\">Dark banner: light ink on a deepened image</h2>\n</esp-image>\n```\n\n### The scrim follows its content\n\nA directional scrim holds full strength over at least the first 20% of the\nframe, and further when the overlay content reaches past it, so every line\nof a long heading on a short card cover sits on the full-strength scrim. A\nradial scrim grows an opaque core that covers centered content. Overlays\nthat already fit the 20% band render exactly as before.\n\n```html\n<div style=\"max-width: 24rem\">\n <esp-image banner ratio=\"3/1\" compact-ratio=\"16/9\" focus=\"0.65 0.4\">\n <img src=\"/assets/focus-picker-unsplash.jpg\" alt=\"A woman standing in a sunlit field with her back to the camera\" />\n <h3 slot=\"overlay\">A three-line title stays on the scrim however short the cover gets</h3>\n </esp-image>\n</div>\n```\n\n### Meeting an adjoining band\n\nA directional scrim is capped by `scrim-strength`, so even `strong` leaves\nroughly a quarter of the photograph showing at the very edge it is anchored\nto. `scrim-edge=\"flush\"` paints that edge at full alpha and relaxes to the\nordinary strength over `--esp-image-scrim-edge-depth` (12% of the gradient\naxis by default), so a banner dissolves into a section band directly beneath\nit with no seam. Above that band the scrim is unchanged — `flush` is an edge\ntreatment, not a heavier veil over the subject.\n\nThe join is exact because both sides paint the same token: when\n`banner-scheme` resolves to the page scheme the scrim ink *is*\n`--esp-color-background`, which is what [esp-section](/components/section)\npaints its band with. Put the banner in a section with no padding, directly\nabove a section in the same context:\n\n```html\n<esp-section style=\"--esp-section-max-width: none; --esp-section-padding-inline: 0; --esp-section-padding-block: 0;\">\n <esp-image banner ratio=\"3/1\" scrim=\"bottom\" scrim-strength=\"strong\" scrim-edge=\"flush\" focus=\"0.65 0.4\">\n <img src=\"/assets/focus-picker-unsplash.jpg\" alt=\"A woman standing in a sunlit field with her back to the camera\" />\n <h2 slot=\"overlay\">Grow somewhere wonderful</h2>\n </esp-image>\n</esp-section>\n<esp-section>\n <p>The banner's bottom edge and this band are the same color, so the photograph fades into the page rather than stopping at a line.</p>\n</esp-section>\n```\n\n`flush` is inert unless the *resolved* scrim is directional — an `auto`\nscrim that resolves to `top` or `bottom` picks it up, while `none`, `flat`,\nand `radial` ignore it, having no anchored edge for it to close.\n\nIt is its own attribute rather than a fourth `scrim-strength` because the two\nare independent: `scrim-strength` is how much veil sits over the subject,\n`scrim-edge` is whether the anchored edge completes. A light veil with a\nclosed edge is exactly the composition this exists for, and a strength step\ncould not express it.\n\nA seamless tile can bleed across the join too — any `background-image` value,\na tile the application serves, a data URI, or a gradient. Hand the edge the\nsame background the adjoining section paints and it tiles up over the\nphotograph, fading out over the same band:\n\n```html\n<esp-image\n banner\n ratio=\"3/1\"\n scrim=\"bottom\"\n scrim-edge=\"flush\"\n style=\"--esp-image-scrim-edge-image: repeating-linear-gradient(45deg, oklch(0.55 0.08 255 / 0.18) 0 6px, transparent 6px 18px); --esp-image-scrim-edge-size: 18px 18px; --esp-image-scrim-edge-depth: 22%;\"\n>\n <img src=\"/assets/focus-picker-unsplash.jpg\" alt=\"A woman standing in a sunlit field with her back to the camera\" />\n</esp-image>\n```\n\nThe component anchors its own side of the tile to the scrim's edge, so the\nbanner's last tile row ends exactly at the join. Continuity into the section\nbelow is the consumer's half of the contract: the same `background-size`,\nand a `top`-anchored `background-position` on the adjoining band.\n\nEvery procedural preset supports `fine`, `medium`, and `coarse` scales.\n\n### Dots\n\nA uniform tiled ink screen (shown at `fine`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"dots\" texture-scale=\"fine\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Halftone\n\nThe dot screen ramping toward the content edge (shown at `medium`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"halftone\" texture-scale=\"medium\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Paper\n\nSoft fractal-noise mottle, like printed stock (shown at `coarse`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"paper\" texture-scale=\"coarse\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Grain\n\nHigh-frequency film grain that also kills banding (shown at `fine`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"grain\" texture-scale=\"fine\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Grunge\n\nThresholded turbulence, a distressed ink wash (shown at `medium`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"grunge\" texture-scale=\"medium\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Scanlines\n\nHairline rules along the block axis (shown at `coarse`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"scanlines\" texture-scale=\"coarse\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Duotone\n\nThe photo mapped onto two theme-derived hues:\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"duotone\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Consumer texture tokens\n\nAny `background-image` value — a tile the application serves, a data URI,\nor a gradient — can be wired directly through the public consumer-image\ntoken path (the optional `@taprootio/espalier/textures/banner-textures.css`\nstylesheet ships `.esp-texture-paper`/`-grain`/`-grunge` classes with the\ntiles inlined as data URIs, so nothing needs asset configuration):\n\n```html\n<esp-image\n banner\n ratio=\"3/1\"\n style=\"--esp-image-texture-image: repeating-linear-gradient(45deg, oklch(0.1 0 0 / 0.2) 0 2px, transparent 2px 7px); --esp-image-texture-blend-mode: multiply;\"\n>\n <img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" />\n</esp-image>\n```\n\n### Registered textures\n\nWiring those tokens on every banner is verbose and not reusable. The\napplication's texture vocabulary lives on [esp-root](/components/root):\nregister the look once — `registerImageTexture(name, definition)` from\nthe module, or the root's `registerTexture` static in script-only\ncontexts — and any banner under the root selects it by name. Live\nbanners re-render on registration, so markup order and registration\norder never race, and the root's `registeredTextures()` enumerates the\npresets for an editor's texture picker:\n\n```html\n<esp-image id=\"registered-texture-banner\" banner ratio=\"3/1\" texture=\"pressed-paper\">\n <img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" />\n</esp-image>\n<script>\n const banner = findById(\"registered-texture-banner\");\n const root = banner.closest(\"esp-root\");\n root.constructor.registerTexture(\"pressed-paper\", {\n image:\n \"repeating-linear-gradient(-45deg, oklch(1 0 0 / 0.3) 0 3px, transparent 3px 9px)\",\n blendMode: \"soft-light\",\n opacity: 0.9,\n });\n</script>\n```",
@@ -38560,6 +38641,40 @@
38560
38641
  }
38561
38642
  ]
38562
38643
  },
38644
+ {
38645
+ "kind": "javascript-module",
38646
+ "path": "dist/image/image-ratio.js",
38647
+ "declarations": [
38648
+ {
38649
+ "kind": "function",
38650
+ "name": "parseImageRatio",
38651
+ "return": {
38652
+ "type": {
38653
+ "text": "string | null"
38654
+ }
38655
+ },
38656
+ "parameters": [
38657
+ {
38658
+ "name": "value",
38659
+ "type": {
38660
+ "text": "string | null | undefined"
38661
+ }
38662
+ }
38663
+ ],
38664
+ "description": "Resolve a supported CSS aspect-ratio string, or null when it is invalid.\nA removed attribute arrives as `null` and is invalid like an empty one."
38665
+ }
38666
+ ],
38667
+ "exports": [
38668
+ {
38669
+ "kind": "js",
38670
+ "name": "parseImageRatio",
38671
+ "declaration": {
38672
+ "name": "parseImageRatio",
38673
+ "module": "dist/image/image-ratio.js"
38674
+ }
38675
+ }
38676
+ ]
38677
+ },
38563
38678
  {
38564
38679
  "kind": "javascript-module",
38565
38680
  "path": "dist/image/image-texture-filters.js",
@@ -38823,21 +38938,9 @@
38823
38938
  },
38824
38939
  {
38825
38940
  "kind": "field",
38826
- "name": "formItemDescription",
38827
- "type": {
38828
- "text": "string | null"
38829
- },
38830
- "privacy": "private",
38831
- "default": "null"
38832
- },
38833
- {
38834
- "kind": "field",
38835
- "name": "formItemLabel",
38836
- "type": {
38837
- "text": "string | null"
38838
- },
38941
+ "name": "formItemRelay",
38839
38942
  "privacy": "private",
38840
- "default": "null"
38943
+ "default": "new FormItemRelay(() => this.picker as EspalierFormField | undefined)"
38841
38944
  },
38842
38945
  {
38843
38946
  "kind": "field",
@@ -39115,16 +39218,6 @@
39115
39218
  }
39116
39219
  ]
39117
39220
  },
39118
- {
39119
- "kind": "method",
39120
- "name": "syncFormItemDescription",
39121
- "privacy": "private",
39122
- "return": {
39123
- "type": {
39124
- "text": "void"
39125
- }
39126
- }
39127
- },
39128
39221
  {
39129
39222
  "kind": "field",
39130
39223
  "name": "handlePickerValueChanged",
@@ -43900,7 +43993,7 @@
43900
43993
  "declarations": [
43901
43994
  {
43902
43995
  "kind": "class",
43903
- "description": "Wraps a standard HTML input field,\nproviding value binding, input validation, and programmatic\nfocus for Espalier forms and UIs. It supports several input\ntypes, and has an `icon` attribute plus a slotted icon escape\nhatch.\n\n```html\n<esp-box>\n <h2>esp-input example</h2>\n <p>Hello from the input!</p>\n <esp-input\n value=\"Hello from the input!\"\n icon=\"user-circle\"\n icon-label=\"Open user details\">\n </esp-input>\n</esp-box>\n<script>\n const theInput = findByTagName(\"esp-input\")[0];\n const theP = findByTagName(\"p\")[0];\n theInput.addEventListener(\"esp-value-changed\", (ev) => {\n theP.innerText = ev.detail.length ? ev.detail : \"The input is empty...\";\n });\n</script>\n```\n\nInputs pick up zone theming from an ancestor's `context` attribute —\nsee the [theming guide](/espalier-guides/) for theme-defined contexts:\n\n```html\n<esp-box context=\"quiet\">\n <esp-input label=\"Inside the quiet zone\"></esp-input>\n</esp-box>\n```\n\n### Email validation\n\nSet `input-type=\"email\"` to get built-in email validation. You\ncan customize the error message with `validation-message`:\n\n```html\n<esp-box>\n <esp-form-item label=\"Email address\">\n <esp-input name=\"email\" input-type=\"email\" required\n validation-message=\"Please enter a valid work email.\">\n </esp-input>\n </esp-form-item>\n</esp-box>\n```\n\n### Number input with min/max\n\n```html\n<esp-box>\n <esp-form-item label=\"Quantity (1-100)\">\n <esp-input name=\"qty\" input-type=\"number\"\n min=\"1\" max=\"100\" step=\"1\" required>\n </esp-input>\n </esp-form-item>\n</esp-box>\n```\n\n### Telephone with locale-aware formatting\n\nThe `tel` type uses phone localities to parse and format phone\nnumbers on blur. The value is submitted as E.164 (e.g.\n`\"+15551234567\"`). Use `tel-localities` to specify which regions\nare accepted (space-separated region codes); when omitted, a\nsensible default set of 10 common regions is used.\n\n```html\n<esp-box>\n <esp-form-item label=\"Phone number\">\n <esp-input name=\"phone\" input-type=\"tel\"\n tel-localities=\"US CA GB\">\n </esp-input>\n </esp-form-item>\n</esp-box>\n```\n\n### Date input with locale-aware mask\n\nThe `date` type displays a placeholder based on the user's\nlocale (e.g. \"MM/DD/YYYY\"). The user types freely and the\ninput parses the date on blur, submitting the value as\nISO 8601 (`2025-01-15`).\n\n```html\n<esp-box>\n <esp-form-item label=\"Start date\">\n <esp-input name=\"startDate\" input-type=\"date\" required>\n </esp-input>\n </esp-form-item>\n</esp-box>\n```",
43996
+ "description": "Wraps a standard HTML input field,\nproviding value binding, input validation, and programmatic\nfocus for Espalier forms and UIs. It supports several input\ntypes, and has an `icon` attribute plus a slotted icon escape\nhatch.\n\n```html\n<esp-box>\n <h2>esp-input example</h2>\n <p>Hello from the input!</p>\n <esp-input\n value=\"Hello from the input!\"\n icon=\"user-circle\"\n icon-label=\"Open user details\">\n </esp-input>\n</esp-box>\n<script>\n const theInput = findByTagName(\"esp-input\")[0];\n const theP = findByTagName(\"p\")[0];\n theInput.addEventListener(\"esp-value-changed\", (ev) => {\n theP.innerText = ev.detail.length ? ev.detail : \"The input is empty...\";\n });\n</script>\n```\n\nInputs pick up zone theming from an ancestor's `context` attribute —\nsee the [theming guide](/espalier-guides/) for theme-defined contexts:\n\n```html\n<esp-box context=\"quiet\">\n <esp-input label=\"Inside the quiet zone\"></esp-input>\n</esp-box>\n```\n\n### Email validation\n\nSet `input-type=\"email\"` to get built-in email validation. You\ncan customize the error message with `validation-message`:\n\n```html\n<esp-box>\n <esp-form-item label=\"Email address\">\n <esp-input name=\"email\" input-type=\"email\" required\n validation-message=\"Please enter a valid work email.\">\n </esp-input>\n </esp-form-item>\n</esp-box>\n```\n\n### Number input with min/max\n\n```html\n<esp-box>\n <esp-form-item label=\"Quantity (1-100)\">\n <esp-input name=\"qty\" input-type=\"number\"\n min=\"1\" max=\"100\" step=\"1\" required>\n </esp-input>\n </esp-form-item>\n</esp-box>\n```\n\n### Decimal numbers\n\nA number input's native step defaults to `1`, which rejects every decimal.\nSet `step=\"any\"` to accept any decimal, or a numeric step such as `0.01`\nto limit precision.\n\n```html\n<esp-box>\n <esp-form-item label=\"Weight (kg)\" hint=\"Any decimal, such as 2.75.\">\n <esp-input input-type=\"number\" step=\"any\" min=\"0\"></esp-input>\n </esp-form-item>\n <esp-form-item label=\"Price\" hint=\"To the cent.\">\n <esp-input input-type=\"number\" step=\"0.01\" min=\"0\"></esp-input>\n </esp-form-item>\n</esp-box>\n```\n\n### Telephone with locale-aware formatting\n\nThe `tel` type uses phone localities to parse and format phone\nnumbers on blur. The value is submitted as E.164 (e.g.\n`\"+15551234567\"`). Use `tel-localities` to specify which regions\nare accepted (space-separated region codes); when omitted, a\nsensible default set of 10 common regions is used.\n\n```html\n<esp-box>\n <esp-form-item label=\"Phone number\">\n <esp-input name=\"phone\" input-type=\"tel\"\n tel-localities=\"US CA GB\">\n </esp-input>\n </esp-form-item>\n</esp-box>\n```\n\n### Date input with locale-aware mask\n\nThe `date` type displays a placeholder based on the user's\nlocale (e.g. \"MM/DD/YYYY\"). The user types freely and the\ninput parses the date on blur, submitting the value as\nISO 8601 (`2025-01-15`).\n\n```html\n<esp-box>\n <esp-form-item label=\"Start date\">\n <esp-input name=\"startDate\" input-type=\"date\" required>\n </esp-input>\n </esp-form-item>\n</esp-box>\n```",
43904
43997
  "name": "EspalierInput",
43905
43998
  "slots": [
43906
43999
  {
@@ -43938,7 +44031,7 @@
43938
44031
  "kind": "field",
43939
44032
  "name": "formCtrl",
43940
44033
  "privacy": "private",
43941
- "default": "new FormFieldController({ host: this, internals: this.internals, getFormValue: () => this.value || null, getValidity: () => this._getValidity(), getValidationAnchor: (): HTMLElement => this.theInput.value ?? this, onReset: () => { this.value = \"\"; this._dateDisplayText = \"\"; this._dateManaged = false; this._telDisplayText = \"\"; this._telManaged = false; this._hasVisibleError = false; }, onRestore: (state: string) => { this.value = state; if (this.inputType === \"date\" && state) { this._restoreDateDisplay(state); } if (this.inputType === \"tel\" && state) { this._restoreTelDisplay(state); } }, onDisabled: (isDisabled: boolean) => { this.disabled = isDisabled; }, })"
44034
+ "default": "new FormFieldController({ host: this, internals: this.internals, getFormValue: () => this.value || null, getValidity: () => this._getValidity(), getValidationAnchor: (): HTMLElement => this.theInput.value ?? this, onReset: () => { this.value = \"\"; this._dateDisplayText = \"\"; this._telDisplayText = \"\"; this._hasVisibleError = false; }, onRestore: (state: string) => { this.value = state; if (this.inputType === \"date\" && state) { this._restoreDateDisplay(state); } if (this.inputType === \"tel\" && state) { this._restoreTelDisplay(state); } }, onDisabled: (isDisabled: boolean) => { this.disabled = isDisabled; }, })"
43942
44035
  },
43943
44036
  {
43944
44037
  "kind": "field",
@@ -43975,12 +44068,13 @@
43975
44068
  },
43976
44069
  {
43977
44070
  "kind": "field",
43978
- "name": "_dateManaged",
44071
+ "name": "_ownValue",
43979
44072
  "type": {
43980
- "text": "boolean"
44073
+ "text": "string | null"
43981
44074
  },
43982
44075
  "privacy": "private",
43983
- "default": "false"
44076
+ "default": "null",
44077
+ "description": "The value this input last produced itself, from typing or blur\nfinalization. A date or tel `value` that differs from it came from code,\nso its display text is recomputed — even when the host sets it from the\nvery `esp-value-changed` the blur emitted, before that update runs."
43984
44078
  },
43985
44079
  {
43986
44080
  "kind": "method",
@@ -44010,15 +44104,6 @@
44010
44104
  "privacy": "private",
44011
44105
  "default": "\"\""
44012
44106
  },
44013
- {
44014
- "kind": "field",
44015
- "name": "_telManaged",
44016
- "type": {
44017
- "text": "boolean"
44018
- },
44019
- "privacy": "private",
44020
- "default": "false"
44021
- },
44022
44107
  {
44023
44108
  "kind": "method",
44024
44109
  "name": "_getPhoneLocalities",
@@ -44229,10 +44314,10 @@
44229
44314
  "kind": "field",
44230
44315
  "name": "step",
44231
44316
  "type": {
44232
- "text": "number | undefined"
44317
+ "text": "number | \"any\" | undefined"
44233
44318
  },
44234
44319
  "privacy": "public",
44235
- "description": "Step value for `input-type=\"number\"`.",
44320
+ "description": "Step value for `input-type=\"number\"`. `\"any\"` (as an attribute or the\nproperty) allows any decimal: without it the native default step of `1`\nflags every decimal as a step mismatch. Numeric steps are passed through\nunchanged.\n\n```html\n<esp-form-item label=\"Weight (kg)\">\n <esp-input input-type=\"number\" step=\"any\" min=\"0\"></esp-input>\n</esp-form-item>\n```",
44236
44321
  "attribute": "step"
44237
44322
  },
44238
44323
  {
@@ -45467,9 +45552,9 @@
45467
45552
  {
45468
45553
  "name": "step",
45469
45554
  "type": {
45470
- "text": "number | undefined"
45555
+ "text": "number | \"any\" | undefined"
45471
45556
  },
45472
- "description": "Step value for `input-type=\"number\"`.",
45557
+ "description": "Step value for `input-type=\"number\"`. `\"any\"` (as an attribute or the\nproperty) allows any decimal: without it the native default step of `1`\nflags every decimal as a step mismatch. Numeric steps are passed through\nunchanged.\n\n```html\n<esp-form-item label=\"Weight (kg)\">\n <esp-input input-type=\"number\" step=\"any\" min=\"0\"></esp-input>\n</esp-form-item>\n```",
45473
45558
  "fieldName": "step"
45474
45559
  },
45475
45560
  {
@@ -50676,16 +50761,6 @@
50676
50761
  "description": "Compatibility hook used by `esp-header` when inline navigation\nbecomes too wide. Prefer `mode`/`overflow` in application code.",
50677
50762
  "attribute": "sliding"
50678
50763
  },
50679
- {
50680
- "kind": "field",
50681
- "name": "collapsed",
50682
- "type": {
50683
- "text": "boolean"
50684
- },
50685
- "privacy": "public",
50686
- "description": "Compatibility alias for the old vertical menu drawer state.",
50687
- "attribute": "collapsed"
50688
- },
50689
50764
  {
50690
50765
  "kind": "field",
50691
50766
  "name": "drawerOpen",
@@ -50831,17 +50906,6 @@
50831
50906
  ],
50832
50907
  "description": "Toggle the drawer overlay open or closed."
50833
50908
  },
50834
- {
50835
- "kind": "method",
50836
- "name": "toggleOpened",
50837
- "privacy": "public",
50838
- "return": {
50839
- "type": {
50840
- "text": "void"
50841
- }
50842
- },
50843
- "description": "Compatibility alias used by the old header menu integration."
50844
- },
50845
50909
  {
50846
50910
  "kind": "field",
50847
50911
  "name": "isDrawerOpen",
@@ -52088,14 +52152,6 @@
52088
52152
  "description": "Compatibility hook used by `esp-header` when inline navigation\nbecomes too wide. Prefer `mode`/`overflow` in application code.",
52089
52153
  "fieldName": "sliding"
52090
52154
  },
52091
- {
52092
- "name": "collapsed",
52093
- "type": {
52094
- "text": "boolean"
52095
- },
52096
- "description": "Compatibility alias for the old vertical menu drawer state.",
52097
- "fieldName": "collapsed"
52098
- },
52099
52155
  {
52100
52156
  "name": "scheme",
52101
52157
  "type": {
@@ -53042,7 +53098,7 @@
53042
53098
  "declarations": [
53043
53099
  {
53044
53100
  "kind": "class",
53045
- "description": "A Lit ReactiveController that implements bi-directional sticky behavior\nfor sidebar elements. Short sidebars stick below the header. Tall sidebars\n(taller than viewport) stick to the bottom when scrolling down, stick to\nthe top when scrolling up, and scroll naturally when direction reverses.\n\nUses only `position: sticky` (never `absolute`) so the element always\nremains in normal flow, preserving `min-content` grid column widths.\n\nWhen the header is not fixed (no `fixed-menus` attribute on `esp-page`),\nthe effective top offset tracks the header's visible bottom edge. As the\nheader scrolls out of view, the offset decreases from `headerHeight` to\n`0`, so the sidebar smoothly rises to the top of the viewport with no gap.",
53101
+ "description": "A Lit ReactiveController that implements bi-directional sticky behavior\nfor sidebar elements. Short sidebars stick below the header. Tall sidebars\n(taller than viewport) stick to the bottom when scrolling down, stick to\nthe top when scrolling up, and scroll naturally when direction reverses.\n\nUses only `position: sticky` (never `absolute`) so the element always\nremains in normal flow, preserving `min-content` grid column widths.\n\nWhen the header is not fixed (`esp-page`'s `header-position` is not `fixed`),\nthe effective top offset tracks the header's visible bottom edge. As the\nheader scrolls out of view, the offset decreases from `headerHeight` to\n`0`, so the sidebar smoothly rises to the top of the viewport with no gap.",
53046
53102
  "name": "BiDirectionalStickyController",
53047
53103
  "members": [
53048
53104
  {
@@ -53226,7 +53282,7 @@
53226
53282
  "type": {
53227
53283
  "text": "array"
53228
53284
  },
53229
- "default": "[ ...EspalierElementBase.styles, css` :host { --_esp-page-resolved-max-width: var( --esp-page-main-max-width, var(--esp-page-max-width, 1536px) ); --_esp-page-main-min-width: var( --esp-page-main-min-width, var(--esp-page-preview-min-main-width, 30rem) ); --_esp-page-preview-resolved-min-width: var( --esp-page-preview-width, var(--esp-page-preview-min-width, 22.5rem) ); --_esp-page-preview-resolved-max-width: var( --esp-page-preview-width, var(--esp-page-preview-max-width, 48rem) ); --_esp-page-flyout-resolved-min-width: var( --esp-page-flyout-width, var(--esp-page-flyout-min-width, 20rem) ); --_esp-page-flyout-resolved-max-width: var( --esp-page-flyout-width, var(--esp-page-flyout-max-width, 30rem) ); --_esp-page-preview-used-width: 0px; --_esp-page-flyout-used-width: 0px; --_esp-page-workspace-track: minmax( calc(var(--_esp-page-flyout-min) + var(--_esp-page-preview-min)), calc(var(--_esp-page-preview-used-width) + var(--_esp-page-flyout-used-width)) ); /* The cap sizes the main content *well*: the main track maxes at the cap and the surplus beyond the surface becomes canvas gutters. (Contract preserved from v2.1.0.) */ --_esp-page-main-track: minmax(0, var(--_esp-page-resolved-max-width)); /* Canvas gutter weights (physical left/right in LTR). The flexible gutters claim only the space left once the main track reaches its cap, so they appear exactly when the surface exceeds the cap — at any cap, with no viewport breakpoint (ADR-011). The start default keeps no leading gutter and lets all surplus collect trailing. Empty asides collapse to zero because the header and footer regions are inline-size-contained and no longer leak their min-content into the aside tracks (see the .esp-page-top / .esp-page-footer rules). The fr weights are written as 0fr/1fr (never bare 0) so the track list stays pairwise-interpolable and the flyout open/close can animate via grid track interpolation where supported. */ --_esp-page-gutter-left: 0fr; --_esp-page-gutter-right: 1fr; /* Help and preview share an exact auxiliary workspace track before the outer trailing gutter. Opening help raises that track's safe minimum; the allocator supplies its concrete used width. */ --_esp-page-flyout-min: 0px; /* Preview starts at the surface edge before help. Reclaiming raises this minimum so the auxiliary track reserves both pane floors while the main track retains its own floor. */ --_esp-page-preview-min: 0px; /* A raised surface edge's drop shadow, cast in the trailing (inline-end) direction. The surface mirrors it onto both inline edges; a match-surface flyout casts only this trailing edge, so it lifts off the canvas while staying attached at its leading perforation (no shadow bleeding over the content it flies out from). */ --_esp-page-surface-edge-shadow: 0.75rem 0 1.5rem -0.75rem var(--esp-color-shadow); /* Resolve the surface shadow/border once (public override plus the built-in default) so the value is readable downstream — the surface layer and the match-surface flyout contract both consume it, and it stays a single source of truth. */ --_esp-page-surface-shadow: var( --esp-page-surface-shadow, -0.75rem 0 1.5rem -0.75rem var(--esp-color-shadow), var(--_esp-page-surface-edge-shadow) ); --_esp-page-surface-border: var(--esp-page-surface-border, none); --_esp-page-fixed-header-offset: var( --esp-page-fixed-header-offset, var(--esp-header-height, calc(4.5 * var(--esp-size-small))) ); display: block; } :host([kind=\"narrow\"]) { --_esp-page-resolved-max-width: var( --esp-page-main-max-width, var(--esp-page-max-width, 768px) ); } :host([align=\"center\"]) { --_esp-page-gutter-left: 1fr; --_esp-page-gutter-right: 1fr; } :host([align=\"end\"]) { --_esp-page-gutter-left: 1fr; --_esp-page-gutter-right: 0fr; } /* Full is unbounded only while the consumer has not supplied the explicit main maximum. The internal marker is synchronized from the computed public token before workspace allocation, allowing a finite full-kind authoring surface to retain normal alignment gutters. */ :host([kind=\"full\"]) { --_esp-page-resolved-max-width: var( --esp-page-main-max-width, var(--esp-page-max-width, none) ); } :host([kind=\"full\"]:not([data-main-max-configured])) { --_esp-page-main-track: 1fr; --_esp-page-gutter-left: 0fr; --_esp-page-gutter-right: 0fr; } /* Site: the marketing shell (ESP0174). The main region is always unbounded — slotted esp-section bands own their centered wells — so this is the unconfigured-full geometry, unconditionally, and align's gutter weights do not apply. Declared after the align rules so it wins on order. */ :host([kind=\"site\"]) { --_esp-page-resolved-max-width: none; --_esp-page-main-track: 1fr; --_esp-page-gutter-left: 0fr; --_esp-page-gutter-right: 0fr; } /* Flyout placement ladder (ESP0111). Preview and help occupy an exact auxiliary track between the main surface and the outer gutter: 1. spare canvas is converted into the allocated auxiliary track while main remains at its natural maximum 2. help and preview contract to their floors 3. an eligible linked navigation rail yields before the main track narrows toward its minimum 4. if the complete main + preview + help minimums do not fit, JS requests the flyout's existing modal overlay presentation 5. mobile → the media query below releases the minimum and esp-flyout itself becomes a fixed overlay drawer. The flyout-open host attribute is maintained by syncFlyoutState (Blink cannot match :has() inside :host(), so the state is mirrored instead). Declared after the align/kind rules so the open state wins. */ :host([flyout-open]) { --_esp-page-flyout-min: var(--_esp-page-flyout-resolved-min-width); } :host([preview-reclaiming]) { --_esp-page-preview-min: var(--_esp-page-preview-resolved-min-width); } /* Configured defaults and retained drag/keyboard choices participate in the same grid minima as automatic allocation. This lets a target that must reclaim main do so instead of overflowing the gutter, while the allocator still clamps private used widths before they reach Grid. */ :host([flyout-open][data-workspace-targeted]) { --_esp-page-flyout-min: var(--_esp-page-flyout-used-width); } :host([preview-reclaiming][data-workspace-targeted]) { --_esp-page-preview-min: var(--_esp-page-preview-used-width); } :host([flyout-open]), :host([preview-reclaiming]) { --_esp-page-main-track: minmax( var(--_esp-page-main-min-width), var(--_esp-page-resolved-max-width) ); } :host([kind=\"full\"]:not([data-main-max-configured])[flyout-open]), :host([kind=\"full\"]:not([data-main-max-configured])[preview-reclaiming]), :host([kind=\"site\"][flyout-open]), :host([kind=\"site\"][preview-reclaiming]) { --_esp-page-main-track: minmax(var(--_esp-page-main-min-width), 1fr); } /* Deliberate ADR-011 exception: like the esp-menu drawer (same 50em threshold), the flyout's overlay rung is a genuinely viewport-level decision — below it the flyout leaves the grid for a fixed drawer, so the gutter must release the minimum it was reserving. */ @media ${unsafeCSS(COMPACT_VIEWPORT_MEDIA_QUERY)} { :host([flyout-open]) { --_esp-page-flyout-min: 0px; } :host([preview-reclaiming]) { --_esp-page-preview-min: 0px; } :host([flyout-open]), :host([preview-reclaiming]) { --_esp-page-main-track: minmax(0, var(--_esp-page-resolved-max-width)); } :host([kind=\"full\"]:not([data-main-max-configured])[flyout-open]), :host([kind=\"full\"]:not([data-main-max-configured])[preview-reclaiming]), :host([kind=\"site\"][flyout-open]), :host([kind=\"site\"][preview-reclaiming]) { --_esp-page-main-track: 1fr; } .esp-page > .esp-page-preview-space, .esp-page > .esp-page-workspace-resize-handles { display: none; } } /* A forced mode=\"overlay\" flyout is a fixed drawer at any viewport width, so its aside needs the same above-the-header hoist the sub-50em media rule gives the auto-mode drawer — without this the desktop drawer paints beneath the header (aside z 5 vs header z 20). Keyed off the flyout-overlay-open state attribute maintained by syncFlyoutState. */ :host([flyout-overlay-open]) .esp-page > div.esp-page-flyout { z-index: var(--esp-flyout-z-index, 3000); } /* A nested page's fixed drawer still participates in this page's main stacking context. Raise that complete context while the descendant modal is open so the drawer and vellum paint above this page's header instead of trapping their close control beneath it. */ :host([descendant-flyout-overlay-open]) .esp-page > div.esp-page-main { z-index: var(--esp-flyout-z-index, 3000); overflow: visible; } /* Trigger-anchored panels belong to the same document flow as their initiator. BiDirectionalStickyController can keep updating the wrapper's inline top, but position: static deliberately makes it inert until the anchored panel closes. */ :host([flyout-anchored]:not([flyout-full-height])) .esp-page > div.esp-page-flyout > .sticky-wrapper { position: static; } /* Full-document help owns the visible block-size rather than following one field. Span header through footer so the page footer cannot push a viewport-height panel out of view at the document boundary; the sticky controller pins this presentation to the viewport top. */ :host([flyout-full-height]) .esp-page > div.esp-page-flyout { grid-row: top-start / footer-end; z-index: calc(var(--esp-page-header-z-index, 20) + 2); } /* Anchored help connects back to its trigger across a visible preview. Raise only the sized help wrapper above the preview surface; the bridge itself is non-interactive inside esp-flyout. */ :host([flyout-anchored][preview-visible]) .esp-page > div.esp-page-flyout { z-index: calc(var(--esp-page-header-z-index, 20) + 2); } slot[name=\"flyout\"]::slotted(esp-flyout) { --_esp-flyout-used-width: var(--_esp-page-flyout-used-width); --_esp-flyout-preview-bridge-width: 0px; --_esp-flyout-preview-anchor-display: none; } :host([preview-visible]) slot[name=\"flyout\"]::slotted(esp-flyout) { --_esp-flyout-preview-bridge-width: var(--_esp-page-preview-used-width); --_esp-flyout-preview-anchor-display: block; } /* Flyout surface-match contract. A slotted esp-flyout with match-surface lifts its trailing edge with the surface's edge shadow, so the panel reads as a raised peer of the content surface rather than the shadowless tear-off. The leading edge keeps its perforation (the border is not overridden) and casts no shadow, so it stays attached at the tear line instead of floating over the content it flies out from. Opt-in and page-scoped: a standalone flyout has no surface to match and is unaffected. */ slot[name=\"flyout\"]::slotted(esp-flyout[match-surface]) { --esp-flyout-shadow: var(--_esp-page-surface-edge-shadow); } /* Header alignment contract. Published on the slotted esp-header (not :host) so it never shadows an inherited or author override and only the header-slot header adopts it — see the host-token-defaults architecture rule and ADR-005. max-width is the resolved cap; lead is the fraction of surplus placed before the content (0 = start, .5 = center, 1 = end). */ slot[name=\"header\"]::slotted(esp-header) { --esp-header-content-max-width: var(--_esp-page-resolved-max-width); --esp-header-content-lead: 0; } :host([align=\"center\"]) slot[name=\"header\"]::slotted(esp-header) { --esp-header-content-lead: 0.5; } :host([align=\"end\"]) slot[name=\"header\"]::slotted(esp-header) { --esp-header-content-lead: 1; } /* full has no cap; publish 100% (not none) so the header contract calc resolves to zero surplus and leaves the header full-bleed. */ :host([kind=\"full\"]:not([data-main-max-configured])) slot[name=\"header\"]::slotted(esp-header) { --esp-header-content-max-width: 100%; } /* Footer alignment mirrors the header behavior with footer-owned contract names. The full-bleed footer paints its own background; only its internal content adopts the page's cap and lead fraction. */ slot[name=\"footer\"]::slotted(esp-footer) { --esp-footer-content-max-width: var(--_esp-page-resolved-max-width); --esp-footer-content-lead: 0; } :host([align=\"center\"]) slot[name=\"footer\"]::slotted(esp-footer) { --esp-footer-content-lead: 0.5; } :host([align=\"end\"]) slot[name=\"footer\"]::slotted(esp-footer) { --esp-footer-content-lead: 1; } :host([kind=\"full\"]:not([data-main-max-configured])) slot[name=\"footer\"]::slotted(esp-footer) { --esp-footer-content-max-width: 100%; } /* Site: bar content aligns with the section wells, not a main cap. One public token governs the header, the footer, and every esp-section well, and the well is centered — declared after the align lead rules so the centered site column wins on order. */ :host([kind=\"site\"]) slot[name=\"header\"]::slotted(esp-header) { --esp-header-content-max-width: var(--esp-page-well-max-width, 72rem); --esp-header-content-lead: 0.5; } :host([kind=\"site\"]) slot[name=\"footer\"]::slotted(esp-footer) { --esp-footer-content-max-width: var(--esp-page-well-max-width, 72rem); --esp-footer-content-lead: 0.5; } /* Contained shell: the header and footer ride the surface instead of the full viewport, so the whole page reads as one band on the canvas. Only their column placement changes — the full-height canvas and surface layers already do the rest. */ :host([contained]) .esp-page > div.esp-page-top, :host([contained]) .esp-page > div.esp-page-footer { grid-column: surface; } /* On the surface the bar already spans the content width, so the header fills it (no content padding) and drops its own shadow — the continuous surface edge shadow does the framing. */ :host([contained]) slot[name=\"header\"]::slotted(esp-header) { --esp-header-shadow: none; } /* The 100% contract belongs to the capped kinds, where the surface column IS the content width. A contained site keeps the shared well column (ESP0174) — sections and chrome must not split columns just because the shell is contained. */ :host([contained]:not([kind=\"site\"])) slot[name=\"header\"]::slotted(esp-header) { --esp-header-content-max-width: 100%; } :host([contained]:not([kind=\"site\"])) slot[name=\"footer\"]::slotted(esp-footer) { --esp-footer-content-max-width: 100%; } :host([kind=\"narrow\"]) .esp-page > div.esp-page-main { /* The golden reading measure, from the shared token (ESP0174); 66ch remains the fallback and the default emission. */ > ::slotted(*) { max-inline-size: var(--esp-measure, 66ch); } } #dialog-drop-zone { z-index: 4000; position: absolute; } :host([preview-visible]) .esp-page > div.esp-page-preview-space > .esp-page-preview { display: block; } :host([workspace-resizable][preview-visible]:not([flyout-overlay-open])) .esp-page > div.esp-page-workspace-resize-handles > .esp-page-main-preview-resize-handle, :host([workspace-resizable][flyout-open]:not([flyout-overlay-open])) .esp-page > div.esp-page-workspace-resize-handles > .esp-page-preview-flyout-resize-handle { display: block; } :host([workspace-resizable]:not([data-workspace-resize-ready])) .esp-page > .esp-page-workspace-resize-handles { display: none; } :host([workspace-resizable][data-workspace-trailing-edge-exposed]:not([flyout-overlay-open])) .esp-page > div.esp-page-workspace-resize-handles > .esp-page-workspace-end-resize-handle, :host([workspace-resizable][data-workspace-position-held]:not([flyout-overlay-open])) .esp-page > div.esp-page-workspace-resize-handles > .esp-page-workspace-end-resize-handle { display: block; } /* Pointer-owned resizing controls the logical leading gutter. It stays fixed for zero-sum pane trades and yields as an outward drag consumes centered outside canvas, keeping the active separator under the pointer in both cases. On release JS moves this pixel track to the aligned target; the normal grid transition supplies the glide before the intrinsic fr gutter contract resumes. */ :host([data-workspace-position-held]) { --_esp-page-gutter-left: var(--_esp-page-resize-leading-gutter-width, 0px); --_esp-page-gutter-right: 1fr; } :host([data-workspace-resizing]) { cursor: col-resize; user-select: none; } /* The grid-track glide that makes programmatic open/close feel animated reads as cursor lag while a pointer owns the seam, and a mid-glide grid feeds stale geometry into the clamp ranges of the next keyboard step. Track changes must be immediate for the whole resize session, pointer or keyboard. */ :host([data-workspace-resizing]) .esp-page, :host([data-workspace-keyboard-adjusting]) .esp-page { transition: none; } /* The linked menu switches its rail to a fixed drawer, but light-DOM menu content can still contribute an intrinsic width to the page's sidebar track. Collapse the owning aside only after the validated header/menu handshake is active, so unmatched navigation remains visible and reachable. */ :host([data-preview-navigation-collapsed]) .esp-page > aside.esp-page-left { inline-size: 0; min-inline-size: 0; } :host([data-preview-measuring]) .esp-page { transition: none; } /* New preview layouts are validated against visible geometry before presentation. Keep that candidate surface non-visible so a rejected measurement can never flash on screen. */ :host([data-preview-validating]) .esp-page-preview { visibility: hidden; } .esp-page { /* dvh keeps the shell within the visible mobile viewport; 100vh is the fallback for browsers without dvh (dvh === vh on desktop). */ min-height: 100vh; min-height: 100dvh; display: grid; grid-template-columns: [full-start canvas-left-start] var(--_esp-page-gutter-left) [canvas-left-end surface-start left-start] min-content [left-end main-start] var(--_esp-page-main-track) [main-end right-start] min-content [right-end surface-end canvas-right-start flyout-start] var(--_esp-page-workspace-track) [flyout-end] var(--_esp-page-gutter-right) [canvas-right-end full-end]; /* Animate the flyout claiming/releasing its track (and the gutter rebalance) via grid track interpolation where supported; elsewhere the modes still apply, just without the glide. Keep this 0.25s duration synchronized with WORKSPACE_RECENTER_DURATION in workspace-resize-session.ts. */ transition: grid-template-columns 0.25s ease; grid-template-rows: [top-start] min-content [top-end content-start] 1fr [content-end footer-start] min-content [footer-end]; overflow-x: clip; background: var(--esp-page-background, var(--esp-color-background)); line-height: 1.5; font-family: var( --_esp-font-body-effective, var(--esp-font-body, var(--_esp-font-body-fallback)) ); font-size: var(--esp-type-normal); color: var(--esp-color-text); position: relative; &:before { content: \" \"; display: block; position: absolute; left: 0; top: 0; right: 0; bottom: 0; opacity: var(--esp-page-background-image-opacity, 1); background-image: var(--esp-page-background-image, none); z-index: 1; } > div.esp-page-top { z-index: var(--esp-page-header-z-index, 20); grid-column: full; grid-row: top; position: relative; /* The header spans every column, so without inline-size containment its content's min-content would be distributed into the aside tracks and inflate them below the cap (the asides would never collapse). Contain it like the main well so it takes its width from the grid, not the grid from it. */ contain: inline-size; } /* Sidebar containers — act as the \"track\" for sticky positioning */ aside.esp-page-left, aside.esp-page-right { z-index: 5; grid-row: content; position: relative; /* No explicit height — grid stretch fills the 1fr row, and the aside's bottom edge constrains sticky children. */ } > aside.esp-page-left { grid-column: left; } > div.esp-page-main { z-index: 5; grid-column: main; grid-row: content; position: relative; /* Clip oversized content without becoming a scroll container. A hidden-overflow wrapper traps descendant view timelines (and sticky positioning) on this non-scrolling main well. */ overflow: clip; /* The main well's own background. Defaults to transparent, so the well shows the page background exactly as before; set it to give the content well a distinct card color, or leave it transparent (with --esp-page-surface-shadow: none) for content that floats directly on the page/canvas. */ background: var(--esp-page-main-background, transparent); /* The cap now lives in the main track (minmax(0, cap)); the element fills its column. contain: inline-size still holds because the track gives a definite inline size. */ contain: inline-size; } > aside.esp-page-right { grid-column: right; } /* A neutral div, unlike the left/right asides: the slotted esp-flyout is itself the complementary landmark, and an aside wrapper would nest a second, empty landmark around it. */ > div.esp-page-flyout { /* Sits after preview inside the exact auxiliary track and sizes to the allocated help width. Outer surplus remains in the following gutter rather than under an empty stretched wrapper. */ grid-column: flyout; justify-self: start; grid-row: content; z-index: 5; position: relative; inline-size: var(--_esp-page-flyout-used-width); min-inline-size: 0; margin-inline-start: var(--_esp-page-preview-used-width); /* In the drawer rung the fixed esp-flyout paints inside this wrapper's stacking context, so the wrapper itself must rise above the header (z 20) while staying below the dialog drop zone (4000). Uses the same token as the drawer — set it on the page (or higher) to move both together. */ @media ${unsafeCSS(COMPACT_VIEWPORT_MEDIA_QUERY)} { z-index: var(--esp-flyout-z-index, 3000); } } /* Preview begins at the surface edge and help is offset after its resolved width, producing the logical order main → preview → help inside their exact shared auxiliary track. */ > div.esp-page-preview-space { grid-column: flyout; grid-row: content; z-index: 4; position: relative; min-inline-size: 0; > .esp-page-preview { display: none; box-sizing: border-box; inline-size: var(--_esp-page-preview-used-width); position: relative; background: var( --esp-page-preview-background, var(--esp-page-main-background, var(--esp-page-background, var(--esp-color-background))) ); border-inline-start: var(--esp-page-preview-border, 1px dotted var(--esp-color-border)); box-shadow: var(--esp-page-preview-shadow, var(--_esp-page-surface-edge-shadow)); } } /* A transparent interaction layer keeps the complete hit target above main, preview, and anchored help without raising either painted surface (which would hide the help bridge). */ > div.esp-page-workspace-resize-handles { grid-column: flyout; grid-row: top-start / footer-end; z-index: calc(var(--esp-page-header-z-index, 20) + 3); position: relative; min-inline-size: 0; pointer-events: none; } > div.esp-page-workspace-resize-handles > .esp-page-workspace-resize-handle { display: none; box-sizing: border-box; position: absolute; inset-block: 0; z-index: 3; inline-size: var(--esp-page-resize-handle-hit-size, 2.75rem); margin-inline-start: calc(-0.5 * var(--esp-page-resize-handle-hit-size, 2.75rem)); border: 0; outline: 0; background: transparent; cursor: col-resize; pointer-events: auto; touch-action: none; /* The target spans the seam so it remains reachable during document scroll. Focus draws a dashed line along the seam itself — the visible height of the dotted tear-off edge, capped at one viewport — kept in view by sticky positioning while the page scrolls. */ &::after { content: \"\"; display: block; position: sticky; inset-block-start: 0; box-sizing: border-box; inline-size: 0; margin-inline: auto; block-size: 100dvh; max-block-size: 100%; pointer-events: none; } &:focus-visible::after { border-inline-start: var( --esp-page-resize-focus-outline, 2px dashed var(--esp-color-link) ); /* Glow in the line's own color so the focused seam reads as one deliberate highlight rather than a neutral drop shadow. */ box-shadow: var(--esp-page-resize-focus-shadow, 0 0 0.75rem var(--esp-color-link)); } } > div.esp-page-workspace-resize-handles > .esp-page-main-preview-resize-handle { inset-inline-start: 0; } > div.esp-page-workspace-resize-handles > .esp-page-preview-flyout-resize-handle { inset-inline-start: var(--_esp-page-preview-used-width); } > div.esp-page-workspace-resize-handles > .esp-page-workspace-end-resize-handle { inset-inline-start: calc( var(--_esp-page-preview-used-width) + var(--_esp-page-flyout-used-width) ); } /* Keep token-sized probes outside the width-constrained gutter. If they lived inside a zero-width preview space, layout would clamp their rectangles to zero before JS could decide whether the reclaim rung should be activated. */ > .esp-page-preview-min-width-probe, > .esp-page-preview-default-width-probe, > .esp-page-preview-max-width-probe, > .esp-page-flyout-min-width-probe, > .esp-page-flyout-default-width-probe, > .esp-page-flyout-max-width-probe, > .esp-page-main-min-width-probe, > .esp-page-main-configured-max-width-probe, > .esp-page-main-max-width-probe, > .esp-page-resize-step-probe, > .esp-page-resize-large-step-probe { position: absolute; block-size: 0; visibility: hidden; pointer-events: none; contain: strict; } > .esp-page-preview-min-width-probe { inline-size: var(--_esp-page-preview-resolved-min-width); } > .esp-page-preview-max-width-probe { inline-size: var(--_esp-page-preview-resolved-max-width); } > .esp-page-preview-default-width-probe { inline-size: var(--esp-page-preview-default-width, 0px); } > .esp-page-flyout-min-width-probe { inline-size: var(--_esp-page-flyout-resolved-min-width); } > .esp-page-flyout-max-width-probe { inline-size: var(--_esp-page-flyout-resolved-max-width); } > .esp-page-flyout-default-width-probe { inline-size: var(--esp-page-flyout-default-width, 0px); } > .esp-page-main-min-width-probe { inline-size: var(--_esp-page-main-min-width); } > .esp-page-main-configured-max-width-probe { inline-size: var(--esp-page-main-max-width, 0px); } > .esp-page-main-max-width-probe { inline-size: var(--_esp-page-resolved-max-width); } > .esp-page-resize-step-probe { inline-size: var(--esp-page-resize-step, 1rem); } > .esp-page-resize-large-step-probe { inline-size: var(--esp-page-resize-large-step, 4rem); } /* Canvas: the outer gutter regions promoted to a styleable layer. They only have width once the viewport exceeds the cap, so with the transparent defaults they are invisible and narrow pages are unchanged. Painted below the content (z-index 5) and below the surface shadow. */ > .esp-page-canvas { /* Full height: the canvas runs top-to-bottom. In the default full-bleed chrome the header/footer span the gutters and cover it there, so only the content-band gutters show; in contained mode the header/footer sit on the surface and the canvas shows full height beside them. */ grid-row: top-start / footer-end; z-index: 1; position: relative; pointer-events: none; background-color: var(--esp-page-canvas-background, transparent); /* Image layer mirrors the page background-image knobs: a separate layer so its opacity does not fade the color. */ &::before { content: \"\"; position: absolute; inset: 0; background-image: var(--esp-page-canvas-background-image, none); opacity: var(--esp-page-canvas-background-image-opacity, 1); } } > .esp-page-canvas--left { grid-column: canvas-left; } > .esp-page-canvas--right { /* The canvas spans both the auxiliary workspace and the independent outer gutter beneath it. */ grid-column: canvas-right; } /* Surface: a transparent backdrop spanning the surface cluster (left aside + main + right aside). It carries the surface edge shadow (on by default) and an optional hairline border. The shadow casts outward into the gutters; when there are no gutters the surface fills the viewport and overflow-x: clip on .esp-page trims the shadow away — so it is visible only above the cap with no container query needed. */ > .esp-page-surface { grid-column: surface; /* Full height so the edge shadow is one continuous line from the header through the content to the footer — no fade at the region joins. */ grid-row: top-start / footer-end; z-index: 2; pointer-events: none; box-shadow: var(--_esp-page-surface-shadow); border-inline: var(--_esp-page-surface-border); } /* Internal wrapper manipulated by BiDirectionalStickyController */ .sticky-wrapper { will-change: top; display: block; width: 100%; position: sticky; top: 0; } > div.esp-page-footer { grid-column: full; grid-row: footer; z-index: 10; background: var(--esp-page-background, var(--esp-color-background)); /* Spans every column; contain it so its content does not inflate the aside tracks (see the header note above). */ contain: inline-size; } &.sticky-header { slot[name=\"header\"]::slotted(esp-header), slot[name=\"sidebar\"]::slotted(esp-menu), slot[name=\"right\"]::slotted(esp-menu) { --esp-menu-top-offset: var(--_esp-page-fixed-header-offset); } > div.esp-page-top { position: sticky; top: var(--esp-page-sticky-header-top, 0); } } &.fixed-header { slot[name=\"header\"]::slotted(esp-header), slot[name=\"sidebar\"]::slotted(esp-menu), slot[name=\"right\"]::slotted(esp-menu) { --esp-menu-top-offset: var(--_esp-page-fixed-header-offset); } > div.esp-page-top { position: fixed; top: 0; left: 0; right: 0; } > div.esp-page-main, > aside.esp-page-left, > aside.esp-page-right, > div.esp-page-flyout, > .esp-page-surface, > .esp-page-canvas { margin-top: var(--_esp-page-fixed-header-offset); } } /* Inherit fonts for inputs and buttons */ input, button, textarea, select { font-family: inherit; font-size: inherit; } /* Make sure text areas without a rows attribute are not tiny */ textarea:not([rows]) { min-height: 10em; } /* Anything that has been anchored to should have extra scroll margin */ :target { scroll-margin-block: 5ex; } } .esp-page:has(esp-dialog[is-open=\"true\"]) { overflow: hidden; } /* Main and preview are additive surfaces. Once preview owns the trailing edge, move elevation there so no shadow appears at the internal join; the surface resumes its normal frame when preview is hidden. */ :host([preview-visible]) .esp-page > .esp-page-surface { box-shadow: none; } /* The preview's outer grid item and landmark span the complete page so the sticky wrapper's containing block has enough travel to remain pinned throughout a tall document. Its dynamic-viewport block size and vertical overflow keep its scroll position independent from the main document. It paints above full-bleed header chrome only in the preview's negotiated gutter track; overlay flyouts still take precedence by hiding preview presentation. */ .esp-page > div.esp-page-preview-space { grid-row: top-start / footer-end; z-index: calc(var(--esp-page-header-z-index, 20) + 1); pointer-events: none; > .esp-page-preview { position: absolute; inset-block: 0; inset-inline-start: 0; pointer-events: auto; > .sticky-wrapper { block-size: 100vh; block-size: 100dvh; overflow-y: auto; /* Scroll preview first, then hand wheel/trackpad motion back to the document at either boundary (or when no overflow exists). */ overscroll-behavior-y: auto; } } } @media (prefers-reduced-motion: reduce) { .esp-page { transition: none; } } `, ]"
53285
+ "default": "[ ...EspalierElementBase.styles, css` :host { --_esp-page-resolved-max-width: var(--esp-page-main-max-width, 1536px); --_esp-page-main-min-width: var(--esp-page-main-min-width, 30rem); --_esp-page-preview-resolved-min-width: var(--esp-page-preview-min-width, 22.5rem); --_esp-page-preview-resolved-max-width: var(--esp-page-preview-max-width, 48rem); --_esp-page-flyout-resolved-min-width: var(--esp-page-flyout-min-width, 20rem); --_esp-page-flyout-resolved-max-width: var(--esp-page-flyout-max-width, 30rem); --_esp-page-preview-used-width: 0px; --_esp-page-flyout-used-width: 0px; --_esp-page-workspace-track: minmax( calc(var(--_esp-page-flyout-min) + var(--_esp-page-preview-min)), calc(var(--_esp-page-preview-used-width) + var(--_esp-page-flyout-used-width)) ); /* The cap sizes the main content *well*: the main track maxes at the cap and the surplus beyond the surface becomes canvas gutters. (Contract preserved from v2.1.0.) */ --_esp-page-main-track: minmax(0, var(--_esp-page-resolved-max-width)); /* Canvas gutter weights (physical left/right in LTR). The flexible gutters claim only the space left once the main track reaches its cap, so they appear exactly when the surface exceeds the cap — at any cap, with no viewport breakpoint (ADR-011). The start default keeps no leading gutter and lets all surplus collect trailing. Empty asides collapse to zero because the header and footer regions are inline-size-contained and no longer leak their min-content into the aside tracks (see the .esp-page-top / .esp-page-footer rules). The fr weights are written as 0fr/1fr (never bare 0) so the track list stays pairwise-interpolable and the flyout open/close can animate via grid track interpolation where supported. */ --_esp-page-gutter-left: 0fr; --_esp-page-gutter-right: 1fr; /* Help and preview share an exact auxiliary workspace track before the outer trailing gutter. Opening help raises that track's safe minimum; the allocator supplies its concrete used width. */ --_esp-page-flyout-min: 0px; /* Preview starts at the surface edge before help. Reclaiming raises this minimum so the auxiliary track reserves both pane floors while the main track retains its own floor. */ --_esp-page-preview-min: 0px; /* A raised surface edge's drop shadow, cast in the trailing (inline-end) direction. The surface mirrors it onto both inline edges; a match-surface flyout casts only this trailing edge, so it lifts off the canvas while staying attached at its leading perforation (no shadow bleeding over the content it flies out from). */ --_esp-page-surface-edge-shadow: 0.75rem 0 1.5rem -0.75rem var(--esp-color-shadow); /* Resolve the surface shadow/border once (public override plus the built-in default) so the value is readable downstream — the surface layer and the match-surface flyout contract both consume it, and it stays a single source of truth. */ --_esp-page-surface-shadow: var( --esp-page-surface-shadow, -0.75rem 0 1.5rem -0.75rem var(--esp-color-shadow), var(--_esp-page-surface-edge-shadow) ); --_esp-page-surface-border: var(--esp-page-surface-border, none); --_esp-page-fixed-header-offset: var( --esp-page-fixed-header-offset, var(--esp-header-height, calc(4.5 * var(--esp-size-small))) ); display: block; } :host([kind=\"narrow\"]) { --_esp-page-resolved-max-width: var(--esp-page-main-max-width, 768px); } :host([align=\"center\"]) { --_esp-page-gutter-left: 1fr; --_esp-page-gutter-right: 1fr; } :host([align=\"end\"]) { --_esp-page-gutter-left: 1fr; --_esp-page-gutter-right: 0fr; } /* Full is unbounded only while the consumer has not supplied the explicit main maximum. The internal marker is synchronized from the computed public token before workspace allocation, allowing a finite full-kind authoring surface to retain normal alignment gutters. */ :host([kind=\"full\"]) { --_esp-page-resolved-max-width: var(--esp-page-main-max-width, none); } :host([kind=\"full\"]:not([data-main-max-configured])) { --_esp-page-main-track: 1fr; --_esp-page-gutter-left: 0fr; --_esp-page-gutter-right: 0fr; } /* Site: the marketing shell (ESP0174). The main region is always unbounded — slotted esp-section bands own their centered wells — so this is the unconfigured-full geometry, unconditionally, and align's gutter weights do not apply. Declared after the align rules so it wins on order. */ :host([kind=\"site\"]) { --_esp-page-resolved-max-width: none; --_esp-page-main-track: 1fr; --_esp-page-gutter-left: 0fr; --_esp-page-gutter-right: 0fr; } /* Flyout placement ladder (ESP0111). Preview and help occupy an exact auxiliary track between the main surface and the outer gutter: 1. spare canvas is converted into the allocated auxiliary track while main remains at its natural maximum 2. help and preview contract to their floors 3. an eligible linked navigation rail yields before the main track narrows toward its minimum 4. if the complete main + preview + help minimums do not fit, JS requests the flyout's existing modal overlay presentation 5. mobile → the media query below releases the minimum and esp-flyout itself becomes a fixed overlay drawer. The flyout-open host attribute is maintained by syncFlyoutState (Blink cannot match :has() inside :host(), so the state is mirrored instead). Declared after the align/kind rules so the open state wins. */ :host([flyout-open]) { --_esp-page-flyout-min: var(--_esp-page-flyout-resolved-min-width); } :host([preview-reclaiming]) { --_esp-page-preview-min: var(--_esp-page-preview-resolved-min-width); } /* Configured defaults and retained drag/keyboard choices participate in the same grid minima as automatic allocation. This lets a target that must reclaim main do so instead of overflowing the gutter, while the allocator still clamps private used widths before they reach Grid. */ :host([flyout-open][data-workspace-targeted]) { --_esp-page-flyout-min: var(--_esp-page-flyout-used-width); } :host([preview-reclaiming][data-workspace-targeted]) { --_esp-page-preview-min: var(--_esp-page-preview-used-width); } :host([flyout-open]), :host([preview-reclaiming]) { --_esp-page-main-track: minmax( var(--_esp-page-main-min-width), var(--_esp-page-resolved-max-width) ); } :host([kind=\"full\"]:not([data-main-max-configured])[flyout-open]), :host([kind=\"full\"]:not([data-main-max-configured])[preview-reclaiming]), :host([kind=\"site\"][flyout-open]), :host([kind=\"site\"][preview-reclaiming]) { --_esp-page-main-track: minmax(var(--_esp-page-main-min-width), 1fr); } /* Deliberate ADR-011 exception: like the esp-menu drawer (same 50em threshold), the flyout's overlay rung is a genuinely viewport-level decision — below it the flyout leaves the grid for a fixed drawer, so the gutter must release the minimum it was reserving. */ @media ${unsafeCSS(COMPACT_VIEWPORT_MEDIA_QUERY)} { :host([flyout-open]) { --_esp-page-flyout-min: 0px; } :host([preview-reclaiming]) { --_esp-page-preview-min: 0px; } :host([flyout-open]), :host([preview-reclaiming]) { --_esp-page-main-track: minmax(0, var(--_esp-page-resolved-max-width)); } :host([kind=\"full\"]:not([data-main-max-configured])[flyout-open]), :host([kind=\"full\"]:not([data-main-max-configured])[preview-reclaiming]), :host([kind=\"site\"][flyout-open]), :host([kind=\"site\"][preview-reclaiming]) { --_esp-page-main-track: 1fr; } .esp-page > .esp-page-preview-space, .esp-page > .esp-page-workspace-resize-handles { display: none; } } /* A forced mode=\"overlay\" flyout is a fixed drawer at any viewport width, so its aside needs the same above-the-header hoist the sub-50em media rule gives the auto-mode drawer — without this the desktop drawer paints beneath the header (aside z 5 vs header z 20). Keyed off the flyout-overlay-open state attribute maintained by syncFlyoutState. */ :host([flyout-overlay-open]) .esp-page > div.esp-page-flyout { z-index: var(--esp-flyout-z-index, 3000); } /* A nested page's fixed drawer still participates in this page's main stacking context. Raise that complete context while the descendant modal is open so the drawer and vellum paint above this page's header instead of trapping their close control beneath it. */ :host([descendant-flyout-overlay-open]) .esp-page > div.esp-page-main { z-index: var(--esp-flyout-z-index, 3000); overflow: visible; } /* Trigger-anchored panels belong to the same document flow as their initiator. BiDirectionalStickyController can keep updating the wrapper's inline top, but position: static deliberately makes it inert until the anchored panel closes. */ :host([flyout-anchored]:not([flyout-full-height])) .esp-page > div.esp-page-flyout > .sticky-wrapper { position: static; } /* Full-document help owns the visible block-size rather than following one field. Span header through footer so the page footer cannot push a viewport-height panel out of view at the document boundary; the sticky controller pins this presentation to the viewport top. */ :host([flyout-full-height]) .esp-page > div.esp-page-flyout { grid-row: top-start / footer-end; z-index: calc(var(--esp-page-header-z-index, 20) + 2); } /* Anchored help connects back to its trigger across a visible preview. Raise only the sized help wrapper above the preview surface; the bridge itself is non-interactive inside esp-flyout. */ :host([flyout-anchored][preview-visible]) .esp-page > div.esp-page-flyout { z-index: calc(var(--esp-page-header-z-index, 20) + 2); } slot[name=\"flyout\"]::slotted(esp-flyout) { --_esp-flyout-used-width: var(--_esp-page-flyout-used-width); --_esp-flyout-preview-bridge-width: 0px; --_esp-flyout-preview-anchor-display: none; } :host([preview-visible]) slot[name=\"flyout\"]::slotted(esp-flyout) { --_esp-flyout-preview-bridge-width: var(--_esp-page-preview-used-width); --_esp-flyout-preview-anchor-display: block; } /* Flyout surface-match contract. A slotted esp-flyout with match-surface lifts its trailing edge with the surface's edge shadow, so the panel reads as a raised peer of the content surface rather than the shadowless tear-off. The leading edge keeps its perforation (the border is not overridden) and casts no shadow, so it stays attached at the tear line instead of floating over the content it flies out from. Opt-in and page-scoped: a standalone flyout has no surface to match and is unaffected. */ slot[name=\"flyout\"]::slotted(esp-flyout[match-surface]) { --esp-flyout-shadow: var(--_esp-page-surface-edge-shadow); } /* Header alignment contract. Published on the slotted esp-header (not :host) so it never shadows an inherited or author override and only the header-slot header adopts it — see the host-token-defaults architecture rule and ADR-005. max-width is the resolved cap; lead is the fraction of surplus placed before the content (0 = start, .5 = center, 1 = end). */ slot[name=\"header\"]::slotted(esp-header) { --esp-header-content-max-width: var(--_esp-page-resolved-max-width); --esp-header-content-lead: 0; } :host([align=\"center\"]) slot[name=\"header\"]::slotted(esp-header) { --esp-header-content-lead: 0.5; } :host([align=\"end\"]) slot[name=\"header\"]::slotted(esp-header) { --esp-header-content-lead: 1; } /* full has no cap; publish 100% (not none) so the header contract calc resolves to zero surplus and leaves the header full-bleed. */ :host([kind=\"full\"]:not([data-main-max-configured])) slot[name=\"header\"]::slotted(esp-header) { --esp-header-content-max-width: 100%; } /* Footer alignment mirrors the header behavior with footer-owned contract names. The full-bleed footer paints its own background; only its internal content adopts the page's cap and lead fraction. */ slot[name=\"footer\"]::slotted(esp-footer) { --esp-footer-content-max-width: var(--_esp-page-resolved-max-width); --esp-footer-content-lead: 0; } :host([align=\"center\"]) slot[name=\"footer\"]::slotted(esp-footer) { --esp-footer-content-lead: 0.5; } :host([align=\"end\"]) slot[name=\"footer\"]::slotted(esp-footer) { --esp-footer-content-lead: 1; } :host([kind=\"full\"]:not([data-main-max-configured])) slot[name=\"footer\"]::slotted(esp-footer) { --esp-footer-content-max-width: 100%; } /* Site: bar content aligns with the section wells, not a main cap. One public token governs the header, the footer, and every esp-section well, and the well is centered — declared after the align lead rules so the centered site column wins on order. */ :host([kind=\"site\"]) slot[name=\"header\"]::slotted(esp-header) { --esp-header-content-max-width: var(--esp-page-well-max-width, 72rem); --esp-header-content-lead: 0.5; } :host([kind=\"site\"]) slot[name=\"footer\"]::slotted(esp-footer) { --esp-footer-content-max-width: var(--esp-page-well-max-width, 72rem); --esp-footer-content-lead: 0.5; } /* Contained shell: the header and footer ride the surface instead of the full viewport, so the whole page reads as one band on the canvas. Only their column placement changes — the full-height canvas and surface layers already do the rest. */ :host([contained]) .esp-page > div.esp-page-top, :host([contained]) .esp-page > div.esp-page-footer { grid-column: surface; } /* On the surface the bar already spans the content width, so the header fills it (no content padding) and drops its own shadow — the continuous surface edge shadow does the framing. */ :host([contained]) slot[name=\"header\"]::slotted(esp-header) { --esp-header-shadow: none; } /* The 100% contract belongs to the capped kinds, where the surface column IS the content width. A contained site keeps the shared well column (ESP0174) — sections and chrome must not split columns just because the shell is contained. */ :host([contained]:not([kind=\"site\"])) slot[name=\"header\"]::slotted(esp-header) { --esp-header-content-max-width: 100%; } :host([contained]:not([kind=\"site\"])) slot[name=\"footer\"]::slotted(esp-footer) { --esp-footer-content-max-width: 100%; } :host([kind=\"narrow\"]) .esp-page > div.esp-page-main { /* The golden reading measure, from the shared token (ESP0174); 66ch remains the fallback and the default emission. */ > ::slotted(*) { max-inline-size: var(--esp-measure, 66ch); } } #dialog-drop-zone { z-index: 4000; position: absolute; } :host([preview-visible]) .esp-page > div.esp-page-preview-space > .esp-page-preview { display: block; } :host([workspace-resizable][preview-visible]:not([flyout-overlay-open])) .esp-page > div.esp-page-workspace-resize-handles > .esp-page-main-preview-resize-handle, :host([workspace-resizable][flyout-open]:not([flyout-overlay-open])) .esp-page > div.esp-page-workspace-resize-handles > .esp-page-preview-flyout-resize-handle { display: block; } :host([workspace-resizable]:not([data-workspace-resize-ready])) .esp-page > .esp-page-workspace-resize-handles { display: none; } :host([workspace-resizable][data-workspace-trailing-edge-exposed]:not([flyout-overlay-open])) .esp-page > div.esp-page-workspace-resize-handles > .esp-page-workspace-end-resize-handle, :host([workspace-resizable][data-workspace-position-held]:not([flyout-overlay-open])) .esp-page > div.esp-page-workspace-resize-handles > .esp-page-workspace-end-resize-handle { display: block; } /* Pointer-owned resizing controls the logical leading gutter. It stays fixed for zero-sum pane trades and yields as an outward drag consumes centered outside canvas, keeping the active separator under the pointer in both cases. On release JS moves this pixel track to the aligned target; the normal grid transition supplies the glide before the intrinsic fr gutter contract resumes. */ :host([data-workspace-position-held]) { --_esp-page-gutter-left: var(--_esp-page-resize-leading-gutter-width, 0px); --_esp-page-gutter-right: 1fr; } :host([data-workspace-resizing]) { cursor: col-resize; user-select: none; } /* The grid-track glide that makes programmatic open/close feel animated reads as cursor lag while a pointer owns the seam, and a mid-glide grid feeds stale geometry into the clamp ranges of the next keyboard step. Track changes must be immediate for the whole resize session, pointer or keyboard. */ :host([data-workspace-resizing]) .esp-page, :host([data-workspace-keyboard-adjusting]) .esp-page { transition: none; } /* The linked menu switches its rail to a fixed drawer, but light-DOM menu content can still contribute an intrinsic width to the page's sidebar track. Collapse the owning aside only after the validated header/menu handshake is active, so unmatched navigation remains visible and reachable. */ :host([data-preview-navigation-collapsed]) .esp-page > aside.esp-page-left { inline-size: 0; min-inline-size: 0; } :host([data-preview-measuring]) .esp-page { transition: none; } /* New preview layouts are validated against visible geometry before presentation. Keep that candidate surface non-visible so a rejected measurement can never flash on screen. */ :host([data-preview-validating]) .esp-page-preview { visibility: hidden; } .esp-page { /* dvh keeps the shell within the visible mobile viewport; 100vh is the fallback for browsers without dvh (dvh === vh on desktop). */ min-height: 100vh; min-height: 100dvh; display: grid; grid-template-columns: [full-start canvas-left-start] var(--_esp-page-gutter-left) [canvas-left-end surface-start left-start] min-content [left-end main-start] var(--_esp-page-main-track) [main-end right-start] min-content [right-end surface-end canvas-right-start flyout-start] var(--_esp-page-workspace-track) [flyout-end] var(--_esp-page-gutter-right) [canvas-right-end full-end]; /* Animate the flyout claiming/releasing its track (and the gutter rebalance) via grid track interpolation where supported; elsewhere the modes still apply, just without the glide. Keep this 0.25s duration synchronized with WORKSPACE_RECENTER_DURATION in workspace-resize-session.ts. */ transition: grid-template-columns 0.25s ease; grid-template-rows: [top-start] min-content [top-end content-start] 1fr [content-end footer-start] min-content [footer-end]; overflow-x: clip; background: var(--esp-page-background, var(--esp-color-background)); line-height: 1.5; font-family: var( --_esp-font-body-effective, var(--esp-font-body, var(--_esp-font-body-fallback)) ); font-size: var(--esp-type-normal); color: var(--esp-color-text); position: relative; &:before { content: \" \"; display: block; position: absolute; left: 0; top: 0; right: 0; bottom: 0; opacity: var(--esp-page-background-image-opacity, 1); background-image: var(--esp-page-background-image, none); z-index: 1; } > div.esp-page-top { z-index: var(--esp-page-header-z-index, 20); grid-column: full; grid-row: top; position: relative; /* The header spans every column, so without inline-size containment its content's min-content would be distributed into the aside tracks and inflate them below the cap (the asides would never collapse). Contain it like the main well so it takes its width from the grid, not the grid from it. */ contain: inline-size; } /* Sidebar containers — act as the \"track\" for sticky positioning */ aside.esp-page-left, aside.esp-page-right { z-index: 5; grid-row: content; position: relative; /* No explicit height — grid stretch fills the 1fr row, and the aside's bottom edge constrains sticky children. */ } > aside.esp-page-left { grid-column: left; } > div.esp-page-main { z-index: 5; grid-column: main; grid-row: content; position: relative; /* Clip oversized content without becoming a scroll container. A hidden-overflow wrapper traps descendant view timelines (and sticky positioning) on this non-scrolling main well. */ overflow: clip; /* The main well's own background. Defaults to transparent, so the well shows the page background exactly as before; set it to give the content well a distinct card color, or leave it transparent (with --esp-page-surface-shadow: none) for content that floats directly on the page/canvas. */ background: var(--esp-page-main-background, transparent); /* The cap now lives in the main track (minmax(0, cap)); the element fills its column. contain: inline-size still holds because the track gives a definite inline size. */ contain: inline-size; } > aside.esp-page-right { grid-column: right; } /* A neutral div, unlike the left/right asides: the slotted esp-flyout is itself the complementary landmark, and an aside wrapper would nest a second, empty landmark around it. */ > div.esp-page-flyout { /* Sits after preview inside the exact auxiliary track and sizes to the allocated help width. Outer surplus remains in the following gutter rather than under an empty stretched wrapper. */ grid-column: flyout; justify-self: start; grid-row: content; z-index: 5; position: relative; inline-size: var(--_esp-page-flyout-used-width); min-inline-size: 0; margin-inline-start: var(--_esp-page-preview-used-width); /* In the drawer rung the fixed esp-flyout paints inside this wrapper's stacking context, so the wrapper itself must rise above the header (z 20) while staying below the dialog drop zone (4000). Uses the same token as the drawer — set it on the page (or higher) to move both together. */ @media ${unsafeCSS(COMPACT_VIEWPORT_MEDIA_QUERY)} { z-index: var(--esp-flyout-z-index, 3000); } } /* Preview begins at the surface edge and help is offset after its resolved width, producing the logical order main → preview → help inside their exact shared auxiliary track. */ > div.esp-page-preview-space { grid-column: flyout; grid-row: content; z-index: 4; position: relative; min-inline-size: 0; > .esp-page-preview { display: none; box-sizing: border-box; inline-size: var(--_esp-page-preview-used-width); position: relative; background: var( --esp-page-preview-background, var(--esp-page-main-background, var(--esp-page-background, var(--esp-color-background))) ); border-inline-start: var(--esp-page-preview-border, 1px dotted var(--esp-color-border)); box-shadow: var(--esp-page-preview-shadow, var(--_esp-page-surface-edge-shadow)); } } /* A transparent interaction layer keeps the complete hit target above main, preview, and anchored help without raising either painted surface (which would hide the help bridge). */ > div.esp-page-workspace-resize-handles { grid-column: flyout; grid-row: top-start / footer-end; z-index: calc(var(--esp-page-header-z-index, 20) + 3); position: relative; min-inline-size: 0; pointer-events: none; } > div.esp-page-workspace-resize-handles > .esp-page-workspace-resize-handle { display: none; box-sizing: border-box; position: absolute; inset-block: 0; z-index: 3; inline-size: var(--esp-page-resize-handle-hit-size, 2.75rem); margin-inline-start: calc(-0.5 * var(--esp-page-resize-handle-hit-size, 2.75rem)); border: 0; outline: 0; background: transparent; cursor: col-resize; pointer-events: auto; touch-action: none; /* The target spans the seam so it remains reachable during document scroll. Focus draws a dashed line along the seam itself — the visible height of the dotted tear-off edge, capped at one viewport — kept in view by sticky positioning while the page scrolls. */ &::after { content: \"\"; display: block; position: sticky; inset-block-start: 0; box-sizing: border-box; inline-size: 0; margin-inline: auto; block-size: 100dvh; max-block-size: 100%; pointer-events: none; } &:focus-visible::after { border-inline-start: var( --esp-page-resize-focus-outline, 2px dashed var(--esp-color-link) ); /* Glow in the line's own color so the focused seam reads as one deliberate highlight rather than a neutral drop shadow. */ box-shadow: var(--esp-page-resize-focus-shadow, 0 0 0.75rem var(--esp-color-link)); } } > div.esp-page-workspace-resize-handles > .esp-page-main-preview-resize-handle { inset-inline-start: 0; } > div.esp-page-workspace-resize-handles > .esp-page-preview-flyout-resize-handle { inset-inline-start: var(--_esp-page-preview-used-width); } > div.esp-page-workspace-resize-handles > .esp-page-workspace-end-resize-handle { inset-inline-start: calc( var(--_esp-page-preview-used-width) + var(--_esp-page-flyout-used-width) ); } /* Keep token-sized probes outside the width-constrained gutter. If they lived inside a zero-width preview space, layout would clamp their rectangles to zero before JS could decide whether the reclaim rung should be activated. */ > .esp-page-preview-min-width-probe, > .esp-page-preview-default-width-probe, > .esp-page-preview-max-width-probe, > .esp-page-flyout-min-width-probe, > .esp-page-flyout-default-width-probe, > .esp-page-flyout-max-width-probe, > .esp-page-main-min-width-probe, > .esp-page-main-configured-max-width-probe, > .esp-page-main-max-width-probe, > .esp-page-resize-step-probe, > .esp-page-resize-large-step-probe { position: absolute; block-size: 0; visibility: hidden; pointer-events: none; contain: strict; } > .esp-page-preview-min-width-probe { inline-size: var(--_esp-page-preview-resolved-min-width); } > .esp-page-preview-max-width-probe { inline-size: var(--_esp-page-preview-resolved-max-width); } > .esp-page-preview-default-width-probe { inline-size: var(--esp-page-preview-default-width, 0px); } > .esp-page-flyout-min-width-probe { inline-size: var(--_esp-page-flyout-resolved-min-width); } > .esp-page-flyout-max-width-probe { inline-size: var(--_esp-page-flyout-resolved-max-width); } > .esp-page-flyout-default-width-probe { inline-size: var(--esp-page-flyout-default-width, 0px); } > .esp-page-main-min-width-probe { inline-size: var(--_esp-page-main-min-width); } > .esp-page-main-configured-max-width-probe { inline-size: var(--esp-page-main-max-width, 0px); } > .esp-page-main-max-width-probe { inline-size: var(--_esp-page-resolved-max-width); } > .esp-page-resize-step-probe { inline-size: var(--esp-page-resize-step, 1rem); } > .esp-page-resize-large-step-probe { inline-size: var(--esp-page-resize-large-step, 4rem); } /* Canvas: the outer gutter regions promoted to a styleable layer. They only have width once the viewport exceeds the cap, so with the transparent defaults they are invisible and narrow pages are unchanged. Painted below the content (z-index 5) and below the surface shadow. */ > .esp-page-canvas { /* Full height: the canvas runs top-to-bottom. In the default full-bleed chrome the header/footer span the gutters and cover it there, so only the content-band gutters show; in contained mode the header/footer sit on the surface and the canvas shows full height beside them. */ grid-row: top-start / footer-end; z-index: 1; position: relative; pointer-events: none; background-color: var(--esp-page-canvas-background, transparent); /* Image layer mirrors the page background-image knobs: a separate layer so its opacity does not fade the color. */ &::before { content: \"\"; position: absolute; inset: 0; background-image: var(--esp-page-canvas-background-image, none); opacity: var(--esp-page-canvas-background-image-opacity, 1); } } > .esp-page-canvas--left { grid-column: canvas-left; } > .esp-page-canvas--right { /* The canvas spans both the auxiliary workspace and the independent outer gutter beneath it. */ grid-column: canvas-right; } /* Surface: a transparent backdrop spanning the surface cluster (left aside + main + right aside). It carries the surface edge shadow (on by default) and an optional hairline border. The shadow casts outward into the gutters; when there are no gutters the surface fills the viewport and overflow-x: clip on .esp-page trims the shadow away — so it is visible only above the cap with no container query needed. */ > .esp-page-surface { grid-column: surface; /* Full height so the edge shadow is one continuous line from the header through the content to the footer — no fade at the region joins. */ grid-row: top-start / footer-end; z-index: 2; pointer-events: none; box-shadow: var(--_esp-page-surface-shadow); border-inline: var(--_esp-page-surface-border); } /* Internal wrapper manipulated by BiDirectionalStickyController */ .sticky-wrapper { will-change: top; display: block; width: 100%; position: sticky; top: 0; } > div.esp-page-footer { grid-column: full; grid-row: footer; z-index: 10; background: var(--esp-page-background, var(--esp-color-background)); /* Spans every column; contain it so its content does not inflate the aside tracks (see the header note above). */ contain: inline-size; } &.sticky-header { slot[name=\"header\"]::slotted(esp-header), slot[name=\"sidebar\"]::slotted(esp-menu), slot[name=\"right\"]::slotted(esp-menu) { --esp-menu-top-offset: var(--_esp-page-fixed-header-offset); } > div.esp-page-top { position: sticky; top: var(--esp-page-sticky-header-top, 0); } } &.fixed-header { slot[name=\"header\"]::slotted(esp-header), slot[name=\"sidebar\"]::slotted(esp-menu), slot[name=\"right\"]::slotted(esp-menu) { --esp-menu-top-offset: var(--_esp-page-fixed-header-offset); } > div.esp-page-top { position: fixed; top: 0; left: 0; right: 0; } > div.esp-page-main, > aside.esp-page-left, > aside.esp-page-right, > div.esp-page-flyout, > .esp-page-surface, > .esp-page-canvas { margin-top: var(--_esp-page-fixed-header-offset); } } /* Inherit fonts for inputs and buttons */ input, button, textarea, select { font-family: inherit; font-size: inherit; } /* Make sure text areas without a rows attribute are not tiny */ textarea:not([rows]) { min-height: 10em; } /* Anything that has been anchored to should have extra scroll margin */ :target { scroll-margin-block: 5ex; } } .esp-page:has(esp-dialog[is-open=\"true\"]) { overflow: hidden; } /* Main and preview are additive surfaces. Once preview owns the trailing edge, move elevation there so no shadow appears at the internal join; the surface resumes its normal frame when preview is hidden. */ :host([preview-visible]) .esp-page > .esp-page-surface { box-shadow: none; } /* The preview's outer grid item and landmark span the complete page so the sticky wrapper's containing block has enough travel to remain pinned throughout a tall document. Its dynamic-viewport block size and vertical overflow keep its scroll position independent from the main document. It paints above full-bleed header chrome only in the preview's negotiated gutter track; overlay flyouts still take precedence by hiding preview presentation. */ .esp-page > div.esp-page-preview-space { grid-row: top-start / footer-end; z-index: calc(var(--esp-page-header-z-index, 20) + 1); pointer-events: none; > .esp-page-preview { position: absolute; inset-block: 0; inset-inline-start: 0; pointer-events: auto; > .sticky-wrapper { block-size: 100vh; block-size: 100dvh; overflow-y: auto; /* Scroll preview first, then hand wheel/trackpad motion back to the document at either boundary (or when no overflow exists). */ overscroll-behavior-y: auto; } } } @media (prefers-reduced-motion: reduce) { .esp-page { transition: none; } } `, ]"
53230
53286
  }
53231
53287
  ],
53232
53288
  "exports": [
@@ -53265,10 +53321,6 @@
53265
53321
  "description": "The shared content-column width in `kind=\"site\"`: the header and footer alignment contract and every `esp-section` well default to it. Defaults to `72rem`.",
53266
53322
  "name": "--esp-page-well-max-width"
53267
53323
  },
53268
- {
53269
- "description": "Legacy fallback for `--esp-page-main-max-width`.",
53270
- "name": "--esp-page-max-width"
53271
- },
53272
53324
  {
53273
53325
  "description": "The background image to display behind page content. Defaults to `none`.",
53274
53326
  "name": "--esp-page-background-image"
@@ -53357,18 +53409,6 @@
53357
53409
  "description": "Glow cast by the focused seam line, in the line's color. Defaults to `0 0 0.75rem var(--esp-color-link)`.",
53358
53410
  "name": "--esp-page-resize-focus-shadow"
53359
53411
  },
53360
- {
53361
- "description": "Legacy fixed-width alias that pins both preview bounds.",
53362
- "name": "--esp-page-preview-width"
53363
- },
53364
- {
53365
- "description": "Legacy fixed-width alias that pins both help bounds.",
53366
- "name": "--esp-page-flyout-width"
53367
- },
53368
- {
53369
- "description": "Legacy fallback for `--esp-page-main-min-width`.",
53370
- "name": "--esp-page-preview-min-main-width"
53371
- },
53372
53412
  {
53373
53413
  "description": "Offset reserved for fixed headers. Defaults to `var(--esp-header-height)`.",
53374
53414
  "name": "--esp-page-fixed-header-offset"
@@ -53476,7 +53516,7 @@
53476
53516
  "name": "preview-end-resize-handle"
53477
53517
  },
53478
53518
  {
53479
- "description": "The exposed trailing edge of in-grid flyout/help while help is the last auxiliary pane. ```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> <esp-footer slot=\"footer\" brand-text=\"Espalier\"></esp-footer> <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> <esp-footer slot=\"footer\" brand-text=\"Espalier\"></esp-footer> <div style=\"background-color: black; overflow: hidden;\"> content </div> </esp-page> ```",
53519
+ "description": "The exposed trailing edge of in-grid flyout/help while help is the last auxiliary pane. ```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> <esp-footer slot=\"footer\" brand-text=\"Espalier\"></esp-footer> <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-main-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-main-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> <esp-footer slot=\"footer\" brand-text=\"Espalier\"></esp-footer> <div style=\"background-color: black; overflow: hidden;\"> content </div> </esp-page> ```",
53480
53520
  "name": "flyout-end-resize-handle"
53481
53521
  }
53482
53522
  ],
@@ -54905,22 +54945,10 @@
54905
54945
  },
54906
54946
  "privacy": "public",
54907
54947
  "default": "\"normal\"",
54908
- "description": "Header positioning behavior for the `header` slot.\n\n- `normal` lets the header scroll with content.\n- `sticky` keeps the header stuck to the top after it reaches it.\n- `fixed` pins the header to the viewport and offsets page content.\n\n`fixed-menus` remains as a compatibility alias for `fixed`.",
54948
+ "description": "Header positioning behavior for the `header` slot.\n\n- `normal` lets the header scroll with content.\n- `sticky` keeps the header stuck to the top after it reaches it.\n- `fixed` pins the header to the viewport and offsets page content.",
54909
54949
  "attribute": "header-position",
54910
54950
  "reflects": true
54911
54951
  },
54912
- {
54913
- "kind": "field",
54914
- "name": "fixedMenus",
54915
- "type": {
54916
- "text": "boolean"
54917
- },
54918
- "privacy": "public",
54919
- "default": "false",
54920
- "description": "Compatibility alias for the original fixed header/sidebar behavior.\nPrefer `header-position=\"fixed\"` for new code.",
54921
- "attribute": "fixed-menus",
54922
- "reflects": true
54923
- },
54924
54952
  {
54925
54953
  "kind": "field",
54926
54954
  "name": "previewOpen",
@@ -55863,18 +55891,9 @@
55863
55891
  "text": "HeaderPosition"
55864
55892
  },
55865
55893
  "default": "\"normal\"",
55866
- "description": "Header positioning behavior for the `header` slot.\n\n- `normal` lets the header scroll with content.\n- `sticky` keeps the header stuck to the top after it reaches it.\n- `fixed` pins the header to the viewport and offsets page content.\n\n`fixed-menus` remains as a compatibility alias for `fixed`.",
55894
+ "description": "Header positioning behavior for the `header` slot.\n\n- `normal` lets the header scroll with content.\n- `sticky` keeps the header stuck to the top after it reaches it.\n- `fixed` pins the header to the viewport and offsets page content.",
55867
55895
  "fieldName": "headerPosition"
55868
55896
  },
55869
- {
55870
- "name": "fixed-menus",
55871
- "type": {
55872
- "text": "boolean"
55873
- },
55874
- "default": "false",
55875
- "description": "Compatibility alias for the original fixed header/sidebar behavior.\nPrefer `header-position=\"fixed\"` for new code.",
55876
- "fieldName": "fixedMenus"
55877
- },
55878
55897
  {
55879
55898
  "name": "preview-open",
55880
55899
  "type": {
@@ -56782,7 +56801,7 @@
56782
56801
  "declarations": [
56783
56802
  {
56784
56803
  "kind": "class",
56785
- "description": "Component for selecting a single option from a list of\noptions. For selecting multiple options, see\n[esp-pick-some](/components/pickers/pick-some/).\n\n```html\n<esp-box>\n <esp-form-item label=\"Pick a fruit\">\n <esp-pick-one>\n <esp-picker-item text=\"Apple\" value=\"apple\" icon=\"apple\"></esp-picker-item>\n <esp-picker-item text=\"Cherry\" value=\"cherry\" icon=\"cherry\"></esp-picker-item>\n <esp-picker-item text=\"Lemon\" value=\"lemon\" icon=\"lemon\"></esp-picker-item>\n </esp-pick-one>\n </esp-form-item>\n</esp-box>\n```\n\n### Initial state\n\nSet the initially selected item using the `selected` attribute\non the `esp-picker-item`:\n\n```html\n<esp-box>\n <esp-form-item label=\"Pick a fruit\">\n <esp-pick-one>\n <esp-picker-item text=\"Apple\" value=\"apple\" icon=\"apple\"></esp-picker-item>\n <esp-picker-item text=\"Cherry\" value=\"cherry\" icon=\"cherry\"></esp-picker-item>\n <esp-picker-item text=\"Lemon\" value=\"lemon\" icon=\"lemon\" selected></esp-picker-item>\n </esp-pick-one>\n </esp-form-item>\n</esp-box>\n```\n\n### Variable-height items\n\nItems with longer text wrap naturally. The menu sizes itself\nto fit the actual content height of each item:\n\n```html\n<div style=\"width: 260px\">\n <esp-box>\n <esp-form-item label=\"Pick a plan\">\n <esp-pick-one>\n <esp-picker-item\n text=\"Basic - includes core features only\"\n value=\"basic\">\n </esp-picker-item>\n <esp-picker-item\n text=\"Pro - adds analytics, priority support, and advanced integrations for growing teams\"\n value=\"pro\">\n </esp-picker-item>\n <esp-picker-item\n text=\"Enterprise - everything in Pro plus SSO, audit logs, dedicated account management, and custom SLAs\"\n value=\"enterprise\">\n </esp-picker-item>\n <esp-picker-item\n text=\"Starter\"\n value=\"starter\">\n </esp-picker-item>\n </esp-pick-one>\n </esp-form-item>\n </esp-box>\n</div>\n```\n\n### Typeahead (local filtering)\n\nAdd the `typeahead` attribute to let the user type to filter\nthe option list. The input becomes editable and the picker\nswitches to a filter icon on focus:\n\n```html\n<esp-box>\n <esp-form-item label=\"Pick a fruit\">\n <esp-pick-one typeahead>\n <esp-picker-item text=\"Apple\" value=\"apple\"></esp-picker-item>\n <esp-picker-item text=\"Cherry\" value=\"cherry\"></esp-picker-item>\n <esp-picker-item text=\"Lemon\" value=\"lemon\"></esp-picker-item>\n </esp-pick-one>\n </esp-form-item>\n</esp-box>\n```\n\n### Typeahead (remote filtering)\n\nSet the `fetchItems` property to a callback that returns\nmatching items from a server. The picker debounces input\n(300 ms by default) and shows a loading indicator while\nthe request is in flight. Use the provided `AbortSignal`\nto cancel stale requests:\n\n```html\n<esp-box>\n <esp-form-item label=\"Search users\">\n <esp-pick-one typeahead placeholder=\"Type a name\\u2026\">\n </esp-pick-one>\n </esp-form-item>\n</esp-box>\n<script>\n const picker = findByTagName(\"esp-pick-one\")[0];\n picker.fetchItems = async (query, signal) => {\n const res = await fetch(\"/assets/data/grid-data.json\", { signal });\n const all = await res.json();\n const q = query.toLowerCase();\n return all\n .filter(u =>\n u.first_name.toLowerCase().includes(q) ||\n u.last_name.toLowerCase().includes(q))\n .slice(0, 20)\n .map(u => ({\n text: `${u.first_name} ${u.last_name}`,\n value: String(u.id),\n }));\n };\n</script>\n```",
56804
+ "description": "Component for selecting a single option from a list of\noptions. For selecting multiple options, see\n[esp-pick-some](/components/pickers/pick-some/).\n\n```html\n<esp-box>\n <esp-form-item label=\"Pick a fruit\">\n <esp-pick-one>\n <esp-picker-item text=\"Apple\" value=\"apple\" icon=\"apple\"></esp-picker-item>\n <esp-picker-item text=\"Cherry\" value=\"cherry\" icon=\"cherry\"></esp-picker-item>\n <esp-picker-item text=\"Lemon\" value=\"lemon\" icon=\"lemon\"></esp-picker-item>\n </esp-pick-one>\n </esp-form-item>\n</esp-box>\n```\n\n### Initial state\n\nSet the initially selected item using the `selected` attribute\non the `esp-picker-item`:\n\n```html\n<esp-box>\n <esp-form-item label=\"Pick a fruit\">\n <esp-pick-one>\n <esp-picker-item text=\"Apple\" value=\"apple\" icon=\"apple\"></esp-picker-item>\n <esp-picker-item text=\"Cherry\" value=\"cherry\" icon=\"cherry\"></esp-picker-item>\n <esp-picker-item text=\"Lemon\" value=\"lemon\" icon=\"lemon\" selected></esp-picker-item>\n </esp-pick-one>\n </esp-form-item>\n</esp-box>\n```\n\nAn item's `text`, `icon`, and `styles` may change after it is selected (a\nplaceholder label replaced once its data loads, say), and the picker shows\nthe new label; the selection follows the item's `value`.\n\n### Variable-height items\n\nItems with longer text wrap naturally. The menu sizes itself\nto fit the actual content height of each item:\n\n```html\n<div style=\"width: 260px\">\n <esp-box>\n <esp-form-item label=\"Pick a plan\">\n <esp-pick-one>\n <esp-picker-item\n text=\"Basic - includes core features only\"\n value=\"basic\">\n </esp-picker-item>\n <esp-picker-item\n text=\"Pro - adds analytics, priority support, and advanced integrations for growing teams\"\n value=\"pro\">\n </esp-picker-item>\n <esp-picker-item\n text=\"Enterprise - everything in Pro plus SSO, audit logs, dedicated account management, and custom SLAs\"\n value=\"enterprise\">\n </esp-picker-item>\n <esp-picker-item\n text=\"Starter\"\n value=\"starter\">\n </esp-picker-item>\n </esp-pick-one>\n </esp-form-item>\n </esp-box>\n</div>\n```\n\n### Typeahead (local filtering)\n\nAdd the `typeahead` attribute to let the user type to filter\nthe option list. The input becomes editable and the picker\nswitches to a filter icon on focus:\n\n```html\n<esp-box>\n <esp-form-item label=\"Pick a fruit\">\n <esp-pick-one typeahead>\n <esp-picker-item text=\"Apple\" value=\"apple\"></esp-picker-item>\n <esp-picker-item text=\"Cherry\" value=\"cherry\"></esp-picker-item>\n <esp-picker-item text=\"Lemon\" value=\"lemon\"></esp-picker-item>\n </esp-pick-one>\n </esp-form-item>\n</esp-box>\n```\n\n### Typeahead (remote filtering)\n\nSet the `fetchItems` property to a callback that returns\nmatching items from a server. The picker debounces input\n(300 ms by default) and shows a loading indicator while\nthe request is in flight. Use the provided `AbortSignal`\nto cancel stale requests:\n\n```html\n<esp-box>\n <esp-form-item label=\"Search users\">\n <esp-pick-one typeahead placeholder=\"Type a name\\u2026\">\n </esp-pick-one>\n </esp-form-item>\n</esp-box>\n<script>\n const picker = findByTagName(\"esp-pick-one\")[0];\n picker.fetchItems = async (query, signal) => {\n const res = await fetch(\"/assets/data/grid-data.json\", { signal });\n const all = await res.json();\n const q = query.toLowerCase();\n return all\n .filter(u =>\n u.first_name.toLowerCase().includes(q) ||\n u.last_name.toLowerCase().includes(q))\n .slice(0, 20)\n .map(u => ({\n text: `${u.first_name} ${u.last_name}`,\n value: String(u.id),\n }));\n };\n</script>\n```",
56786
56805
  "name": "EspalierPickOne",
56787
56806
  "members": [
56788
56807
  {
@@ -57788,6 +57807,28 @@
57788
57807
  "module": "dist/pickers/esp-picker-base.js"
57789
57808
  }
57790
57809
  },
57810
+ {
57811
+ "kind": "field",
57812
+ "name": "extractedElements",
57813
+ "privacy": "private",
57814
+ "default": "new WeakMap<EspalierPickerItem, { index: number; value: string }>()",
57815
+ "description": "The elements of the latest extraction, each with the position and value\nits record was filed under. Each extraction replaces the map, so an\nelement from an earlier one is retired and its changes are ignored.",
57816
+ "inheritedFrom": {
57817
+ "name": "EspalierPickerBase",
57818
+ "module": "dist/pickers/esp-picker-base.js"
57819
+ }
57820
+ },
57821
+ {
57822
+ "kind": "field",
57823
+ "name": "recordElements",
57824
+ "privacy": "private",
57825
+ "default": "new WeakMap<PickerItem, EspalierPickerItem>()",
57826
+ "description": "The element behind each record extraction produced, and behind the\npicker's own copies of those records (see copyPickerItem). A\nrecord the host assigned has none, so an element never patches it.",
57827
+ "inheritedFrom": {
57828
+ "name": "EspalierPickerBase",
57829
+ "module": "dist/pickers/esp-picker-base.js"
57830
+ }
57831
+ },
57791
57832
  {
57792
57833
  "kind": "method",
57793
57834
  "name": "extractSlotItems",
@@ -59012,6 +59053,24 @@
59012
59053
  "module": "dist/pickers/esp-picker-base.js"
59013
59054
  }
59014
59055
  },
59056
+ {
59057
+ "kind": "field",
59058
+ "name": "seenDisplay",
59059
+ "privacy": "private",
59060
+ "default": "new Map<string, Pick<PickerItem, \"text\" | \"icon\" | \"styles\">>()",
59061
+ "description": "The text, icon, and styles last seen on the item carrying each value."
59062
+ },
59063
+ {
59064
+ "kind": "method",
59065
+ "name": "followSelectedDisplay",
59066
+ "privacy": "private",
59067
+ "return": {
59068
+ "type": {
59069
+ "text": "void"
59070
+ }
59071
+ },
59072
+ "description": "A chip follows its item when that item's text, icon, or styles change (a\nplaceholder row replaced by the loaded one, or an item that first arrives\nafter its value was picked from remote results). A chip is left alone\nwhen no item carries its value, when it already shows the item's label,\nor when the item still shows what it showed last time: a re-flag of\n`selected` (from `setSelectedItems`) is not a change, so it never\noverwrites the label of a picked remote result. Nothing is reassigned\nwhen no chip moved."
59073
+ },
59015
59074
  {
59016
59075
  "kind": "method",
59017
59076
  "name": "setSelectedItems",
@@ -59022,8 +59081,13 @@
59022
59081
  "type": {
59023
59082
  "text": "Array<PickerItem>"
59024
59083
  }
59084
+ },
59085
+ {
59086
+ "name": "{ visitor = true }",
59087
+ "default": "{}"
59025
59088
  }
59026
- ]
59089
+ ],
59090
+ "description": "Apply a new selection. A visitor's change validates, emits\n`esp-value-changed`, and keeps focus on the picker; a programmatic item\nupdate (`visitor: false`) syncs the form value quietly, as a\nprogrammatic `value` set does."
59027
59091
  },
59028
59092
  {
59029
59093
  "kind": "field",
@@ -59851,6 +59915,28 @@
59851
59915
  "module": "dist/pickers/esp-picker-base.js"
59852
59916
  }
59853
59917
  },
59918
+ {
59919
+ "kind": "field",
59920
+ "name": "extractedElements",
59921
+ "privacy": "private",
59922
+ "default": "new WeakMap<EspalierPickerItem, { index: number; value: string }>()",
59923
+ "description": "The elements of the latest extraction, each with the position and value\nits record was filed under. Each extraction replaces the map, so an\nelement from an earlier one is retired and its changes are ignored.",
59924
+ "inheritedFrom": {
59925
+ "name": "EspalierPickerBase",
59926
+ "module": "dist/pickers/esp-picker-base.js"
59927
+ }
59928
+ },
59929
+ {
59930
+ "kind": "field",
59931
+ "name": "recordElements",
59932
+ "privacy": "private",
59933
+ "default": "new WeakMap<PickerItem, EspalierPickerItem>()",
59934
+ "description": "The element behind each record extraction produced, and behind the\npicker's own copies of those records (see copyPickerItem). A\nrecord the host assigned has none, so an element never patches it.",
59935
+ "inheritedFrom": {
59936
+ "name": "EspalierPickerBase",
59937
+ "module": "dist/pickers/esp-picker-base.js"
59938
+ }
59939
+ },
59854
59940
  {
59855
59941
  "kind": "method",
59856
59942
  "name": "extractSlotItems",
@@ -61567,6 +61653,20 @@
61567
61653
  "privacy": "private",
61568
61654
  "default": "false"
61569
61655
  },
61656
+ {
61657
+ "kind": "field",
61658
+ "name": "extractedElements",
61659
+ "privacy": "private",
61660
+ "default": "new WeakMap<EspalierPickerItem, { index: number; value: string }>()",
61661
+ "description": "The elements of the latest extraction, each with the position and value\nits record was filed under. Each extraction replaces the map, so an\nelement from an earlier one is retired and its changes are ignored."
61662
+ },
61663
+ {
61664
+ "kind": "field",
61665
+ "name": "recordElements",
61666
+ "privacy": "private",
61667
+ "default": "new WeakMap<PickerItem, EspalierPickerItem>()",
61668
+ "description": "The element behind each record extraction produced, and behind the\npicker's own copies of those records (see copyPickerItem). A\nrecord the host assigned has none, so an element never patches it."
61669
+ },
61570
61670
  {
61571
61671
  "kind": "method",
61572
61672
  "name": "extractSlotItems",
@@ -62504,6 +62604,14 @@
62504
62604
  }
62505
62605
  ],
62506
62606
  "events": []
62607
+ },
62608
+ {
62609
+ "kind": "variable",
62610
+ "name": "slotNodes"
62611
+ },
62612
+ {
62613
+ "kind": "variable",
62614
+ "name": "value"
62507
62615
  }
62508
62616
  ],
62509
62617
  "exports": [
@@ -62747,6 +62855,15 @@
62747
62855
  "default": "null",
62748
62856
  "description": "Scroll position owned by the current internal pick and its parent echo."
62749
62857
  },
62858
+ {
62859
+ "kind": "field",
62860
+ "name": "visitorPicking",
62861
+ "type": {
62862
+ "text": "boolean"
62863
+ },
62864
+ "privacy": "private",
62865
+ "default": "false"
62866
+ },
62750
62867
  {
62751
62868
  "kind": "field",
62752
62869
  "name": "lastPoolFingerprint",
@@ -62999,6 +63116,19 @@
62999
63116
  }
63000
63117
  ]
63001
63118
  },
63119
+ {
63120
+ "kind": "method",
63121
+ "name": "applyPick",
63122
+ "privacy": "private",
63123
+ "parameters": [
63124
+ {
63125
+ "name": "pickerItem",
63126
+ "type": {
63127
+ "text": "PickerItem"
63128
+ }
63129
+ }
63130
+ ]
63131
+ },
63002
63132
  {
63003
63133
  "kind": "method",
63004
63134
  "name": "selectItemByIndex",
@@ -63742,6 +63872,16 @@
63742
63872
  },
63743
63873
  "privacy": "private"
63744
63874
  },
63875
+ {
63876
+ "kind": "field",
63877
+ "name": "_showingRemoteResults",
63878
+ "type": {
63879
+ "text": "boolean"
63880
+ },
63881
+ "privacy": "private",
63882
+ "default": "false",
63883
+ "description": "The last list emitted came from `fetchItems`, not the canonical items."
63884
+ },
63745
63885
  {
63746
63886
  "kind": "method",
63747
63887
  "name": "hostConnected",
@@ -63776,7 +63916,7 @@
63776
63916
  }
63777
63917
  }
63778
63918
  ],
63779
- "description": "Store the canonical (unfiltered) items. Re-filters if a query is active."
63919
+ "description": "Store the canonical (unfiltered) items and re-emit them, re-filtered if a\nquery is active. While a remote result set is shown, only store them: the\ncanonical items must not replace the server's results."
63780
63920
  },
63781
63921
  {
63782
63922
  "kind": "method",
@@ -63794,7 +63934,7 @@
63794
63934
  }
63795
63935
  }
63796
63936
  ],
63797
- "description": "Set or clear the remote fetch callback."
63937
+ "description": "Set or clear the remote fetch callback. A different callback cancels the\nold one's pending request and clears loading, so a late response cannot\nrestore results from a source that is gone; a new callback re-runs an\nactive query."
63798
63938
  },
63799
63939
  {
63800
63940
  "kind": "method",
@@ -63861,6 +64001,15 @@
63861
64001
  "description": "The current search text.",
63862
64002
  "readonly": true
63863
64003
  },
64004
+ {
64005
+ "kind": "field",
64006
+ "name": "showingRemoteResults",
64007
+ "type": {
64008
+ "text": "boolean"
64009
+ },
64010
+ "description": "Whether the shown items are a remote result set rather than canonical ones.",
64011
+ "readonly": true
64012
+ },
63864
64013
  {
63865
64014
  "kind": "field",
63866
64015
  "name": "isRemote",
@@ -63910,6 +64059,25 @@
63910
64059
  }
63911
64060
  }
63912
64061
  },
64062
+ {
64063
+ "kind": "method",
64064
+ "name": "emit",
64065
+ "privacy": "private",
64066
+ "return": {
64067
+ "type": {
64068
+ "text": "void"
64069
+ }
64070
+ },
64071
+ "parameters": [
64072
+ {
64073
+ "name": "items",
64074
+ "type": {
64075
+ "text": "PickerItem[]"
64076
+ }
64077
+ }
64078
+ ],
64079
+ "description": "Emit a canonical (or locally filtered) list."
64080
+ },
63913
64081
  {
63914
64082
  "kind": "method",
63915
64083
  "name": "runRemoteFetch",
@@ -65507,7 +65675,7 @@
65507
65675
  "declarations": [
65508
65676
  {
65509
65677
  "kind": "class",
65510
- "description": "Groups multiple [esp-radio-button](/components/radio-button)\ncomponents and ensures only one can be selected at a time.\nWorks with [esp-form-item](/components/form-item) for label\nand error message integration.\n\n```html\n<esp-box>\n <esp-form-item label=\"Choose a size\">\n <esp-radio-button-group>\n <esp-radio-button value=\"small\">Small</esp-radio-button>\n <esp-radio-button value=\"medium\" checked>Medium</esp-radio-button>\n <esp-radio-button value=\"large\">Large</esp-radio-button>\n </esp-radio-button-group>\n </esp-form-item>\n</esp-box>\n```\n\n```html\n<esp-box>\n <esp-form-item label=\"Disabled group\">\n <esp-radio-button-group disabled>\n <esp-radio-button value=\"a\">Option A</esp-radio-button>\n <esp-radio-button value=\"b\" checked>Option B</esp-radio-button>\n <esp-radio-button value=\"c\">Option C</esp-radio-button>\n </esp-radio-button-group>\n </esp-form-item>\n</esp-box>\n```",
65678
+ "description": "Groups multiple [esp-radio-button](/components/radio-button)\ncomponents and ensures only one can be selected at a time.\nWorks with [esp-form-item](/components/form-item) for label\nand error message integration.\n\n```html\n<esp-box>\n <esp-form-item label=\"Choose a size\">\n <esp-radio-button-group>\n <esp-radio-button value=\"small\">Small</esp-radio-button>\n <esp-radio-button value=\"medium\" checked>Medium</esp-radio-button>\n <esp-radio-button value=\"large\">Large</esp-radio-button>\n </esp-radio-button-group>\n </esp-form-item>\n</esp-box>\n```\n\n```html\n<esp-box>\n <esp-form-item label=\"Disabled group\">\n <esp-radio-button-group disabled>\n <esp-radio-button value=\"a\">Option A</esp-radio-button>\n <esp-radio-button value=\"b\" checked>Option B</esp-radio-button>\n <esp-radio-button value=\"c\">Option C</esp-radio-button>\n </esp-radio-button-group>\n </esp-form-item>\n</esp-box>\n```\n\nInside an `esp-form-item`, the group's `radiogroup` element takes the form\nitem's `label` as its accessible name and its hint as its description.\nWhile the form item shows an error, that element is `aria-invalid=\"true\"`\nand the error joins its description after the hint. Each option keeps its\nown label and is not described separately.",
65511
65679
  "name": "EspalierRadioButtonGroup",
65512
65680
  "slots": [
65513
65681
  {
@@ -65543,18 +65711,15 @@
65543
65711
  },
65544
65712
  {
65545
65713
  "kind": "field",
65546
- "name": "formItemDescription",
65547
- "type": {
65548
- "text": "string | null"
65549
- },
65550
- "privacy": "private",
65551
- "default": "null"
65714
+ "name": "groupElement",
65715
+ "privacy": "private"
65552
65716
  },
65553
65717
  {
65554
65718
  "kind": "field",
65555
- "name": "describedRadios",
65719
+ "name": "formItemDescription",
65556
65720
  "privacy": "private",
65557
- "default": "new Set<EspalierRadioButton>()"
65721
+ "default": "new FormFieldDescriptionController({ host: this, getTarget: () => this.groupElement.value, })",
65722
+ "description": "The wrapping form item names, describes, and marks invalid the\n`radiogroup` element itself, so its label, hint, and error are read\nonce for the group rather than once per option (ESP0232)."
65558
65723
  },
65559
65724
  {
65560
65725
  "kind": "field",
@@ -65675,16 +65840,6 @@
65675
65840
  }
65676
65841
  }
65677
65842
  },
65678
- {
65679
- "kind": "method",
65680
- "name": "applyFormItemDescription",
65681
- "privacy": "private",
65682
- "return": {
65683
- "type": {
65684
- "text": "void"
65685
- }
65686
- }
65687
- },
65688
65843
  {
65689
65844
  "kind": "field",
65690
65845
  "name": "handleItemsSlotChange",
@@ -66691,7 +66846,7 @@
66691
66846
  "declarations": [
66692
66847
  {
66693
66848
  "kind": "class",
66694
- "description": "A radio button control for choosing one option from a set.\nUses Tabler SVG icons for selected and unselected states.\nShould be used inside an\n[esp-radio-button-group](/components/radio-button/group)\nfor single-select behavior.\n\n```html\n<esp-box>\n <esp-form-item label=\"Favorite color\">\n <esp-radio-button-group>\n <esp-radio-button value=\"red\">Red</esp-radio-button>\n <esp-radio-button value=\"green\">Green</esp-radio-button>\n <esp-radio-button value=\"blue\">Blue</esp-radio-button>\n </esp-radio-button-group>\n </esp-form-item>\n</esp-box>\n```\n\n```html\n<esp-box>\n <esp-radio-button-group>\n <esp-radio-button checked value=\"small\">Small</esp-radio-button>\n <esp-radio-button value=\"medium\">Medium</esp-radio-button>\n <esp-radio-button disabled value=\"large\">Large</esp-radio-button>\n </esp-radio-button-group>\n</esp-box>\n```",
66849
+ "description": "A radio button control for choosing one option from a set.\nUses Tabler SVG icons for selected and unselected states.\nShould be used inside an\n[esp-radio-button-group](/components/radio-button/group)\nfor single-select behavior.\n\n```html\n<esp-box>\n <esp-form-item label=\"Favorite color\">\n <esp-radio-button-group>\n <esp-radio-button value=\"red\">Red</esp-radio-button>\n <esp-radio-button value=\"green\">Green</esp-radio-button>\n <esp-radio-button value=\"blue\">Blue</esp-radio-button>\n </esp-radio-button-group>\n </esp-form-item>\n</esp-box>\n```\n\n```html\n<esp-box>\n <esp-radio-button-group>\n <esp-radio-button checked value=\"small\">Small</esp-radio-button>\n <esp-radio-button value=\"medium\">Medium</esp-radio-button>\n <esp-radio-button disabled value=\"large\">Large</esp-radio-button>\n </esp-radio-button-group>\n</esp-box>\n```\n\n### Group name and errors\n\nInside an `esp-form-item`, the group's `radiogroup` takes the form item's\n`label` as its accessible name and its hint as its description, so no\nwrapper `role=\"group\"` or `aria-labelledby` is needed. While the form item\nshows an error, the `radiogroup` is `aria-invalid=\"true\"`, the error joins\nits description after the hint, and the error is announced once. Each\nradio button keeps its own label. A choice clears an error set through the\nform item's `error` or `errorPool`, since a radio choice cannot be undone;\npick an option below to see it.\n\n```html\n<esp-box>\n <esp-form-item label=\"Delivery speed\" hint=\"Express ships the same day.\" error=\"Choose a delivery speed.\">\n <esp-radio-button-group required>\n <esp-radio-button value=\"standard\">Standard</esp-radio-button>\n <esp-radio-button value=\"express\">Express</esp-radio-button>\n </esp-radio-button-group>\n </esp-form-item>\n</esp-box>\n```",
66695
66850
  "name": "EspalierRadioButton",
66696
66851
  "cssProperties": [
66697
66852
  {
@@ -70605,7 +70760,7 @@
70605
70760
  }
70606
70761
  }
70607
70762
  ],
70608
- "description": "Inject (or replace) a Google Fonts `<link>` for a given slot.\n\nEach slot (`body`, `headings`, `brand`, `monospace`) gets at most one\n`<link>` scoped to this root instance by its correlationId,\nidentified by `data-esp-font=\"<correlationId>-<slot>\"`.\nThis ensures multiple `<esp-root>` instances on the same page\ndo not overwrite each other's font links."
70763
+ "description": "Inject (or replace) a Google Fonts `<link>` for a given slot.\n\nEach slot (`body`, `headings`, `brand`, `monospace`) gets at most one\n`<link>` scoped to this root instance by its correlationId,\nowned by the key `root:<correlationId>-<slot>` (`data-esp-google-font-owner`).\nThis ensures multiple `<esp-root>` instances on the same page\ndo not overwrite each other's font links."
70609
70764
  },
70610
70765
  {
70611
70766
  "kind": "method",
@@ -77099,6 +77254,46 @@
77099
77254
  }
77100
77255
  ]
77101
77256
  },
77257
+ {
77258
+ "kind": "javascript-module",
77259
+ "path": "dist/shared/enum-converter.js",
77260
+ "declarations": [
77261
+ {
77262
+ "kind": "function",
77263
+ "name": "enumConverter",
77264
+ "return": {
77265
+ "type": {
77266
+ "text": "ComplexAttributeConverter<T>"
77267
+ }
77268
+ },
77269
+ "parameters": [
77270
+ {
77271
+ "name": "values",
77272
+ "type": {
77273
+ "text": "ReadonlySet<T>"
77274
+ }
77275
+ },
77276
+ {
77277
+ "name": "fallback",
77278
+ "type": {
77279
+ "text": "T"
77280
+ }
77281
+ }
77282
+ ],
77283
+ "description": "An attribute converter for a closed vocabulary: a value outside `values`\n(or a removed attribute) reads as `fallback`, and only members are\nreflected. Pair it with a `willUpdate` check for values set as properties."
77284
+ }
77285
+ ],
77286
+ "exports": [
77287
+ {
77288
+ "kind": "js",
77289
+ "name": "enumConverter",
77290
+ "declaration": {
77291
+ "name": "enumConverter",
77292
+ "module": "dist/shared/enum-converter.js"
77293
+ }
77294
+ }
77295
+ ]
77296
+ },
77102
77297
  {
77103
77298
  "kind": "javascript-module",
77104
77299
  "path": "dist/shared/esp-bus.js",
@@ -77869,9 +78064,9 @@
77869
78064
  "kind": "variable",
77870
78065
  "name": "ESP_EVENTS",
77871
78066
  "type": {
77872
- "text": "{\n // ── Shared cross-control protocols ──────────────────────────\n VALUE_CHANGED: \"esp-value-changed\",\n VALIDITY_CHANGED: VALIDITY_CHANGED_EVENT,\n CLICKED: \"esp-clicked\",\n\n // ── Action menu ─────────────────────────────────────────────\n ACTION_MENU_SELECT: \"esp-action-menu-select\",\n\n // ── Burger ──────────────────────────────────────────────────\n BURGER_OPENED: \"esp-burger-opened\",\n BURGER_CLOSED: \"esp-burger-closed\",\n\n // ── Details ─────────────────────────────────────────────────\n DETAILS_TOGGLE: \"esp-details-toggle\",\n DETAILS_GROUP_CHANGE: \"esp-details-group-change\",\n\n // ── Dialog ──────────────────────────────────────────────────\n DIALOG_OPENED: \"esp-dialog-opened\",\n DIALOG_CLOSING: \"esp-dialog-closing\",\n DIALOG_CLOSED: \"esp-dialog-closed\",\n\n // ── File upload ─────────────────────────────────────────────\n FILE_UPLOAD_FILES_SELECTED: \"esp-file-upload-files-selected\",\n\n // ── Flyout ──────────────────────────────────────────────────\n FLYOUT_OPENED: \"esp-flyout-opened\",\n FLYOUT_CLOSED: \"esp-flyout-closed\",\n FLYOUT_STATE_CHANGED: \"esp-flyout-state-changed\",\n\n // ── Focus picker ────────────────────────────────────────────\n FOCUS_PICKER_CHANGED: \"esp-focus-picker-changed\",\n\n // ── Form ────────────────────────────────────────────────────\n FORM_DIALOG_CLOSE_REQUESTED: \"esp-form-dialog-close-requested\",\n FORM_SUBMIT: \"esp-form-submit\",\n FORM_SUBMIT_RESPONSE: \"esp-form-submit-response\",\n FORM_SUBMIT_ERROR: \"esp-form-submit-error\",\n\n // ── Grid ────────────────────────────────────────────────────\n GRID_EVENT: \"esp-grid-event\",\n GRID_LOAD_START: \"esp-grid-load-start\",\n GRID_LOAD_SUCCESS: \"esp-grid-load-success\",\n GRID_LOAD_ERROR: \"esp-grid-load-error\",\n GRID_ITEMS_CHANGED: \"esp-grid-items-changed\",\n\n // ── Header ──────────────────────────────────────────────────\n HEADER_THEME_TOGGLE: \"esp-header-theme-toggle\",\n\n // ── Image upload ────────────────────────────────────────────\n IMAGE_UPLOAD_FILE_SELECTED: \"esp-image-upload-file-selected\",\n IMAGE_UPLOAD_FILE_REMOVED: \"esp-image-upload-file-removed\",\n IMAGE_UPLOAD_FILES_REJECTED: \"esp-image-upload-files-rejected\",\n IMAGE_UPLOAD_RETRY: \"esp-image-upload-retry\",\n IMAGE_UPLOAD_IMAGES_REORDERED: \"esp-image-upload-images-reordered\",\n\n // ── Info / input / lightbox ─────────────────────────────────\n INFO_DESTROY: \"esp-info-destroy\",\n INPUT_ICON_CLICKED: \"esp-input-icon-clicked\",\n LIGHTBOX_CHANGED: \"esp-lightbox-changed\",\n\n // ── Menu ────────────────────────────────────────────────────\n MENU_GROUP_TOGGLE: \"esp-menu-group-toggle\",\n MENU_DRAWER_OPENED: \"esp-menu-drawer-opened\",\n MENU_DRAWER_CLOSED: \"esp-menu-drawer-closed\",\n MENU_DRAWER_PRESENTED: \"esp-menu-drawer-presented\",\n\n // ── Page / picker menu / popover ────────────────────────────\n PAGE_WORKSPACE_RESIZE: \"esp-page-workspace-resize\",\n PICKER_MENU_SELECTION_CHANGED: \"esp-picker-menu-selection-changed\",\n PICKER_MENU_CLOSE_REQUESTED: \"esp-picker-menu-close-requested\",\n PICKER_MENU_DISMISS_REQUESTED: \"esp-picker-menu-dismiss-requested\",\n PICKER_MENU_RANGE_CHANGED: \"esp-picker-menu-range-changed\",\n POPOVER_OPENED: \"esp-popover-opened\",\n POPOVER_CLOSED: \"esp-popover-closed\",\n\n // ── Search ──────────────────────────────────────────────────\n SEARCH_REQUESTED: \"esp-search-requested\",\n SEARCH_RESULT_SELECTED: \"esp-search-result-selected\",\n SEARCH_CLOSED: \"esp-search-closed\",\n\n // ── Tabs ────────────────────────────────────────────────────\n TAB_GROUP_CHANGED: \"esp-tab-group-changed\",\n\n // ── Tree ────────────────────────────────────────────────────\n TREE_SELECT: \"esp-tree-select\",\n TREE_TOGGLE: \"esp-tree-toggle\",\n TREE_GRAFT_REQUEST: \"esp-tree-graft-request\",\n TREE_GRAFT: \"esp-tree-graft\",\n TREE_EDIT: \"esp-tree-edit\",\n TREE_DELETE: \"esp-tree-delete\",\n TREE_MOVE_UP: \"esp-tree-move-up\",\n TREE_MOVE_DOWN: \"esp-tree-move-down\",\n TREE_CUT: \"esp-tree-cut\",\n TREE_PASTE_CHILD: \"esp-tree-paste-child\",\n TREE_GRAFT_INPUT: \"esp-tree-graft-input\",\n}"
78067
+ "text": "{\n // ── Shared cross-control protocols ──────────────────────────\n VALUE_CHANGED: \"esp-value-changed\",\n VALIDITY_CHANGED: VALIDITY_CHANGED_EVENT,\n CLICKED: \"esp-clicked\",\n\n // ── Action menu ─────────────────────────────────────────────\n ACTION_MENU_SELECT: \"esp-action-menu-select\",\n\n // ── Burger ──────────────────────────────────────────────────\n BURGER_OPENED: \"esp-burger-opened\",\n BURGER_CLOSED: \"esp-burger-closed\",\n\n // ── Details ─────────────────────────────────────────────────\n DETAILS_TOGGLE: \"esp-details-toggle\",\n DETAILS_GROUP_CHANGE: \"esp-details-group-change\",\n\n // ── Dialog ──────────────────────────────────────────────────\n DIALOG_OPENED: \"esp-dialog-opened\",\n DIALOG_CLOSING: \"esp-dialog-closing\",\n DIALOG_CLOSED: \"esp-dialog-closed\",\n\n // ── File upload ─────────────────────────────────────────────\n FILE_UPLOAD_FILES_SELECTED: \"esp-file-upload-files-selected\",\n\n // ── Flyout ──────────────────────────────────────────────────\n FLYOUT_OPENED: \"esp-flyout-opened\",\n FLYOUT_CLOSED: \"esp-flyout-closed\",\n FLYOUT_STATE_CHANGED: \"esp-flyout-state-changed\",\n\n // ── Focus picker ────────────────────────────────────────────\n FOCUS_PICKER_CHANGED: \"esp-focus-picker-changed\",\n\n // ── Form ────────────────────────────────────────────────────\n FORM_DIALOG_CLOSE_REQUESTED: \"esp-form-dialog-close-requested\",\n FORM_SUBMIT: \"esp-form-submit\",\n FORM_SUBMIT_RESPONSE: \"esp-form-submit-response\",\n FORM_SUBMIT_ERROR: \"esp-form-submit-error\",\n\n // ── Grid ────────────────────────────────────────────────────\n GRID_EVENT: \"esp-grid-event\",\n GRID_LOAD_START: \"esp-grid-load-start\",\n GRID_LOAD_SUCCESS: \"esp-grid-load-success\",\n GRID_LOAD_ERROR: \"esp-grid-load-error\",\n GRID_ITEMS_CHANGED: \"esp-grid-items-changed\",\n\n // ── Header ──────────────────────────────────────────────────\n HEADER_THEME_TOGGLE: \"esp-header-theme-toggle\",\n\n // ── Image upload ────────────────────────────────────────────\n IMAGE_UPLOAD_FILE_SELECTED: \"esp-image-upload-file-selected\",\n IMAGE_UPLOAD_FILE_REMOVED: \"esp-image-upload-file-removed\",\n IMAGE_UPLOAD_FILES_REJECTED: \"esp-image-upload-files-rejected\",\n IMAGE_UPLOAD_RETRY: \"esp-image-upload-retry\",\n IMAGE_UPLOAD_IMAGES_REORDERED: \"esp-image-upload-images-reordered\",\n\n // ── Info / input / lightbox ─────────────────────────────────\n INFO_DESTROY: \"esp-info-destroy\",\n INPUT_ICON_CLICKED: \"esp-input-icon-clicked\",\n LIGHTBOX_CHANGED: \"esp-lightbox-changed\",\n\n // ── Menu ────────────────────────────────────────────────────\n MENU_GROUP_TOGGLE: \"esp-menu-group-toggle\",\n MENU_DRAWER_OPENED: \"esp-menu-drawer-opened\",\n MENU_DRAWER_CLOSED: \"esp-menu-drawer-closed\",\n MENU_DRAWER_PRESENTED: \"esp-menu-drawer-presented\",\n\n // ── Page / picker menu / popover ────────────────────────────\n PAGE_WORKSPACE_RESIZE: \"esp-page-workspace-resize\",\n PICKER_MENU_SELECTION_CHANGED: \"esp-picker-menu-selection-changed\",\n PICKER_MENU_CLOSE_REQUESTED: \"esp-picker-menu-close-requested\",\n PICKER_MENU_DISMISS_REQUESTED: \"esp-picker-menu-dismiss-requested\",\n PICKER_MENU_RANGE_CHANGED: \"esp-picker-menu-range-changed\",\n POPOVER_OPENED: \"esp-popover-opened\",\n POPOVER_CLOSED: \"esp-popover-closed\",\n\n // ── Search ──────────────────────────────────────────────────\n SEARCH_REQUESTED: \"esp-search-requested\",\n SEARCH_RESULT_SELECTED: \"esp-search-result-selected\",\n SEARCH_CLOSED: \"esp-search-closed\",\n\n // ── Tabs ────────────────────────────────────────────────────\n TAB_GROUP_CHANGED: \"esp-tab-group-changed\",\n\n // ── Tree ────────────────────────────────────────────────────\n TREE_SELECT: \"esp-tree-select\",\n TREE_TOGGLE: \"esp-tree-toggle\",\n TREE_GRAFT_REQUEST: \"esp-tree-graft-request\",\n TREE_GRAFT: \"esp-tree-graft\",\n TREE_EDIT: \"esp-tree-edit\",\n TREE_DELETE: \"esp-tree-delete\",\n TREE_MOVE_UP: \"esp-tree-move-up\",\n TREE_MOVE_DOWN: \"esp-tree-move-down\",\n TREE_CUT: \"esp-tree-cut\",\n TREE_PASTE_CHILD: \"esp-tree-paste-child\",\n TREE_GRAFT_INPUT: \"esp-tree-graft-input\",\n\n // ── Video ───────────────────────────────────────────────────\n VIDEO_READY: \"esp-video-ready\",\n VIDEO_PLAY: \"esp-video-play\",\n VIDEO_ERROR: \"esp-video-error\",\n}"
77873
78068
  },
77874
- "default": "{ // ── Shared cross-control protocols ────────────────────────── VALUE_CHANGED: \"esp-value-changed\", VALIDITY_CHANGED: VALIDITY_CHANGED_EVENT, CLICKED: \"esp-clicked\", // ── Action menu ───────────────────────────────────────────── ACTION_MENU_SELECT: \"esp-action-menu-select\", // ── Burger ────────────────────────────────────────────────── BURGER_OPENED: \"esp-burger-opened\", BURGER_CLOSED: \"esp-burger-closed\", // ── Details ───────────────────────────────────────────────── DETAILS_TOGGLE: \"esp-details-toggle\", DETAILS_GROUP_CHANGE: \"esp-details-group-change\", // ── Dialog ────────────────────────────────────────────────── DIALOG_OPENED: \"esp-dialog-opened\", DIALOG_CLOSING: \"esp-dialog-closing\", DIALOG_CLOSED: \"esp-dialog-closed\", // ── File upload ───────────────────────────────────────────── FILE_UPLOAD_FILES_SELECTED: \"esp-file-upload-files-selected\", // ── Flyout ────────────────────────────────────────────────── FLYOUT_OPENED: \"esp-flyout-opened\", FLYOUT_CLOSED: \"esp-flyout-closed\", FLYOUT_STATE_CHANGED: \"esp-flyout-state-changed\", // ── Focus picker ──────────────────────────────────────────── FOCUS_PICKER_CHANGED: \"esp-focus-picker-changed\", // ── Form ──────────────────────────────────────────────────── FORM_DIALOG_CLOSE_REQUESTED: \"esp-form-dialog-close-requested\", FORM_SUBMIT: \"esp-form-submit\", FORM_SUBMIT_RESPONSE: \"esp-form-submit-response\", FORM_SUBMIT_ERROR: \"esp-form-submit-error\", // ── Grid ──────────────────────────────────────────────────── GRID_EVENT: \"esp-grid-event\", GRID_LOAD_START: \"esp-grid-load-start\", GRID_LOAD_SUCCESS: \"esp-grid-load-success\", GRID_LOAD_ERROR: \"esp-grid-load-error\", GRID_ITEMS_CHANGED: \"esp-grid-items-changed\", // ── Header ────────────────────────────────────────────────── HEADER_THEME_TOGGLE: \"esp-header-theme-toggle\", // ── Image upload ──────────────────────────────────────────── IMAGE_UPLOAD_FILE_SELECTED: \"esp-image-upload-file-selected\", IMAGE_UPLOAD_FILE_REMOVED: \"esp-image-upload-file-removed\", IMAGE_UPLOAD_FILES_REJECTED: \"esp-image-upload-files-rejected\", IMAGE_UPLOAD_RETRY: \"esp-image-upload-retry\", IMAGE_UPLOAD_IMAGES_REORDERED: \"esp-image-upload-images-reordered\", // ── Info / input / lightbox ───────────────────────────────── INFO_DESTROY: \"esp-info-destroy\", INPUT_ICON_CLICKED: \"esp-input-icon-clicked\", LIGHTBOX_CHANGED: \"esp-lightbox-changed\", // ── Menu ──────────────────────────────────────────────────── MENU_GROUP_TOGGLE: \"esp-menu-group-toggle\", MENU_DRAWER_OPENED: \"esp-menu-drawer-opened\", MENU_DRAWER_CLOSED: \"esp-menu-drawer-closed\", MENU_DRAWER_PRESENTED: \"esp-menu-drawer-presented\", // ── Page / picker menu / popover ──────────────────────────── PAGE_WORKSPACE_RESIZE: \"esp-page-workspace-resize\", PICKER_MENU_SELECTION_CHANGED: \"esp-picker-menu-selection-changed\", PICKER_MENU_CLOSE_REQUESTED: \"esp-picker-menu-close-requested\", PICKER_MENU_DISMISS_REQUESTED: \"esp-picker-menu-dismiss-requested\", PICKER_MENU_RANGE_CHANGED: \"esp-picker-menu-range-changed\", POPOVER_OPENED: \"esp-popover-opened\", POPOVER_CLOSED: \"esp-popover-closed\", // ── Search ────────────────────────────────────────────────── SEARCH_REQUESTED: \"esp-search-requested\", SEARCH_RESULT_SELECTED: \"esp-search-result-selected\", SEARCH_CLOSED: \"esp-search-closed\", // ── Tabs ──────────────────────────────────────────────────── TAB_GROUP_CHANGED: \"esp-tab-group-changed\", // ── Tree ──────────────────────────────────────────────────── TREE_SELECT: \"esp-tree-select\", TREE_TOGGLE: \"esp-tree-toggle\", TREE_GRAFT_REQUEST: \"esp-tree-graft-request\", TREE_GRAFT: \"esp-tree-graft\", TREE_EDIT: \"esp-tree-edit\", TREE_DELETE: \"esp-tree-delete\", TREE_MOVE_UP: \"esp-tree-move-up\", TREE_MOVE_DOWN: \"esp-tree-move-down\", TREE_CUT: \"esp-tree-cut\", TREE_PASTE_CHILD: \"esp-tree-paste-child\", TREE_GRAFT_INPUT: \"esp-tree-graft-input\", }",
78069
+ "default": "{ // ── Shared cross-control protocols ────────────────────────── VALUE_CHANGED: \"esp-value-changed\", VALIDITY_CHANGED: VALIDITY_CHANGED_EVENT, CLICKED: \"esp-clicked\", // ── Action menu ───────────────────────────────────────────── ACTION_MENU_SELECT: \"esp-action-menu-select\", // ── Burger ────────────────────────────────────────────────── BURGER_OPENED: \"esp-burger-opened\", BURGER_CLOSED: \"esp-burger-closed\", // ── Details ───────────────────────────────────────────────── DETAILS_TOGGLE: \"esp-details-toggle\", DETAILS_GROUP_CHANGE: \"esp-details-group-change\", // ── Dialog ────────────────────────────────────────────────── DIALOG_OPENED: \"esp-dialog-opened\", DIALOG_CLOSING: \"esp-dialog-closing\", DIALOG_CLOSED: \"esp-dialog-closed\", // ── File upload ───────────────────────────────────────────── FILE_UPLOAD_FILES_SELECTED: \"esp-file-upload-files-selected\", // ── Flyout ────────────────────────────────────────────────── FLYOUT_OPENED: \"esp-flyout-opened\", FLYOUT_CLOSED: \"esp-flyout-closed\", FLYOUT_STATE_CHANGED: \"esp-flyout-state-changed\", // ── Focus picker ──────────────────────────────────────────── FOCUS_PICKER_CHANGED: \"esp-focus-picker-changed\", // ── Form ──────────────────────────────────────────────────── FORM_DIALOG_CLOSE_REQUESTED: \"esp-form-dialog-close-requested\", FORM_SUBMIT: \"esp-form-submit\", FORM_SUBMIT_RESPONSE: \"esp-form-submit-response\", FORM_SUBMIT_ERROR: \"esp-form-submit-error\", // ── Grid ──────────────────────────────────────────────────── GRID_EVENT: \"esp-grid-event\", GRID_LOAD_START: \"esp-grid-load-start\", GRID_LOAD_SUCCESS: \"esp-grid-load-success\", GRID_LOAD_ERROR: \"esp-grid-load-error\", GRID_ITEMS_CHANGED: \"esp-grid-items-changed\", // ── Header ────────────────────────────────────────────────── HEADER_THEME_TOGGLE: \"esp-header-theme-toggle\", // ── Image upload ──────────────────────────────────────────── IMAGE_UPLOAD_FILE_SELECTED: \"esp-image-upload-file-selected\", IMAGE_UPLOAD_FILE_REMOVED: \"esp-image-upload-file-removed\", IMAGE_UPLOAD_FILES_REJECTED: \"esp-image-upload-files-rejected\", IMAGE_UPLOAD_RETRY: \"esp-image-upload-retry\", IMAGE_UPLOAD_IMAGES_REORDERED: \"esp-image-upload-images-reordered\", // ── Info / input / lightbox ───────────────────────────────── INFO_DESTROY: \"esp-info-destroy\", INPUT_ICON_CLICKED: \"esp-input-icon-clicked\", LIGHTBOX_CHANGED: \"esp-lightbox-changed\", // ── Menu ──────────────────────────────────────────────────── MENU_GROUP_TOGGLE: \"esp-menu-group-toggle\", MENU_DRAWER_OPENED: \"esp-menu-drawer-opened\", MENU_DRAWER_CLOSED: \"esp-menu-drawer-closed\", MENU_DRAWER_PRESENTED: \"esp-menu-drawer-presented\", // ── Page / picker menu / popover ──────────────────────────── PAGE_WORKSPACE_RESIZE: \"esp-page-workspace-resize\", PICKER_MENU_SELECTION_CHANGED: \"esp-picker-menu-selection-changed\", PICKER_MENU_CLOSE_REQUESTED: \"esp-picker-menu-close-requested\", PICKER_MENU_DISMISS_REQUESTED: \"esp-picker-menu-dismiss-requested\", PICKER_MENU_RANGE_CHANGED: \"esp-picker-menu-range-changed\", POPOVER_OPENED: \"esp-popover-opened\", POPOVER_CLOSED: \"esp-popover-closed\", // ── Search ────────────────────────────────────────────────── SEARCH_REQUESTED: \"esp-search-requested\", SEARCH_RESULT_SELECTED: \"esp-search-result-selected\", SEARCH_CLOSED: \"esp-search-closed\", // ── Tabs ──────────────────────────────────────────────────── TAB_GROUP_CHANGED: \"esp-tab-group-changed\", // ── Tree ──────────────────────────────────────────────────── TREE_SELECT: \"esp-tree-select\", TREE_TOGGLE: \"esp-tree-toggle\", TREE_GRAFT_REQUEST: \"esp-tree-graft-request\", TREE_GRAFT: \"esp-tree-graft\", TREE_EDIT: \"esp-tree-edit\", TREE_DELETE: \"esp-tree-delete\", TREE_MOVE_UP: \"esp-tree-move-up\", TREE_MOVE_DOWN: \"esp-tree-move-down\", TREE_CUT: \"esp-tree-cut\", TREE_PASTE_CHILD: \"esp-tree-paste-child\", TREE_GRAFT_INPUT: \"esp-tree-graft-input\", // ── Video ─────────────────────────────────────────────────── VIDEO_READY: \"esp-video-ready\", VIDEO_PLAY: \"esp-video-play\", VIDEO_ERROR: \"esp-video-error\", }",
77875
78070
  "description": "Canonical event name strings for every CustomEvent in the library.\n\nUse these instead of raw strings to catch typos at compile time:\n\n```ts\nel.addEventListener(ESP_EVENTS.VALUE_CHANGED, handler);\n```"
77876
78071
  }
77877
78072
  ],
@@ -78044,6 +78239,46 @@
78044
78239
  "module": "../header/esp-header.js"
78045
78240
  }
78046
78241
  },
78242
+ {
78243
+ "kind": "js",
78244
+ "name": "VideoErrorDetail",
78245
+ "declaration": {
78246
+ "name": "VideoErrorDetail",
78247
+ "module": "../video/esp-video.js"
78248
+ }
78249
+ },
78250
+ {
78251
+ "kind": "js",
78252
+ "name": "VideoErrorReason",
78253
+ "declaration": {
78254
+ "name": "VideoErrorReason",
78255
+ "module": "../video/esp-video.js"
78256
+ }
78257
+ },
78258
+ {
78259
+ "kind": "js",
78260
+ "name": "VideoPlayDetail",
78261
+ "declaration": {
78262
+ "name": "VideoPlayDetail",
78263
+ "module": "../video/esp-video.js"
78264
+ }
78265
+ },
78266
+ {
78267
+ "kind": "js",
78268
+ "name": "VideoReadyDetail",
78269
+ "declaration": {
78270
+ "name": "VideoReadyDetail",
78271
+ "module": "../video/esp-video.js"
78272
+ }
78273
+ },
78274
+ {
78275
+ "kind": "js",
78276
+ "name": "VideoSourceMode",
78277
+ "declaration": {
78278
+ "name": "VideoSourceMode",
78279
+ "module": "../video/esp-video.js"
78280
+ }
78281
+ },
78047
78282
  {
78048
78283
  "kind": "js",
78049
78284
  "name": "PageWorkspaceResizeDetail",
@@ -79015,7 +79250,7 @@
79015
79250
  "declarations": [
79016
79251
  {
79017
79252
  "kind": "class",
79018
- "description": "Mirrors `esp-form-item` text into the same shadow root as a form\ncontrol's real focus target. ARIA ID references do not cross shadow\nboundaries, so neither the form item's hint nor its visible label can\nreach an inner input, textarea, or ARIA control from outside.\n\nTwo mirrors are maintained:\n\n- the hint, as a hidden span referenced from the target's\n `aria-describedby` (accessible description), and\n- the label, written directly as the target's `aria-label`\n (accessible name), so role/label queries — assistive technology and\n Playwright's `getByLabel`/`getByRole(..., { name })` alike — resolve\n the control without shadow-DOM knowledge (ESP0156).\n\nA name the component authored on its own target is never overwritten:\nonly an `aria-label` this controller wrote is updated or removed.",
79253
+ "description": "Mirrors `esp-form-item` text into the same shadow root as a form\ncontrol's real focus target. ARIA ID references do not cross shadow\nboundaries, so neither the form item's hint nor its visible label can\nreach an inner input, textarea, or ARIA control from outside.\n\nThree mirrors are maintained:\n\n- the description (the hint, followed by any error or warning), as a\n hidden span referenced from the target's `aria-describedby`,\n- the label, written directly as the target's `aria-label`\n (accessible name), so role/label queries — assistive technology and\n Playwright's `getByLabel`/`getByRole(..., { name })` alike — resolve\n the control without shadow-DOM knowledge (ESP0156), and\n- the error state, as `aria-invalid=\"true\"` on the target while the\n form item shows an error (ESP0232).\n\nA name or invalid state the component authored on its own target is\nnever overwritten: only attributes this controller wrote are updated or\nremoved.",
79019
79254
  "name": "FormFieldDescriptionController",
79020
79255
  "members": [
79021
79256
  {
@@ -79061,6 +79296,25 @@
79061
79296
  "privacy": "private",
79062
79297
  "default": "null"
79063
79298
  },
79299
+ {
79300
+ "kind": "field",
79301
+ "name": "invalid",
79302
+ "type": {
79303
+ "text": "boolean"
79304
+ },
79305
+ "privacy": "private",
79306
+ "default": "false"
79307
+ },
79308
+ {
79309
+ "kind": "field",
79310
+ "name": "invalidTarget",
79311
+ "type": {
79312
+ "text": "HTMLElement | null"
79313
+ },
79314
+ "privacy": "private",
79315
+ "default": "null",
79316
+ "description": "The target this controller set `aria-invalid` on, if any."
79317
+ },
79064
79318
  {
79065
79319
  "kind": "field",
79066
79320
  "name": "target",
@@ -79133,6 +79387,24 @@
79133
79387
  ],
79134
79388
  "description": "Set or clear the accessible name supplied by the wrapping form item."
79135
79389
  },
79390
+ {
79391
+ "kind": "method",
79392
+ "name": "setInvalid",
79393
+ "return": {
79394
+ "type": {
79395
+ "text": "void"
79396
+ }
79397
+ },
79398
+ "parameters": [
79399
+ {
79400
+ "name": "invalid",
79401
+ "type": {
79402
+ "text": "boolean"
79403
+ }
79404
+ }
79405
+ ],
79406
+ "description": "Mark the target invalid while the wrapping form item shows an error."
79407
+ },
79136
79408
  {
79137
79409
  "kind": "method",
79138
79410
  "name": "sync",
@@ -79143,6 +79415,27 @@
79143
79415
  }
79144
79416
  }
79145
79417
  },
79418
+ {
79419
+ "kind": "method",
79420
+ "name": "syncInvalid",
79421
+ "privacy": "private",
79422
+ "return": {
79423
+ "type": {
79424
+ "text": "void"
79425
+ }
79426
+ },
79427
+ "description": "Write `aria-invalid=\"true\"` while invalid. A value the component set on\nits own target is left alone, and only a value this controller wrote is\nremoved."
79428
+ },
79429
+ {
79430
+ "kind": "method",
79431
+ "name": "removeAppliedInvalid",
79432
+ "privacy": "private",
79433
+ "return": {
79434
+ "type": {
79435
+ "text": "void"
79436
+ }
79437
+ }
79438
+ },
79146
79439
  {
79147
79440
  "kind": "method",
79148
79441
  "name": "syncDescription",
@@ -79233,6 +79526,117 @@
79233
79526
  }
79234
79527
  ]
79235
79528
  },
79529
+ {
79530
+ "kind": "javascript-module",
79531
+ "path": "dist/shared/form-item-relay.js",
79532
+ "declarations": [
79533
+ {
79534
+ "kind": "class",
79535
+ "description": "Passes what a wrapping `esp-form-item` hands a composite control (its\ndescription, label, and invalid state) on to the inner control that takes\nfocus, and re-delivers it whenever that inner control is re-rendered.\n`esp-font-picker` and `esp-image-picker` each wrap an `esp-pick-one`.",
79536
+ "name": "FormItemRelay",
79537
+ "members": [
79538
+ {
79539
+ "kind": "field",
79540
+ "name": "description",
79541
+ "type": {
79542
+ "text": "string | null"
79543
+ },
79544
+ "privacy": "private",
79545
+ "default": "null"
79546
+ },
79547
+ {
79548
+ "kind": "field",
79549
+ "name": "label",
79550
+ "type": {
79551
+ "text": "string | null"
79552
+ },
79553
+ "privacy": "private",
79554
+ "default": "null"
79555
+ },
79556
+ {
79557
+ "kind": "field",
79558
+ "name": "invalid",
79559
+ "type": {
79560
+ "text": "boolean"
79561
+ },
79562
+ "privacy": "private",
79563
+ "default": "false"
79564
+ },
79565
+ {
79566
+ "kind": "method",
79567
+ "name": "setDescription",
79568
+ "return": {
79569
+ "type": {
79570
+ "text": "void"
79571
+ }
79572
+ },
79573
+ "parameters": [
79574
+ {
79575
+ "name": "description",
79576
+ "type": {
79577
+ "text": "string | null"
79578
+ }
79579
+ }
79580
+ ]
79581
+ },
79582
+ {
79583
+ "kind": "method",
79584
+ "name": "setLabel",
79585
+ "return": {
79586
+ "type": {
79587
+ "text": "void"
79588
+ }
79589
+ },
79590
+ "parameters": [
79591
+ {
79592
+ "name": "label",
79593
+ "type": {
79594
+ "text": "string | null"
79595
+ }
79596
+ }
79597
+ ]
79598
+ },
79599
+ {
79600
+ "kind": "method",
79601
+ "name": "setInvalid",
79602
+ "return": {
79603
+ "type": {
79604
+ "text": "void"
79605
+ }
79606
+ },
79607
+ "parameters": [
79608
+ {
79609
+ "name": "invalid",
79610
+ "type": {
79611
+ "text": "boolean"
79612
+ }
79613
+ }
79614
+ ]
79615
+ },
79616
+ {
79617
+ "kind": "method",
79618
+ "name": "apply",
79619
+ "return": {
79620
+ "type": {
79621
+ "text": "void"
79622
+ }
79623
+ },
79624
+ "description": "Deliver the current state to the inner control, if it exists yet."
79625
+ }
79626
+ ]
79627
+ }
79628
+ ],
79629
+ "exports": [
79630
+ {
79631
+ "kind": "js",
79632
+ "name": "FormItemRelay",
79633
+ "declaration": {
79634
+ "name": "FormItemRelay",
79635
+ "module": "dist/shared/form-item-relay.js"
79636
+ }
79637
+ }
79638
+ ]
79639
+ },
79236
79640
  {
79237
79641
  "kind": "javascript-module",
79238
79642
  "path": "dist/shared/help-events.js",
@@ -79694,64 +80098,6 @@
79694
80098
  ],
79695
80099
  "description": "Resolve a consumer-provided image-row ceiling to a usable positive integer.\nValues outside `1…maximum`, including non-numbers and non-finite values,\nreturn `fallback`. The default fallback and maximum leave row lengths\nunlimited."
79696
80100
  },
79697
- {
79698
- "kind": "function",
79699
- "name": "calculatePhotoLayout",
79700
- "return": {
79701
- "type": {
79702
- "text": "PhotoRow<T>[]"
79703
- }
79704
- },
79705
- "parameters": [
79706
- {
79707
- "name": "images",
79708
- "type": {
79709
- "text": "T[]"
79710
- },
79711
- "description": "Items with intrinsic `width`/`height` (extra fields preserved)"
79712
- },
79713
- {
79714
- "name": "containerWidth",
79715
- "type": {
79716
- "text": "number"
79717
- },
79718
- "description": "Available width in px that each row must fill"
79719
- },
79720
- {
79721
- "name": "targetRowHeight",
79722
- "default": "220",
79723
- "type": {
79724
- "text": "number"
79725
- },
79726
- "description": "Preferred row height in px before scaling"
79727
- },
79728
- {
79729
- "name": "gap",
79730
- "default": "8",
79731
- "type": {
79732
- "text": "number"
79733
- },
79734
- "description": "Horizontal gap in px between images within a row"
79735
- },
79736
- {
79737
- "name": "maxRowHeight",
79738
- "default": "Number.POSITIVE_INFINITY",
79739
- "type": {
79740
- "text": "number"
79741
- },
79742
- "description": "Ceiling for row heights — keeps a stretched final row\n(or a single image) from ballooning past e.g. half the viewport height"
79743
- },
79744
- {
79745
- "name": "maxImagesPerRow",
79746
- "default": "Number.POSITIVE_INFINITY",
79747
- "type": {
79748
- "text": "number"
79749
- },
79750
- "description": "Optional positive-integer ceiling for the number of\nimages in each row. Omit it, or pass an invalid value, for an unlimited row\nlength."
79751
- }
79752
- ],
79753
- "description": "Compute justified layout rows for a list of images.\n\nAlgorithm:\n1. Add images to a row one at a time, scaled to the target row height\n2. When the combined width overflows the container, the row breaks either\n before or after the overflowing image — whichever leaves the row height\n closer to the target. Keeping the image shrinks the row slightly below\n target; pushing it to the next row stretches the row above target.\n3. Scale the finished row so it fills the container width exactly.\n\nEvery row — including the last — is scaled to fill `containerWidth`\nexactly, except rows clamped by `maxRowHeight`: those keep their aspect\nratios at the clamped height and no longer span the full width. Items are\nreturned by reference in their original order."
79754
- },
79755
80101
  {
79756
80102
  "kind": "function",
79757
80103
  "name": "calculateAlbumLayout",
@@ -79774,7 +80120,7 @@
79774
80120
  }
79775
80121
  }
79776
80122
  ],
79777
- "description": "Compute bounded album geometry with explicit cell widths.\n\nThis is the opt-in counterpart to calculatePhotoLayout. Completed\nrows always span the available width. When their justified height exceeds\n`maxRowHeight`, their cells retain their relative widths while expanding to\nthe row width at the ceiling; a renderer can use `object-fit: cover` to crop\nthe resulting cells. A final row below a finite `maxImagesPerRow` instead\nremains partial and preserves its image proportions. Its height is capped by\nthe tallest preceding row (or the target height when it is the only row) and\nby `maxRowHeight`.\n\nWith an omitted or invalid `maxImagesPerRow`, every row remains completed.\nThat preserves the legacy unlimited-row distribution for callers that have\nnot opted into bounded albums."
80123
+ "description": "Compute justified album rows with explicit cell widths.\n\nAlgorithm:\n1. Add images to a row one at a time, scaled to the target row height.\n2. When the combined width overflows the container, the row breaks either\n before or after the overflowing image — whichever leaves the row height\n closer to the target. Keeping the image shrinks the row slightly below\n target; pushing it to the next row stretches the row above target.\n3. Scale the finished row so it fills the container width exactly.\n\nCompleted rows always span the available width. When their justified height exceeds\n`maxRowHeight`, their cells retain their relative widths while expanding to\nthe row width at the ceiling; a renderer can use `object-fit: cover` to crop\nthe resulting cells. A final row below a finite `maxImagesPerRow` instead\nremains partial and preserves its image proportions. Its height is capped by\nthe tallest preceding row (or the target height when it is the only row) and\nby `maxRowHeight`.\n\nWith an omitted or invalid `maxImagesPerRow`, rows are unlimited in length\nand every row is completed."
79778
80124
  }
79779
80125
  ],
79780
80126
  "exports": [
@@ -79802,14 +80148,6 @@
79802
80148
  "module": "dist/shared/justified-layout.js"
79803
80149
  }
79804
80150
  },
79805
- {
79806
- "kind": "js",
79807
- "name": "calculatePhotoLayout",
79808
- "declaration": {
79809
- "name": "calculatePhotoLayout",
79810
- "module": "dist/shared/justified-layout.js"
79811
- }
79812
- },
79813
80151
  {
79814
80152
  "kind": "js",
79815
80153
  "name": "calculateAlbumLayout",
@@ -96288,206 +96626,2051 @@
96288
96626
  "events": [
96289
96627
  {
96290
96628
  "type": {
96291
- "text": "CustomEvent<EspTreeSelectDetail>"
96292
- },
96293
- "description": "Fired when a node is selected.",
96294
- "name": "esp-tree-select"
96295
- },
96296
- {
96297
- "type": {
96298
- "text": "CustomEvent<EspTreeToggleDetail>"
96299
- },
96300
- "description": "Fired when a branch is expanded or collapsed.",
96301
- "name": "esp-tree-toggle"
96302
- },
96303
- {
96304
- "type": {
96305
- "text": "CustomEvent<EspTreeGraftRequestDetail>"
96629
+ "text": "CustomEvent<EspTreeSelectDetail>"
96630
+ },
96631
+ "description": "Fired when a node is selected.",
96632
+ "name": "esp-tree-select"
96633
+ },
96634
+ {
96635
+ "type": {
96636
+ "text": "CustomEvent<EspTreeToggleDetail>"
96637
+ },
96638
+ "description": "Fired when a branch is expanded or collapsed.",
96639
+ "name": "esp-tree-toggle"
96640
+ },
96641
+ {
96642
+ "type": {
96643
+ "text": "CustomEvent<EspTreeGraftRequestDetail>"
96644
+ },
96645
+ "description": "Cancelable event fired before the default graft dialog opens.",
96646
+ "name": "esp-tree-graft-request"
96647
+ },
96648
+ {
96649
+ "type": {
96650
+ "text": "CustomEvent<EspTreeGraftDetail>"
96651
+ },
96652
+ "description": "Fired when a new branch is committed.",
96653
+ "name": "esp-tree-graft"
96654
+ },
96655
+ {
96656
+ "type": {
96657
+ "text": "CustomEvent<EspTreeEditDetail>"
96658
+ },
96659
+ "description": "Fired when an editable node's edit affordance is invoked.",
96660
+ "name": "esp-tree-edit"
96661
+ },
96662
+ {
96663
+ "type": {
96664
+ "text": "CustomEvent<EspTreeNodeActionDetail>"
96665
+ },
96666
+ "description": "Fired when a deletable node's delete action is invoked.",
96667
+ "name": "esp-tree-delete"
96668
+ },
96669
+ {
96670
+ "type": {
96671
+ "text": "CustomEvent<EspTreeNodeActionDetail>"
96672
+ },
96673
+ "description": "Fired when a movable-up node's move-up action is invoked.",
96674
+ "name": "esp-tree-move-up"
96675
+ },
96676
+ {
96677
+ "type": {
96678
+ "text": "CustomEvent<EspTreeNodeActionDetail>"
96679
+ },
96680
+ "description": "Fired when a movable-down node's move-down action is invoked.",
96681
+ "name": "esp-tree-move-down"
96682
+ },
96683
+ {
96684
+ "type": {
96685
+ "text": "CustomEvent<EspTreeNodeActionDetail>"
96686
+ },
96687
+ "description": "Fired when a cuttable node's cut action is invoked.",
96688
+ "name": "esp-tree-cut"
96689
+ },
96690
+ {
96691
+ "type": {
96692
+ "text": "CustomEvent<EspTreeNodeActionDetail>"
96693
+ },
96694
+ "description": "Fired when a paste child target node's paste-as-child action is invoked.",
96695
+ "name": "esp-tree-paste-child"
96696
+ },
96697
+ {
96698
+ "type": {
96699
+ "text": "CustomEvent<EspTreeGraftInputDetail>"
96700
+ },
96701
+ "description": "Fired live as the graft input changes.",
96702
+ "name": "esp-tree-graft-input"
96703
+ },
96704
+ {
96705
+ "type": {
96706
+ "text": "CustomEvent<{ valid: boolean; message: string }>"
96707
+ },
96708
+ "description": "Fired whenever constraint validation runs.",
96709
+ "name": "esp-validity-changed"
96710
+ }
96711
+ ],
96712
+ "attributes": [
96713
+ {
96714
+ "name": "nodes",
96715
+ "type": {
96716
+ "text": "Array<EspTreeNode>"
96717
+ },
96718
+ "default": "[]",
96719
+ "description": "The hierarchy to display, as the primary data-driven API. When\nnon-empty this takes precedence over slotted `esp-tree-item`\nchildren.",
96720
+ "fieldName": "nodes"
96721
+ },
96722
+ {
96723
+ "name": "value",
96724
+ "type": {
96725
+ "text": "string"
96726
+ },
96727
+ "default": "\"\"",
96728
+ "description": "The selected node's full path (segments joined by `/`), or the\nempty string when nothing is selected. This is also the form\nsubmission value.",
96729
+ "fieldName": "value"
96730
+ },
96731
+ {
96732
+ "name": "name",
96733
+ "type": {
96734
+ "text": "string"
96735
+ },
96736
+ "default": "\"\"",
96737
+ "description": "The name used when the tree participates in a `<form>`.",
96738
+ "fieldName": "name"
96739
+ },
96740
+ {
96741
+ "name": "required",
96742
+ "type": {
96743
+ "text": "boolean"
96744
+ },
96745
+ "default": "false",
96746
+ "description": "When true, a selection must be made before the form can submit.",
96747
+ "fieldName": "required"
96748
+ },
96749
+ {
96750
+ "name": "required-message",
96751
+ "type": {
96752
+ "text": "string"
96753
+ },
96754
+ "default": "\"\"",
96755
+ "description": "A custom message shown when the tree is required but nothing is\nselected. Defaults to `\"Please select an option.\"`.",
96756
+ "fieldName": "requiredMessage"
96757
+ },
96758
+ {
96759
+ "name": "disabled",
96760
+ "type": {
96761
+ "text": "boolean"
96762
+ },
96763
+ "default": "false",
96764
+ "description": "When true, the tree is disabled and cannot be interacted with.",
96765
+ "fieldName": "disabled"
96766
+ },
96767
+ {
96768
+ "name": "allow-graft",
96769
+ "type": {
96770
+ "text": "boolean"
96771
+ },
96772
+ "default": "false",
96773
+ "description": "When true, an inline `+` affordance is shown on every node and at\nthe root for grafting a new child branch.",
96774
+ "fieldName": "allowGraft"
96775
+ },
96776
+ {
96777
+ "name": "root-label",
96778
+ "type": {
96779
+ "text": "string"
96780
+ },
96781
+ "default": "\"/\"",
96782
+ "description": "Label shown for the always-rendered root ancestor row.",
96783
+ "fieldName": "rootLabel"
96784
+ },
96785
+ {
96786
+ "name": "root-path",
96787
+ "type": {
96788
+ "text": "string"
96789
+ },
96790
+ "default": "\"/\"",
96791
+ "description": "Path submitted when the root row is selected. Defaults to `/`.",
96792
+ "fieldName": "rootPath"
96793
+ },
96794
+ {
96795
+ "name": "root-selectable",
96796
+ "type": {
96797
+ "text": "boolean"
96798
+ },
96799
+ "default": "true",
96800
+ "description": "Whether the root row can be selected. Defaults to true. Set\n`root-selectable=\"false\"` to make it navigation-only.",
96801
+ "fieldName": "rootSelectable"
96802
+ },
96803
+ {
96804
+ "name": "label",
96805
+ "type": {
96806
+ "text": "string"
96807
+ },
96808
+ "default": "\"\"",
96809
+ "description": "An accessible name for the tree. Usually unnecessary inside an\n`esp-form-item`, which provides the label; set it for standalone\nuse.",
96810
+ "fieldName": "label"
96811
+ },
96812
+ {
96813
+ "name": "graft-error",
96814
+ "type": {
96815
+ "text": "string | null"
96816
+ },
96817
+ "default": "null",
96818
+ "description": "A consumer-supplied error for the in-progress graft, shown inline\nand blocking commit until cleared. Set it with `setGraftError`,\ntypically in response to an `esp-tree-graft-input` event after an\nasync check. Mirrored as the `graft-error` attribute.",
96819
+ "fieldName": "graftError"
96820
+ },
96821
+ {
96822
+ "name": "keep-expanded",
96823
+ "type": {
96824
+ "text": "boolean"
96825
+ },
96826
+ "default": "false",
96827
+ "description": "Keep already-expanded branches open when selecting a row outside of\nthe selected branch. By default, selecting outside an open selected\nbranch collapses that branch.",
96828
+ "fieldName": "keepExpanded"
96829
+ },
96830
+ {
96831
+ "name": "scheme",
96832
+ "type": {
96833
+ "text": "\"dark\" | \"light\""
96834
+ },
96835
+ "default": "\"light\"",
96836
+ "description": "The scheme to use for the component. This property is used internally\nto control the light/dark scheme so it matches the scheme of\n[esp-root](/components/root). It should not be set manually, it\nexists as an attribute for styling purposes.",
96837
+ "fieldName": "scheme",
96838
+ "inheritedFrom": {
96839
+ "name": "EspalierElementBase",
96840
+ "module": "dist/shared/esp-element-base.js"
96841
+ }
96842
+ },
96843
+ {
96844
+ "name": "intent",
96845
+ "type": {
96846
+ "text": "EspalierIntentVariant"
96847
+ },
96848
+ "description": "The element's [intent](/espalier-guides/) — its meaning: `neutral`\n(no pin), or `success`, `warning`, `danger`, `info` — each pinned\nto its fixed status family (blue for `info`), retunable per theme\nvia `intents`. On token-emitting controls a non-neutral intent pins the\nfilled-action pair to that family, derived over the governing\nzone's theme; class-styled chrome (badges, callouts, status pills)\nopts out of inline emission and renders its treatment from CSS\nclasses instead. An intent never repaints the surrounding zone,\nwhich is what contexts are for. Removing the attribute restores\nthe element's own default intent.",
96849
+ "fieldName": "intent",
96850
+ "inheritedFrom": {
96851
+ "name": "EspalierElementBase",
96852
+ "module": "dist/shared/esp-element-base.js"
96853
+ }
96854
+ },
96855
+ {
96856
+ "name": "context",
96857
+ "type": {
96858
+ "text": "string"
96859
+ },
96860
+ "description": "A theme-defined color zone. The selected context rebinds designer-facing\nroles and emits a complete, contrast-enforced semantic token table on this\nhost for descendants to inherit.",
96861
+ "fieldName": "context",
96862
+ "inheritedFrom": {
96863
+ "name": "EspalierElementBase",
96864
+ "module": "dist/shared/esp-element-base.js"
96865
+ }
96866
+ }
96867
+ ],
96868
+ "superclass": {
96869
+ "name": "EspalierElementBase",
96870
+ "module": "dist/shared/esp-element-base.js"
96871
+ },
96872
+ "docPageTitle": {
96873
+ "name": "Tree",
96874
+ "description": ""
96875
+ },
96876
+ "docUrl": {
96877
+ "name": "/components/tree",
96878
+ "description": ""
96879
+ },
96880
+ "menuGroup": {
96881
+ "name": "Form",
96882
+ "description": "Controls"
96883
+ },
96884
+ "menuLabel": {
96885
+ "name": "Tree",
96886
+ "description": ""
96887
+ },
96888
+ "menuIcon": {
96889
+ "name": "sitemap",
96890
+ "description": ""
96891
+ },
96892
+ "tagName": "esp-tree",
96893
+ "customElement": true
96894
+ },
96895
+ {
96896
+ "kind": "variable",
96897
+ "name": "id"
96898
+ },
96899
+ {
96900
+ "kind": "variable",
96901
+ "name": "selected"
96902
+ },
96903
+ {
96904
+ "kind": "variable",
96905
+ "name": "segment"
96906
+ },
96907
+ {
96908
+ "kind": "variable",
96909
+ "name": "selectable"
96910
+ },
96911
+ {
96912
+ "kind": "variable",
96913
+ "name": "hasChildren"
96914
+ }
96915
+ ],
96916
+ "exports": [
96917
+ {
96918
+ "kind": "js",
96919
+ "name": "normalizeSegment",
96920
+ "declaration": {
96921
+ "name": "normalizeSegment",
96922
+ "module": "dist/tree/esp-tree.js"
96923
+ }
96924
+ },
96925
+ {
96926
+ "kind": "js",
96927
+ "name": "buildWebTreePath",
96928
+ "declaration": {
96929
+ "name": "buildWebTreePath",
96930
+ "module": "dist/tree/esp-tree.js"
96931
+ }
96932
+ },
96933
+ {
96934
+ "kind": "js",
96935
+ "name": "EspalierTree",
96936
+ "declaration": {
96937
+ "name": "EspalierTree",
96938
+ "module": "dist/tree/esp-tree.js"
96939
+ }
96940
+ },
96941
+ {
96942
+ "kind": "custom-element-definition",
96943
+ "name": "esp-tree",
96944
+ "declaration": {
96945
+ "name": "EspalierTree",
96946
+ "module": "dist/tree/esp-tree.js"
96947
+ }
96948
+ }
96949
+ ]
96950
+ },
96951
+ {
96952
+ "kind": "javascript-module",
96953
+ "path": "dist/video/esp-video.js",
96954
+ "declarations": [
96955
+ {
96956
+ "kind": "variable",
96957
+ "name": "HLS_MEDIA_TYPE",
96958
+ "type": {
96959
+ "text": "string"
96960
+ },
96961
+ "default": "\"application/vnd.apple.mpegurl\"",
96962
+ "description": "The HLS master-playlist media type, and the default `type`."
96963
+ },
96964
+ {
96965
+ "kind": "variable",
96966
+ "name": "DEFAULT_VIDEO_ASPECT_RATIO",
96967
+ "type": {
96968
+ "text": "string"
96969
+ },
96970
+ "default": "\"16 / 9\"",
96971
+ "description": "The `aspect-ratio` used when the attribute is absent or invalid."
96972
+ },
96973
+ {
96974
+ "kind": "class",
96975
+ "description": "A native video player: a `<video>` element with a lazy poster, a fixed\naspect ratio, captions, native controls, and visible loading and error\nstates. It is never an iframe and never loads a third-party player.\n\nNothing but the poster loads until the visitor presses play, so a page of\nvideos costs one image request each. The play affordance over the poster is\na real button named from `label` or `poster-alt`.\n\n```html\n<esp-video\n src=\"/assets/esp-video/espalier-sample.mp4\"\n type=\"video/mp4\"\n poster=\"/assets/esp-video/espalier-sample-poster.jpg\"\n poster-alt=\"Espalier's palette drifting across a trellis grid\"\n playsinline\n></esp-video>\n```\n\n### Sources\n\nThe element plays `src` only where the browser reports it can play `type`.\n`type` is an HLS master playlist by default, which Safari, iOS, and current\nChrome play natively; elsewhere `fallback-src` (an MP4) plays instead.\nEspalier attaches no streaming library, so for a source every browser can\nplay, set `type=\"video/mp4\"`.\n\n### Captions\n\nGive the element `<track>` children. A media element only reads its own\nchild tracks, so the element mirrors them into its video when it attaches\nthe source; `kind`, `srclang`, `label`, and `default` carry through. With\nthe default `preload=\"none\"`, no caption file loads before the visitor\npresses play.\n\n```html\n<esp-video\n src=\"/assets/esp-video/espalier-sample.mp4\"\n type=\"video/mp4\"\n poster=\"/assets/esp-video/espalier-sample-poster.jpg\"\n poster-alt=\"The trellis sample, with English captions\"\n aspect-ratio=\"21 / 9\"\n>\n <track kind=\"captions\" src=\"/assets/esp-video/espalier-sample.en.vtt\" srclang=\"en\" label=\"English\" default />\n</esp-video>\n```\n\n### Autoplay\n\n`autoplay` starts playback once the element is admitted (immediately, or\nnear the viewport with `defer-offscreen`). It always plays muted: while\n`autoplay` is set the video stays muted whatever `muted` says, so a page\nnever starts sound unprompted. It does not start for a visitor who prefers\nreduced motion, or where the browser blocks it; the poster and play button\nremain.",
96976
+ "name": "EspalierVideo",
96977
+ "cssProperties": [
96978
+ {
96979
+ "description": "Background behind the video and an absent poster. Defaults to a near-black derived from `--esp-color-text`.",
96980
+ "name": "--esp-video-surface"
96981
+ },
96982
+ {
96983
+ "description": "Corner radius. Defaults to `var(--esp-size-border-radius)`.",
96984
+ "name": "--esp-video-border-radius"
96985
+ },
96986
+ {
96987
+ "description": "How the video fills its box. Defaults to `contain`; the poster always covers.",
96988
+ "name": "--esp-video-object-fit"
96989
+ },
96990
+ {
96991
+ "description": "`accent-color` for the native controls, where the browser honors it. Defaults to `var(--esp-color-primary)`.",
96992
+ "name": "--esp-video-accent"
96993
+ },
96994
+ {
96995
+ "description": "Focus-visible outline on the play button, the video, and the error message. Defaults to `3px solid var(--esp-color-link)`.",
96996
+ "name": "--esp-video-focus-outline"
96997
+ },
96998
+ {
96999
+ "description": "Diameter of the play disc. Defaults to `var(--esp-size-huge)`.",
97000
+ "name": "--esp-video-play-size"
97001
+ },
97002
+ {
97003
+ "description": "Fill of the play disc. Defaults to `var(--esp-color-background)` at 90% opacity.",
97004
+ "name": "--esp-video-play-background"
97005
+ },
97006
+ {
97007
+ "description": "Play glyph and loading-indicator color. Defaults to `var(--esp-color-text)`.",
97008
+ "name": "--esp-video-play-color"
97009
+ },
97010
+ {
97011
+ "description": "Error message background. Defaults to `var(--esp-color-background)`.",
97012
+ "name": "--esp-video-message-background"
97013
+ },
97014
+ {
97015
+ "description": "Error message text color. Defaults to `var(--esp-color-text)`.",
97016
+ "name": "--esp-video-message-color"
97017
+ },
97018
+ {
97019
+ "description": "Background color of the shared field shell. Defaults to `var(--esp-color-layer-2)`.",
97020
+ "name": "--esp-field-background",
97021
+ "inheritedFrom": {
97022
+ "name": "EspalierElementBase",
97023
+ "module": "dist/shared/esp-element-base.js"
97024
+ }
97025
+ },
97026
+ {
97027
+ "description": "Border color of the shared field shell. Defaults to `var(--esp-color-border)`.",
97028
+ "name": "--esp-field-border-color",
97029
+ "inheritedFrom": {
97030
+ "name": "EspalierElementBase",
97031
+ "module": "dist/shared/esp-element-base.js"
97032
+ }
97033
+ },
97034
+ {
97035
+ "description": "Border width of the shared field shell. Defaults to `1px`.",
97036
+ "name": "--esp-field-border-width",
97037
+ "inheritedFrom": {
97038
+ "name": "EspalierElementBase",
97039
+ "module": "dist/shared/esp-element-base.js"
97040
+ }
97041
+ },
97042
+ {
97043
+ "description": "Text color used inside the shared field shell. Defaults to `var(--esp-color-text)`.",
97044
+ "name": "--esp-field-text-color",
97045
+ "inheritedFrom": {
97046
+ "name": "EspalierElementBase",
97047
+ "module": "dist/shared/esp-element-base.js"
97048
+ }
97049
+ },
97050
+ {
97051
+ "description": "Hover background color of the shared field shell. Derived from `--esp-field-background`.",
97052
+ "name": "--esp-field-hover-bg",
97053
+ "inheritedFrom": {
97054
+ "name": "EspalierElementBase",
97055
+ "module": "dist/shared/esp-element-base.js"
97056
+ }
97057
+ },
97058
+ {
97059
+ "description": "Focus background color of the shared field shell. Derived from `--esp-field-background`.",
97060
+ "name": "--esp-field-focus-bg",
97061
+ "inheritedFrom": {
97062
+ "name": "EspalierElementBase",
97063
+ "module": "dist/shared/esp-element-base.js"
97064
+ }
97065
+ },
97066
+ {
97067
+ "description": "Shadow color used for shared field focus treatment. Defaults to `var(--esp-color-shadow)`.",
97068
+ "name": "--esp-field-focus-shadow",
97069
+ "inheritedFrom": {
97070
+ "name": "EspalierElementBase",
97071
+ "module": "dist/shared/esp-element-base.js"
97072
+ }
97073
+ }
97074
+ ],
97075
+ "cssParts": [
97076
+ {
97077
+ "description": "The element's full box.",
97078
+ "name": "frame"
97079
+ },
97080
+ {
97081
+ "description": "The native `<video>` element.",
97082
+ "name": "video"
97083
+ },
97084
+ {
97085
+ "description": "The layer over the video holding the poster, play button, and messages.",
97086
+ "name": "poster"
97087
+ },
97088
+ {
97089
+ "description": "The poster `<img>`.",
97090
+ "name": "poster-image"
97091
+ },
97092
+ {
97093
+ "description": "The play `<button>`, which covers the poster.",
97094
+ "name": "play"
97095
+ },
97096
+ {
97097
+ "description": "The visible play disc inside the button.",
97098
+ "name": "play-icon"
97099
+ },
97100
+ {
97101
+ "description": "The error message.",
97102
+ "name": "message"
97103
+ }
97104
+ ],
97105
+ "slots": [
97106
+ {
97107
+ "description": "`<track>` elements for captions, subtitles, descriptions, and chapters.",
97108
+ "name": ""
97109
+ },
97110
+ {
97111
+ "description": "Replaces the error message shown over the poster.",
97112
+ "name": "error"
97113
+ }
97114
+ ],
97115
+ "members": [
97116
+ {
97117
+ "kind": "field",
97118
+ "name": "src",
97119
+ "type": {
97120
+ "text": "string"
97121
+ },
97122
+ "privacy": "public",
97123
+ "default": "\"\"",
97124
+ "description": "The media URL: an HLS master playlist by default, or a file matching `type`.",
97125
+ "attribute": "src"
97126
+ },
97127
+ {
97128
+ "kind": "field",
97129
+ "name": "type",
97130
+ "type": {
97131
+ "text": "string"
97132
+ },
97133
+ "privacy": "public",
97134
+ "default": "\"application/vnd.apple.mpegurl\"",
97135
+ "description": "The media type of `src`, checked with `canPlayType` to choose between\n`src` and `fallback-src`. `video/mp4` plays as a plain file.",
97136
+ "attribute": "type"
97137
+ },
97138
+ {
97139
+ "kind": "field",
97140
+ "name": "poster",
97141
+ "type": {
97142
+ "text": "string"
97143
+ },
97144
+ "privacy": "public",
97145
+ "default": "\"\"",
97146
+ "description": "Poster image URL, shown until playback starts and behind error messages.",
97147
+ "attribute": "poster"
97148
+ },
97149
+ {
97150
+ "kind": "field",
97151
+ "name": "posterAlt",
97152
+ "type": {
97153
+ "text": "string"
97154
+ },
97155
+ "privacy": "public",
97156
+ "default": "\"\"",
97157
+ "description": "Describes the poster; names the play button when `label` is unset.",
97158
+ "attribute": "poster-alt"
97159
+ },
97160
+ {
97161
+ "kind": "field",
97162
+ "name": "label",
97163
+ "type": {
97164
+ "text": "string"
97165
+ },
97166
+ "privacy": "public",
97167
+ "default": "\"\"",
97168
+ "description": "The play button's accessible name. Defaults to `Play video: <poster-alt>`,\nor `Play video` with no poster text.",
97169
+ "attribute": "label"
97170
+ },
97171
+ {
97172
+ "kind": "field",
97173
+ "name": "aspectRatio",
97174
+ "type": {
97175
+ "text": "string"
97176
+ },
97177
+ "privacy": "public",
97178
+ "default": "\"16 / 9\"",
97179
+ "description": "The box's aspect ratio, as a positive number or `number / number`. Invalid\nvalues fall back to `16 / 9`.",
97180
+ "attribute": "aspect-ratio"
97181
+ },
97182
+ {
97183
+ "kind": "field",
97184
+ "name": "preload",
97185
+ "type": {
97186
+ "text": "VideoPreload"
97187
+ },
97188
+ "privacy": "public",
97189
+ "default": "\"none\"",
97190
+ "description": "How much media to load before play intent. `none` (the default) loads\nonly the poster. `metadata` and `auto` apply where the browser plays\n`type`; the choice of the fallback over it still waits for play intent.\nA preloaded source that fails switches to `fallback-src` at once.",
97191
+ "attribute": "preload",
97192
+ "reflects": true
97193
+ },
97194
+ {
97195
+ "kind": "field",
97196
+ "name": "autoplay",
97197
+ "type": {
97198
+ "text": "boolean"
97199
+ },
97200
+ "privacy": "public",
97201
+ "default": "false",
97202
+ "description": "Start muted playback once admitted. Implies `muted`.",
97203
+ "attribute": "autoplay"
97204
+ },
97205
+ {
97206
+ "kind": "field",
97207
+ "name": "muted",
97208
+ "type": {
97209
+ "text": "boolean"
97210
+ },
97211
+ "privacy": "public",
97212
+ "default": "false",
97213
+ "description": "Start muted. Always on while `autoplay` is set. Clearing it (or\n`autoplay`) unmutes only a paused video; a playing one stays muted until\nthe visitor unmutes it.",
97214
+ "attribute": "muted"
97215
+ },
97216
+ {
97217
+ "kind": "field",
97218
+ "name": "loop",
97219
+ "type": {
97220
+ "text": "boolean"
97221
+ },
97222
+ "privacy": "public",
97223
+ "default": "false",
97224
+ "description": "Restart from the beginning when playback ends.",
97225
+ "attribute": "loop"
97226
+ },
97227
+ {
97228
+ "kind": "field",
97229
+ "name": "playsinline",
97230
+ "type": {
97231
+ "text": "boolean"
97232
+ },
97233
+ "privacy": "public",
97234
+ "default": "false",
97235
+ "description": "Play inline on iOS rather than entering full screen. Needed for inline autoplay there.",
97236
+ "attribute": "playsinline"
97237
+ },
97238
+ {
97239
+ "kind": "field",
97240
+ "name": "controls",
97241
+ "type": {
97242
+ "text": "VideoControls"
97243
+ },
97244
+ "privacy": "public",
97245
+ "default": "\"native\"",
97246
+ "description": "`native` (the default) shows the browser's controls once playback starts.\n`none` shows none; use it only for short decorative loops, since a visitor\nthen has no way to pause.",
97247
+ "attribute": "controls",
97248
+ "reflects": true
97249
+ },
97250
+ {
97251
+ "kind": "field",
97252
+ "name": "deferOffscreen",
97253
+ "type": {
97254
+ "text": "boolean"
97255
+ },
97256
+ "privacy": "public",
97257
+ "default": "false",
97258
+ "description": "Defer everything — the poster, `preload`, and `autoplay` — until the\nelement is within 200px of the viewport. The poster is not rendered\nbefore then, so the browser's own lazy-loading distance cannot fetch it\nearly.",
97259
+ "attribute": "defer-offscreen"
97260
+ },
97261
+ {
97262
+ "kind": "field",
97263
+ "name": "fallbackSrc",
97264
+ "type": {
97265
+ "text": "string"
97266
+ },
97267
+ "privacy": "public",
97268
+ "default": "\"\"",
97269
+ "description": "An MP4 played when the browser cannot play `type`, and tried once when\nthe primary source fails.",
97270
+ "attribute": "fallback-src"
97271
+ },
97272
+ {
97273
+ "kind": "field",
97274
+ "name": "crossOrigin",
97275
+ "type": {
97276
+ "text": "VideoCrossOrigin"
97277
+ },
97278
+ "privacy": "public",
97279
+ "default": "\"\"",
97280
+ "description": "CORS mode for the video's requests, as on `<video crossorigin>`. Needed\nfor caption tracks served from another origin.",
97281
+ "attribute": "crossorigin"
97282
+ },
97283
+ {
97284
+ "kind": "field",
97285
+ "name": "phase",
97286
+ "type": {
97287
+ "text": "VideoPhase"
97288
+ },
97289
+ "privacy": "private",
97290
+ "default": "\"idle\""
97291
+ },
97292
+ {
97293
+ "kind": "field",
97294
+ "name": "started",
97295
+ "type": {
97296
+ "text": "boolean"
97297
+ },
97298
+ "privacy": "private",
97299
+ "default": "false",
97300
+ "description": "Playback has begun at least once for this source, so the poster is dismissed."
97301
+ },
97302
+ {
97303
+ "kind": "field",
97304
+ "name": "offscreenAdmitted",
97305
+ "type": {
97306
+ "text": "boolean"
97307
+ },
97308
+ "privacy": "private",
97309
+ "default": "false"
97310
+ },
97311
+ {
97312
+ "kind": "field",
97313
+ "name": "attached",
97314
+ "type": {
97315
+ "text": "{ mode: VideoSourceMode } | null"
97316
+ },
97317
+ "privacy": "private",
97318
+ "default": "null"
97319
+ },
97320
+ {
97321
+ "kind": "field",
97322
+ "name": "mutedVideo",
97323
+ "type": {
97324
+ "text": "HTMLVideoElement | null"
97325
+ },
97326
+ "privacy": "private",
97327
+ "default": "null",
97328
+ "description": "The video element `syncMuted` last applied the muted state to."
97329
+ },
97330
+ {
97331
+ "kind": "field",
97332
+ "name": "generation",
97333
+ "type": {
97334
+ "text": "number"
97335
+ },
97336
+ "privacy": "private",
97337
+ "default": "0",
97338
+ "description": "Bumped whenever the source resets, so late async work can tell it is stale."
97339
+ },
97340
+ {
97341
+ "kind": "field",
97342
+ "name": "triedFallback",
97343
+ "type": {
97344
+ "text": "boolean"
97345
+ },
97346
+ "privacy": "private",
97347
+ "default": "false"
97348
+ },
97349
+ {
97350
+ "kind": "field",
97351
+ "name": "readyEmitted",
97352
+ "type": {
97353
+ "text": "boolean"
97354
+ },
97355
+ "privacy": "private",
97356
+ "default": "false"
97357
+ },
97358
+ {
97359
+ "kind": "field",
97360
+ "name": "playIntent",
97361
+ "type": {
97362
+ "text": "boolean"
97363
+ },
97364
+ "privacy": "private",
97365
+ "default": "false"
97366
+ },
97367
+ {
97368
+ "kind": "field",
97369
+ "name": "autoplayAttempted",
97370
+ "type": {
97371
+ "text": "boolean"
97372
+ },
97373
+ "privacy": "private",
97374
+ "default": "false"
97375
+ },
97376
+ {
97377
+ "kind": "field",
97378
+ "name": "intentAutoplay",
97379
+ "type": {
97380
+ "text": "boolean"
97381
+ },
97382
+ "privacy": "private",
97383
+ "default": "false",
97384
+ "description": "Whether the current play intent came from `autoplay` rather than the visitor."
97385
+ },
97386
+ {
97387
+ "kind": "field",
97388
+ "name": "autoplayPending",
97389
+ "type": {
97390
+ "text": "boolean"
97391
+ },
97392
+ "privacy": "private",
97393
+ "default": "false",
97394
+ "description": "Whether the next `play` event was started by `autoplay`."
97395
+ },
97396
+ {
97397
+ "kind": "field",
97398
+ "name": "playAttempt",
97399
+ "type": {
97400
+ "text": "number"
97401
+ },
97402
+ "privacy": "private",
97403
+ "default": "0",
97404
+ "description": "Counts `play()` calls, so a superseded rejection cannot touch a newer attempt."
97405
+ },
97406
+ {
97407
+ "kind": "field",
97408
+ "name": "pendingPlay",
97409
+ "type": {
97410
+ "text": "{ autoplay: boolean } | null"
97411
+ },
97412
+ "privacy": "private",
97413
+ "default": "null",
97414
+ "description": "A `play` event awaiting its `playing`. `play` fires as soon as `play()` is\ncalled, before anything is decoded, so `esp-video-play` waits for\n`playing` and a source that fails first never reports a play."
97415
+ },
97416
+ {
97417
+ "kind": "field",
97418
+ "name": "focusMessageOnError",
97419
+ "type": {
97420
+ "text": "boolean"
97421
+ },
97422
+ "privacy": "private",
97423
+ "default": "false",
97424
+ "description": "The play button held focus when the element failed; hand it to the message."
97425
+ },
97426
+ {
97427
+ "kind": "field",
97428
+ "name": "offscreenObserver",
97429
+ "type": {
97430
+ "text": "IntersectionObserver | null"
97431
+ },
97432
+ "privacy": "private",
97433
+ "default": "null"
97434
+ },
97435
+ {
97436
+ "kind": "field",
97437
+ "name": "video",
97438
+ "type": {
97439
+ "text": "HTMLVideoElement | null"
97440
+ },
97441
+ "privacy": "private",
97442
+ "readonly": true
97443
+ },
97444
+ {
97445
+ "kind": "method",
97446
+ "name": "canPlayNatively",
97447
+ "privacy": "private",
97448
+ "return": {
97449
+ "type": {
97450
+ "text": "boolean"
97451
+ }
97452
+ },
97453
+ "parameters": [
97454
+ {
97455
+ "name": "video",
97456
+ "type": {
97457
+ "text": "HTMLVideoElement"
97458
+ }
97459
+ }
97460
+ ]
97461
+ },
97462
+ {
97463
+ "kind": "method",
97464
+ "name": "playLabel",
97465
+ "privacy": "private",
97466
+ "return": {
97467
+ "type": {
97468
+ "text": "string"
97469
+ }
97470
+ }
97471
+ },
97472
+ {
97473
+ "kind": "method",
97474
+ "name": "syncOffscreenDeferral",
97475
+ "privacy": "private",
97476
+ "return": {
97477
+ "type": {
97478
+ "text": "void"
97479
+ }
97480
+ }
97481
+ },
97482
+ {
97483
+ "kind": "method",
97484
+ "name": "syncMuted",
97485
+ "privacy": "private",
97486
+ "return": {
97487
+ "type": {
97488
+ "text": "void"
97489
+ }
97490
+ },
97491
+ "parameters": [
97492
+ {
97493
+ "name": "changedProperties",
97494
+ "type": {
97495
+ "text": "PropertyValues"
97496
+ }
97497
+ }
97498
+ ],
97499
+ "description": "Keep the video muted while `muted` or `autoplay` is set. Clearing them\nunmutes only a paused video: a playing one stays muted until the visitor\nunmutes it, so a page never starts sound unprompted."
97500
+ },
97501
+ {
97502
+ "kind": "method",
97503
+ "name": "requestPlayback",
97504
+ "privacy": "private",
97505
+ "return": {
97506
+ "type": {
97507
+ "text": "Promise<void>"
97508
+ }
97509
+ },
97510
+ "parameters": [
97511
+ {
97512
+ "name": "autoplay",
97513
+ "type": {
97514
+ "text": "boolean"
97515
+ }
97516
+ }
97517
+ ],
97518
+ "description": "Run the play intent: attach the source if it is not attached yet, then\nplay. `autoplay` marks intents that did not come from the visitor."
97519
+ },
97520
+ {
97521
+ "kind": "method",
97522
+ "name": "startPlayback",
97523
+ "privacy": "private",
97524
+ "return": {
97525
+ "type": {
97526
+ "text": "Promise<void>"
97527
+ }
97528
+ },
97529
+ "parameters": [
97530
+ {
97531
+ "name": "video",
97532
+ "type": {
97533
+ "text": "HTMLVideoElement"
97534
+ }
97535
+ },
97536
+ {
97537
+ "name": "generation",
97538
+ "type": {
97539
+ "text": "number"
97540
+ }
97541
+ }
97542
+ ]
97543
+ },
97544
+ {
97545
+ "kind": "method",
97546
+ "name": "attachSource",
97547
+ "privacy": "private",
97548
+ "return": {
97549
+ "type": {
97550
+ "text": "boolean"
97551
+ }
97552
+ },
97553
+ "parameters": [
97554
+ {
97555
+ "name": "video",
97556
+ "type": {
97557
+ "text": "HTMLVideoElement"
97558
+ }
97559
+ },
97560
+ {
97561
+ "name": "early",
97562
+ "type": {
97563
+ "text": "boolean"
97564
+ }
97565
+ }
97566
+ ],
97567
+ "description": "Choose and attach the source: `src` where the browser can play `type`,\notherwise `fallback-src`. An early (preload) attach only ever attaches\n`src`; the fallback waits for play intent."
97568
+ },
97569
+ {
97570
+ "kind": "method",
97571
+ "name": "useSource",
97572
+ "privacy": "private",
97573
+ "return": {
97574
+ "type": {
97575
+ "text": "void"
97576
+ }
97577
+ },
97578
+ "parameters": [
97579
+ {
97580
+ "name": "video",
97581
+ "type": {
97582
+ "text": "HTMLVideoElement"
97583
+ }
97584
+ },
97585
+ {
97586
+ "name": "url",
97587
+ "type": {
97588
+ "text": "string"
97589
+ }
97590
+ },
97591
+ {
97592
+ "name": "mode",
97593
+ "type": {
97594
+ "text": "VideoSourceMode"
97595
+ }
97596
+ }
97597
+ ]
97598
+ },
97599
+ {
97600
+ "kind": "method",
97601
+ "name": "failOver",
97602
+ "privacy": "private",
97603
+ "return": {
97604
+ "type": {
97605
+ "text": "boolean"
97606
+ }
97607
+ },
97608
+ "parameters": [
97609
+ {
97610
+ "name": "video",
97611
+ "type": {
97612
+ "text": "HTMLVideoElement"
97613
+ }
97614
+ },
97615
+ {
97616
+ "name": "reason",
97617
+ "type": {
97618
+ "text": "VideoErrorReason"
97619
+ }
97620
+ },
97621
+ {
97622
+ "name": "message",
97623
+ "type": {
97624
+ "text": "string"
97625
+ }
97626
+ }
97627
+ ],
97628
+ "description": "The primary source failed. Report it and try `fallback-src` once; with nothing left to try, show the error state."
97629
+ },
97630
+ {
97631
+ "kind": "method",
97632
+ "name": "emitError",
97633
+ "privacy": "private",
97634
+ "return": {
97635
+ "type": {
97636
+ "text": "void"
97637
+ }
97638
+ },
97639
+ "parameters": [
97640
+ {
97641
+ "name": "mode",
97642
+ "type": {
97643
+ "text": "VideoSourceMode | null"
97644
+ }
97645
+ },
97646
+ {
97647
+ "name": "reason",
97648
+ "type": {
97649
+ "text": "VideoErrorReason"
97650
+ }
97651
+ },
97652
+ {
97653
+ "name": "message",
97654
+ "type": {
97655
+ "text": "string"
97656
+ }
97657
+ },
97658
+ {
97659
+ "name": "fallback",
97660
+ "type": {
97661
+ "text": "boolean"
97662
+ }
97663
+ }
97664
+ ]
97665
+ },
97666
+ {
97667
+ "kind": "method",
97668
+ "name": "fail",
97669
+ "privacy": "private",
97670
+ "return": {
97671
+ "type": {
97672
+ "text": "void"
97673
+ }
97674
+ },
97675
+ "parameters": [
97676
+ {
97677
+ "name": "mode",
97678
+ "type": {
97679
+ "text": "VideoSourceMode | null"
97680
+ }
97681
+ },
97682
+ {
97683
+ "name": "reason",
97684
+ "type": {
97685
+ "text": "VideoErrorReason"
97686
+ }
97687
+ },
97688
+ {
97689
+ "name": "message",
97690
+ "type": {
97691
+ "text": "string"
97692
+ }
97693
+ }
97694
+ ]
97695
+ },
97696
+ {
97697
+ "kind": "method",
97698
+ "name": "enterError",
97699
+ "privacy": "private",
97700
+ "return": {
97701
+ "type": {
97702
+ "text": "void"
97703
+ }
97704
+ },
97705
+ "description": "Show the error state. The message replaces the play button, so a visitor\nwho pressed it from the keyboard keeps focus there instead of on the\ndocument."
97706
+ },
97707
+ {
97708
+ "kind": "method",
97709
+ "name": "resetSource",
97710
+ "privacy": "private",
97711
+ "return": {
97712
+ "type": {
97713
+ "text": "void"
97714
+ }
97715
+ },
97716
+ "description": "Forget the attached source and return to the poster."
97717
+ },
97718
+ {
97719
+ "kind": "method",
97720
+ "name": "mirrorTracks",
97721
+ "privacy": "private",
97722
+ "return": {
97723
+ "type": {
97724
+ "text": "void"
97725
+ }
97726
+ },
97727
+ "parameters": [
97728
+ {
97729
+ "name": "video",
97730
+ "type": {
97731
+ "text": "HTMLVideoElement"
97732
+ }
97733
+ }
97734
+ ],
97735
+ "description": "A media element only reads its own child `<track>`s, so slotting cannot\nreach the shadow `<video>`. Mirror the light-DOM tracks into it instead."
97736
+ },
97737
+ {
97738
+ "kind": "field",
97739
+ "name": "handleTrackSlotChange",
97740
+ "privacy": "private"
97741
+ },
97742
+ {
97743
+ "kind": "field",
97744
+ "name": "handlePlayClick",
97745
+ "privacy": "private"
97746
+ },
97747
+ {
97748
+ "kind": "field",
97749
+ "name": "handleLoadedMetadata",
97750
+ "privacy": "private"
97751
+ },
97752
+ {
97753
+ "kind": "method",
97754
+ "name": "emitReady",
97755
+ "privacy": "private",
97756
+ "return": {
97757
+ "type": {
97758
+ "text": "void"
97759
+ }
97760
+ },
97761
+ "parameters": [
97762
+ {
97763
+ "name": "video",
97764
+ "type": {
97765
+ "text": "HTMLVideoElement"
97766
+ }
97767
+ }
97768
+ ],
97769
+ "description": "Fire `esp-video-ready` once for the attached source."
97770
+ },
97771
+ {
97772
+ "kind": "field",
97773
+ "name": "handlePlay",
97774
+ "privacy": "private"
97775
+ },
97776
+ {
97777
+ "kind": "field",
97778
+ "name": "handlePlaying",
97779
+ "privacy": "private"
97780
+ },
97781
+ {
97782
+ "kind": "field",
97783
+ "name": "handleVideoError",
97784
+ "privacy": "private"
97785
+ },
97786
+ {
97787
+ "kind": "method",
97788
+ "name": "renderPosterContent",
97789
+ "privacy": "private"
97790
+ },
97791
+ {
97792
+ "kind": "field",
97793
+ "name": "seedColorBacker",
97794
+ "type": {
97795
+ "text": "string"
97796
+ },
97797
+ "privacy": "private",
97798
+ "default": "\"oklch(0.7 0.125 216)\"",
97799
+ "inheritedFrom": {
97800
+ "name": "EspalierElementBase",
97801
+ "module": "dist/shared/esp-element-base.js"
97802
+ }
97803
+ },
97804
+ {
97805
+ "kind": "field",
97806
+ "name": "espRoot",
97807
+ "type": {
97808
+ "text": "EspalierRoot | null"
97809
+ },
97810
+ "privacy": "private",
97811
+ "default": "null",
97812
+ "inheritedFrom": {
97813
+ "name": "EspalierElementBase",
97814
+ "module": "dist/shared/esp-element-base.js"
97815
+ }
97816
+ },
97817
+ {
97818
+ "kind": "field",
97819
+ "name": "contextBacker",
97820
+ "type": {
97821
+ "text": "string"
97822
+ },
97823
+ "privacy": "private",
97824
+ "default": "\"\"",
97825
+ "inheritedFrom": {
97826
+ "name": "EspalierElementBase",
97827
+ "module": "dist/shared/esp-element-base.js"
97828
+ }
97829
+ },
97830
+ {
97831
+ "kind": "field",
97832
+ "name": "scopedTokenOriginalValues",
97833
+ "privacy": "private",
97834
+ "readonly": true,
97835
+ "default": "new Map< string, { generatedPriority: string; generatedValue: string; originalPriority: string; originalValue: string | null; } >()",
97836
+ "inheritedFrom": {
97837
+ "name": "EspalierElementBase",
97838
+ "module": "dist/shared/esp-element-base.js"
97839
+ }
97840
+ },
97841
+ {
97842
+ "kind": "field",
97843
+ "name": "warnedUnknownContexts",
97844
+ "privacy": "private",
97845
+ "readonly": true,
97846
+ "default": "new Set<string>()",
97847
+ "inheritedFrom": {
97848
+ "name": "EspalierElementBase",
97849
+ "module": "dist/shared/esp-element-base.js"
97850
+ }
97851
+ },
97852
+ {
97853
+ "kind": "field",
97854
+ "name": "pendingInitialThemeRoot",
97855
+ "type": {
97856
+ "text": "EspalierRoot | null"
97857
+ },
97858
+ "privacy": "private",
97859
+ "default": "null",
97860
+ "inheritedFrom": {
97861
+ "name": "EspalierElementBase",
97862
+ "module": "dist/shared/esp-element-base.js"
97863
+ }
97864
+ },
97865
+ {
97866
+ "kind": "field",
97867
+ "name": "rootEventSubscriptionsActive",
97868
+ "type": {
97869
+ "text": "boolean"
97870
+ },
97871
+ "privacy": "private",
97872
+ "default": "false",
97873
+ "inheritedFrom": {
97874
+ "name": "EspalierElementBase",
97875
+ "module": "dist/shared/esp-element-base.js"
97876
+ }
97877
+ },
97878
+ {
97879
+ "kind": "field",
97880
+ "name": "subscribedRoot",
97881
+ "type": {
97882
+ "text": "EspalierRoot | null"
97883
+ },
97884
+ "privacy": "private",
97885
+ "default": "null",
97886
+ "inheritedFrom": {
97887
+ "name": "EspalierElementBase",
97888
+ "module": "dist/shared/esp-element-base.js"
97889
+ }
97890
+ },
97891
+ {
97892
+ "kind": "field",
97893
+ "name": "rootEventUnsubscribers",
97894
+ "type": {
97895
+ "text": "Array<() => void>"
97896
+ },
97897
+ "privacy": "private",
97898
+ "default": "[]",
97899
+ "inheritedFrom": {
97900
+ "name": "EspalierElementBase",
97901
+ "module": "dist/shared/esp-element-base.js"
97902
+ }
97903
+ },
97904
+ {
97905
+ "kind": "field",
97906
+ "name": "intentBacker",
97907
+ "type": {
97908
+ "text": "EspalierIntentVariant"
97909
+ },
97910
+ "privacy": "protected",
97911
+ "default": "\"neutral\"",
97912
+ "inheritedFrom": {
97913
+ "name": "EspalierElementBase",
97914
+ "module": "dist/shared/esp-element-base.js"
97915
+ }
97916
+ },
97917
+ {
97918
+ "kind": "field",
97919
+ "name": "intentEmitsTokens",
97920
+ "type": {
97921
+ "text": "boolean"
97922
+ },
97923
+ "privacy": "protected",
97924
+ "default": "true",
97925
+ "description": "Whether a non-neutral intent emits its inline tokens\n(`--esp-color-primary` and the filled-action pair). Interactive\ncontrols need them; chrome whose intent treatment is class-based\n(badges, callouts, status pills — see `intentSurfaceTokens`) opts\nout so a decorative intent never rewrites inherited tokens for\nslotted content.",
97926
+ "inheritedFrom": {
97927
+ "name": "EspalierElementBase",
97928
+ "module": "dist/shared/esp-element-base.js"
97929
+ }
97930
+ },
97931
+ {
97932
+ "kind": "field",
97933
+ "name": "warnedUnknownIntents",
97934
+ "privacy": "private",
97935
+ "readonly": true,
97936
+ "default": "new Set<string>()",
97937
+ "inheritedFrom": {
97938
+ "name": "EspalierElementBase",
97939
+ "module": "dist/shared/esp-element-base.js"
97940
+ }
97941
+ },
97942
+ {
97943
+ "kind": "field",
97944
+ "name": "seedColor",
97945
+ "type": {
97946
+ "text": "string"
97947
+ },
97948
+ "privacy": "public",
97949
+ "inheritedFrom": {
97950
+ "name": "EspalierElementBase",
97951
+ "module": "dist/shared/esp-element-base.js"
97952
+ }
97953
+ },
97954
+ {
97955
+ "kind": "field",
97956
+ "name": "correlationId",
97957
+ "privacy": "public",
97958
+ "inheritedFrom": {
97959
+ "name": "EspalierElementBase",
97960
+ "module": "dist/shared/esp-element-base.js"
97961
+ }
97962
+ },
97963
+ {
97964
+ "kind": "method",
97965
+ "name": "focusResolvedElementAfterUpdate",
97966
+ "privacy": "protected",
97967
+ "return": {
97968
+ "type": {
97969
+ "text": "void"
97970
+ }
97971
+ },
97972
+ "parameters": [
97973
+ {
97974
+ "name": "resolveTarget",
97975
+ "type": {
97976
+ "text": "() => HTMLElement | null | undefined"
97977
+ }
97978
+ },
97979
+ {
97980
+ "name": "options",
97981
+ "optional": true,
97982
+ "type": {
97983
+ "text": "FocusOptions"
97984
+ }
97985
+ }
97986
+ ],
97987
+ "inheritedFrom": {
97988
+ "name": "EspalierElementBase",
97989
+ "module": "dist/shared/esp-element-base.js"
97990
+ }
97991
+ },
97992
+ {
97993
+ "kind": "method",
97994
+ "name": "focusShadowElementAfterUpdate",
97995
+ "privacy": "protected",
97996
+ "return": {
97997
+ "type": {
97998
+ "text": "void"
97999
+ }
98000
+ },
98001
+ "parameters": [
98002
+ {
98003
+ "name": "selector",
98004
+ "type": {
98005
+ "text": "string"
98006
+ }
98007
+ },
98008
+ {
98009
+ "name": "options",
98010
+ "optional": true,
98011
+ "type": {
98012
+ "text": "FocusOptions"
98013
+ }
98014
+ }
98015
+ ],
98016
+ "inheritedFrom": {
98017
+ "name": "EspalierElementBase",
98018
+ "module": "dist/shared/esp-element-base.js"
98019
+ }
98020
+ },
98021
+ {
98022
+ "kind": "method",
98023
+ "name": "emitValueChanged",
98024
+ "privacy": "protected",
98025
+ "return": {
98026
+ "type": {
98027
+ "text": "void"
98028
+ }
98029
+ },
98030
+ "parameters": [
98031
+ {
98032
+ "name": "detail",
98033
+ "type": {
98034
+ "text": "T"
98035
+ }
98036
+ }
98037
+ ],
98038
+ "inheritedFrom": {
98039
+ "name": "EspalierElementBase",
98040
+ "module": "dist/shared/esp-element-base.js"
98041
+ }
98042
+ },
98043
+ {
98044
+ "kind": "field",
98045
+ "name": "scheme",
98046
+ "type": {
98047
+ "text": "\"dark\" | \"light\""
98048
+ },
98049
+ "privacy": "public",
98050
+ "default": "\"light\"",
98051
+ "description": "The scheme to use for the component. This property is used internally\nto control the light/dark scheme so it matches the scheme of\n[esp-root](/components/root). It should not be set manually, it\nexists as an attribute for styling purposes.",
98052
+ "attribute": "scheme",
98053
+ "reflects": true,
98054
+ "inheritedFrom": {
98055
+ "name": "EspalierElementBase",
98056
+ "module": "dist/shared/esp-element-base.js"
98057
+ }
98058
+ },
98059
+ {
98060
+ "kind": "field",
98061
+ "name": "intent",
98062
+ "type": {
98063
+ "text": "EspalierIntentVariant"
98064
+ },
98065
+ "privacy": "public",
98066
+ "description": "The element's [intent](/espalier-guides/) — its meaning: `neutral`\n(no pin), or `success`, `warning`, `danger`, `info` — each pinned\nto its fixed status family (blue for `info`), retunable per theme\nvia `intents`. On token-emitting controls a non-neutral intent pins the\nfilled-action pair to that family, derived over the governing\nzone's theme; class-styled chrome (badges, callouts, status pills)\nopts out of inline emission and renders its treatment from CSS\nclasses instead. An intent never repaints the surrounding zone,\nwhich is what contexts are for. Removing the attribute restores\nthe element's own default intent.",
98067
+ "attribute": "intent",
98068
+ "reflects": true,
98069
+ "inheritedFrom": {
98070
+ "name": "EspalierElementBase",
98071
+ "module": "dist/shared/esp-element-base.js"
98072
+ }
98073
+ },
98074
+ {
98075
+ "kind": "field",
98076
+ "name": "context",
98077
+ "type": {
98078
+ "text": "string"
98079
+ },
98080
+ "privacy": "public",
98081
+ "description": "A theme-defined color zone. The selected context rebinds designer-facing\nroles and emits a complete, contrast-enforced semantic token table on this\nhost for descendants to inherit.",
98082
+ "attribute": "context",
98083
+ "reflects": true,
98084
+ "inheritedFrom": {
98085
+ "name": "EspalierElementBase",
98086
+ "module": "dist/shared/esp-element-base.js"
98087
+ }
98088
+ },
98089
+ {
98090
+ "kind": "method",
98091
+ "name": "refreshDescendantIntentDerivations",
98092
+ "privacy": "private",
98093
+ "return": {
98094
+ "type": {
98095
+ "text": "void"
98096
+ }
98097
+ },
98098
+ "description": "A zone rebinding must reach descendants whose intent pairs derive\nover it. Root-level changes arrive over the bus, but the context\nattribute is plain DOM state with no bus event, so the zone host\nwalks its FLATTENED subtree — crossing open shadow roots and\nfollowing slot assignment, the tree its table cascades along — and\nlets each Espalier descendant re-apply against the current\nancestry. Idempotent for hosts this zone doesn't govern; the\nvisited set guards the slot/light diamond (a host's light children\nare also its slots' assigned elements).",
98099
+ "inheritedFrom": {
98100
+ "name": "EspalierElementBase",
98101
+ "module": "dist/shared/esp-element-base.js"
98102
+ }
98103
+ },
98104
+ {
98105
+ "kind": "method",
98106
+ "name": "flattenedParent",
98107
+ "privacy": "private",
98108
+ "static": true,
98109
+ "return": {
98110
+ "type": {
98111
+ "text": "Node | null"
98112
+ }
98113
+ },
98114
+ "parameters": [
98115
+ {
98116
+ "name": "node",
98117
+ "type": {
98118
+ "text": "Node"
98119
+ }
98120
+ }
98121
+ ],
98122
+ "description": "The flattened-tree parent: through the assigned slot, out of shadow roots.",
98123
+ "inheritedFrom": {
98124
+ "name": "EspalierElementBase",
98125
+ "module": "dist/shared/esp-element-base.js"
98126
+ }
98127
+ },
98128
+ {
98129
+ "kind": "method",
98130
+ "name": "resolveAncestorZoneTheme",
98131
+ "privacy": "private",
98132
+ "return": {
98133
+ "type": {
98134
+ "text": "EspalierTheme | null"
98135
+ }
98136
+ },
98137
+ "parameters": [
98138
+ {
98139
+ "name": "activeTheme",
98140
+ "type": {
98141
+ "text": "EspalierTheme"
98142
+ }
98143
+ }
98144
+ ],
98145
+ "description": "The nearest ancestor zone the active theme can actually compile,\nwalking the flattened tree — through assigned slots and out of\nshadow roots — because that is the tree the zone's emitted table\ncascades along: a consumer shell may declare its zone around a\n`<slot>` inside its own shadow. A zone host with an unresolvable\ncontext emits nothing and inherits the next zone out, so unknown\nnames are skipped rather than collapsed to the root theme. Bounded\nby this element's esp-root.",
98146
+ "inheritedFrom": {
98147
+ "name": "EspalierElementBase",
98148
+ "module": "dist/shared/esp-element-base.js"
98149
+ }
98150
+ },
98151
+ {
98152
+ "kind": "method",
98153
+ "name": "syncRootFromDom",
98154
+ "privacy": "private",
98155
+ "return": {
98156
+ "type": {
98157
+ "text": "EspalierRoot | null"
98158
+ }
98159
+ },
98160
+ "parameters": [
98161
+ {
98162
+ "name": "throwIfMissing",
98163
+ "type": {
98164
+ "text": "boolean"
98165
+ }
98166
+ }
98167
+ ],
98168
+ "inheritedFrom": {
98169
+ "name": "EspalierElementBase",
98170
+ "module": "dist/shared/esp-element-base.js"
98171
+ }
98172
+ },
98173
+ {
98174
+ "kind": "method",
98175
+ "name": "applyTokensWhenRootReady",
98176
+ "privacy": "private",
98177
+ "return": {
98178
+ "type": {
98179
+ "text": "void"
98180
+ }
98181
+ },
98182
+ "description": "Apply scoped tokens now, or defer until the active root's initial theme is ready.",
98183
+ "inheritedFrom": {
98184
+ "name": "EspalierElementBase",
98185
+ "module": "dist/shared/esp-element-base.js"
98186
+ }
98187
+ },
98188
+ {
98189
+ "kind": "method",
98190
+ "name": "applyTokensAfterInitialRootUpdate",
98191
+ "privacy": "private",
98192
+ "return": {
98193
+ "type": {
98194
+ "text": "void"
98195
+ }
98196
+ },
98197
+ "parameters": [
98198
+ {
98199
+ "name": "root",
98200
+ "type": {
98201
+ "text": "EspalierRoot"
98202
+ }
98203
+ }
98204
+ ],
98205
+ "description": "Apply initial scoped tokens once the exact root this host found is ready.",
98206
+ "inheritedFrom": {
98207
+ "name": "EspalierElementBase",
98208
+ "module": "dist/shared/esp-element-base.js"
98209
+ }
98210
+ },
98211
+ {
98212
+ "kind": "method",
98213
+ "name": "subscribeToRootEvents",
98214
+ "privacy": "private",
98215
+ "return": {
98216
+ "type": {
98217
+ "text": "void"
98218
+ }
98219
+ },
98220
+ "parameters": [
98221
+ {
98222
+ "name": "root",
98223
+ "type": {
98224
+ "text": "EspalierRoot"
98225
+ }
98226
+ }
98227
+ ],
98228
+ "inheritedFrom": {
98229
+ "name": "EspalierElementBase",
98230
+ "module": "dist/shared/esp-element-base.js"
98231
+ }
98232
+ },
98233
+ {
98234
+ "kind": "method",
98235
+ "name": "unsubscribeFromRootEvents",
98236
+ "privacy": "private",
98237
+ "return": {
98238
+ "type": {
98239
+ "text": "void"
98240
+ }
98241
+ },
98242
+ "inheritedFrom": {
98243
+ "name": "EspalierElementBase",
98244
+ "module": "dist/shared/esp-element-base.js"
98245
+ }
98246
+ },
98247
+ {
98248
+ "kind": "field",
98249
+ "name": "handleSeedColorChanged",
98250
+ "privacy": "private",
98251
+ "inheritedFrom": {
98252
+ "name": "EspalierElementBase",
98253
+ "module": "dist/shared/esp-element-base.js"
98254
+ }
98255
+ },
98256
+ {
98257
+ "kind": "field",
98258
+ "name": "handleSchemeChanged",
98259
+ "privacy": "private",
98260
+ "inheritedFrom": {
98261
+ "name": "EspalierElementBase",
98262
+ "module": "dist/shared/esp-element-base.js"
98263
+ }
98264
+ },
98265
+ {
98266
+ "kind": "field",
98267
+ "name": "handleThemeChanged",
98268
+ "privacy": "private",
98269
+ "inheritedFrom": {
98270
+ "name": "EspalierElementBase",
98271
+ "module": "dist/shared/esp-element-base.js"
98272
+ }
98273
+ },
98274
+ {
98275
+ "kind": "field",
98276
+ "name": "handleIconSpriteUrlChanged",
98277
+ "privacy": "private",
98278
+ "inheritedFrom": {
98279
+ "name": "EspalierElementBase",
98280
+ "module": "dist/shared/esp-element-base.js"
98281
+ }
98282
+ },
98283
+ {
98284
+ "kind": "method",
98285
+ "name": "traverseToClosest",
98286
+ "privacy": "public",
98287
+ "return": {
98288
+ "type": {
98289
+ "text": ""
98290
+ }
98291
+ },
98292
+ "parameters": [
98293
+ {
98294
+ "name": "selector",
98295
+ "type": {
98296
+ "text": "string"
98297
+ },
98298
+ "description": "The selector of the element to look for"
98299
+ }
98300
+ ],
98301
+ "description": "Traverse up the DOM tree to find the closest element that\nmatches the selector. This method is aware of shadow DOM\nboundaries and will traverse through them to find the element.",
98302
+ "inheritedFrom": {
98303
+ "name": "EspalierElementBase",
98304
+ "module": "dist/shared/esp-element-base.js"
98305
+ }
98306
+ },
98307
+ {
98308
+ "kind": "field",
98309
+ "name": "intentColorSources",
98310
+ "type": {
98311
+ "text": "Readonly<\n Record<EspalierIntentVariant, ColorSource | \"\">\n >"
98312
+ },
98313
+ "privacy": "private",
98314
+ "static": true,
98315
+ "readonly": true,
98316
+ "default": "{ danger: \"danger\", info: \"info\", neutral: \"\", success: \"success\", warning: \"warning\", }",
98317
+ "description": "Intent color sources: the meaning-bearing families an intent pins\nthe filled-action pair to. `neutral` pins nothing; each other\nintent maps to its own fixed status family (ESP0172 gave `info` a\nreal blue family, replacing the complementary borrow the retired\nvariant system used), and a theme's `intents` field retunes the\nfamily colors themselves.",
98318
+ "inheritedFrom": {
98319
+ "name": "EspalierElementBase",
98320
+ "module": "dist/shared/esp-element-base.js"
98321
+ }
98322
+ },
98323
+ {
98324
+ "kind": "field",
98325
+ "name": "semanticActionTokens",
98326
+ "type": {
98327
+ "text": "ReadonlySet<SemanticColorName>"
98328
+ },
98329
+ "privacy": "private",
98330
+ "static": true,
98331
+ "readonly": true,
98332
+ "default": "new Set<SemanticColorName>([\"actionBackground\", \"actionText\"])",
98333
+ "description": "Semantic tokens that paint a control's filled action surface — the\npair an intent recolors. Everything else inherits the zone cascade,\nwhich is the ESP0166 fix: under the SHY fitted theme the retired\nvariant re-seeding painted a danger callout's *background* mint\ngreen; an intent leaves the zone's surfaces alone and pins only the\naction pair (plus `--esp-color-primary` for state derivations) to\nthe intent's own status family — fixed hues by default, retunable\nper theme via `intents`. Task 098's\nguarantee survives structurally: the\npin is by source, so no theme mapping can rotate a destructive /\nwarning / positive action off its semantic hue.",
98334
+ "inheritedFrom": {
98335
+ "name": "EspalierElementBase",
98336
+ "module": "dist/shared/esp-element-base.js"
98337
+ }
98338
+ },
98339
+ {
98340
+ "kind": "method",
98341
+ "name": "applyScopedColorTokens",
98342
+ "privacy": "protected",
98343
+ "return": {
98344
+ "type": {
98345
+ "text": "void"
98346
+ }
98347
+ },
98348
+ "description": "Compute and apply context- and intent-specific semantic color tokens.\n\nWith no context and a `neutral` intent, all semantic tokens cascade\nfrom `<esp-root>` and previous overrides are cleared. A context\nemits the zone's complete twenty-three-token table; a non-neutral intent\nthen overlays the filled-action pair, derived over the governing\nzone's theme — this host's own context, or the nearest ancestor\nzone's — so its APCA enforcement runs against the zone's actual\naction surface.",
98349
+ "inheritedFrom": {
98350
+ "name": "EspalierElementBase",
98351
+ "module": "dist/shared/esp-element-base.js"
98352
+ }
98353
+ },
98354
+ {
98355
+ "kind": "method",
98356
+ "name": "resolveContextTheme",
98357
+ "privacy": "private",
98358
+ "return": {
98359
+ "type": {
98360
+ "text": "{ applied: boolean; theme: EspalierTheme }"
98361
+ }
98362
+ },
98363
+ "parameters": [
98364
+ {
98365
+ "name": "theme",
98366
+ "type": {
98367
+ "text": "EspalierTheme"
98368
+ }
98369
+ }
98370
+ ],
98371
+ "description": "Resolve this host's own context name over the active root theme.",
98372
+ "inheritedFrom": {
98373
+ "name": "EspalierElementBase",
98374
+ "module": "dist/shared/esp-element-base.js"
98375
+ }
98376
+ },
98377
+ {
98378
+ "kind": "method",
98379
+ "name": "warnUnknownContextWhenSettled",
98380
+ "privacy": "private",
98381
+ "return": {
98382
+ "type": {
98383
+ "text": "void"
98384
+ }
98385
+ },
98386
+ "parameters": [
98387
+ {
98388
+ "name": "name",
98389
+ "type": {
98390
+ "text": "string"
98391
+ }
98392
+ }
98393
+ ],
98394
+ "description": "Warn about an unknown context only once the root's theme has\nsettled (ESP0177).\n\nA script-mounted theme lands after its descendants first render, so\na zone naming a context the theme *does* define would otherwise\nwarn about a state that was never rendered to anyone. Waiting for\nthe root to settle — an author theme resolved, or the pre-theme\nwindow closed with none — makes the warning describe the settled\ntheme instead of a transient one. It is deferred, never dropped: a\ngenuinely missing context still warns, just after the window.",
98395
+ "inheritedFrom": {
98396
+ "name": "EspalierElementBase",
98397
+ "module": "dist/shared/esp-element-base.js"
98398
+ }
98399
+ },
98400
+ {
98401
+ "kind": "method",
98402
+ "name": "resolveNamedContextTheme",
98403
+ "privacy": "private",
98404
+ "return": {
98405
+ "type": {
98406
+ "text": "EspalierTheme | null"
98407
+ }
98408
+ },
98409
+ "parameters": [
98410
+ {
98411
+ "name": "theme",
98412
+ "type": {
98413
+ "text": "EspalierTheme"
98414
+ }
98415
+ },
98416
+ {
98417
+ "name": "name",
98418
+ "type": {
98419
+ "text": "string"
98420
+ }
98421
+ }
98422
+ ],
98423
+ "description": "Compile a named context's theme, or null when the theme doesn't\ndefine it. Delegates to the exported compiler so a DOM-less consumer\n(the fit-report suite, a check script) inspects exactly the theme a\nzone host renders — the drift this shared path exists to prevent\n(ESP0175).",
98424
+ "inheritedFrom": {
98425
+ "name": "EspalierElementBase",
98426
+ "module": "dist/shared/esp-element-base.js"
98427
+ }
98428
+ },
98429
+ {
98430
+ "kind": "method",
98431
+ "name": "applyScopedTokenProperties",
98432
+ "privacy": "private",
98433
+ "return": {
98434
+ "type": {
98435
+ "text": "void"
98436
+ }
98437
+ },
98438
+ "parameters": [
98439
+ {
98440
+ "name": "properties",
98441
+ "type": {
98442
+ "text": "Record<string, string>"
98443
+ }
98444
+ }
98445
+ ],
98446
+ "inheritedFrom": {
98447
+ "name": "EspalierElementBase",
98448
+ "module": "dist/shared/esp-element-base.js"
98449
+ }
98450
+ },
98451
+ {
98452
+ "kind": "method",
98453
+ "name": "setScopedTokenProperty",
98454
+ "privacy": "private",
98455
+ "return": {
98456
+ "type": {
98457
+ "text": "void"
98458
+ }
98459
+ },
98460
+ "parameters": [
98461
+ {
98462
+ "name": "name",
98463
+ "type": {
98464
+ "text": "string"
98465
+ }
98466
+ },
98467
+ {
98468
+ "name": "value",
98469
+ "type": {
98470
+ "text": "string"
98471
+ }
98472
+ }
98473
+ ],
98474
+ "inheritedFrom": {
98475
+ "name": "EspalierElementBase",
98476
+ "module": "dist/shared/esp-element-base.js"
98477
+ }
98478
+ },
98479
+ {
98480
+ "kind": "method",
98481
+ "name": "restoreScopedTokenProperty",
98482
+ "privacy": "private",
98483
+ "return": {
98484
+ "type": {
98485
+ "text": "void"
98486
+ }
98487
+ },
98488
+ "parameters": [
98489
+ {
98490
+ "name": "name",
98491
+ "type": {
98492
+ "text": "string"
98493
+ }
98494
+ }
98495
+ ],
98496
+ "inheritedFrom": {
98497
+ "name": "EspalierElementBase",
98498
+ "module": "dist/shared/esp-element-base.js"
98499
+ }
98500
+ },
98501
+ {
98502
+ "kind": "method",
98503
+ "name": "clearScopedTokens",
98504
+ "privacy": "private",
98505
+ "return": {
98506
+ "type": {
98507
+ "text": "void"
98508
+ }
98509
+ },
98510
+ "inheritedFrom": {
98511
+ "name": "EspalierElementBase",
98512
+ "module": "dist/shared/esp-element-base.js"
98513
+ }
98514
+ }
98515
+ ],
98516
+ "events": [
98517
+ {
98518
+ "type": {
98519
+ "text": "CustomEvent<VideoReadyDetail>"
96306
98520
  },
96307
- "description": "Cancelable event fired before the default graft dialog opens.",
96308
- "name": "esp-tree-graft-request"
98521
+ "description": "Bubbles across shadow boundaries once the attached source has loaded its metadata; `detail` is `{ src, mode, duration }`. Fires once per attached source.",
98522
+ "name": "esp-video-ready"
96309
98523
  },
96310
98524
  {
96311
98525
  "type": {
96312
- "text": "CustomEvent<EspTreeGraftDetail>"
98526
+ "text": "CustomEvent<VideoPlayDetail>"
96313
98527
  },
96314
- "description": "Fired when a new branch is committed.",
96315
- "name": "esp-tree-graft"
98528
+ "description": "Bubbles across shadow boundaries each time playback starts, or resumes after a pause, on the media's `playing`. Recovery from buffering or a seek is not reported, and a source that fails first reports no play; `detail` is `{ src, mode, currentTime, autoplay }`.",
98529
+ "name": "esp-video-play"
96316
98530
  },
96317
98531
  {
96318
98532
  "type": {
96319
- "text": "CustomEvent<EspTreeEditDetail>"
98533
+ "text": "CustomEvent<VideoErrorDetail>"
96320
98534
  },
96321
- "description": "Fired when an editable node's edit affordance is invoked.",
96322
- "name": "esp-tree-edit"
96323
- },
96324
- {
96325
- "type": {
96326
- "text": "CustomEvent<EspTreeNodeActionDetail>"
96327
- },
96328
- "description": "Fired when a deletable node's delete action is invoked.",
96329
- "name": "esp-tree-delete"
96330
- },
96331
- {
96332
- "type": {
96333
- "text": "CustomEvent<EspTreeNodeActionDetail>"
96334
- },
96335
- "description": "Fired when a movable-up node's move-up action is invoked.",
96336
- "name": "esp-tree-move-up"
96337
- },
96338
- {
96339
- "type": {
96340
- "text": "CustomEvent<EspTreeNodeActionDetail>"
96341
- },
96342
- "description": "Fired when a movable-down node's move-down action is invoked.",
96343
- "name": "esp-tree-move-down"
96344
- },
98535
+ "description": "Bubbles across shadow boundaries when a source fails; `detail` is `{ src, mode, reason, message, fallback }`. When `fallback` is true the element is already trying `fallback-src`.",
98536
+ "name": "esp-video-error"
98537
+ }
98538
+ ],
98539
+ "attributes": [
96345
98540
  {
98541
+ "name": "src",
96346
98542
  "type": {
96347
- "text": "CustomEvent<EspTreeNodeActionDetail>"
98543
+ "text": "string"
96348
98544
  },
96349
- "description": "Fired when a cuttable node's cut action is invoked.",
96350
- "name": "esp-tree-cut"
98545
+ "default": "\"\"",
98546
+ "description": "The media URL: an HLS master playlist by default, or a file matching `type`.",
98547
+ "fieldName": "src"
96351
98548
  },
96352
98549
  {
98550
+ "name": "type",
96353
98551
  "type": {
96354
- "text": "CustomEvent<EspTreeNodeActionDetail>"
98552
+ "text": "string"
96355
98553
  },
96356
- "description": "Fired when a paste child target node's paste-as-child action is invoked.",
96357
- "name": "esp-tree-paste-child"
98554
+ "default": "\"application/vnd.apple.mpegurl\"",
98555
+ "description": "The media type of `src`, checked with `canPlayType` to choose between\n`src` and `fallback-src`. `video/mp4` plays as a plain file.",
98556
+ "fieldName": "type"
96358
98557
  },
96359
98558
  {
98559
+ "name": "poster",
96360
98560
  "type": {
96361
- "text": "CustomEvent<EspTreeGraftInputDetail>"
98561
+ "text": "string"
96362
98562
  },
96363
- "description": "Fired live as the graft input changes.",
96364
- "name": "esp-tree-graft-input"
98563
+ "default": "\"\"",
98564
+ "description": "Poster image URL, shown until playback starts and behind error messages.",
98565
+ "fieldName": "poster"
96365
98566
  },
96366
98567
  {
98568
+ "name": "poster-alt",
96367
98569
  "type": {
96368
- "text": "CustomEvent<{ valid: boolean; message: string }>"
96369
- },
96370
- "description": "Fired whenever constraint validation runs.",
96371
- "name": "esp-validity-changed"
96372
- }
96373
- ],
96374
- "attributes": [
96375
- {
96376
- "name": "nodes",
96377
- "type": {
96378
- "text": "Array<EspTreeNode>"
98570
+ "text": "string"
96379
98571
  },
96380
- "default": "[]",
96381
- "description": "The hierarchy to display, as the primary data-driven API. When\nnon-empty this takes precedence over slotted `esp-tree-item`\nchildren.",
96382
- "fieldName": "nodes"
98572
+ "default": "\"\"",
98573
+ "description": "Describes the poster; names the play button when `label` is unset.",
98574
+ "fieldName": "posterAlt"
96383
98575
  },
96384
98576
  {
96385
- "name": "value",
98577
+ "name": "label",
96386
98578
  "type": {
96387
98579
  "text": "string"
96388
98580
  },
96389
98581
  "default": "\"\"",
96390
- "description": "The selected node's full path (segments joined by `/`), or the\nempty string when nothing is selected. This is also the form\nsubmission value.",
96391
- "fieldName": "value"
98582
+ "description": "The play button's accessible name. Defaults to `Play video: <poster-alt>`,\nor `Play video` with no poster text.",
98583
+ "fieldName": "label"
96392
98584
  },
96393
98585
  {
96394
- "name": "name",
98586
+ "name": "aspect-ratio",
96395
98587
  "type": {
96396
98588
  "text": "string"
96397
98589
  },
96398
- "default": "\"\"",
96399
- "description": "The name used when the tree participates in a `<form>`.",
96400
- "fieldName": "name"
98590
+ "default": "\"16 / 9\"",
98591
+ "description": "The box's aspect ratio, as a positive number or `number / number`. Invalid\nvalues fall back to `16 / 9`.",
98592
+ "fieldName": "aspectRatio"
96401
98593
  },
96402
98594
  {
96403
- "name": "required",
98595
+ "name": "preload",
96404
98596
  "type": {
96405
- "text": "boolean"
98597
+ "text": "VideoPreload"
96406
98598
  },
96407
- "default": "false",
96408
- "description": "When true, a selection must be made before the form can submit.",
96409
- "fieldName": "required"
98599
+ "default": "\"none\"",
98600
+ "description": "How much media to load before play intent. `none` (the default) loads\nonly the poster. `metadata` and `auto` apply where the browser plays\n`type`; the choice of the fallback over it still waits for play intent.\nA preloaded source that fails switches to `fallback-src` at once.",
98601
+ "fieldName": "preload"
96410
98602
  },
96411
98603
  {
96412
- "name": "required-message",
98604
+ "name": "autoplay",
96413
98605
  "type": {
96414
- "text": "string"
98606
+ "text": "boolean"
96415
98607
  },
96416
- "default": "\"\"",
96417
- "description": "A custom message shown when the tree is required but nothing is\nselected. Defaults to `\"Please select an option.\"`.",
96418
- "fieldName": "requiredMessage"
98608
+ "default": "false",
98609
+ "description": "Start muted playback once admitted. Implies `muted`.",
98610
+ "fieldName": "autoplay"
96419
98611
  },
96420
98612
  {
96421
- "name": "disabled",
98613
+ "name": "muted",
96422
98614
  "type": {
96423
98615
  "text": "boolean"
96424
98616
  },
96425
98617
  "default": "false",
96426
- "description": "When true, the tree is disabled and cannot be interacted with.",
96427
- "fieldName": "disabled"
98618
+ "description": "Start muted. Always on while `autoplay` is set. Clearing it (or\n`autoplay`) unmutes only a paused video; a playing one stays muted until\nthe visitor unmutes it.",
98619
+ "fieldName": "muted"
96428
98620
  },
96429
98621
  {
96430
- "name": "allow-graft",
98622
+ "name": "loop",
96431
98623
  "type": {
96432
98624
  "text": "boolean"
96433
98625
  },
96434
98626
  "default": "false",
96435
- "description": "When true, an inline `+` affordance is shown on every node and at\nthe root for grafting a new child branch.",
96436
- "fieldName": "allowGraft"
98627
+ "description": "Restart from the beginning when playback ends.",
98628
+ "fieldName": "loop"
96437
98629
  },
96438
98630
  {
96439
- "name": "root-label",
98631
+ "name": "playsinline",
96440
98632
  "type": {
96441
- "text": "string"
98633
+ "text": "boolean"
96442
98634
  },
96443
- "default": "\"/\"",
96444
- "description": "Label shown for the always-rendered root ancestor row.",
96445
- "fieldName": "rootLabel"
98635
+ "default": "false",
98636
+ "description": "Play inline on iOS rather than entering full screen. Needed for inline autoplay there.",
98637
+ "fieldName": "playsinline"
96446
98638
  },
96447
98639
  {
96448
- "name": "root-path",
98640
+ "name": "controls",
96449
98641
  "type": {
96450
- "text": "string"
98642
+ "text": "VideoControls"
96451
98643
  },
96452
- "default": "\"/\"",
96453
- "description": "Path submitted when the root row is selected. Defaults to `/`.",
96454
- "fieldName": "rootPath"
98644
+ "default": "\"native\"",
98645
+ "description": "`native` (the default) shows the browser's controls once playback starts.\n`none` shows none; use it only for short decorative loops, since a visitor\nthen has no way to pause.",
98646
+ "fieldName": "controls"
96455
98647
  },
96456
98648
  {
96457
- "name": "root-selectable",
98649
+ "name": "defer-offscreen",
96458
98650
  "type": {
96459
98651
  "text": "boolean"
96460
98652
  },
96461
- "default": "true",
96462
- "description": "Whether the root row can be selected. Defaults to true. Set\n`root-selectable=\"false\"` to make it navigation-only.",
96463
- "fieldName": "rootSelectable"
98653
+ "default": "false",
98654
+ "description": "Defer everything — the poster, `preload`, and `autoplay` — until the\nelement is within 200px of the viewport. The poster is not rendered\nbefore then, so the browser's own lazy-loading distance cannot fetch it\nearly.",
98655
+ "fieldName": "deferOffscreen"
96464
98656
  },
96465
98657
  {
96466
- "name": "label",
98658
+ "name": "fallback-src",
96467
98659
  "type": {
96468
98660
  "text": "string"
96469
98661
  },
96470
98662
  "default": "\"\"",
96471
- "description": "An accessible name for the tree. Usually unnecessary inside an\n`esp-form-item`, which provides the label; set it for standalone\nuse.",
96472
- "fieldName": "label"
96473
- },
96474
- {
96475
- "name": "graft-error",
96476
- "type": {
96477
- "text": "string | null"
96478
- },
96479
- "default": "null",
96480
- "description": "A consumer-supplied error for the in-progress graft, shown inline\nand blocking commit until cleared. Set it with `setGraftError`,\ntypically in response to an `esp-tree-graft-input` event after an\nasync check. Mirrored as the `graft-error` attribute.",
96481
- "fieldName": "graftError"
98663
+ "description": "An MP4 played when the browser cannot play `type`, and tried once when\nthe primary source fails.",
98664
+ "fieldName": "fallbackSrc"
96482
98665
  },
96483
98666
  {
96484
- "name": "keep-expanded",
98667
+ "name": "crossorigin",
96485
98668
  "type": {
96486
- "text": "boolean"
98669
+ "text": "VideoCrossOrigin"
96487
98670
  },
96488
- "default": "false",
96489
- "description": "Keep already-expanded branches open when selecting a row outside of\nthe selected branch. By default, selecting outside an open selected\nbranch collapses that branch.",
96490
- "fieldName": "keepExpanded"
98671
+ "default": "\"\"",
98672
+ "description": "CORS mode for the video's requests, as on `<video crossorigin>`. Needed\nfor caption tracks served from another origin.",
98673
+ "fieldName": "crossOrigin"
96491
98674
  },
96492
98675
  {
96493
98676
  "name": "scheme",
@@ -96532,80 +98715,361 @@
96532
98715
  "module": "dist/shared/esp-element-base.js"
96533
98716
  },
96534
98717
  "docPageTitle": {
96535
- "name": "Tree",
98718
+ "name": "Video",
96536
98719
  "description": ""
96537
98720
  },
96538
98721
  "docUrl": {
96539
- "name": "/components/tree",
98722
+ "name": "/components/video",
96540
98723
  "description": ""
96541
98724
  },
96542
98725
  "menuGroup": {
96543
- "name": "Form",
96544
- "description": "Controls"
98726
+ "name": "Media",
98727
+ "description": ""
96545
98728
  },
96546
98729
  "menuLabel": {
96547
- "name": "Tree",
98730
+ "name": "Video",
96548
98731
  "description": ""
96549
98732
  },
96550
98733
  "menuIcon": {
96551
- "name": "sitemap",
98734
+ "name": "photo",
96552
98735
  "description": ""
96553
98736
  },
96554
- "tagName": "esp-tree",
98737
+ "tagName": "esp-video",
96555
98738
  "customElement": true
98739
+ }
98740
+ ],
98741
+ "exports": [
98742
+ {
98743
+ "kind": "js",
98744
+ "name": "HLS_MEDIA_TYPE",
98745
+ "declaration": {
98746
+ "name": "HLS_MEDIA_TYPE",
98747
+ "module": "dist/video/esp-video.js"
98748
+ }
96556
98749
  },
96557
98750
  {
96558
- "kind": "variable",
96559
- "name": "id"
98751
+ "kind": "js",
98752
+ "name": "DEFAULT_VIDEO_ASPECT_RATIO",
98753
+ "declaration": {
98754
+ "name": "DEFAULT_VIDEO_ASPECT_RATIO",
98755
+ "module": "dist/video/esp-video.js"
98756
+ }
96560
98757
  },
96561
98758
  {
96562
- "kind": "variable",
96563
- "name": "selected"
98759
+ "kind": "js",
98760
+ "name": "EspalierVideo",
98761
+ "declaration": {
98762
+ "name": "EspalierVideo",
98763
+ "module": "dist/video/esp-video.js"
98764
+ }
96564
98765
  },
96565
98766
  {
96566
- "kind": "variable",
96567
- "name": "segment"
98767
+ "kind": "custom-element-definition",
98768
+ "name": "esp-video",
98769
+ "declaration": {
98770
+ "name": "EspalierVideo",
98771
+ "module": "dist/video/esp-video.js"
98772
+ }
98773
+ }
98774
+ ]
98775
+ },
98776
+ {
98777
+ "kind": "javascript-module",
98778
+ "path": "dist/root/helpers/compute-semantic-properties.js",
98779
+ "declarations": [
98780
+ {
98781
+ "kind": "function",
98782
+ "name": "computeSemanticProperties",
98783
+ "return": {
98784
+ "type": {
98785
+ "text": "Record<string, string>"
98786
+ }
98787
+ },
98788
+ "parameters": [
98789
+ {
98790
+ "name": "theme",
98791
+ "type": {
98792
+ "text": "EspalierTheme"
98793
+ }
98794
+ },
98795
+ {
98796
+ "name": "variants",
98797
+ "type": {
98798
+ "text": "Record<ColorSource, OklchColor>"
98799
+ }
98800
+ },
98801
+ {
98802
+ "name": "options",
98803
+ "default": "{}",
98804
+ "type": {
98805
+ "text": "ComputeSemanticPropertiesOptions"
98806
+ }
98807
+ }
98808
+ ],
98809
+ "description": "Derive the complete twenty-three-token semantic table for a resolved theme.\n\nRoot output, context scopes, and intent overlays all use\nthis function so their APCA pairings cannot drift apart."
98810
+ }
98811
+ ],
98812
+ "exports": [
98813
+ {
98814
+ "kind": "js",
98815
+ "name": "computeSemanticProperties",
98816
+ "declaration": {
98817
+ "name": "computeSemanticProperties",
98818
+ "module": "dist/root/helpers/compute-semantic-properties.js"
98819
+ }
98820
+ }
98821
+ ]
98822
+ },
98823
+ {
98824
+ "kind": "javascript-module",
98825
+ "path": "dist/root/helpers/compute-theme-properties.js",
98826
+ "declarations": [
98827
+ {
98828
+ "kind": "function",
98829
+ "name": "dataSeriesProperties",
98830
+ "return": {
98831
+ "type": {
98832
+ "text": "Record<string, string>"
98833
+ }
98834
+ },
98835
+ "parameters": [
98836
+ {
98837
+ "name": "theme",
98838
+ "type": {
98839
+ "text": "EspalierTheme"
98840
+ }
98841
+ },
98842
+ {
98843
+ "name": "seed",
98844
+ "type": {
98845
+ "text": "OklchColor"
98846
+ }
98847
+ },
98848
+ {
98849
+ "name": "resolve",
98850
+ "type": {
98851
+ "text": "(source: unknown, label: string) => string"
98852
+ }
98853
+ },
98854
+ {
98855
+ "description": "Optional sink for the theme fit report (ESP0163). When\nsupplied, each semantic derivation is appended as it is emitted —\nthe emitted value read back out of the property record, and the\npre-enforcement value taken from the derivation itself — so a report\ndescribes this run rather than re-deriving it. When omitted the\npipeline does no reporting work at all.",
98856
+ "name": "trace"
98857
+ }
98858
+ ],
98859
+ "description": "Emit the categorical series identities from a theme's data palette.\n\nShared between the root emission and the zone-table path (the zone\ncompanions need the identities without re-emitting them), so the two\ncan never resolve a palette differently."
98860
+ },
98861
+ {
98862
+ "kind": "function",
98863
+ "name": "computeThemeProperties",
98864
+ "return": {
98865
+ "type": {
98866
+ "text": "Record<string, string>"
98867
+ }
98868
+ },
98869
+ "parameters": [
98870
+ {
98871
+ "name": "theme",
98872
+ "type": {
98873
+ "text": "EspalierTheme"
98874
+ }
98875
+ },
98876
+ {
98877
+ "name": "scheme",
98878
+ "type": {
98879
+ "text": "\"light\" | \"dark\""
98880
+ }
98881
+ },
98882
+ {
98883
+ "name": "trace",
98884
+ "optional": true,
98885
+ "type": {
98886
+ "text": "ThemeFitTraceEntry[]"
98887
+ }
98888
+ }
98889
+ ]
98890
+ }
98891
+ ],
98892
+ "exports": [
98893
+ {
98894
+ "kind": "js",
98895
+ "name": "dataSeriesProperties",
98896
+ "declaration": {
98897
+ "name": "dataSeriesProperties",
98898
+ "module": "dist/root/helpers/compute-theme-properties.js"
98899
+ }
96568
98900
  },
98901
+ {
98902
+ "kind": "js",
98903
+ "name": "computeThemeProperties",
98904
+ "declaration": {
98905
+ "name": "computeThemeProperties",
98906
+ "module": "dist/root/helpers/compute-theme-properties.js"
98907
+ }
98908
+ }
98909
+ ]
98910
+ },
98911
+ {
98912
+ "kind": "javascript-module",
98913
+ "path": "dist/root/helpers/compute-variants.js",
98914
+ "declarations": [],
98915
+ "exports": [
98916
+ {
98917
+ "kind": "js",
98918
+ "name": "computeVariants",
98919
+ "declaration": {
98920
+ "name": "computeVariants",
98921
+ "module": "../../shared/variant-engine.js"
98922
+ }
98923
+ },
98924
+ {
98925
+ "kind": "js",
98926
+ "name": "resolveMappingSource",
98927
+ "declaration": {
98928
+ "name": "resolveMappingSource",
98929
+ "module": "../../shared/variant-engine.js"
98930
+ }
98931
+ }
98932
+ ]
98933
+ },
98934
+ {
98935
+ "kind": "javascript-module",
98936
+ "path": "dist/root/helpers/data-companion-properties.js",
98937
+ "declarations": [
96569
98938
  {
96570
98939
  "kind": "variable",
96571
- "name": "selectable"
98940
+ "name": "DATA_WASH_SERIES_SHARE",
98941
+ "type": {
98942
+ "text": "number"
98943
+ },
98944
+ "default": "0.75",
98945
+ "description": "The series' share of the wash mix — the documented fill recipe."
96572
98946
  },
96573
98947
  {
96574
98948
  "kind": "variable",
96575
- "name": "hasChildren"
98949
+ "name": "DATA_TINT_SERIES_SHARE",
98950
+ "type": {
98951
+ "text": "number"
98952
+ },
98953
+ "default": "0.12",
98954
+ "description": "The series' share of the subtle tint mix behind ordinary copy."
98955
+ },
98956
+ {
98957
+ "kind": "function",
98958
+ "name": "dataCompanionProperties",
98959
+ "return": {
98960
+ "type": {
98961
+ "text": "Record<string, string>"
98962
+ }
98963
+ },
98964
+ "parameters": [
98965
+ {
98966
+ "name": "properties",
98967
+ "type": {
98968
+ "text": "Record<string, string>"
98969
+ }
98970
+ },
98971
+ {
98972
+ "name": "inheritedSeries",
98973
+ "optional": true,
98974
+ "type": {
98975
+ "text": "Record<string, string>"
98976
+ }
98977
+ }
98978
+ ],
98979
+ "description": "Compute the wash, tint, and ink companions for every series token\npresent in an emitted property table, against that table's own background.\n\nReads the *emitted* strings — the same read-back principle as the fit\nreport — so a companion can never disagree with the identity token or\nthe background it composes with. Zone tables pass their own table and\nget zone-correct companions; tables without series tokens (a zone\ntable, which inherits the root's identities) take them from\n`inheritedSeries`, the root-emitted table."
96576
98980
  }
96577
98981
  ],
96578
98982
  "exports": [
96579
98983
  {
96580
98984
  "kind": "js",
96581
- "name": "normalizeSegment",
98985
+ "name": "DATA_WASH_SERIES_SHARE",
96582
98986
  "declaration": {
96583
- "name": "normalizeSegment",
96584
- "module": "dist/tree/esp-tree.js"
98987
+ "name": "DATA_WASH_SERIES_SHARE",
98988
+ "module": "dist/root/helpers/data-companion-properties.js"
96585
98989
  }
96586
98990
  },
96587
98991
  {
96588
98992
  "kind": "js",
96589
- "name": "buildWebTreePath",
98993
+ "name": "DATA_TINT_SERIES_SHARE",
96590
98994
  "declaration": {
96591
- "name": "buildWebTreePath",
96592
- "module": "dist/tree/esp-tree.js"
98995
+ "name": "DATA_TINT_SERIES_SHARE",
98996
+ "module": "dist/root/helpers/data-companion-properties.js"
96593
98997
  }
96594
98998
  },
96595
98999
  {
96596
99000
  "kind": "js",
96597
- "name": "EspalierTree",
99001
+ "name": "dataCompanionProperties",
96598
99002
  "declaration": {
96599
- "name": "EspalierTree",
96600
- "module": "dist/tree/esp-tree.js"
99003
+ "name": "dataCompanionProperties",
99004
+ "module": "dist/root/helpers/data-companion-properties.js"
96601
99005
  }
96602
- },
99006
+ }
99007
+ ]
99008
+ },
99009
+ {
99010
+ "kind": "javascript-module",
99011
+ "path": "dist/root/helpers/lightness-key-to-css.js",
99012
+ "declarations": [
96603
99013
  {
96604
- "kind": "custom-element-definition",
96605
- "name": "esp-tree",
99014
+ "kind": "function",
99015
+ "name": "lightnessKeyToCSS",
99016
+ "return": {
99017
+ "type": {
99018
+ "text": "string"
99019
+ }
99020
+ },
99021
+ "parameters": [
99022
+ {
99023
+ "name": "key",
99024
+ "type": {
99025
+ "text": "string"
99026
+ }
99027
+ }
99028
+ ],
99029
+ "description": "Map a camelCase import(\"../../shared/theme.js\").LightnessKey\nto its CSS custom-property name.\n\n```\nraised1 → --esp-l-raised-1\nsurface → --esp-l-surface\n```"
99030
+ }
99031
+ ],
99032
+ "exports": [
99033
+ {
99034
+ "kind": "js",
99035
+ "name": "lightnessKeyToCSS",
96606
99036
  "declaration": {
96607
- "name": "EspalierTree",
96608
- "module": "dist/tree/esp-tree.js"
99037
+ "name": "lightnessKeyToCSS",
99038
+ "module": "dist/root/helpers/lightness-key-to-css.js"
99039
+ }
99040
+ }
99041
+ ]
99042
+ },
99043
+ {
99044
+ "kind": "javascript-module",
99045
+ "path": "dist/root/helpers/lightness-ramp-properties.js",
99046
+ "declarations": [
99047
+ {
99048
+ "kind": "function",
99049
+ "name": "lightnessRampProperties",
99050
+ "return": {
99051
+ "type": {
99052
+ "text": "Record<string, string>"
99053
+ }
99054
+ },
99055
+ "parameters": [
99056
+ {
99057
+ "name": "theme",
99058
+ "type": {
99059
+ "text": "EspalierTheme"
99060
+ }
99061
+ }
99062
+ ],
99063
+ "description": "The `--esp-l-*` ramp custom properties for a theme, in the percentage\nformat consumers expect inside relative color syntax — for example\n`oklch(from var(--esp-color-danger) var(--esp-l-raised-2) c h)`.\n\nEmitted by `<esp-root>` for the page and re-emitted by context zones\nfrom their merged theme, so ramp-derived styling — the `.intent-*`\nsurface classes, component chrome — re-derives against the zone's\nactual ramp instead of the root's. Without the zone emission a dark\nzone's class-styled chrome mixes provenance: a surface placed by the\nroot's light ramp under ink drawn from the zone's dark-surface table."
99064
+ }
99065
+ ],
99066
+ "exports": [
99067
+ {
99068
+ "kind": "js",
99069
+ "name": "lightnessRampProperties",
99070
+ "declaration": {
99071
+ "name": "lightnessRampProperties",
99072
+ "module": "dist/root/helpers/lightness-ramp-properties.js"
96609
99073
  }
96610
99074
  }
96611
99075
  ]
@@ -97255,307 +99719,6 @@
97255
99719
  "declarations": [],
97256
99720
  "exports": []
97257
99721
  },
97258
- {
97259
- "kind": "javascript-module",
97260
- "path": "dist/root/helpers/compute-semantic-properties.js",
97261
- "declarations": [
97262
- {
97263
- "kind": "function",
97264
- "name": "computeSemanticProperties",
97265
- "return": {
97266
- "type": {
97267
- "text": "Record<string, string>"
97268
- }
97269
- },
97270
- "parameters": [
97271
- {
97272
- "name": "theme",
97273
- "type": {
97274
- "text": "EspalierTheme"
97275
- }
97276
- },
97277
- {
97278
- "name": "variants",
97279
- "type": {
97280
- "text": "Record<ColorSource, OklchColor>"
97281
- }
97282
- },
97283
- {
97284
- "name": "options",
97285
- "default": "{}",
97286
- "type": {
97287
- "text": "ComputeSemanticPropertiesOptions"
97288
- }
97289
- }
97290
- ],
97291
- "description": "Derive the complete twenty-three-token semantic table for a resolved theme.\n\nRoot output, context scopes, and intent overlays all use\nthis function so their APCA pairings cannot drift apart."
97292
- }
97293
- ],
97294
- "exports": [
97295
- {
97296
- "kind": "js",
97297
- "name": "computeSemanticProperties",
97298
- "declaration": {
97299
- "name": "computeSemanticProperties",
97300
- "module": "dist/root/helpers/compute-semantic-properties.js"
97301
- }
97302
- }
97303
- ]
97304
- },
97305
- {
97306
- "kind": "javascript-module",
97307
- "path": "dist/root/helpers/compute-theme-properties.js",
97308
- "declarations": [
97309
- {
97310
- "kind": "function",
97311
- "name": "dataSeriesProperties",
97312
- "return": {
97313
- "type": {
97314
- "text": "Record<string, string>"
97315
- }
97316
- },
97317
- "parameters": [
97318
- {
97319
- "name": "theme",
97320
- "type": {
97321
- "text": "EspalierTheme"
97322
- }
97323
- },
97324
- {
97325
- "name": "seed",
97326
- "type": {
97327
- "text": "OklchColor"
97328
- }
97329
- },
97330
- {
97331
- "name": "resolve",
97332
- "type": {
97333
- "text": "(source: unknown, label: string) => string"
97334
- }
97335
- },
97336
- {
97337
- "description": "Optional sink for the theme fit report (ESP0163). When\nsupplied, each semantic derivation is appended as it is emitted —\nthe emitted value read back out of the property record, and the\npre-enforcement value taken from the derivation itself — so a report\ndescribes this run rather than re-deriving it. When omitted the\npipeline does no reporting work at all.",
97338
- "name": "trace"
97339
- }
97340
- ],
97341
- "description": "Emit the categorical series identities from a theme's data palette.\n\nShared between the root emission and the zone-table path (the zone\ncompanions need the identities without re-emitting them), so the two\ncan never resolve a palette differently."
97342
- },
97343
- {
97344
- "kind": "function",
97345
- "name": "computeThemeProperties",
97346
- "return": {
97347
- "type": {
97348
- "text": "Record<string, string>"
97349
- }
97350
- },
97351
- "parameters": [
97352
- {
97353
- "name": "theme",
97354
- "type": {
97355
- "text": "EspalierTheme"
97356
- }
97357
- },
97358
- {
97359
- "name": "scheme",
97360
- "type": {
97361
- "text": "\"light\" | \"dark\""
97362
- }
97363
- },
97364
- {
97365
- "name": "trace",
97366
- "optional": true,
97367
- "type": {
97368
- "text": "ThemeFitTraceEntry[]"
97369
- }
97370
- }
97371
- ]
97372
- }
97373
- ],
97374
- "exports": [
97375
- {
97376
- "kind": "js",
97377
- "name": "dataSeriesProperties",
97378
- "declaration": {
97379
- "name": "dataSeriesProperties",
97380
- "module": "dist/root/helpers/compute-theme-properties.js"
97381
- }
97382
- },
97383
- {
97384
- "kind": "js",
97385
- "name": "computeThemeProperties",
97386
- "declaration": {
97387
- "name": "computeThemeProperties",
97388
- "module": "dist/root/helpers/compute-theme-properties.js"
97389
- }
97390
- }
97391
- ]
97392
- },
97393
- {
97394
- "kind": "javascript-module",
97395
- "path": "dist/root/helpers/compute-variants.js",
97396
- "declarations": [],
97397
- "exports": [
97398
- {
97399
- "kind": "js",
97400
- "name": "computeVariants",
97401
- "declaration": {
97402
- "name": "computeVariants",
97403
- "module": "../../shared/variant-engine.js"
97404
- }
97405
- },
97406
- {
97407
- "kind": "js",
97408
- "name": "resolveMappingSource",
97409
- "declaration": {
97410
- "name": "resolveMappingSource",
97411
- "module": "../../shared/variant-engine.js"
97412
- }
97413
- }
97414
- ]
97415
- },
97416
- {
97417
- "kind": "javascript-module",
97418
- "path": "dist/root/helpers/data-companion-properties.js",
97419
- "declarations": [
97420
- {
97421
- "kind": "variable",
97422
- "name": "DATA_WASH_SERIES_SHARE",
97423
- "type": {
97424
- "text": "number"
97425
- },
97426
- "default": "0.75",
97427
- "description": "The series' share of the wash mix — the documented fill recipe."
97428
- },
97429
- {
97430
- "kind": "variable",
97431
- "name": "DATA_TINT_SERIES_SHARE",
97432
- "type": {
97433
- "text": "number"
97434
- },
97435
- "default": "0.12",
97436
- "description": "The series' share of the subtle tint mix behind ordinary copy."
97437
- },
97438
- {
97439
- "kind": "function",
97440
- "name": "dataCompanionProperties",
97441
- "return": {
97442
- "type": {
97443
- "text": "Record<string, string>"
97444
- }
97445
- },
97446
- "parameters": [
97447
- {
97448
- "name": "properties",
97449
- "type": {
97450
- "text": "Record<string, string>"
97451
- }
97452
- },
97453
- {
97454
- "name": "inheritedSeries",
97455
- "optional": true,
97456
- "type": {
97457
- "text": "Record<string, string>"
97458
- }
97459
- }
97460
- ],
97461
- "description": "Compute the wash, tint, and ink companions for every series token\npresent in an emitted property table, against that table's own background.\n\nReads the *emitted* strings — the same read-back principle as the fit\nreport — so a companion can never disagree with the identity token or\nthe background it composes with. Zone tables pass their own table and\nget zone-correct companions; tables without series tokens (a zone\ntable, which inherits the root's identities) take them from\n`inheritedSeries`, the root-emitted table."
97462
- }
97463
- ],
97464
- "exports": [
97465
- {
97466
- "kind": "js",
97467
- "name": "DATA_WASH_SERIES_SHARE",
97468
- "declaration": {
97469
- "name": "DATA_WASH_SERIES_SHARE",
97470
- "module": "dist/root/helpers/data-companion-properties.js"
97471
- }
97472
- },
97473
- {
97474
- "kind": "js",
97475
- "name": "DATA_TINT_SERIES_SHARE",
97476
- "declaration": {
97477
- "name": "DATA_TINT_SERIES_SHARE",
97478
- "module": "dist/root/helpers/data-companion-properties.js"
97479
- }
97480
- },
97481
- {
97482
- "kind": "js",
97483
- "name": "dataCompanionProperties",
97484
- "declaration": {
97485
- "name": "dataCompanionProperties",
97486
- "module": "dist/root/helpers/data-companion-properties.js"
97487
- }
97488
- }
97489
- ]
97490
- },
97491
- {
97492
- "kind": "javascript-module",
97493
- "path": "dist/root/helpers/lightness-key-to-css.js",
97494
- "declarations": [
97495
- {
97496
- "kind": "function",
97497
- "name": "lightnessKeyToCSS",
97498
- "return": {
97499
- "type": {
97500
- "text": "string"
97501
- }
97502
- },
97503
- "parameters": [
97504
- {
97505
- "name": "key",
97506
- "type": {
97507
- "text": "string"
97508
- }
97509
- }
97510
- ],
97511
- "description": "Map a camelCase import(\"../../shared/theme.js\").LightnessKey\nto its CSS custom-property name.\n\n```\nraised1 → --esp-l-raised-1\nsurface → --esp-l-surface\n```"
97512
- }
97513
- ],
97514
- "exports": [
97515
- {
97516
- "kind": "js",
97517
- "name": "lightnessKeyToCSS",
97518
- "declaration": {
97519
- "name": "lightnessKeyToCSS",
97520
- "module": "dist/root/helpers/lightness-key-to-css.js"
97521
- }
97522
- }
97523
- ]
97524
- },
97525
- {
97526
- "kind": "javascript-module",
97527
- "path": "dist/root/helpers/lightness-ramp-properties.js",
97528
- "declarations": [
97529
- {
97530
- "kind": "function",
97531
- "name": "lightnessRampProperties",
97532
- "return": {
97533
- "type": {
97534
- "text": "Record<string, string>"
97535
- }
97536
- },
97537
- "parameters": [
97538
- {
97539
- "name": "theme",
97540
- "type": {
97541
- "text": "EspalierTheme"
97542
- }
97543
- }
97544
- ],
97545
- "description": "The `--esp-l-*` ramp custom properties for a theme, in the percentage\nformat consumers expect inside relative color syntax — for example\n`oklch(from var(--esp-color-danger) var(--esp-l-raised-2) c h)`.\n\nEmitted by `<esp-root>` for the page and re-emitted by context zones\nfrom their merged theme, so ramp-derived styling — the `.intent-*`\nsurface classes, component chrome — re-derives against the zone's\nactual ramp instead of the root's. Without the zone emission a dark\nzone's class-styled chrome mixes provenance: a surface placed by the\nroot's light ramp under ink drawn from the zone's dark-surface table."
97546
- }
97547
- ],
97548
- "exports": [
97549
- {
97550
- "kind": "js",
97551
- "name": "lightnessRampProperties",
97552
- "declaration": {
97553
- "name": "lightnessRampProperties",
97554
- "module": "dist/root/helpers/lightness-ramp-properties.js"
97555
- }
97556
- }
97557
- ]
97558
- },
97559
99722
  {
97560
99723
  "kind": "javascript-module",
97561
99724
  "path": "dist/shared/svgs/calendar-icon.js",