luxen-ui 0.19.0 → 0.20.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 (103) hide show
  1. package/README.md +1 -0
  2. package/cdn/custom-elements.json +99 -5
  3. package/cdn/elements/button-group/button-group.d.ts +12 -7
  4. package/cdn/elements/button-group/button-group.d.ts.map +1 -1
  5. package/cdn/elements/button-group/button-group.js.map +1 -1
  6. package/cdn/elements/input-stepper/input-stepper.d.ts +1 -1
  7. package/cdn/elements/input-stepper/input-stepper.js.map +1 -1
  8. package/cdn/elements/popover/popover.d.ts +15 -0
  9. package/cdn/elements/popover/popover.d.ts.map +1 -1
  10. package/cdn/elements/popover/popover.js +1 -1
  11. package/cdn/elements/popover/popover.js.map +1 -1
  12. package/cdn/elements/radio-group/radio-group.meta.d.ts +41 -0
  13. package/cdn/elements/radio-group/radio-group.meta.d.ts.map +1 -0
  14. package/cdn/elements/radio-group/radio-group.meta.js +0 -0
  15. package/cdn/elements/segmented-control/segmented-control.d.ts +8 -4
  16. package/cdn/elements/segmented-control/segmented-control.d.ts.map +1 -1
  17. package/cdn/elements/segmented-control/segmented-control.js.map +1 -1
  18. package/cdn/elements/select/select.js +1 -1
  19. package/cdn/elements/select/select.js.map +1 -1
  20. package/cdn/elements/tag/tag.d.ts +3 -1
  21. package/cdn/elements/tag/tag.d.ts.map +1 -1
  22. package/cdn/elements/tag/tag.js +1 -1
  23. package/cdn/elements/tag/tag.js.map +1 -1
  24. package/cdn/elements/tooltip/tooltip.d.ts +15 -0
  25. package/cdn/elements/tooltip/tooltip.d.ts.map +1 -1
  26. package/cdn/elements/tooltip/tooltip.js +1 -1
  27. package/cdn/elements/tooltip/tooltip.js.map +1 -1
  28. package/cdn/shared/controllers/popover.d.ts +40 -0
  29. package/cdn/shared/controllers/popover.d.ts.map +1 -1
  30. package/cdn/shared/controllers/popover.js +1 -1
  31. package/cdn/shared/controllers/popover.js.map +1 -1
  32. package/cdn/shared/styles/button-core.styles.js +1 -1
  33. package/cdn/shared/styles/button-core.styles.js.map +1 -1
  34. package/cdn/standalone.css +774 -28
  35. package/cdn/styles/elements/button-group.css +4 -3
  36. package/cdn/styles/elements/button.css +28 -1
  37. package/cdn/styles/elements/disclosure.css +3 -3
  38. package/cdn/styles/elements/input-stepper/default.css +29 -2
  39. package/cdn/styles/elements/input-stepper/pill.css +420 -0
  40. package/cdn/styles/elements/input-stepper/rounded.css +69 -8
  41. package/cdn/styles/elements/kbd.css +2 -2
  42. package/cdn/styles/elements/radio-group.css +206 -0
  43. package/cdn/styles/elements/segmented-control.css +3 -3
  44. package/cdn/styles/elements/tabs/enclosed.css +4 -4
  45. package/cdn/styles/elements/tabs/line.css +2 -2
  46. package/dist/css/elements/button-group.css +4 -3
  47. package/dist/css/elements/button.css +28 -1
  48. package/dist/css/elements/disclosure.css +3 -3
  49. package/dist/css/elements/input-stepper/default.css +29 -2
  50. package/dist/css/elements/input-stepper/pill.css +420 -0
  51. package/dist/css/elements/input-stepper/rounded.css +69 -8
  52. package/dist/css/elements/kbd.css +2 -2
  53. package/dist/css/elements/radio-group.css +206 -0
  54. package/dist/css/elements/segmented-control.css +3 -3
  55. package/dist/css/elements/tabs/enclosed.css +4 -4
  56. package/dist/css/elements/tabs/line.css +2 -2
  57. package/dist/custom-elements.json +99 -5
  58. package/dist/elements/button-group/button-group.d.ts +12 -7
  59. package/dist/elements/button-group/button-group.d.ts.map +1 -1
  60. package/dist/elements/button-group/button-group.js +12 -7
  61. package/dist/elements/input-stepper/input-stepper.d.ts +1 -1
  62. package/dist/elements/input-stepper/input-stepper.js +1 -1
  63. package/dist/elements/popover/popover.d.ts +15 -0
  64. package/dist/elements/popover/popover.d.ts.map +1 -1
  65. package/dist/elements/popover/popover.js +61 -14
  66. package/dist/elements/radio-group/radio-group.meta.d.ts +41 -0
  67. package/dist/elements/radio-group/radio-group.meta.d.ts.map +1 -0
  68. package/dist/elements/radio-group/radio-group.meta.js +51 -0
  69. package/dist/elements/segmented-control/segmented-control.d.ts +8 -4
  70. package/dist/elements/segmented-control/segmented-control.d.ts.map +1 -1
  71. package/dist/elements/segmented-control/segmented-control.js +8 -4
  72. package/dist/elements/select/select.css +14 -0
  73. package/dist/elements/tag/tag.css +128 -49
  74. package/dist/elements/tag/tag.d.ts +3 -1
  75. package/dist/elements/tag/tag.d.ts.map +1 -1
  76. package/dist/elements/tag/tag.js +3 -1
  77. package/dist/elements/tooltip/tooltip.d.ts +15 -0
  78. package/dist/elements/tooltip/tooltip.d.ts.map +1 -1
  79. package/dist/elements/tooltip/tooltip.js +61 -14
  80. package/dist/metadata/button-group.json +2 -2
  81. package/dist/metadata/index.json +95 -7
  82. package/dist/metadata/input-stepper.json +3 -2
  83. package/dist/metadata/popover.json +6 -0
  84. package/dist/metadata/radio-group.json +65 -0
  85. package/dist/metadata/segmented-control.json +1 -1
  86. package/dist/metadata/tag.json +11 -1
  87. package/dist/metadata/tooltip.json +6 -0
  88. package/dist/shared/controllers/popover.d.ts +40 -0
  89. package/dist/shared/controllers/popover.d.ts.map +1 -1
  90. package/dist/shared/controllers/popover.js +69 -0
  91. package/dist/shared/styles/button-core.css +28 -1
  92. package/dist/templates/elements/button-group.md +159 -41
  93. package/dist/templates/elements/button.md +1 -1
  94. package/dist/templates/elements/input-stepper.md +220 -58
  95. package/dist/templates/elements/popover.md +3 -0
  96. package/dist/templates/elements/radio-group.md +351 -0
  97. package/dist/templates/elements/radio.md +34 -30
  98. package/dist/templates/elements/segmented-control.md +5 -63
  99. package/dist/templates/elements/tag.md +72 -3
  100. package/dist/templates/elements/tooltip.md +3 -0
  101. package/elements.json +10 -1
  102. package/package.json +1 -1
  103. package/vite-plugin.ts +44 -14
@@ -1 +1 @@
1
- {"version":3,"file":"tag.js","names":[],"sources":["../../../src/html/elements/tag/tag.css?inline","../../../src/html/elements/tag/tag.ts"],"sourcesContent":["@layer components {\n :host {\n /* Size defaults (md). A removable tag is always ≥24px tall (target-size).\n The three public knobs are folded into the private vars here rather than\n at each use site, so an override also carries to what derives from them —\n the toggle's negative inline margins and the × button's spacing math. The\n trio mirrors `.l-button` (`--height` / `--padding-inline` / `--font-size`)\n so a chip can be tuned to a dense filter panel without ::part(base). */\n --_height: var(--height, 1.5rem);\n --_font-size: var(--font-size, var(--l-text-xs));\n --_padding-inline: var(--padding-inline, 0.5rem);\n --_gap: 0.25rem;\n\n /* Skin, resolved once so `[selected]` can swap all three at the source\n instead of re-declaring the properties that consume them. */\n --_background: var(--background, var(--l-color-bg-fill-neutral-subtle));\n --_color: var(--color, var(--l-color-text-neutral));\n --_border-strength: 14%;\n\n display: inline-flex;\n vertical-align: middle;\n max-inline-size: 100%;\n }\n\n :host([size='sm']) {\n --_padding-inline: var(--padding-inline, 0.375rem);\n --_gap: 0.125rem;\n }\n\n :host([size='lg']) {\n --_height: var(--height, 1.75rem);\n --_font-size: var(--font-size, var(--l-text-sm));\n --_padding-inline: var(--padding-inline, 0.625rem);\n }\n\n /* Disabled is conveyed by the greyed × button + cursor, NOT by washing out\n * the label — dimming the whole chip with opacity composites the text below\n * the WCAG AA contrast floor (axe flags it; a disabled <button> is exempt but\n * the label span is not). */\n :host([disabled]) .base {\n cursor: not-allowed;\n }\n\n /*\n ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅲🅷🅸🅿\n ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n */\n\n .base {\n display: inline-flex;\n align-items: center;\n gap: var(--_gap);\n min-block-size: var(--_height);\n padding-inline: var(--_padding-inline);\n /* Discreet border + hover tint derive from the text color, so they adapt to\n any consumer `--color`. */\n border: 1px solid color-mix(in oklab, currentColor var(--_border-strength), transparent);\n border-radius: var(--border-radius, var(--l-radius-full));\n background-color: var(--_background);\n color: var(--_color);\n font-family: var(--l-font-neo-grotesque);\n font-size: var(--_font-size);\n font-weight: var(--l-font-weight-medium);\n line-height: 1;\n min-inline-size: 0;\n transition:\n background-color 120ms ease,\n border-color 120ms ease;\n }\n\n /* Whole-chip hover: a touch more presence, without competing with the ×. */\n :host(:hover:not([disabled])) .base {\n background-color: color-mix(in oklab, var(--_background), currentColor 7%);\n border-color: color-mix(\n in oklab,\n currentColor calc(var(--_border-strength) + 12%),\n transparent\n );\n }\n\n .content {\n display: inline-flex;\n align-items: center;\n min-inline-size: 0;\n /* `.base` sets `line-height: 1` so the chip's height comes from its own size\n tokens rather than the document's leading. That makes the line box exactly\n one em tall, and a plain `overflow: hidden` then shears every descender\n (g, j, p, q, y) at all three sizes — measured 1px short.\n\n Fixed by widening the clip rectangle, not the box: growing the line box\n instead (`line-height: normal`) moves the text half a pixel against the ×\n and the prefix icon, since the font's ascent and descent are not\n symmetrical. `clip` keeps the geometry identical and simply paints the\n tails. The margin applies to every side — there are no per-axis longhands\n in any engine yet — but 0.2em of horizontal bleed is invisible next to a\n label that is hard-cut at the edge anyway. */\n overflow: clip;\n overflow-clip-margin: 0.2em;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n ::slotted([slot='prefix']) {\n display: inline-flex;\n flex: none;\n }\n\n /* A count or trailing glyph. It sits in its own slot so `.base`'s gap gives it\n a gutter by construction — the label is a bare text node and can never be\n targeted from the outside. Lighter weight than the medium label, and\n tabular so a column of counts stays aligned. */\n ::slotted([slot='suffix']) {\n flex: none;\n font-weight: var(--l-font-weight-normal);\n font-variant-numeric: tabular-nums;\n }\n\n /*\n ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🆂🅴🅻🅴🅲🆃🅰🅱🅻🅴 (filter chip — toggle button, or a checkbox riding inside)\n ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n */\n\n :host([selectable]) {\n /* Taller than a display chip at the same `size`: a chip you click is a\n target (WCAG 2.5.8), and this is the dense step between `sm` and `lg` —\n more room, same 12px label, where `lg` would jump the label to body size.\n `--height` / `--font-size` tune it from there. */\n --_height: var(--height, 1.75rem);\n\n /* The selection accent is the same one the library's checkbox, radio and\n switch use, so a chip with a checkbox inside reads as a single control. */\n --_accent: var(--l-form-control-activated-color);\n /* The label is 12px, so it owes 4.5:1 over its own tint — and the raw accent\n misses it on the light one (4.35:1 measured). Darken it there, lighten it\n on the dark tint, and both modes clear 6:1. `color-mix()` resolves\n reliably inside `light-dark()` — `oklch(from …)` does not. */\n --_selected-color: var(\n --selected-color,\n light-dark(\n color-mix(in oklab, var(--_accent), black 14%),\n color-mix(in oklab, var(--_accent), white 45%)\n )\n );\n /* Derived from the text color, so overriding `--selected-color` alone\n repaints the whole chip coherently. Kept at 10%: a consumer accent skips\n the mode correction above, so the tint has to stay pale enough for a\n semantic token (`--l-color-text-success` and friends) to clear 4.5:1 on\n it unaided. The border carries the rest of the emphasis. */\n --_selected-background: var(\n --selected-background,\n color-mix(in oklab, var(--_selected-color) 10%, var(--l-color-surface))\n );\n }\n\n :host([selectable][size='lg']) {\n --_height: var(--height, 2rem);\n }\n\n :host([selectable][selected]) {\n --_background: var(--_selected-background);\n --_color: var(--_selected-color);\n --_border-strength: 45%;\n }\n\n :host([selectable]) .base {\n cursor: pointer;\n /* A chip is tapped, not read: never leave a text selection behind a\n double-click on a filter. */\n user-select: none;\n }\n\n :host([selectable][disabled]) .base {\n cursor: not-allowed;\n }\n\n /* The chip *is* the button. Negative inline margins let it cover `.base`'s\n padding so the whole pill is the target, while `.base` keeps owning the\n border, background, and the × button's spacing math. */\n .toggle {\n flex: 1 1 auto;\n display: inline-flex;\n align-items: center;\n align-self: stretch;\n gap: var(--_gap);\n min-inline-size: 0;\n margin-inline: calc(var(--_padding-inline) * -1);\n padding-inline: var(--_padding-inline);\n border: 0;\n background: none;\n color: inherit;\n font: inherit;\n text-align: start;\n cursor: inherit;\n }\n\n /* Stop before the × instead of running under it. */\n :host([removable]) .toggle {\n margin-inline-end: 0;\n padding-inline-end: 0;\n }\n\n /* One ring for the whole chip: the inner control is a detail of how the chip\n is operated, not a second thing to focus visually — and the ring drawn\n below fully encloses the box, so the indicator only grows (WCAG 2.4.7 /\n 2.4.13).\n\n Qualified by `:host(…)` on purpose. The shared appearance nests\n `&:focus-visible` under a selector list, so it lands as\n `:is(.l-checkbox, … input[type='checkbox']:not(…)):focus-visible` — 0,3,1,\n which outranks a bare `.checkbox:focus-visible` (0,2,0) and paints a second\n ring around the box. `:host([selectable])` brings this to 0,4,0. */\n :host([selectable]) .toggle:focus-visible,\n :host([selectable]) .checkbox:focus-visible {\n outline: none;\n }\n\n .base:has(:is(.toggle, .checkbox):focus-visible) {\n outline: 2px solid var(--l-focus-ring);\n outline-offset: 2px;\n }\n\n /* The skin comes from the shared `.l-checkbox` appearance, applied through\n `cls('checkbox')` rather than a static class literal: `dist` ships that CSS\n as a real file, so a consumer's PostCSS rewrites its selector under a\n renamed `cssPrefix` — a baked `l-checkbox` in the template would stop\n matching it. This rule only carries what is specific to a chip.\n\n The shared appearance sizes the box in `em`, and a UA gives `<input>` its\n own font — inheriting the chip's makes the box track the `size` step. The\n accent follows the selected color, so a themed chip keeps its checkbox in\n the same hue. */\n .checkbox {\n --accent: var(--selected-color, var(--l-form-control-activated-color));\n /* Hover halo — same ratio and same token flip as the light-DOM\n `.l-checkbox`, which keeps this rule out of the shared partial (see\n `css/elements/checkbox.css`). Repeated here rather than moved into the\n partial: `l-tree-item` shares that file and never opted into a glow. */\n --_halo: var(--l-form-control-border-color-hover);\n\n flex: none;\n font: inherit;\n transition-property: background-color, border-color, box-shadow;\n }\n\n .checkbox:checked,\n .checkbox:indeterminate {\n --_halo: var(--accent);\n }\n\n /* Driven by the chip, not by the box: the chip is the label and the whole\n click target, so a halo lighting up only over 15px of a 28px pill would\n read as a glitch. */\n @media (hover: hover) {\n :host([selectable]:not([disabled])) .base:hover .checkbox:not(:disabled) {\n box-shadow: 0 0 0 calc(var(--size) * var(--l-form-control-toggle-halo-ratio))\n color-mix(in oklab, var(--_halo), transparent 84%);\n }\n }\n\n /* Forced colors drop the tint (backgrounds are forced to Canvas), so pin the\n border to the system highlight — otherwise selection becomes invisible. */\n @media (forced-colors: active) {\n :host([selectable][selected]) .base {\n border-color: Highlight;\n }\n }\n\n /*\n ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🆁🅴🅼🅾🆅🅴 (≥24px target-size, mdi:close mask — mirrors l-close)\n ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n */\n\n .remove {\n flex: none;\n display: grid;\n place-items: center;\n /* 24px hit target (WCAG 2.5.8); the visible tint circle is inset to ~18px. */\n inline-size: 1.5rem;\n block-size: 1.5rem;\n margin-inline: calc(var(--_gap) * -0.5) calc(var(--_padding-inline) * -0.5);\n padding: 0;\n border: 0;\n border-radius: var(--l-radius-full);\n color: inherit;\n cursor: pointer;\n /* Tint circle behind the glyph — a gradient disc kept smaller than the 24px\n hit area, grown from 0 on hover so it reads as a neat ~18px dot. */\n background: radial-gradient(\n circle at center,\n color-mix(in oklab, currentColor 15%, transparent) 60%,\n transparent 62%\n )\n no-repeat center / 0 0;\n transition:\n scale 120ms ease,\n background-size 140ms ease;\n\n &::after {\n content: '';\n inline-size: 0.875rem;\n block-size: 0.875rem;\n background-color: currentColor;\n opacity: 0.55;\n transition: opacity 120ms ease;\n /* https://pictogrammers.com/library/mdi/icon/close */\n mask: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z\" /></svg>')\n center / contain no-repeat;\n }\n\n &:hover {\n background-size: 1.125rem 1.125rem;\n }\n &:hover::after {\n opacity: 1;\n }\n\n /* Tactile press — the button dips and the disc deepens. */\n &:active {\n scale: 0.86;\n background-image: radial-gradient(\n circle at center,\n color-mix(in oklab, currentColor 26%, transparent) 60%,\n transparent 62%\n );\n }\n\n &:focus-visible {\n outline: 2px solid var(--l-focus-ring);\n outline-offset: 1px;\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .base,\n .checkbox,\n .remove,\n .remove::after {\n transition-duration: 0ms;\n }\n .remove:active {\n scale: 1;\n }\n }\n}\n","import { html, nothing, unsafeCSS } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { LocalizeController } from '../../shared/localize.js';\nimport { cls } from '../../registry.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport checkboxAppearance from '../../shared/styles/checkbox-appearance.styles.js';\nimport rawStyles from './tag.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\nexport type TagSize = 'sm' | 'md' | 'lg';\nexport type TagControl = 'none' | 'checkbox';\n\n/**\n * Fired when the user asks to remove the tag — clicking the × button, or\n * pressing Backspace/Delete while it is focused. Cancelable: call\n * `preventDefault()` to keep the tag (the controlling host then manages the\n * list itself); otherwise the tag removes itself from the DOM. Does not bubble.\n */\nexport class TagRemoveEvent extends Event {\n constructor() {\n super('remove', { bubbles: false, composed: false, cancelable: true });\n }\n}\n\n/** Fired when a `selectable` tag is toggled. Bubbles; not composed. */\nexport class TagChangeEvent extends Event {\n readonly selected: boolean;\n constructor(selected: boolean) {\n super('change', { bubbles: true, composed: false, cancelable: false });\n this.selected = selected;\n }\n}\n\ninterface TagEventMap {\n change: TagChangeEvent;\n}\n\ndeclare global {\n interface GlobalEventHandlersEventMap {\n remove: TagRemoveEvent;\n }\n}\n\n/**\n * A compact chip for tokens, filters, and multi-select values. Shadow DOM so its\n * styles are self-contained and it renders correctly **anywhere**, including\n * inside another element's shadow root (e.g. the `<l-select>` multi-select\n * trigger).\n *\n * Add `selectable` to turn the chip into a filter control: the tag itself\n * becomes a toggle button (`aria-pressed`), and re-activating a selected tag\n * deselects it — the affordance a radio group cannot offer, so a facet always\n * has a way back to \"all\". Add `control=\"checkbox\"` for a multi-select axis and\n * the library's own checkbox rides inside, with the chip as its label.\n *\n * @summary A compact, optionally removable or selectable chip.\n *\n * @example\n * ```html\n * <l-tag removable>Design</l-tag>\n * <l-tag selectable selected>A3</l-tag>\n * <l-tag selectable control=\"checkbox\">Color <span slot=\"suffix\">79</span></l-tag>\n * ```\n *\n * @event remove - Fired when the user removes the tag (× click or Backspace/Delete). Cancelable; if not prevented the tag removes itself. Not composed, does not bubble.\n * @event change - Fired when a `selectable` tag is toggled. Not cancelable — like the platform's own `change`, and like `l-segmented-control`. Bubbles. Properties: `selected: boolean`. The chip is uncontrolled: it applies the new state before dispatching, so a host that vetoes a toggle (a \"max 3 filters\" rule, an async guard) sets `selected` back in the listener — the revert lands in the same render and is never painted.\n *\n * @slot - The tag label.\n * @slot prefix - Leading content, e.g. an `<l-icon>` or `<l-avatar>`.\n * @slot suffix - Trailing content, e.g. a result count. Gets the chip's own gutter, so no margin is needed.\n *\n * @csspart base - The chip container.\n * @csspart content - The label wrapper.\n * @csspart toggle - The toggle button rendered by `selectable` (not with `control=\"checkbox\"`).\n * @csspart checkbox - The native checkbox rendered by `control=\"checkbox\"`.\n * @csspart remove - The remove button.\n *\n * @cssproperty [--border-radius] - Corner radius. Defaults to a full pill.\n * @cssproperty [--height] - Chip height. Defaults to the `size` step (a selectable chip is taller, to keep a comfortable target).\n * @cssproperty [--font-size] - Label size. Defaults to the `size` step — 12px, or 14px at `size=\"lg\"`. Set it with `--height` to land between the two steps in a dense filter panel.\n * @cssproperty [--padding-inline] - Horizontal padding. Defaults to the `size` step.\n * @cssproperty [--background] - Chip background.\n * @cssproperty [--color] - Text color.\n * @cssproperty [--selected-color] - Text, border, and checkbox accent when selected. Defaults to the library's form-control accent, lightened in dark mode.\n * @cssproperty [--selected-background] - Chip background when selected. Defaults to a tint of `--selected-color`.\n *\n * @customElement l-tag\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below; no uninitialized properties.\nexport class Tag extends LuxenElement {\n static override styles = [hostStyles, checkboxAppearance, styles];\n\n private _localize = new LocalizeController(this);\n\n /** Tag size: `sm`, `md` (default), or `lg`. */\n @property({ reflect: true })\n accessor size: TagSize = 'md';\n\n /** Show a remove button (and enable Backspace/Delete removal). */\n @property({ type: Boolean, reflect: true })\n accessor removable = false;\n\n /** Make the tag a filter control the user can toggle on and off. */\n @property({ type: Boolean, reflect: true })\n accessor selectable = false;\n\n /** Whether the tag is selected. Reflected, so `[selected]` is styleable. */\n @property({ type: Boolean, reflect: true })\n accessor selected = false;\n\n /**\n * What drives the selection: `none` (default) makes the chip itself a toggle\n * button; `checkbox` renders a checkbox inside and makes the chip its label —\n * the right choice for a multi-select facet. Implies `selectable`.\n */\n @property({ reflect: true })\n accessor control: TagControl = 'none';\n\n /** Disable the tag — dims it and blocks selection and removal. */\n @property({ type: Boolean, reflect: true })\n accessor disabled = false;\n\n constructor() {\n super();\n // The chip is operated from inside the shadow root, which a click\n // dispatched on the host never reaches — see `_onHostClick`.\n this.addEventListener('click', this._onHostClick);\n }\n\n override willUpdate() {\n // A checkbox is only ever rendered on a selectable chip, so the attribute\n // (which every selectable style rule keys off) is derived rather than\n // duplicated in every selector — and in the consumer's markup.\n if (this.control === 'checkbox') this.selectable = true;\n }\n\n override updated() {\n // Re-assert the box, don't rely on the `.checked` binding. Lit dirty-checks\n // a property part against the value it last wrote, and a click has the user\n // agent flip `checked` behind its back — so when a controlling host vetoes\n // the toggle by setting `selected` back to what it already was, Lit sees no\n // change, skips the write, and leaves a checked box on an unselected chip\n // (announced \"checked\" too). Cheap: the query is null on every other path.\n const box = this.renderRoot.querySelector<HTMLInputElement>('.checkbox');\n if (box) box.checked = this.selected;\n }\n\n private _requestRemove = () => {\n if (this.disabled) return;\n const event = new TagRemoveEvent();\n this.dispatchEvent(event);\n if (!event.defaultPrevented) this.remove();\n };\n\n private _onKeyDown = (e: KeyboardEvent) => {\n if (!this.removable || this.disabled) return;\n if (e.key === 'Backspace' || e.key === 'Delete') {\n e.preventDefault();\n this._requestRemove();\n }\n };\n\n private _toggle = () => {\n if (this.disabled) return;\n // Toggling, not setting: re-activating a selected tag releases it.\n this.selected = !this.selected;\n this.dispatchEvent(new TagChangeEvent(this.selected));\n };\n\n /**\n * Events do not propagate *down* into a shadow tree, so a click dispatched on\n * the host — `tag.click()`, a test runner resolving the chip by its test id,\n * a runner computing the chip's centre (`elementFromPoint()` retargets shadow\n * content back to the host) — never reaches the toggle button or the\n * checkbox, and silently does nothing. Delegate it.\n *\n * Only when the host *is* the target: a real gesture originates at the inner\n * button, at the checkbox, or at slotted content, and is already handled\n * where it lands — so this never double-toggles. `removable` stays out of it;\n * removing is the × button's own gesture.\n */\n private _onHostClick = (e: Event) => {\n if (!this.selectable || this.disabled) return;\n if (e.composedPath()[0] !== this) return;\n // Right for `control=\"checkbox\"` too: `_toggle()` flips `selected` and\n // fires `change`, and `updated()` re-asserts `box.checked` from it.\n this._toggle();\n };\n\n private _onCheckboxChange = (e: Event) => {\n this.selected = (e.target as HTMLInputElement).checked;\n this.dispatchEvent(new TagChangeEvent(this.selected));\n };\n\n private _label() {\n return html`\n <slot name=\"prefix\"></slot>\n <span\n class=\"content\"\n part=\"content\"\n >\n <slot></slot>\n </span>\n <slot name=\"suffix\"></slot>\n `;\n }\n\n private _removeButton() {\n return this.removable\n ? html`<button\n class=\"remove\"\n part=\"remove\"\n type=\"button\"\n ?disabled=${this.disabled}\n aria-label=${this._localize.term('remove')}\n @click=${this._requestRemove}\n ></button>`\n : nothing;\n }\n\n override render() {\n if (this.control === 'checkbox') {\n return html`\n <label\n class=\"base\"\n part=\"base\"\n @keydown=${this._onKeyDown}\n >\n <input\n class=\"checkbox ${cls('checkbox')}\"\n part=\"checkbox\"\n type=\"checkbox\"\n .checked=${this.selected}\n ?disabled=${this.disabled}\n @change=${this._onCheckboxChange}\n />\n ${this._label()}${this._removeButton()}\n </label>\n `;\n }\n\n if (this.selectable) {\n return html`\n <span\n class=\"base\"\n part=\"base\"\n @keydown=${this._onKeyDown}\n >\n <button\n class=\"toggle\"\n part=\"toggle\"\n type=\"button\"\n aria-pressed=${this.selected ? 'true' : 'false'}\n ?disabled=${this.disabled}\n @click=${this._toggle}\n >\n ${this._label()}\n </button>\n ${this._removeButton()}\n </span>\n `;\n }\n\n return html`\n <span\n class=\"base\"\n part=\"base\"\n @keydown=${this._onKeyDown}\n >\n ${this._label()}${this._removeButton()}\n </span>\n `;\n }\n}\n\n// Type `addEventListener('change', …)` so listeners receive a `TagChangeEvent`\n// (with `.selected`) instead of a bare `Event`. `change` collides with lib.dom's\n// `GlobalEventHandlersEventMap`, so it can't be augmented globally — the\n// overloads are declaration-merged onto this element's interface instead. The\n// interface MUST come after the class (the CEM analyzer dedups on the first\n// declaration of the name). See `tabs.ts` for the fully-annotated reference.\nexport interface Tag {\n addEventListener<K extends keyof TagEventMap>(\n type: K,\n listener: (this: Tag, ev: TagEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n removeEventListener<K extends keyof TagEventMap>(\n type: K,\n listener: (this: Tag, ev: TagEventMap[K]) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n}\n"],"mappings":"saCSA,IAAM,EAAS,EAAU,i/JAAS,EAWrB,EAAb,cAAoC,KAAM,CACxC,aAAc,CACZ,MAAM,SAAU,CAAE,QAAS,GAAO,SAAU,GAAO,WAAY,EAAK,CAAC,CACvE,CACF,EAGa,EAAb,cAAoC,KAAM,CAExC,YAAY,EAAmB,CAC7B,MAAM,SAAU,CAAE,QAAS,GAAM,SAAU,GAAO,WAAY,EAAM,CAAC,EACrE,KAAK,SAAW,CAClB,CACF,EA0Da,EAAb,cAAyB,CAAa,oBACX,CAAC,EAAY,EAAoB,CAAM,SAMvD,MAAA,6CAIA,WAAA,kDAIA,YAAA,mDAIA,UAAA,iDAQA,SAAA,gDAIA,UAAA,0CAET,aAAc,CACZ,MAAM,iBA/BY,IAAI,EAAmB,IAAI,UAItB,aAIJ,WAIC,WAIF,WAQW,eAIX,2BA2BW,CAC7B,GAAI,KAAK,SAAU,OACnB,IAAM,EAAQ,IAAI,EAClB,KAAK,cAAc,CAAK,EACnB,EAAM,kBAAkB,KAAK,OAAO,CAC3C,kBAEsB,GAAqB,CACrC,CAAC,KAAK,WAAa,KAAK,WACxB,EAAE,MAAQ,aAAe,EAAE,MAAQ,YACrC,EAAE,eAAe,EACjB,KAAK,eAAe,EAExB,mBAEwB,CAClB,KAAK,WAET,KAAK,SAAW,CAAC,KAAK,SACtB,KAAK,cAAc,IAAI,EAAe,KAAK,QAAQ,CAAC,EACtD,oBAcwB,GAAa,CAC/B,CAAC,KAAK,YAAc,KAAK,UACzB,EAAE,aAAa,CAAC,CAAC,KAAO,MAG5B,KAAK,QAAQ,CACf,yBAE6B,GAAa,CACxC,KAAK,SAAY,EAAE,OAA4B,QAC/C,KAAK,cAAc,IAAI,EAAe,KAAK,QAAQ,CAAC,CACtD,EAlEE,KAAK,iBAAiB,QAAS,KAAK,YAAY,CAClD,CAEA,YAAsB,CAIhB,KAAK,UAAY,aAAY,KAAK,WAAa,GACrD,CAEA,SAAmB,CAOjB,IAAM,EAAM,KAAK,WAAW,cAAgC,WAAW,EACnE,IAAK,EAAI,QAAU,KAAK,SAC9B,CAiDA,QAAiB,CACf,MAAO,EAAI;;;;;;;;;KAUb,CAEA,eAAwB,CACtB,OAAO,KAAK,UACR,CAAI;;;;sBAIU,KAAK,SAAS;uBACb,KAAK,UAAU,KAAK,QAAQ,EAAE;mBAClC,KAAK,eAAe;oBAE/B,CACN,CAEA,QAAkB,CA2ChB,OA1CI,KAAK,UAAY,WACZ,CAAI;;;;qBAII,KAAK,WAAW;;;8BAGP,EAAI,UAAU,EAAE;;;uBAGvB,KAAK,SAAS;wBACb,KAAK,SAAS;sBAChB,KAAK,kBAAkB;;YAEjC,KAAK,OAAO,IAAI,KAAK,cAAc,EAAE;;QAKzC,KAAK,WACA,CAAI;;;;qBAII,KAAK,WAAW;;;;;;2BAMV,KAAK,SAAW,OAAS,QAAQ;wBACpC,KAAK,SAAS;qBACjB,KAAK,QAAQ;;cAEpB,KAAK,OAAO,EAAE;;YAEhB,KAAK,cAAc,EAAE;;QAKtB,CAAI;;;;mBAII,KAAK,WAAW;;UAEzB,KAAK,OAAO,IAAI,KAAK,cAAc,EAAE;;KAG7C,CACF,KAlLG,EAAS,CAAE,QAAS,EAAK,CAAC,CAAA,EAAA,EAAA,UAAA,OAAA,IAAA,KAI1B,EAAS,CAAE,KAAM,QAAS,QAAS,EAAK,CAAC,CAAA,EAAA,EAAA,UAAA,YAAA,IAAA,KAIzC,EAAS,CAAE,KAAM,QAAS,QAAS,EAAK,CAAC,CAAA,EAAA,EAAA,UAAA,aAAA,IAAA,KAIzC,EAAS,CAAE,KAAM,QAAS,QAAS,EAAK,CAAC,CAAA,EAAA,EAAA,UAAA,WAAA,IAAA,KAQzC,EAAS,CAAE,QAAS,EAAK,CAAC,CAAA,EAAA,EAAA,UAAA,UAAA,IAAA,KAI1B,EAAS,CAAE,KAAM,QAAS,QAAS,EAAK,CAAC,CAAA,EAAA,EAAA,UAAA,WAAA,IAAA"}
1
+ {"version":3,"file":"tag.js","names":[],"sources":["../../../src/html/elements/tag/tag.css?inline","../../../src/html/elements/tag/tag.ts"],"sourcesContent":["@layer components {\n :host {\n /* Size defaults (md). A removable tag is always ≥24px tall (target-size).\n The three public knobs are folded into the private vars here rather than\n at each use site, so an override also carries to what derives from them —\n the toggle's negative inline margins and the × button's spacing math. The\n trio mirrors `.l-button` (`--height` / `--padding-inline` / `--font-size`)\n so a chip can be tuned to a dense filter panel without ::part(base). */\n --_height: var(--height, 1.5rem);\n --_font-size: var(--font-size, var(--l-text-xs));\n --_padding-inline: var(--padding-inline, 0.5rem);\n --_gap: 0.25rem;\n\n /* Skin, resolved once so `[selected]` can swap all three at the source\n instead of re-declaring the properties that consume them. */\n --_background: var(--background, var(--l-color-bg-fill-neutral-subtle));\n --_color: var(--color, var(--l-color-text-neutral));\n --_border-strength: 14%;\n /* By default the line is a tint of the ink, so a chip stays coherent under\n any `--color` with one declaration. `--border-color` replaces that\n derivation outright rather than shifting it: a consumer with a token\n system wants *its* line token, not a 14% mix of its text color — and\n wants it to hold in every state, which is why it is the fallback in both\n of the pair below rather than a value the hover rule mixes further.\n `--_border-strength` stays private. Selecting a chip swaps the pair\n wholesale, the same way it swaps the fill and the ink. */\n --_border-color: var(\n --border-color,\n color-mix(in oklab, currentColor var(--_border-strength), transparent)\n );\n --_border-color-hover: var(\n --border-color,\n color-mix(in oklab, currentColor calc(var(--_border-strength) + 12%), transparent)\n );\n /* Resolved here rather than at `.base`, because the × segment has to round\n its outer end to the same curve. */\n --_border-radius: var(--border-radius, var(--l-radius-full));\n --_remove-glyph: 0.75rem;\n\n display: inline-flex;\n vertical-align: middle;\n max-inline-size: 100%;\n }\n\n :host([size='sm']) {\n --_padding-inline: var(--padding-inline, 0.375rem);\n --_gap: 0.125rem;\n }\n\n :host([size='lg']) {\n --_height: var(--height, 1.75rem);\n --_font-size: var(--font-size, var(--l-text-sm));\n --_padding-inline: var(--padding-inline, 0.625rem);\n --_remove-glyph: 0.875rem;\n }\n\n /* Disabled is conveyed by the greyed × button + cursor, NOT by washing out\n * the label — dimming the whole chip with opacity composites the text below\n * the WCAG AA contrast floor (axe flags it; a disabled <button> is exempt but\n * the label span is not). */\n :host([disabled]) .base {\n cursor: not-allowed;\n }\n\n /*\n ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅲🅷🅸🅿\n ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n */\n\n .base {\n display: inline-flex;\n align-items: center;\n gap: var(--_gap);\n min-block-size: var(--_height);\n padding-inline: var(--_padding-inline);\n border: 1px solid var(--_border-color);\n border-radius: var(--_border-radius);\n background-color: var(--_background);\n color: var(--_color);\n font-family: var(--l-font-neo-grotesque);\n font-size: var(--_font-size);\n font-weight: var(--l-font-weight-medium);\n line-height: 1;\n min-inline-size: 0;\n transition:\n background-color 120ms ease,\n border-color 120ms ease;\n }\n\n /* The remove segment is exactly as tall as the chip, so the chip is where the\n target floor has to live: `--height` still tunes a removable chip, but not\n under the 24px minimum (WCAG 2.5.8). Without this the promise held only for\n the default value. */\n :host([removable]) .base {\n min-block-size: max(1.5rem, var(--_height));\n }\n\n /* Whole-chip hover — only on a chip that is *wholly* a control. A display chip\n has no target at all, and a removable one has exactly its × segment: tinting\n the label under the pointer there promises a click that does nothing, and on\n a filter bar it fights the segment for the eye. `[selectable]` also covers\n `control=\"checkbox\"`, which derives the attribute. */\n @media (hover: hover) {\n :host([selectable]:hover:not([disabled])) .base {\n background-color: color-mix(in oklab, var(--_background), currentColor 7%);\n border-color: var(--_border-color-hover);\n }\n }\n\n .content {\n display: inline-flex;\n align-items: center;\n min-inline-size: 0;\n /* `.base` sets `line-height: 1` so the chip's height comes from its own size\n tokens rather than the document's leading. That makes the line box exactly\n one em tall, and a plain `overflow: hidden` then shears every descender\n (g, j, p, q, y) at all three sizes — measured 1px short.\n\n Fixed by widening the clip rectangle, not the box: growing the line box\n instead (`line-height: normal`) moves the text half a pixel against the ×\n and the prefix icon, since the font's ascent and descent are not\n symmetrical. `clip` keeps the geometry identical and simply paints the\n tails. The margin applies to every side — there are no per-axis longhands\n in any engine yet — but 0.2em of horizontal bleed is invisible next to a\n label that is hard-cut at the edge anyway. */\n overflow: clip;\n overflow-clip-margin: 0.2em;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n ::slotted([slot='prefix']) {\n display: inline-flex;\n flex: none;\n }\n\n /* A count or trailing glyph. It sits in its own slot so `.base`'s gap gives it\n a gutter by construction — the label is a bare text node and can never be\n targeted from the outside. Lighter weight than the medium label, and\n tabular so a column of counts stays aligned. */\n ::slotted([slot='suffix']) {\n flex: none;\n font-weight: var(--l-font-weight-normal);\n font-variant-numeric: tabular-nums;\n }\n\n /*\n ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🆂🅴🅻🅴🅲🆃🅰🅱🅻🅴 (filter chip — toggle button, or a checkbox riding inside)\n ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n */\n\n :host([selectable]) {\n /* Taller than a display chip at the same `size`: a chip you click is a\n target (WCAG 2.5.8), and this is the dense step between `sm` and `lg` —\n more room, same 12px label, where `lg` would jump the label to body size.\n `--height` / `--font-size` tune it from there. */\n --_height: var(--height, 1.75rem);\n\n /* The selection accent is the same one the library's checkbox, radio and\n switch use, so a chip with a checkbox inside reads as a single control. */\n --_accent: var(--l-form-control-activated-color);\n /* The label is 12px, so it owes 4.5:1 over its own tint — and the raw accent\n misses it on the light one (4.35:1 measured). Darken it there, lighten it\n on the dark tint, and both modes clear 6:1. `color-mix()` resolves\n reliably inside `light-dark()` — `oklch(from …)` does not. */\n --_selected-color: var(\n --selected-color,\n light-dark(\n color-mix(in oklab, var(--_accent), black 14%),\n color-mix(in oklab, var(--_accent), white 45%)\n )\n );\n /* Derived from the text color, so overriding `--selected-color` alone\n repaints the whole chip coherently. Kept at 10%: a consumer accent skips\n the mode correction above, so the tint has to stay pale enough for a\n semantic token (`--l-color-text-success` and friends) to clear 4.5:1 on\n it unaided. The border carries the rest of the emphasis. */\n --_selected-background: var(\n --selected-background,\n color-mix(in oklab, var(--_selected-color) 10%, var(--l-color-surface))\n );\n /* The third member of the trio, beside `--selected-color` and\n `--selected-background`. Selection already replaces the fill and the ink\n outright, so the line has to be replaceable the same way — without this,\n a chip that sets `--border-color` keeps that exact line when picked and\n the emphasis step that says \"selected\" never happens. 45% by default, the\n strength the derived line has always used here. */\n --_selected-border-color: var(\n --selected-border-color,\n color-mix(in oklab, var(--_selected-color) 45%, transparent)\n );\n }\n\n :host([selectable][size='lg']) {\n --_height: var(--height, 2rem);\n }\n\n :host([selectable][selected]) {\n --_background: var(--_selected-background);\n --_color: var(--_selected-color);\n --_border-color: var(--_selected-border-color);\n /* Same +12 step the resting line takes on hover, and pinned in the same way\n when the consumer supplies the value. */\n --_border-color-hover: var(\n --selected-border-color,\n color-mix(in oklab, var(--_selected-color) 57%, transparent)\n );\n }\n\n :host([selectable]) .base {\n cursor: pointer;\n /* A chip is tapped, not read: never leave a text selection behind a\n double-click on a filter. */\n user-select: none;\n }\n\n :host([selectable][disabled]) .base {\n cursor: not-allowed;\n }\n\n /* The chip *is* the button. Negative inline margins let it cover `.base`'s\n padding so the whole pill is the target, while `.base` keeps owning the\n border, background, and the × button's spacing math. */\n .toggle {\n flex: 1 1 auto;\n display: inline-flex;\n align-items: center;\n align-self: stretch;\n gap: var(--_gap);\n min-inline-size: 0;\n margin-inline: calc(var(--_padding-inline) * -1);\n padding-inline: var(--_padding-inline);\n border: 0;\n background: none;\n color: inherit;\n font: inherit;\n text-align: start;\n cursor: inherit;\n }\n\n /* Stop before the × instead of running under it. */\n :host([removable]) .toggle {\n margin-inline-end: 0;\n padding-inline-end: 0;\n }\n\n /* One ring for the whole chip: the inner control is a detail of how the chip\n is operated, not a second thing to focus visually — and the ring drawn\n below fully encloses the box, so the indicator only grows (WCAG 2.4.7 /\n 2.4.13).\n\n Qualified by `:host(…)` on purpose. The shared appearance nests\n `&:focus-visible` under a selector list, so it lands as\n `:is(.l-checkbox, … input[type='checkbox']:not(…)):focus-visible` — 0,3,1,\n which outranks a bare `.checkbox:focus-visible` (0,2,0) and paints a second\n ring around the box. `:host([selectable])` brings this to 0,4,0. */\n :host([selectable]) .toggle:focus-visible,\n :host([selectable]) .checkbox:focus-visible {\n outline: none;\n }\n\n .base:has(:is(.toggle, .checkbox):focus-visible) {\n outline: 2px solid var(--l-focus-ring);\n outline-offset: 2px;\n }\n\n /* The skin comes from the shared `.l-checkbox` appearance, applied through\n `cls('checkbox')` rather than a static class literal: `dist` ships that CSS\n as a real file, so a consumer's PostCSS rewrites its selector under a\n renamed `cssPrefix` — a baked `l-checkbox` in the template would stop\n matching it. This rule only carries what is specific to a chip.\n\n The shared appearance sizes the box in `em`, and a UA gives `<input>` its\n own font — inheriting the chip's makes the box track the `size` step. The\n accent follows the selected color, so a themed chip keeps its checkbox in\n the same hue. */\n .checkbox {\n --accent: var(--selected-color, var(--l-form-control-activated-color));\n /* Hover halo — same ratio and same token flip as the light-DOM\n `.l-checkbox`, which keeps this rule out of the shared partial (see\n `css/elements/checkbox.css`). Repeated here rather than moved into the\n partial: `l-tree-item` shares that file and never opted into a glow. */\n --_halo: var(--l-form-control-border-color-hover);\n\n flex: none;\n font: inherit;\n transition-property: background-color, border-color, box-shadow;\n }\n\n .checkbox:checked,\n .checkbox:indeterminate {\n --_halo: var(--accent);\n }\n\n /* Driven by the chip, not by the box: the chip is the label and the whole\n click target, so a halo lighting up only over 15px of a 28px pill would\n read as a glitch. */\n @media (hover: hover) {\n :host([selectable]:not([disabled])) .base:hover .checkbox:not(:disabled) {\n box-shadow: 0 0 0 calc(var(--size) * var(--l-form-control-toggle-halo-ratio))\n color-mix(in oklab, var(--_halo), transparent 84%);\n }\n }\n\n /* Forced colors drop the tint (backgrounds are forced to Canvas), so pin the\n border to the system highlight — otherwise selection becomes invisible. */\n @media (forced-colors: active) {\n :host([selectable][selected]) .base {\n border-color: Highlight;\n }\n }\n\n /*\n ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🆁🅴🅼🅾🆅🅴 (≥24px target-size, mdi:close mask — mirrors l-close)\n ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n */\n\n .remove {\n flex: none;\n display: grid;\n place-items: center;\n /* Full height, and split off by a rule: the affordance is drawn *at rest*\n rather than conjured on hover. Keyboard and touch users never get a hover\n state, and a filter bar of chips whose delete zone only appears under the\n pointer is a row of faint marks until you go hunting. */\n align-self: stretch;\n /* Square, and never under the 24px hit target (WCAG 2.5.8) however a\n consumer tunes `--height`. */\n inline-size: max(1.5rem, var(--_height));\n /* The segment spans `.base`'s *border* box, not its content box: the −1px\n block margins cancel the border on each side (a stretched flex item is\n sized by the line minus its own margins), which both lands the target on\n a full 24px — 22px otherwise, one short — and runs the rule edge to edge\n so it meets the chip's own border instead of stopping just inside it. */\n margin-block: -1px;\n /* End: flush to the chip's inner edge. Start: pushed off the label so it\n gets the same air on both sides. `.base`'s `gap` spaces *slotted items*\n (icon ↔ label) and is deliberately tighter than the chip's padding, so\n letting it also front the rule left the label with 8px on its left and\n 4px on its right — visibly crammed against the divider. This tops the gap\n back up to a full `--_padding-inline`, at every size. */\n margin-inline: calc(var(--_padding-inline) - var(--_gap))\n calc(var(--_padding-inline) * -1 - 1px);\n padding: 0;\n /* Transparent on three sides, so `background-clip` below keeps the hover\n fill off the 1px the chip's own border occupies. */\n border: 1px solid transparent;\n border-inline-start-color: var(--_border-color);\n /* The chip's own curve — the padding box rounds to one pixel less on its\n own, so the fill stops on the arc rather than squaring off the pill. */\n border-start-end-radius: var(--_border-radius);\n border-end-end-radius: var(--_border-radius);\n background-color: transparent;\n background-clip: padding-box;\n color: inherit;\n cursor: pointer;\n transition: background-color 120ms ease;\n /* The press dip goes through a variable so the reduced-motion block can\n cancel it by re-declaring *this* one line. Pairing the two `:active`\n selectors instead is a trap: the override only wins on source order while\n the specificities tie, and adding the `:not(:disabled)` guard below was\n enough to silently break it. */\n --_press-scale: 0.86;\n\n &::after {\n content: '';\n inline-size: var(--_remove-glyph);\n block-size: var(--_remove-glyph);\n background-color: currentColor;\n transition: scale 120ms ease;\n /* https://pictogrammers.com/library/mdi/icon/close */\n mask: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z\" /></svg>')\n center / contain no-repeat;\n }\n\n /* The whole segment fills. The chip already tints under the pointer (7%),\n so this has to read as the stronger of the two — it is what will actually\n be clicked. */\n @media (hover: hover) {\n &:hover:not(:disabled) {\n background-color: color-mix(in oklab, transparent, currentColor 12%);\n }\n }\n\n &:active:not(:disabled) {\n background-color: color-mix(in oklab, transparent, currentColor 22%);\n }\n\n /* The glyph dips, not the segment: scaling the box would shrink the rule\n and pull it away from the chip's border. */\n &:active:not(:disabled)::after {\n scale: var(--_press-scale);\n }\n\n /* Inset, unlike the library's usual +2px: the segment is flush with the\n chip's border on three sides, so an outset ring would sit half outside\n the chip and clip against its neighbour in a wrapped filter bar. */\n &:focus-visible {\n outline: 2px solid var(--l-focus-ring);\n outline-offset: -2px;\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n }\n\n /* `background-color` is forced to Canvas in high contrast, which would erase\n a masked glyph entirely — repaint it in the system button color. */\n @media (forced-colors: active) {\n &::after {\n background-color: ButtonText;\n }\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .base,\n .checkbox,\n .remove,\n .remove::after {\n transition-duration: 0ms;\n }\n .remove {\n --_press-scale: 1;\n }\n }\n}\n","import { html, nothing, unsafeCSS } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { LocalizeController } from '../../shared/localize.js';\nimport { cls } from '../../registry.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport checkboxAppearance from '../../shared/styles/checkbox-appearance.styles.js';\nimport rawStyles from './tag.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\nexport type TagSize = 'sm' | 'md' | 'lg';\nexport type TagControl = 'none' | 'checkbox';\n\n/**\n * Fired when the user asks to remove the tag — clicking the × button, or\n * pressing Backspace/Delete while it is focused. Cancelable: call\n * `preventDefault()` to keep the tag (the controlling host then manages the\n * list itself); otherwise the tag removes itself from the DOM. Does not bubble.\n */\nexport class TagRemoveEvent extends Event {\n constructor() {\n super('remove', { bubbles: false, composed: false, cancelable: true });\n }\n}\n\n/** Fired when a `selectable` tag is toggled. Bubbles; not composed. */\nexport class TagChangeEvent extends Event {\n readonly selected: boolean;\n constructor(selected: boolean) {\n super('change', { bubbles: true, composed: false, cancelable: false });\n this.selected = selected;\n }\n}\n\ninterface TagEventMap {\n change: TagChangeEvent;\n}\n\ndeclare global {\n interface GlobalEventHandlersEventMap {\n remove: TagRemoveEvent;\n }\n}\n\n/**\n * A compact chip for tokens, filters, and multi-select values. Shadow DOM so its\n * styles are self-contained and it renders correctly **anywhere**, including\n * inside another element's shadow root (e.g. the `<l-select>` multi-select\n * trigger).\n *\n * Add `selectable` to turn the chip into a filter control: the tag itself\n * becomes a toggle button (`aria-pressed`), and re-activating a selected tag\n * deselects it — the affordance a radio group cannot offer, so a facet always\n * has a way back to \"all\". Add `control=\"checkbox\"` for a multi-select axis and\n * the library's own checkbox rides inside, with the chip as its label.\n *\n * @summary A compact, optionally removable or selectable chip.\n *\n * @example\n * ```html\n * <l-tag removable>Design</l-tag>\n * <l-tag selectable selected>A3</l-tag>\n * <l-tag selectable control=\"checkbox\">Color <span slot=\"suffix\">79</span></l-tag>\n * ```\n *\n * @event remove - Fired when the user removes the tag (× click or Backspace/Delete). Cancelable; if not prevented the tag removes itself. Not composed, does not bubble.\n * @event change - Fired when a `selectable` tag is toggled. Not cancelable — like the platform's own `change`, and like `l-segmented-control`. Bubbles. Properties: `selected: boolean`. The chip is uncontrolled: it applies the new state before dispatching, so a host that vetoes a toggle (a \"max 3 filters\" rule, an async guard) sets `selected` back in the listener — the revert lands in the same render and is never painted.\n *\n * @slot - The tag label.\n * @slot prefix - Leading content, e.g. an `<l-icon>` or `<l-avatar>`.\n * @slot suffix - Trailing content, e.g. a result count. Gets the chip's own gutter, so no margin is needed.\n *\n * @csspart base - The chip container.\n * @csspart content - The label wrapper.\n * @csspart toggle - The toggle button rendered by `selectable` (not with `control=\"checkbox\"`).\n * @csspart checkbox - The native checkbox rendered by `control=\"checkbox\"`.\n * @csspart remove - The remove button — a full-height segment at the end of the chip.\n *\n * @cssproperty [--border-radius] - Corner radius. Defaults to a full pill.\n * @cssproperty [--height] - Chip height. Defaults to the `size` step (a selectable chip is taller, to keep a comfortable target).\n * @cssproperty [--font-size] - Label size. Defaults to the `size` step — 12px, or 14px at `size=\"lg\"`. Set it with `--height` to land between the two steps in a dense filter panel.\n * @cssproperty [--padding-inline] - Horizontal padding. Defaults to the `size` step.\n * @cssproperty [--background] - Chip background.\n * @cssproperty [--color] - Text color.\n * @cssproperty [--border-color] - Chip border, and the rule that splits off the remove button. Defaults to a tint of `--color`; setting it pins the line in every state, so a token-based theme keeps its own value on hover.\n * @cssproperty [--selected-color] - Text, border, and checkbox accent when selected. Defaults to the library's form-control accent, lightened in dark mode.\n * @cssproperty [--selected-background] - Chip background when selected. Defaults to a tint of `--selected-color`.\n * @cssproperty [--selected-border-color] - Chip border when selected. Defaults to a 45% tint of `--selected-color`. Selection replaces the line the same way it replaces the fill and the ink, so a chip themed with `--border-color` still gets its emphasis step; set this to theme that step too.\n *\n * @customElement l-tag\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below; no uninitialized properties.\nexport class Tag extends LuxenElement {\n static override styles = [hostStyles, checkboxAppearance, styles];\n\n private _localize = new LocalizeController(this);\n\n /** Tag size: `sm`, `md` (default), or `lg`. */\n @property({ reflect: true })\n accessor size: TagSize = 'md';\n\n /** Show a remove button (and enable Backspace/Delete removal). */\n @property({ type: Boolean, reflect: true })\n accessor removable = false;\n\n /** Make the tag a filter control the user can toggle on and off. */\n @property({ type: Boolean, reflect: true })\n accessor selectable = false;\n\n /** Whether the tag is selected. Reflected, so `[selected]` is styleable. */\n @property({ type: Boolean, reflect: true })\n accessor selected = false;\n\n /**\n * What drives the selection: `none` (default) makes the chip itself a toggle\n * button; `checkbox` renders a checkbox inside and makes the chip its label —\n * the right choice for a multi-select facet. Implies `selectable`.\n */\n @property({ reflect: true })\n accessor control: TagControl = 'none';\n\n /** Disable the tag — dims it and blocks selection and removal. */\n @property({ type: Boolean, reflect: true })\n accessor disabled = false;\n\n constructor() {\n super();\n // The chip is operated from inside the shadow root, which a click\n // dispatched on the host never reaches — see `_onHostClick`.\n this.addEventListener('click', this._onHostClick);\n }\n\n override willUpdate() {\n // A checkbox is only ever rendered on a selectable chip, so the attribute\n // (which every selectable style rule keys off) is derived rather than\n // duplicated in every selector — and in the consumer's markup.\n if (this.control === 'checkbox') this.selectable = true;\n }\n\n override updated() {\n // Re-assert the box, don't rely on the `.checked` binding. Lit dirty-checks\n // a property part against the value it last wrote, and a click has the user\n // agent flip `checked` behind its back — so when a controlling host vetoes\n // the toggle by setting `selected` back to what it already was, Lit sees no\n // change, skips the write, and leaves a checked box on an unselected chip\n // (announced \"checked\" too). Cheap: the query is null on every other path.\n const box = this.renderRoot.querySelector<HTMLInputElement>('.checkbox');\n if (box) box.checked = this.selected;\n }\n\n private _requestRemove = () => {\n if (this.disabled) return;\n const event = new TagRemoveEvent();\n this.dispatchEvent(event);\n if (!event.defaultPrevented) this.remove();\n };\n\n private _onKeyDown = (e: KeyboardEvent) => {\n if (!this.removable || this.disabled) return;\n if (e.key === 'Backspace' || e.key === 'Delete') {\n e.preventDefault();\n this._requestRemove();\n }\n };\n\n private _toggle = () => {\n if (this.disabled) return;\n // Toggling, not setting: re-activating a selected tag releases it.\n this.selected = !this.selected;\n this.dispatchEvent(new TagChangeEvent(this.selected));\n };\n\n /**\n * Events do not propagate *down* into a shadow tree, so a click dispatched on\n * the host — `tag.click()`, a test runner resolving the chip by its test id,\n * a runner computing the chip's centre (`elementFromPoint()` retargets shadow\n * content back to the host) — never reaches the toggle button or the\n * checkbox, and silently does nothing. Delegate it.\n *\n * Only when the host *is* the target: a real gesture originates at the inner\n * button, at the checkbox, or at slotted content, and is already handled\n * where it lands — so this never double-toggles. `removable` stays out of it;\n * removing is the × button's own gesture.\n */\n private _onHostClick = (e: Event) => {\n if (!this.selectable || this.disabled) return;\n if (e.composedPath()[0] !== this) return;\n // Right for `control=\"checkbox\"` too: `_toggle()` flips `selected` and\n // fires `change`, and `updated()` re-asserts `box.checked` from it.\n this._toggle();\n };\n\n private _onCheckboxChange = (e: Event) => {\n this.selected = (e.target as HTMLInputElement).checked;\n this.dispatchEvent(new TagChangeEvent(this.selected));\n };\n\n private _label() {\n return html`\n <slot name=\"prefix\"></slot>\n <span\n class=\"content\"\n part=\"content\"\n >\n <slot></slot>\n </span>\n <slot name=\"suffix\"></slot>\n `;\n }\n\n private _removeButton() {\n return this.removable\n ? html`<button\n class=\"remove\"\n part=\"remove\"\n type=\"button\"\n ?disabled=${this.disabled}\n aria-label=${this._localize.term('remove')}\n @click=${this._requestRemove}\n ></button>`\n : nothing;\n }\n\n override render() {\n if (this.control === 'checkbox') {\n return html`\n <label\n class=\"base\"\n part=\"base\"\n @keydown=${this._onKeyDown}\n >\n <input\n class=\"checkbox ${cls('checkbox')}\"\n part=\"checkbox\"\n type=\"checkbox\"\n .checked=${this.selected}\n ?disabled=${this.disabled}\n @change=${this._onCheckboxChange}\n />\n ${this._label()}${this._removeButton()}\n </label>\n `;\n }\n\n if (this.selectable) {\n return html`\n <span\n class=\"base\"\n part=\"base\"\n @keydown=${this._onKeyDown}\n >\n <button\n class=\"toggle\"\n part=\"toggle\"\n type=\"button\"\n aria-pressed=${this.selected ? 'true' : 'false'}\n ?disabled=${this.disabled}\n @click=${this._toggle}\n >\n ${this._label()}\n </button>\n ${this._removeButton()}\n </span>\n `;\n }\n\n return html`\n <span\n class=\"base\"\n part=\"base\"\n @keydown=${this._onKeyDown}\n >\n ${this._label()}${this._removeButton()}\n </span>\n `;\n }\n}\n\n// Type `addEventListener('change', …)` so listeners receive a `TagChangeEvent`\n// (with `.selected`) instead of a bare `Event`. `change` collides with lib.dom's\n// `GlobalEventHandlersEventMap`, so it can't be augmented globally — the\n// overloads are declaration-merged onto this element's interface instead. The\n// interface MUST come after the class (the CEM analyzer dedups on the first\n// declaration of the name). See `tabs.ts` for the fully-annotated reference.\nexport interface Tag {\n addEventListener<K extends keyof TagEventMap>(\n type: K,\n listener: (this: Tag, ev: TagEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n removeEventListener<K extends keyof TagEventMap>(\n type: K,\n listener: (this: Tag, ev: TagEventMap[K]) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n}\n"],"mappings":"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"}
@@ -49,6 +49,7 @@ export declare class Tooltip extends LuxenElement {
49
49
  accessor trigger: string;
50
50
  private _hasTrigger;
51
51
  private get _trigger();
52
+ private _triggerFor;
52
53
  private get _popover();
53
54
  private get _arrowEl();
54
55
  private _getDuration;
@@ -60,6 +61,20 @@ export declare class Tooltip extends LuxenElement {
60
61
  show(): void;
61
62
  hide(): void;
62
63
  toggle(): void;
64
+ /**
65
+ * Recompute the position against the current trigger. Use it when you move
66
+ * the anchor yourself and the tooltip has to follow. No-op when closed;
67
+ * hides if the anchor is gone.
68
+ */
69
+ reposition(): Promise<void>;
70
+ /**
71
+ * Move the open bubble onto whatever `for` names now — a move, not a
72
+ * re-open, so it deliberately bypasses `_handleOpenChange` and its show
73
+ * animation. Shared by the public `reposition()` and the retarget path so
74
+ * the two cannot drift apart.
75
+ */
76
+ private _repositionToTrigger;
77
+ private _positionOptions;
63
78
  private _handleOpenChange;
64
79
  private _onPointerEnter;
65
80
  private _onPointerLeave;
@@ -1 +1 @@
1
- {"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../../../src/html/elements/tooltip/tooltip.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAoBlD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,OAAQ,SAAQ,YAAY;IACvC,OAAgB,MAAM,4BAAwB;IAE9C,OAAO,CAAC,UAAU,CAAuB;IAEzC,OAAO,CAAC,SAAS,CAId;IAEH,yDAAyD;IAEzD,QAAQ,CAAC,GAAG,SAAM;IAElB,8CAA8C;IAE9C,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAS;IAEtC,sDAAsD;IAEtD,QAAQ,CAAC,QAAQ,SAAK;IAEtB;;;OAGG;IAEH,QAAQ,CAAC,SAAS,SAAK;IAEvB;;;;OAIG;IAEH,QAAQ,CAAC,SAAS,SAAK;IAEvB,OAAO,CAAC,UAAU,CAAC,CAAgC;IACnD,OAAO,CAAC,UAAU,CAAC,CAAgC;IAEnD,6CAA6C;IAE7C,QAAQ,CAAC,IAAI,UAAS;IAEtB,kCAAkC;IAElC,QAAQ,CAAC,YAAY,UAAS;IAE9B,kFAAkF;IAElF,QAAQ,CAAC,OAAO,SAAiB;IAEjC,OAAO,CAAC,WAAW;IAInB,OAAO,KAAK,QAAQ,GAQnB;IAED,OAAO,KAAK,QAAQ,GAEnB;IAED,OAAO,KAAK,QAAQ,GAEnB;IAED,OAAO,CAAC,YAAY;IAKX,iBAAiB;IAMjB,oBAAoB;IAQpB,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAY9C,sFAAsF;IACtF,OAAO,CAAC,YAAY;IAOpB,IAAI;IAIJ,IAAI;IAIJ,MAAM;YAIQ,iBAAiB;IAgC/B,OAAO,CAAC,eAAe,CAwBrB;IAEF,OAAO,CAAC,eAAe,CAarB;IAEF,gFAAgF;IAChF,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,UAAU,CAYhB;IACF,OAAO,CAAC,WAAW,CAOjB;IACF,OAAO,CAAC,QAAQ,CAEd;IAEF,OAAO,CAAC,UAAU,CAKhB;IAEF,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,uBAAuB;IAOtB,MAAM;CAoBhB"}
1
+ {"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../../../src/html/elements/tooltip/tooltip.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAoBlD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,OAAQ,SAAQ,YAAY;IACvC,OAAgB,MAAM,4BAAwB;IAE9C,OAAO,CAAC,UAAU,CAAuB;IAEzC,OAAO,CAAC,SAAS,CAId;IAEH,yDAAyD;IAEzD,QAAQ,CAAC,GAAG,SAAM;IAElB,8CAA8C;IAE9C,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAS;IAEtC,sDAAsD;IAEtD,QAAQ,CAAC,QAAQ,SAAK;IAEtB;;;OAGG;IAEH,QAAQ,CAAC,SAAS,SAAK;IAEvB;;;;OAIG;IAEH,QAAQ,CAAC,SAAS,SAAK;IAEvB,OAAO,CAAC,UAAU,CAAC,CAAgC;IACnD,OAAO,CAAC,UAAU,CAAC,CAAgC;IAEnD,6CAA6C;IAE7C,QAAQ,CAAC,IAAI,UAAS;IAEtB,kCAAkC;IAElC,QAAQ,CAAC,YAAY,UAAS;IAE9B,kFAAkF;IAElF,QAAQ,CAAC,OAAO,SAAiB;IAEjC,OAAO,CAAC,WAAW;IAInB,OAAO,KAAK,QAAQ,GAEnB;IAED,OAAO,CAAC,WAAW;IAQnB,OAAO,KAAK,QAAQ,GAEnB;IAED,OAAO,KAAK,QAAQ,GAEnB;IAED,OAAO,CAAC,YAAY;IAKX,iBAAiB;IAMjB,oBAAoB;IAUpB,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAkB9C,sFAAsF;IACtF,OAAO,CAAC,YAAY;IAOpB,IAAI;IAIJ,IAAI;IAIJ,MAAM;IAIN;;;;OAIG;IAKG,UAAU;IAKhB;;;;;OAKG;YACW,oBAAoB;IAqBlC,OAAO,CAAC,gBAAgB;YAIV,iBAAiB;IAgC/B,OAAO,CAAC,eAAe,CAwBrB;IAEF,OAAO,CAAC,eAAe,CAarB;IAEF,gFAAgF;IAChF,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,UAAU,CAYhB;IACF,OAAO,CAAC,WAAW,CAOjB;IACF,OAAO,CAAC,QAAQ,CAEd;IAEF,OAAO,CAAC,UAAU,CAKhB;IAEF,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,uBAAuB;IAItB,MAAM;CAoBhB"}
@@ -1,4 +1,4 @@
1
- import{uniqueId as e}from"../../registry.js";import{i as t}from"../../chunks/lit.js";import{i as n,t as r}from"../../chunks/decorate.js";import{a as i,t as a}from"../../chunks/lit-html.js";import{LuxenElement as o}from"../../shared/luxen-element.js";import s from"../../shared/styles/host.styles.js";import{PopoverController as c}from"../../shared/controllers/popover.js";var l=t(`:host{--background-color:var(--l-tooltip-background-color);--border-radius:4px;--max-width:180px;--arrow-size:6px;--show-duration:.15s;--hide-duration:.15s;display:contents}[popover]{inset:unset;box-sizing:border-box;width:max-content;max-width:var(--max-width);border-radius:var(--border-radius);background:var(--background-color);--_on:calc(255 * ( 1 - sign(.2126 * (r / 255) * (r / 255) + .7152 * (g / 255) * (g / 255) + .0722 * (b / 255) * (b / 255) - .2) ) / 2);color:var(--text-color,rgb(from var(--background-color) var(--_on) var(--_on) var(--_on)));filter:drop-shadow(0 1px 2px #00000029);pointer-events:none;border:0;padding:4px 8px;font-size:.8125rem;line-height:1.4;overflow:visible}@supports (color:contrast-color(red)){[popover]{color:var(--text-color,contrast-color(var(--background-color)))}}i{width:var(--arrow-size);height:var(--arrow-size);background:var(--background-color);display:block;position:absolute;transform:rotate(45deg)}`),u=new Set,d=null,f=class extends o{constructor(...t){super(...t),this._tooltipId=e(`tooltip`),this._floating=new c(this,{getTriggerElement:()=>this._trigger,getFloatingElement:()=>this._popover,getArrowElement:()=>this._arrowEl}),this.#e=``,this.#t=`top`,this.#n=8,this.#r=0,this.#i=0,this.#a=!1,this.#o=!1,this.#s=`hover focus`,this._onPointerEnter=()=>{if(this._hasTrigger(`hover`)){this._floating.cleanupSafePolygon();for(let e of u)e===this||e._hasTrigger(`manual`)||(e._floating.cleanupSafePolygon(),e.hide());clearTimeout(this._hideTimer),this._hideTimer=void 0,this.showDelay>0&&!this.open?(clearTimeout(this._showTimer),this._showTimer=setTimeout(()=>this.show(),this.showDelay)):this.show()}},this._onPointerLeave=e=>{clearTimeout(this._showTimer),this._showTimer=void 0,!(!this._hasTrigger(`hover`)||!this.open)&&this._floating.handlePointerLeave(e,e=>{e===`settled`?this.hide():this._scheduleHide()})},this._onFocusIn=()=>{this._hasTrigger(`focus`)&&this._trigger?.matches(`:focus-visible`)&&(clearTimeout(this._hideTimer),this._hideTimer=void 0,this.show())},this._onFocusOut=()=>{this._hasTrigger(`focus`)&&(clearTimeout(this._showTimer),this._showTimer=void 0,this.hide())},this._onClick=()=>{this._hasTrigger(`click`)&&this.toggle()},this._onKeyDown=e=>{this.open&&e.key===`Escape`&&(e.stopPropagation(),this.hide())}}static{this.styles=[s,l]}#e;get for(){return this.#e}set for(e){this.#e=e}#t;get placement(){return this.#t}set placement(e){this.#t=e}#n;get distance(){return this.#n}set distance(e){this.#n=e}#r;get showDelay(){return this.#r}set showDelay(e){this.#r=e}#i;get hideDelay(){return this.#i}set hideDelay(e){this.#i=e}#a;get open(){return this.#a}set open(e){this.#a=e}#o;get withoutArrow(){return this.#o}set withoutArrow(e){this.#o=e}#s;get trigger(){return this.#s}set trigger(e){this.#s=e}_hasTrigger(e){return this.trigger.split(` `).includes(e)}get _trigger(){if(!this.for)return null;let e=this.getRootNode();return e instanceof Document||e instanceof ShadowRoot?e.getElementById(this.for):null}get _popover(){return this.shadowRoot.querySelector(`[popover]`)}get _arrowEl(){return this.withoutArrow?null:this.shadowRoot.querySelector(`i`)}_getDuration(e){let t=parseFloat(getComputedStyle(this).getPropertyValue(e));return Number.isNaN(t)?150:t}connectedCallback(){super.connectedCallback(),u.add(this),requestAnimationFrame(()=>this._addTriggerListeners())}disconnectedCallback(){super.disconnectedCallback(),u.delete(this),d===this&&(d=null),this._clearTimers(),this._removeTriggerListeners()}updated(e){e.has(`open`)&&this._handleOpenChange(),e.has(`for`)&&(this._clearTimers(),this._removeTriggerListeners(e.get(`for`)),this._addTriggerListeners())}_clearTimers(){clearTimeout(this._showTimer),clearTimeout(this._hideTimer),this._showTimer=void 0,this._hideTimer=void 0}show(){this.open||=!0}hide(){this.open&&=!1}toggle(){this.open=!this.open}async _handleOpenChange(){let e=this._popover;if(!e)return;let t={placement:this.placement,distance:this.distance};if(this.open){if(this._hasTrigger(`manual`)||(d&&d!==this&&d.hide(),d=this),e.showPopover(),await this._floating.updatePosition(t),!this.open)return;await this._floating.animateShow(e,this._getDuration(`--show-duration`)),this._floating.startPositioning(t),this._trigger?.setAttribute(`aria-describedby`,this._tooltipId)}else d===this&&(d=null),this._floating.stopPositioning(),this._floating.cleanupSafePolygon(),this._trigger?.removeAttribute(`aria-describedby`),await this._floating.animateHide(e,this._getDuration(`--hide-duration`)),e.matches(`:popover-open`)&&e.hidePopover()}_scheduleHide(){this.hideDelay>0?(clearTimeout(this._hideTimer),this._hideTimer=setTimeout(()=>this.hide(),this.hideDelay)):this.hide()}_addTriggerListeners(){this._floating.addTriggerListeners({onPointerEnter:this._onPointerEnter,onPointerLeave:this._onPointerLeave,onFocusIn:this._onFocusIn,onFocusOut:this._onFocusOut,onClick:this._onClick,onKeyDown:this._onKeyDown})}_removeTriggerListeners(e){let t=e?this.getRootNode().getElementById(e):void 0;this._floating.removeTriggerListeners(t)}render(){return i`
1
+ import{uniqueId as e}from"../../registry.js";import{i as t}from"../../chunks/lit.js";import{i as n,t as r}from"../../chunks/decorate.js";import{a as i,t as a}from"../../chunks/lit-html.js";import{LuxenElement as o}from"../../shared/luxen-element.js";import s from"../../shared/styles/host.styles.js";import{PopoverController as c}from"../../shared/controllers/popover.js";var l=t(`:host{--background-color:var(--l-tooltip-background-color);--border-radius:4px;--max-width:180px;--arrow-size:6px;--show-duration:.15s;--hide-duration:.15s;display:contents}[popover]{inset:unset;box-sizing:border-box;width:max-content;max-width:var(--max-width);border-radius:var(--border-radius);background:var(--background-color);--_on:calc(255 * ( 1 - sign(.2126 * (r / 255) * (r / 255) + .7152 * (g / 255) * (g / 255) + .0722 * (b / 255) * (b / 255) - .2) ) / 2);color:var(--text-color,rgb(from var(--background-color) var(--_on) var(--_on) var(--_on)));filter:drop-shadow(0 1px 2px #00000029);pointer-events:none;border:0;padding:4px 8px;font-size:.8125rem;line-height:1.4;overflow:visible}@supports (color:contrast-color(red)){[popover]{color:var(--text-color,contrast-color(var(--background-color)))}}i{width:var(--arrow-size);height:var(--arrow-size);background:var(--background-color);display:block;position:absolute;transform:rotate(45deg)}`),u=new Set,d=null,f=class extends o{constructor(...t){super(...t),this._tooltipId=e(`tooltip`),this._floating=new c(this,{getTriggerElement:()=>this._trigger,getFloatingElement:()=>this._popover,getArrowElement:()=>this._arrowEl}),this.#e=``,this.#t=`top`,this.#n=8,this.#r=0,this.#i=0,this.#a=!1,this.#o=!1,this.#s=`hover focus`,this._onPointerEnter=()=>{if(this._hasTrigger(`hover`)){this._floating.cleanupSafePolygon();for(let e of u)e===this||e._hasTrigger(`manual`)||(e._floating.cleanupSafePolygon(),e.hide());clearTimeout(this._hideTimer),this._hideTimer=void 0,this.showDelay>0&&!this.open?(clearTimeout(this._showTimer),this._showTimer=setTimeout(()=>this.show(),this.showDelay)):this.show()}},this._onPointerLeave=e=>{clearTimeout(this._showTimer),this._showTimer=void 0,!(!this._hasTrigger(`hover`)||!this.open)&&this._floating.handlePointerLeave(e,e=>{e===`settled`?this.hide():this._scheduleHide()})},this._onFocusIn=()=>{this._hasTrigger(`focus`)&&this._trigger?.matches(`:focus-visible`)&&(clearTimeout(this._hideTimer),this._hideTimer=void 0,this.show())},this._onFocusOut=()=>{this._hasTrigger(`focus`)&&(clearTimeout(this._showTimer),this._showTimer=void 0,this.hide())},this._onClick=()=>{this._hasTrigger(`click`)&&this.toggle()},this._onKeyDown=e=>{this.open&&e.key===`Escape`&&(e.stopPropagation(),this.hide())}}static{this.styles=[s,l]}#e;get for(){return this.#e}set for(e){this.#e=e}#t;get placement(){return this.#t}set placement(e){this.#t=e}#n;get distance(){return this.#n}set distance(e){this.#n=e}#r;get showDelay(){return this.#r}set showDelay(e){this.#r=e}#i;get hideDelay(){return this.#i}set hideDelay(e){this.#i=e}#a;get open(){return this.#a}set open(e){this.#a=e}#o;get withoutArrow(){return this.#o}set withoutArrow(e){this.#o=e}#s;get trigger(){return this.#s}set trigger(e){this.#s=e}_hasTrigger(e){return this.trigger.split(` `).includes(e)}get _trigger(){return this._triggerFor(this.for)}_triggerFor(e){if(!e)return null;let t=this.getRootNode();return t instanceof Document||t instanceof ShadowRoot?t.getElementById(e):null}get _popover(){return this.shadowRoot.querySelector(`[popover]`)}get _arrowEl(){return this.withoutArrow?null:this.shadowRoot.querySelector(`i`)}_getDuration(e){let t=parseFloat(getComputedStyle(this).getPropertyValue(e));return Number.isNaN(t)?150:t}connectedCallback(){super.connectedCallback(),u.add(this),requestAnimationFrame(()=>this._addTriggerListeners())}disconnectedCallback(){super.disconnectedCallback(),u.delete(this),d===this&&(d=null),this._clearTimers(),this._removeTriggerListeners()}updated(e){e.has(`for`)&&(this._clearTimers(),this._removeTriggerListeners(e.get(`for`)),this._addTriggerListeners(),this.open&&!e.has(`open`)&&this._repositionToTrigger()),e.has(`open`)&&this._handleOpenChange()}_clearTimers(){clearTimeout(this._showTimer),clearTimeout(this._hideTimer),this._showTimer=void 0,this._hideTimer=void 0}show(){this.open||=!0}hide(){this.open&&=!1}toggle(){this.open=!this.open}async reposition(){this.open&&await this._repositionToTrigger()}async _repositionToTrigger(){this._floating.cleanupSafePolygon();let e=this.for,t=await this._floating.reposition(this._positionOptions(),()=>this.open&&this.for===e);if(t!==`stale`){if(t===`no-anchor`){this.hide();return}this._floating.markTrigger(`aria-describedby`,this._tooltipId,null)}}_positionOptions(){return{placement:this.placement,distance:this.distance}}async _handleOpenChange(){let e=this._popover;if(!e)return;let t=this._positionOptions();if(this.open){if(this._hasTrigger(`manual`)||(d&&d!==this&&d.hide(),d=this),e.showPopover(),await this._floating.updatePosition(t),!this.open)return;await this._floating.animateShow(e,this._getDuration(`--show-duration`)),this._floating.startPositioning(t),this._floating.markTrigger(`aria-describedby`,this._tooltipId,null)}else d===this&&(d=null),this._floating.stopPositioning(),this._floating.cleanupSafePolygon(),this._floating.releaseTrigger(null),await this._floating.animateHide(e,this._getDuration(`--hide-duration`)),e.matches(`:popover-open`)&&e.hidePopover()}_scheduleHide(){this.hideDelay>0?(clearTimeout(this._hideTimer),this._hideTimer=setTimeout(()=>this.hide(),this.hideDelay)):this.hide()}_addTriggerListeners(){this._floating.addTriggerListeners({onPointerEnter:this._onPointerEnter,onPointerLeave:this._onPointerLeave,onFocusIn:this._onFocusIn,onFocusOut:this._onFocusOut,onClick:this._onClick,onKeyDown:this._onKeyDown})}_removeTriggerListeners(e){this._floating.removeTriggerListeners(e?this._triggerFor(e):void 0)}render(){return i`
2
2
  <div
3
3
  id=${this._tooltipId}
4
4
  popover="manual"
@@ -1 +1 @@
1
- {"version":3,"file":"tooltip.js","names":[],"sources":["../../../src/html/elements/tooltip/tooltip.css?inline","../../../src/html/elements/tooltip/tooltip.ts"],"sourcesContent":[":host {\n --background-color: var(--l-tooltip-background-color);\n --border-radius: 4px;\n --max-width: 180px;\n --arrow-size: 6px;\n --show-duration: 150ms;\n --hide-duration: 150ms;\n\n display: contents;\n}\n\n[popover] {\n inset: unset;\n overflow: visible;\n box-sizing: border-box;\n width: max-content;\n max-width: var(--max-width);\n padding: 4px 8px;\n border: 0;\n border-radius: var(--border-radius);\n background: var(--background-color);\n /*\n * Text over --background-color, decided by the background's own luminance —\n * never the document mode, since --background-color is consumer-set and\n * ignores light/dark. The contrast-color() block below is the primary path\n * (Baseline 2026); this --_on calc() is the fallback for browsers predating\n * it. It approximates WCAG relative luminance with squared sRGB channels\n * (Rec. 709 weights) and flips at 0.2, just above the black/white crossover\n * (~0.179) — holding AA across saturated custom colors where an\n * OKLCH-lightness threshold collapses to ≈1:1 (OKLCH L tracks hue, not\n * luminance). --text-color lets consumers force a specific text color.\n */\n --_on: calc(\n 255 *\n (\n 1 -\n sign(\n 0.2126 * (r / 255) * (r / 255) + 0.7152 * (g / 255) * (g / 255) + 0.0722 * (b / 255) *\n (b / 255) - 0.2\n )\n ) /\n 2\n );\n color: var(--text-color, rgb(from var(--background-color) var(--_on) var(--_on) var(--_on)));\n font-size: 0.8125rem;\n line-height: 1.4;\n filter: drop-shadow(0 1px 2px rgb(0 0 0 / 16%));\n pointer-events: none;\n}\n\n/* Primary path: native, guaranteed-contrast black/white. @supports (not\n * declaration order) is required — the value contains var(), so a non-supporting\n * browser treats contrast-color() as invalid-at-computed-value-time and resets to\n * inherit rather than falling back to the calc() line. */\n@supports (color: contrast-color(red)) {\n [popover] {\n color: var(--text-color, contrast-color(var(--background-color)));\n }\n}\n\ni {\n position: absolute;\n display: block;\n width: var(--arrow-size);\n height: var(--arrow-size);\n background: var(--background-color);\n transform: rotate(45deg);\n}\n","import { html, nothing, unsafeCSS, type PropertyValues } from 'lit';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { property } from 'lit/decorators.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { PopoverController } from '../../shared/controllers/popover.js';\nimport { uniqueId } from '../../registry.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport rawStyles from './tooltip.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * Module-level registry of connected tooltips. A tooltip behaves like a label,\n * not a dialog: at most one pointer/focus/click-triggered tooltip is visible at\n * a time. Without this, on a dense grid of small triggers the safe polygon of\n * the previous tooltip overlaps the neighbouring triggers and two (or more)\n * tooltips end up visible during a horizontal sweep. `trigger=\"manual\"`\n * tooltips opt out: they never claim the active slot and are never evicted, so\n * programmatic multi-tooltip scenarios (annotations, coach marks) keep working.\n */\nconst tooltipInstances = new Set<Tooltip>();\nlet activeTooltip: Tooltip | null = null;\n\n/**\n * @summary A tooltip that displays contextual text on hover or focus.\n * @customElement l-tooltip\n *\n * @slot - Tooltip content (text or rich HTML).\n *\n * @csspart body - The tooltip popover container.\n * @csspart arrow - The directional arrow element.\n *\n * @cssproperty --background-color - Background color for this tooltip instance. Defaults to the global `--l-tooltip-background-color` token (a neutral inverse surface, dark in light mode / light in dark mode) — override that token to re-skin every tooltip at once.\n * @cssproperty --text-color - Text color. If unset, auto-derived from `--background-color` luminance.\n * @cssproperty --border-radius - Border radius. Default `4px`.\n * @cssproperty --max-width - Maximum width. Default `180px`.\n * @cssproperty --arrow-size - Arrow size. Default `6px`.\n * @cssproperty --show-duration - Show animation duration. Default `150ms`.\n * @cssproperty --hide-duration - Hide animation duration. Default `150ms`.\n */\nexport class Tooltip extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _tooltipId = uniqueId('tooltip');\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._trigger,\n getFloatingElement: () => this._popover,\n getArrowElement: () => this._arrowEl,\n });\n\n /** The HTML id of the element triggering the tooltip. */\n @property({ reflect: true })\n accessor for = '';\n\n /** The preferred placement of the tooltip. */\n @property()\n accessor placement: Placement = 'top';\n\n /** The distance in pixels from the target element. */\n @property({ type: Number })\n accessor distance = 8;\n\n /**\n * Milliseconds the pointer must dwell on the trigger before the tooltip\n * shows. Applies to `hover` only — focus shows immediately.\n */\n @property({ type: Number, attribute: 'show-delay' })\n accessor showDelay = 0;\n\n /**\n * Milliseconds to wait after the pointer leaves the trigger (and its safe\n * polygon) before hiding. Bridges a brief exit-and-return without flicker.\n * Applies to `hover` only.\n */\n @property({ type: Number, attribute: 'hide-delay' })\n accessor hideDelay = 0;\n\n private _showTimer?: ReturnType<typeof setTimeout>;\n private _hideTimer?: ReturnType<typeof setTimeout>;\n\n /** Whether or not the tooltip is visible. */\n @property({ type: Boolean, reflect: true })\n accessor open = false;\n\n /** Hide the directional arrow. */\n @property({ type: Boolean, reflect: true, attribute: 'without-arrow' })\n accessor withoutArrow = false;\n\n /** Space-separated list of trigger modes: `hover`, `focus`, `click`, `manual`. */\n @property()\n accessor trigger = 'hover focus';\n\n private _hasTrigger(type: string) {\n return this.trigger.split(' ').includes(type);\n }\n\n private get _trigger(): HTMLElement | null {\n if (!this.for) return null;\n // A detached tooltip's root is a plain element with no getElementById —\n // async open/close work (e.g. an eviction) may land after removal.\n const root = this.getRootNode();\n return root instanceof Document || root instanceof ShadowRoot\n ? root.getElementById(this.for)\n : null;\n }\n\n private get _popover(): HTMLElement {\n return this.shadowRoot!.querySelector('[popover]')!;\n }\n\n private get _arrowEl(): HTMLElement | null {\n return this.withoutArrow ? null : this.shadowRoot!.querySelector('i');\n }\n\n private _getDuration(prop: '--show-duration' | '--hide-duration'): number {\n const parsed = parseFloat(getComputedStyle(this).getPropertyValue(prop));\n return Number.isNaN(parsed) ? 150 : parsed;\n }\n\n override connectedCallback() {\n super.connectedCallback();\n tooltipInstances.add(this);\n requestAnimationFrame(() => this._addTriggerListeners());\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n tooltipInstances.delete(this);\n if (activeTooltip === this) activeTooltip = null;\n this._clearTimers();\n this._removeTriggerListeners();\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('open')) {\n void this._handleOpenChange();\n }\n if (changed.has('for')) {\n // A queued show/hide targets the old trigger — drop it before rewiring.\n this._clearTimers();\n this._removeTriggerListeners(changed.get('for') as string);\n this._addTriggerListeners();\n }\n }\n\n /** Cancel any pending delayed show/hide (trigger gone, `for` changed, disconnect). */\n private _clearTimers() {\n clearTimeout(this._showTimer);\n clearTimeout(this._hideTimer);\n this._showTimer = undefined;\n this._hideTimer = undefined;\n }\n\n show() {\n if (!this.open) this.open = true;\n }\n\n hide() {\n if (this.open) this.open = false;\n }\n\n toggle() {\n this.open = !this.open;\n }\n\n private async _handleOpenChange() {\n const popover = this._popover;\n if (!popover) return;\n\n const posOpts = { placement: this.placement, distance: this.distance };\n\n if (this.open) {\n // Single-open invariant: opening a non-manual tooltip evicts the current\n // one. The evicted tooltip's hide flows back through this method, which\n // also removes its safe-polygon `pointermove` listener.\n if (!this._hasTrigger('manual')) {\n if (activeTooltip && activeTooltip !== this) activeTooltip.hide();\n activeTooltip = this;\n }\n popover.showPopover();\n await this._floating.updatePosition(posOpts);\n if (!this.open) return;\n await this._floating.animateShow(popover, this._getDuration('--show-duration'));\n this._floating.startPositioning(posOpts);\n this._trigger?.setAttribute('aria-describedby', this._tooltipId);\n } else {\n if (activeTooltip === this) activeTooltip = null;\n this._floating.stopPositioning();\n this._floating.cleanupSafePolygon();\n this._trigger?.removeAttribute('aria-describedby');\n await this._floating.animateHide(popover, this._getDuration('--hide-duration'));\n if (popover.matches(':popover-open')) popover.hidePopover();\n }\n }\n\n // --- Trigger event handlers ---\n\n private _onPointerEnter = () => {\n if (!this._hasTrigger('hover')) return;\n // The safe polygon means \"the pointer is travelling toward the bubble\" —\n // provably false once the pointer enters another tooltip's trigger. Drop\n // our own stale polygon, and close every peer now: invalidating a peer's\n // polygon without hiding it leaves the peer stuck open, because its only\n // remaining close path was eviction when *this* tooltip opens — and with\n // `show-delay` that can be much later, or never if the pointer moves on\n // before the dwell elapses (a fast sweep across a button group).\n this._floating.cleanupSafePolygon();\n for (const tooltip of tooltipInstances) {\n if (tooltip === this || tooltip._hasTrigger('manual')) continue;\n tooltip._floating.cleanupSafePolygon();\n tooltip.hide();\n }\n // The pointer is back on the trigger: cancel a pending hide (exit-and-return).\n clearTimeout(this._hideTimer);\n this._hideTimer = undefined;\n if (this.showDelay > 0 && !this.open) {\n clearTimeout(this._showTimer);\n this._showTimer = setTimeout(() => this.show(), this.showDelay);\n } else {\n this.show();\n }\n };\n\n private _onPointerLeave = (e: PointerEvent) => {\n // Cancel a still-pending show — the pointer left before the dwell elapsed.\n clearTimeout(this._showTimer);\n this._showTimer = undefined;\n if (!this._hasTrigger('hover') || !this.open) return;\n // `exit` — the pointer actively moved off the trigger/bubble: honour\n // `hide-delay` so a brief exit-and-return doesn't flicker. `settled` — the\n // pointer left and sat motionless in the corridor: there is no in-flight\n // gesture to bridge, so hide at once (never stack the delay on top).\n this._floating.handlePointerLeave(e, (reason) => {\n if (reason === 'settled') this.hide();\n else this._scheduleHide();\n });\n };\n\n /** Hide now, or after `hide-delay` if set (cancellable by a pointer return). */\n private _scheduleHide() {\n if (this.hideDelay > 0) {\n clearTimeout(this._hideTimer);\n this._hideTimer = setTimeout(() => this.hide(), this.hideDelay);\n } else {\n this.hide();\n }\n }\n\n private _onFocusIn = () => {\n if (!this._hasTrigger('focus')) return;\n // Keyboard focus only. A pointer click focuses the trigger too, but the\n // pointer already drives the `hover` trigger — showing on mouse focus would\n // resurrect the tooltip when a modal (e.g. `l-dialog`) restores focus to\n // the trigger on close. `:focus-visible` is exactly \"focus that warrants a\n // visible affordance\", matching the platform's own `title`/APG behaviour.\n if (!this._trigger?.matches(':focus-visible')) return;\n // Focus is a discrete affordance — show immediately, ignoring the dwell delay.\n clearTimeout(this._hideTimer);\n this._hideTimer = undefined;\n this.show();\n };\n private _onFocusOut = () => {\n if (this._hasTrigger('focus')) {\n // A hover may have queued a show; blur must not resurrect it.\n clearTimeout(this._showTimer);\n this._showTimer = undefined;\n this.hide();\n }\n };\n private _onClick = () => {\n if (this._hasTrigger('click')) this.toggle();\n };\n\n private _onKeyDown = (e: KeyboardEvent) => {\n if (this.open && e.key === 'Escape') {\n e.stopPropagation();\n this.hide();\n }\n };\n\n private _addTriggerListeners() {\n this._floating.addTriggerListeners({\n onPointerEnter: this._onPointerEnter,\n onPointerLeave: this._onPointerLeave,\n onFocusIn: this._onFocusIn,\n onFocusOut: this._onFocusOut,\n onClick: this._onClick,\n onKeyDown: this._onKeyDown,\n });\n }\n\n private _removeTriggerListeners(forId?: string) {\n const trigger = forId\n ? (this.getRootNode() as Document | ShadowRoot).getElementById(forId)\n : undefined;\n this._floating.removeTriggerListeners(trigger);\n }\n\n override render() {\n return html`\n <div\n id=${this._tooltipId}\n popover=\"manual\"\n role=\"tooltip\"\n part=\"body\"\n >\n ${this.withoutArrow\n ? nothing\n : html`\n <i\n part=\"arrow\"\n role=\"presentation\"\n ></i>\n `}\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tooltip.js","names":[],"sources":["../../../src/html/elements/tooltip/tooltip.css?inline","../../../src/html/elements/tooltip/tooltip.ts"],"sourcesContent":[":host {\n --background-color: var(--l-tooltip-background-color);\n --border-radius: 4px;\n --max-width: 180px;\n --arrow-size: 6px;\n --show-duration: 150ms;\n --hide-duration: 150ms;\n\n display: contents;\n}\n\n[popover] {\n inset: unset;\n overflow: visible;\n box-sizing: border-box;\n width: max-content;\n max-width: var(--max-width);\n padding: 4px 8px;\n border: 0;\n border-radius: var(--border-radius);\n background: var(--background-color);\n /*\n * Text over --background-color, decided by the background's own luminance —\n * never the document mode, since --background-color is consumer-set and\n * ignores light/dark. The contrast-color() block below is the primary path\n * (Baseline 2026); this --_on calc() is the fallback for browsers predating\n * it. It approximates WCAG relative luminance with squared sRGB channels\n * (Rec. 709 weights) and flips at 0.2, just above the black/white crossover\n * (~0.179) — holding AA across saturated custom colors where an\n * OKLCH-lightness threshold collapses to ≈1:1 (OKLCH L tracks hue, not\n * luminance). --text-color lets consumers force a specific text color.\n */\n --_on: calc(\n 255 *\n (\n 1 -\n sign(\n 0.2126 * (r / 255) * (r / 255) + 0.7152 * (g / 255) * (g / 255) + 0.0722 * (b / 255) *\n (b / 255) - 0.2\n )\n ) /\n 2\n );\n color: var(--text-color, rgb(from var(--background-color) var(--_on) var(--_on) var(--_on)));\n font-size: 0.8125rem;\n line-height: 1.4;\n filter: drop-shadow(0 1px 2px rgb(0 0 0 / 16%));\n pointer-events: none;\n}\n\n/* Primary path: native, guaranteed-contrast black/white. @supports (not\n * declaration order) is required — the value contains var(), so a non-supporting\n * browser treats contrast-color() as invalid-at-computed-value-time and resets to\n * inherit rather than falling back to the calc() line. */\n@supports (color: contrast-color(red)) {\n [popover] {\n color: var(--text-color, contrast-color(var(--background-color)));\n }\n}\n\ni {\n position: absolute;\n display: block;\n width: var(--arrow-size);\n height: var(--arrow-size);\n background: var(--background-color);\n transform: rotate(45deg);\n}\n","import { html, nothing, unsafeCSS, type PropertyValues } from 'lit';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { property } from 'lit/decorators.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { PopoverController } from '../../shared/controllers/popover.js';\nimport { uniqueId } from '../../registry.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport rawStyles from './tooltip.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * Module-level registry of connected tooltips. A tooltip behaves like a label,\n * not a dialog: at most one pointer/focus/click-triggered tooltip is visible at\n * a time. Without this, on a dense grid of small triggers the safe polygon of\n * the previous tooltip overlaps the neighbouring triggers and two (or more)\n * tooltips end up visible during a horizontal sweep. `trigger=\"manual\"`\n * tooltips opt out: they never claim the active slot and are never evicted, so\n * programmatic multi-tooltip scenarios (annotations, coach marks) keep working.\n */\nconst tooltipInstances = new Set<Tooltip>();\nlet activeTooltip: Tooltip | null = null;\n\n/**\n * @summary A tooltip that displays contextual text on hover or focus.\n * @customElement l-tooltip\n *\n * @slot - Tooltip content (text or rich HTML).\n *\n * @csspart body - The tooltip popover container.\n * @csspart arrow - The directional arrow element.\n *\n * @cssproperty --background-color - Background color for this tooltip instance. Defaults to the global `--l-tooltip-background-color` token (a neutral inverse surface, dark in light mode / light in dark mode) — override that token to re-skin every tooltip at once.\n * @cssproperty --text-color - Text color. If unset, auto-derived from `--background-color` luminance.\n * @cssproperty --border-radius - Border radius. Default `4px`.\n * @cssproperty --max-width - Maximum width. Default `180px`.\n * @cssproperty --arrow-size - Arrow size. Default `6px`.\n * @cssproperty --show-duration - Show animation duration. Default `150ms`.\n * @cssproperty --hide-duration - Hide animation duration. Default `150ms`.\n */\nexport class Tooltip extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _tooltipId = uniqueId('tooltip');\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._trigger,\n getFloatingElement: () => this._popover,\n getArrowElement: () => this._arrowEl,\n });\n\n /** The HTML id of the element triggering the tooltip. */\n @property({ reflect: true })\n accessor for = '';\n\n /** The preferred placement of the tooltip. */\n @property()\n accessor placement: Placement = 'top';\n\n /** The distance in pixels from the target element. */\n @property({ type: Number })\n accessor distance = 8;\n\n /**\n * Milliseconds the pointer must dwell on the trigger before the tooltip\n * shows. Applies to `hover` only — focus shows immediately.\n */\n @property({ type: Number, attribute: 'show-delay' })\n accessor showDelay = 0;\n\n /**\n * Milliseconds to wait after the pointer leaves the trigger (and its safe\n * polygon) before hiding. Bridges a brief exit-and-return without flicker.\n * Applies to `hover` only.\n */\n @property({ type: Number, attribute: 'hide-delay' })\n accessor hideDelay = 0;\n\n private _showTimer?: ReturnType<typeof setTimeout>;\n private _hideTimer?: ReturnType<typeof setTimeout>;\n\n /** Whether or not the tooltip is visible. */\n @property({ type: Boolean, reflect: true })\n accessor open = false;\n\n /** Hide the directional arrow. */\n @property({ type: Boolean, reflect: true, attribute: 'without-arrow' })\n accessor withoutArrow = false;\n\n /** Space-separated list of trigger modes: `hover`, `focus`, `click`, `manual`. */\n @property()\n accessor trigger = 'hover focus';\n\n private _hasTrigger(type: string) {\n return this.trigger.split(' ').includes(type);\n }\n\n private get _trigger(): HTMLElement | null {\n return this._triggerFor(this.for);\n }\n\n private _triggerFor(id: string): HTMLElement | null {\n if (!id) return null;\n // A detached tooltip's root is a plain element with no getElementById —\n // async open/close work (e.g. an eviction) may land after removal.\n const root = this.getRootNode();\n return root instanceof Document || root instanceof ShadowRoot ? root.getElementById(id) : null;\n }\n\n private get _popover(): HTMLElement {\n return this.shadowRoot!.querySelector('[popover]')!;\n }\n\n private get _arrowEl(): HTMLElement | null {\n return this.withoutArrow ? null : this.shadowRoot!.querySelector('i');\n }\n\n private _getDuration(prop: '--show-duration' | '--hide-duration'): number {\n const parsed = parseFloat(getComputedStyle(this).getPropertyValue(prop));\n return Number.isNaN(parsed) ? 150 : parsed;\n }\n\n override connectedCallback() {\n super.connectedCallback();\n tooltipInstances.add(this);\n requestAnimationFrame(() => this._addTriggerListeners());\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n tooltipInstances.delete(this);\n if (activeTooltip === this) activeTooltip = null;\n this._clearTimers();\n // `aria-describedby` is dropped by the controller's hostDisconnected: our\n // id stops resolving once we leave the tree.\n this._removeTriggerListeners();\n }\n\n override updated(changed: PropertyValues<this>) {\n // `for` before `open`: retargeting an already-open bubble has to settle\n // against the new anchor, and when both change in the same update the open\n // path owns the positioning outright (see the guard below).\n if (changed.has('for')) {\n // A queued show/hide targets the old trigger — drop it before rewiring.\n this._clearTimers();\n this._removeTriggerListeners(changed.get('for') as string);\n this._addTriggerListeners();\n if (this.open && !changed.has('open')) {\n void this._repositionToTrigger();\n }\n }\n if (changed.has('open')) {\n void this._handleOpenChange();\n }\n }\n\n /** Cancel any pending delayed show/hide (trigger gone, `for` changed, disconnect). */\n private _clearTimers() {\n clearTimeout(this._showTimer);\n clearTimeout(this._hideTimer);\n this._showTimer = undefined;\n this._hideTimer = undefined;\n }\n\n show() {\n if (!this.open) this.open = true;\n }\n\n hide() {\n if (this.open) this.open = false;\n }\n\n toggle() {\n this.open = !this.open;\n }\n\n /**\n * Recompute the position against the current trigger. Use it when you move\n * the anchor yourself and the tooltip has to follow. No-op when closed;\n * hides if the anchor is gone.\n */\n // Worth calling even though positioning is otherwise automatic: the\n // underlying observers detect a moved anchor a frame late, and an anchor\n // driven at frame rate (a marker following the pointer) can starve them for\n // as long as a second. An explicit call bypasses them.\n async reposition() {\n if (!this.open) return;\n await this._repositionToTrigger();\n }\n\n /**\n * Move the open bubble onto whatever `for` names now — a move, not a\n * re-open, so it deliberately bypasses `_handleOpenChange` and its show\n * animation. Shared by the public `reposition()` and the retarget path so\n * the two cannot drift apart.\n */\n private async _repositionToTrigger() {\n // The polygon was armed against the anchor's previous geometry.\n this._floating.cleanupSafePolygon();\n const openFor = this.for;\n const result = await this._floating.reposition(\n this._positionOptions(),\n () => this.open && this.for === openFor,\n );\n // Closed or retargeted again while `computePosition` was in flight —\n // whatever superseded us owns the bubble and its ARIA now.\n if (result === 'stale') return;\n // Retargeted onto an id that resolves to nothing — a virtualised row\n // recycled between `pointerenter` and here, say. A bubble left floating\n // over unrelated content while describing no element is worse than none.\n if (result === 'no-anchor') {\n this.hide();\n return;\n }\n this._floating.markTrigger('aria-describedby', this._tooltipId, null);\n }\n\n private _positionOptions() {\n return { placement: this.placement, distance: this.distance };\n }\n\n private async _handleOpenChange() {\n const popover = this._popover;\n if (!popover) return;\n\n const posOpts = this._positionOptions();\n\n if (this.open) {\n // Single-open invariant: opening a non-manual tooltip evicts the current\n // one. The evicted tooltip's hide flows back through this method, which\n // also removes its safe-polygon `pointermove` listener.\n if (!this._hasTrigger('manual')) {\n if (activeTooltip && activeTooltip !== this) activeTooltip.hide();\n activeTooltip = this;\n }\n popover.showPopover();\n await this._floating.updatePosition(posOpts);\n if (!this.open) return;\n await this._floating.animateShow(popover, this._getDuration('--show-duration'));\n this._floating.startPositioning(posOpts);\n this._floating.markTrigger('aria-describedby', this._tooltipId, null);\n } else {\n if (activeTooltip === this) activeTooltip = null;\n this._floating.stopPositioning();\n this._floating.cleanupSafePolygon();\n this._floating.releaseTrigger(null);\n await this._floating.animateHide(popover, this._getDuration('--hide-duration'));\n if (popover.matches(':popover-open')) popover.hidePopover();\n }\n }\n\n // --- Trigger event handlers ---\n\n private _onPointerEnter = () => {\n if (!this._hasTrigger('hover')) return;\n // The safe polygon means \"the pointer is travelling toward the bubble\" —\n // provably false once the pointer enters another tooltip's trigger. Drop\n // our own stale polygon, and close every peer now: invalidating a peer's\n // polygon without hiding it leaves the peer stuck open, because its only\n // remaining close path was eviction when *this* tooltip opens — and with\n // `show-delay` that can be much later, or never if the pointer moves on\n // before the dwell elapses (a fast sweep across a button group).\n this._floating.cleanupSafePolygon();\n for (const tooltip of tooltipInstances) {\n if (tooltip === this || tooltip._hasTrigger('manual')) continue;\n tooltip._floating.cleanupSafePolygon();\n tooltip.hide();\n }\n // The pointer is back on the trigger: cancel a pending hide (exit-and-return).\n clearTimeout(this._hideTimer);\n this._hideTimer = undefined;\n if (this.showDelay > 0 && !this.open) {\n clearTimeout(this._showTimer);\n this._showTimer = setTimeout(() => this.show(), this.showDelay);\n } else {\n this.show();\n }\n };\n\n private _onPointerLeave = (e: PointerEvent) => {\n // Cancel a still-pending show — the pointer left before the dwell elapsed.\n clearTimeout(this._showTimer);\n this._showTimer = undefined;\n if (!this._hasTrigger('hover') || !this.open) return;\n // `exit` — the pointer actively moved off the trigger/bubble: honour\n // `hide-delay` so a brief exit-and-return doesn't flicker. `settled` — the\n // pointer left and sat motionless in the corridor: there is no in-flight\n // gesture to bridge, so hide at once (never stack the delay on top).\n this._floating.handlePointerLeave(e, (reason) => {\n if (reason === 'settled') this.hide();\n else this._scheduleHide();\n });\n };\n\n /** Hide now, or after `hide-delay` if set (cancellable by a pointer return). */\n private _scheduleHide() {\n if (this.hideDelay > 0) {\n clearTimeout(this._hideTimer);\n this._hideTimer = setTimeout(() => this.hide(), this.hideDelay);\n } else {\n this.hide();\n }\n }\n\n private _onFocusIn = () => {\n if (!this._hasTrigger('focus')) return;\n // Keyboard focus only. A pointer click focuses the trigger too, but the\n // pointer already drives the `hover` trigger — showing on mouse focus would\n // resurrect the tooltip when a modal (e.g. `l-dialog`) restores focus to\n // the trigger on close. `:focus-visible` is exactly \"focus that warrants a\n // visible affordance\", matching the platform's own `title`/APG behaviour.\n if (!this._trigger?.matches(':focus-visible')) return;\n // Focus is a discrete affordance — show immediately, ignoring the dwell delay.\n clearTimeout(this._hideTimer);\n this._hideTimer = undefined;\n this.show();\n };\n private _onFocusOut = () => {\n if (this._hasTrigger('focus')) {\n // A hover may have queued a show; blur must not resurrect it.\n clearTimeout(this._showTimer);\n this._showTimer = undefined;\n this.hide();\n }\n };\n private _onClick = () => {\n if (this._hasTrigger('click')) this.toggle();\n };\n\n private _onKeyDown = (e: KeyboardEvent) => {\n if (this.open && e.key === 'Escape') {\n e.stopPropagation();\n this.hide();\n }\n };\n\n private _addTriggerListeners() {\n this._floating.addTriggerListeners({\n onPointerEnter: this._onPointerEnter,\n onPointerLeave: this._onPointerLeave,\n onFocusIn: this._onFocusIn,\n onFocusOut: this._onFocusOut,\n onClick: this._onClick,\n onKeyDown: this._onKeyDown,\n });\n }\n\n private _removeTriggerListeners(forId?: string) {\n this._floating.removeTriggerListeners(forId ? this._triggerFor(forId) : undefined);\n }\n\n override render() {\n return html`\n <div\n id=${this._tooltipId}\n popover=\"manual\"\n role=\"tooltip\"\n part=\"body\"\n >\n ${this.withoutArrow\n ? nothing\n : html`\n <i\n part=\"arrow\"\n role=\"presentation\"\n ></i>\n `}\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
@@ -6,6 +6,12 @@ export interface PopoverControllerConfig {
6
6
  getArrowElement: () => HTMLElement | null;
7
7
  onPlacementChange?: (placement: string) => void;
8
8
  }
9
+ /**
10
+ * Outcome of {@link PopoverController.reposition}. `stale` means the host
11
+ * closed or retargeted again while `computePosition` was in flight — the caller
12
+ * must not act on it, because whatever superseded it owns the element now.
13
+ */
14
+ export type RepositionResult = 'repositioned' | 'no-anchor' | 'stale';
9
15
  export interface PositionOptions {
10
16
  placement: Placement;
11
17
  distance: number;
@@ -34,9 +40,43 @@ export declare class PopoverController implements ReactiveController {
34
40
  constructor(host: ReactiveControllerHost & HTMLElement, config: PopoverControllerConfig);
35
41
  hostConnected(): void;
36
42
  hostDisconnected(): void;
43
+ private _markedTrigger;
44
+ private _markedAttribute;
45
+ /**
46
+ * Write an ARIA attribute on the current trigger, remembering the element
47
+ * written to and settling the previously marked one at `resting`.
48
+ *
49
+ * Remembering matters: re-resolving the trigger from `for` at teardown time
50
+ * is wrong the moment `for` moved in between — the attribute gets stripped
51
+ * from the new anchor, which never had it, and stays on the old one for good.
52
+ */
53
+ markTrigger(attribute: string, value: string, resting: string | null): void;
54
+ /**
55
+ * Undo `markTrigger` on the element that actually received it. Pass a resting
56
+ * value when the trigger keeps its relationship in a closed state
57
+ * (`aria-expanded="false"`), or `null` to remove the attribute outright.
58
+ */
59
+ releaseTrigger(value: string | null): void;
60
+ private _settleMarked;
37
61
  get currentPlacement(): string;
38
62
  updatePosition(options: PositionOptions): Promise<void>;
39
63
  startPositioning(options: PositionOptions): void;
64
+ /**
65
+ * Re-anchor an already-open floating element onto the current trigger.
66
+ *
67
+ * `startPositioning`'s `autoUpdate` observers are bound to whichever element
68
+ * was the trigger when it ran, so they never see a retarget — the loop keeps
69
+ * watching the old anchor while `updatePosition` resolves the new one. Tear
70
+ * it down before recomputing, or the stale cleanup leaks and two loops drive
71
+ * the same floating element.
72
+ *
73
+ * `isCurrent` is re-checked after the await, which is a real gap:
74
+ * `computePosition` is async, so the host may have closed or retargeted again
75
+ * meanwhile. Re-arming `autoUpdate` past that point would strand a loop on a
76
+ * hidden element — the host's close path has already run its own
77
+ * `stopPositioning` and will never run another.
78
+ */
79
+ reposition(options: PositionOptions, isCurrent: () => boolean): Promise<RepositionResult>;
40
80
  stopPositioning(): void;
41
81
  handlePointerLeave(e: PointerEvent, onHide: (reason: 'exit' | 'settled') => void): void;
42
82
  cleanupSafePolygon(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"popover.d.ts","sourceRoot":"","sources":["../../../src/html/shared/controllers/popover.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,KAAK,CAAC;AACtE,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,kBAAkB,CAAC;AA+N1B,MAAM,WAAW,uBAAuB;IACtC,iBAAiB,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IAC5C,kBAAkB,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IAC7C,eAAe,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IAC1C,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,sGAAsG;IACtG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,KAAK,eAAe,GAAG;IACrB,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,CAAC;IAC3C,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,CAAC;IAC3C,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAClC,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC;CACxC,CAAC;AAEF,qBAAa,iBAAkB,YAAW,kBAAkB;IAC1D,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,kBAAkB,CAAC,CAAa;IACxC,OAAO,CAAC,mBAAmB,CAAC,CAAa;IACzC,OAAO,CAAC,SAAS,CAAC,CAAkB;IACpC,OAAO,CAAC,eAAe,CAAC,CAAqB;IAC7C,OAAO,CAAC,iBAAiB,CAAM;gBAEnB,IAAI,EAAE,sBAAsB,GAAG,WAAW,EAAE,MAAM,EAAE,uBAAuB;IAMvF,aAAa;IAEb,gBAAgB;IAMhB,IAAI,gBAAgB,WAEnB;IAIK,cAAc,CAAC,OAAO,EAAE,eAAe;IAuD7C,gBAAgB,CAAC,OAAO,EAAE,eAAe;IASzC,eAAe;IAOf,kBAAkB,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI;IAkFhF,kBAAkB;IAOlB,mBAAmB,CAAC,QAAQ,EAAE,eAAe;IAoB7C,sBAAsB,CAAC,eAAe,CAAC,EAAE,WAAW,GAAG,IAAI;IAyBrD,WAAW,CAAC,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,QAAQ,EAAE;IAYrE,WAAW,CAAC,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,QAAQ,EAAE;CAW5E"}
1
+ {"version":3,"file":"popover.d.ts","sourceRoot":"","sources":["../../../src/html/shared/controllers/popover.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{a as e,i as t,n,o as r,r as i,s as a,t as o}from"../../chunks/floating-ui.dom.js";var s=200;function c([e,t],n){let r=!1;for(let i=0,a=n.length-1;i<n.length;a=i++){let[o,s]=n[i],[c,l]=n[a];s>=t!=l>=t&&e<=(c-o)*(t-s)/(l-s)+o&&(r=!r)}return r}function l([e,t],n){return e>=n.left&&e<=n.right&&t>=n.top&&t<=n.bottom}function u(e,t,n,r,i){let a=i.split(`-`)[0],o=n.width>r.width,s=n.height>r.height,c=e>n.right-n.width/2,l=t>n.bottom-n.height/2;switch(a){case`top`:return[[o?e+2/2:c?e+8:e-8,t+2+1],[o?e-2/2:c?e+8:e-8,t+2+1],[n.left,c||o?n.bottom-2:n.top],[n.right,c?o?n.bottom-2:n.top:n.bottom-2]];case`bottom`:return[[o?e+2/2:c?e+8:e-8,t-2],[o?e-2/2:c?e+8:e-8,t-2],[n.left,c||o?n.top+2:n.bottom],[n.right,c?o?n.top+2:n.bottom:n.top+2]];case`left`:{let r=[e+2+1,s?t+2/2:l?t+8:t-8],i=[e+2+1,s?t-2/2:l?t+8:t-8];return[[l||s?n.right-2:n.left,n.top],[l?s?n.right-2:n.left:n.right-2,n.bottom],r,i]}case`right`:{let r=[e-2,s?t+2/2:l?t+8:t-8],i=[e-2,s?t-2/2:l?t+8:t-8];return[[l||s?n.left+2:n.right,n.top],[l?s?n.left+2:n.right:n.left+2,n.bottom],r,i]}default:return[]}}var d={top:`bottom`,right:`left`,bottom:`top`,left:`right`},f=typeof window<`u`?window.matchMedia(`(prefers-reduced-motion: reduce)`):void 0,p=class{constructor(e,t){this._currentPlacement=``,this._host=e,this._config=t,e.addController(this)}hostConnected(){}hostDisconnected(){this.stopPositioning(),this.cleanupSafePolygon(),this.removeTriggerListeners()}get currentPlacement(){return this._currentPlacement}async updatePosition(n){let s=this._config.getTriggerElement(),c=this._config.getFloatingElement(),l=this._config.getArrowElement();if(!s||!c)return;let u=[e({mainAxis:n.distance,crossAxis:n.skidding??0}),...n.minWidth===`trigger`?[a({apply({rects:e,elements:t}){t.floating.style.minWidth=`${e.reference.width}px`}})]:[],t(),r(),...l?[o({element:l,padding:8})]:[]],{x:f,y:p,placement:m,middlewareData:h}=await i(s,c,{placement:n.placement,strategy:`fixed`,middleware:u});if(Object.assign(c.style,{position:`fixed`,left:n.fullWidth?`0px`:`${f}px`,top:`${p}px`,width:n.fullWidth?`100vw`:``}),n.minWidth!==`trigger`&&(c.style.minWidth=``),m!==this._currentPlacement&&(this._currentPlacement=m,this._config.onPlacementChange?.(m)),h.arrow&&l){let{x:e,y:t}=h.arrow,n=d[m.split(`-`)[0]];Object.assign(l.style,{left:e==null?``:`${e}px`,top:t==null?``:`${t}px`,[n]:`${-l.offsetWidth/2}px`})}}startPositioning(e){let t=this._config.getTriggerElement(),r=this._config.getFloatingElement();!t||!r||(this._cleanupAutoUpdate?.(),this._cleanupAutoUpdate=n(t,r,()=>this.updatePosition(e)))}stopPositioning(){this._cleanupAutoUpdate?.(),this._cleanupAutoUpdate=void 0}handlePointerLeave(e,t){let n=this._config.getFloatingElement(),r=this._config.getTriggerElement();if(!n||!r){t(`exit`);return}let i=e.clientX,a=e.clientY,o,d=e=>{h(),t(e)},f=()=>clearTimeout(o),p=()=>{clearTimeout(o),o=setTimeout(()=>d(`settled`),s)},m=e=>{let{clientX:t,clientY:o}=e,s=[t,o],m=n.getBoundingClientRect(),h=r.getBoundingClientRect(),g=this._currentPlacement.split(`-`)[0];if(l(s,m)){f();return}if(l(s,h)){f();return}if(g===`top`&&a>=h.bottom-1||g===`bottom`&&a<=h.top+1||g===`left`&&i>=h.right-1||g===`right`&&i<=h.left+1){d(`exit`);return}if(c(s,u(i,a,m,h,this._currentPlacement))){p();return}d(`exit`)},h=()=>{clearTimeout(o),document.removeEventListener(`pointermove`,m)};document.addEventListener(`pointermove`,m),p(),this._cleanupSafePolygon?.(),this._cleanupSafePolygon=h}cleanupSafePolygon(){this._cleanupSafePolygon?.(),this._cleanupSafePolygon=void 0}addTriggerListeners(e){let t=this._config.getTriggerElement();t&&(this._handlers=e,this._triggerElement=t,e.onPointerEnter&&t.addEventListener(`pointerenter`,e.onPointerEnter),e.onPointerLeave&&t.addEventListener(`pointerleave`,e.onPointerLeave),e.onFocusIn&&t.addEventListener(`focusin`,e.onFocusIn),e.onFocusOut&&t.addEventListener(`focusout`,e.onFocusOut),e.onClick&&t.addEventListener(`click`,e.onClick),e.onKeyDown&&t.addEventListener(`keydown`,e.onKeyDown))}removeTriggerListeners(e){let t=e??this._triggerElement,n=this._handlers;!t||!n||(n.onPointerEnter&&t.removeEventListener(`pointerenter`,n.onPointerEnter),n.onPointerLeave&&t.removeEventListener(`pointerleave`,n.onPointerLeave),n.onFocusIn&&t.removeEventListener(`focusin`,n.onFocusIn),n.onFocusOut&&t.removeEventListener(`focusout`,n.onFocusOut),n.onClick&&t.removeEventListener(`click`,n.onClick),n.onKeyDown&&t.removeEventListener(`keydown`,n.onKeyDown),e||(this._handlers=void 0,this._triggerElement=void 0))}async animateShow(e,t,n){let r=f?.matches?0:t;e.getAnimations().forEach(e=>e.cancel()),e.animate(n??[{opacity:0,scale:.96},{opacity:1,scale:1}],{duration:r,fill:`forwards`})}async animateHide(e,t,n){let r=f?.matches?0:t;await e.animate(n??[{opacity:1,scale:1},{opacity:0,scale:.96}],{duration:r,fill:`forwards`}).finished}};export{p as PopoverController};
1
+ import{a as e,i as t,n,o as r,r as i,s as a,t as o}from"../../chunks/floating-ui.dom.js";var s=200;function c([e,t],n){let r=!1;for(let i=0,a=n.length-1;i<n.length;a=i++){let[o,s]=n[i],[c,l]=n[a];s>=t!=l>=t&&e<=(c-o)*(t-s)/(l-s)+o&&(r=!r)}return r}function l([e,t],n){return e>=n.left&&e<=n.right&&t>=n.top&&t<=n.bottom}function u(e,t,n,r,i){let a=i.split(`-`)[0],o=n.width>r.width,s=n.height>r.height,c=e>n.right-n.width/2,l=t>n.bottom-n.height/2;switch(a){case`top`:return[[o?e+2/2:c?e+8:e-8,t+2+1],[o?e-2/2:c?e+8:e-8,t+2+1],[n.left,c||o?n.bottom-2:n.top],[n.right,c?o?n.bottom-2:n.top:n.bottom-2]];case`bottom`:return[[o?e+2/2:c?e+8:e-8,t-2],[o?e-2/2:c?e+8:e-8,t-2],[n.left,c||o?n.top+2:n.bottom],[n.right,c?o?n.top+2:n.bottom:n.top+2]];case`left`:{let r=[e+2+1,s?t+2/2:l?t+8:t-8],i=[e+2+1,s?t-2/2:l?t+8:t-8];return[[l||s?n.right-2:n.left,n.top],[l?s?n.right-2:n.left:n.right-2,n.bottom],r,i]}case`right`:{let r=[e-2,s?t+2/2:l?t+8:t-8],i=[e-2,s?t-2/2:l?t+8:t-8];return[[l||s?n.left+2:n.right,n.top],[l?s?n.left+2:n.right:n.left+2,n.bottom],r,i]}default:return[]}}var d={top:`bottom`,right:`left`,bottom:`top`,left:`right`},f=typeof window<`u`?window.matchMedia(`(prefers-reduced-motion: reduce)`):void 0,p=class{constructor(e,t){this._currentPlacement=``,this._markedTrigger=null,this._markedAttribute=null,this._host=e,this._config=t,e.addController(this)}hostConnected(){}hostDisconnected(){this.stopPositioning(),this.cleanupSafePolygon(),this.releaseTrigger(null),this.removeTriggerListeners()}markTrigger(e,t,n){let r=this._config.getTriggerElement();this._markedTrigger===r&&this._markedAttribute===e||(this._settleMarked(n),r?.setAttribute(e,t),this._markedTrigger=r,this._markedAttribute=e)}releaseTrigger(e){this._settleMarked(e),this._markedTrigger=null,this._markedAttribute=null}_settleMarked(e){let t=this._markedTrigger,n=this._markedAttribute;!t||!n||(e===null?t.removeAttribute(n):t.setAttribute(n,e))}get currentPlacement(){return this._currentPlacement}async updatePosition(n){let s=this._config.getTriggerElement(),c=this._config.getFloatingElement(),l=this._config.getArrowElement();if(!s||!c)return;let u=[e({mainAxis:n.distance,crossAxis:n.skidding??0}),...n.minWidth===`trigger`?[a({apply({rects:e,elements:t}){t.floating.style.minWidth=`${e.reference.width}px`}})]:[],t(),r(),...l?[o({element:l,padding:8})]:[]],{x:f,y:p,placement:m,middlewareData:h}=await i(s,c,{placement:n.placement,strategy:`fixed`,middleware:u});if(Object.assign(c.style,{position:`fixed`,left:n.fullWidth?`0px`:`${f}px`,top:`${p}px`,width:n.fullWidth?`100vw`:``}),n.minWidth!==`trigger`&&(c.style.minWidth=``),m!==this._currentPlacement&&(this._currentPlacement=m,this._config.onPlacementChange?.(m)),h.arrow&&l){let{x:e,y:t}=h.arrow,n=d[m.split(`-`)[0]];Object.assign(l.style,{left:e==null?``:`${e}px`,top:t==null?``:`${t}px`,[n]:`${-l.offsetWidth/2}px`})}}startPositioning(e){let t=this._config.getTriggerElement(),r=this._config.getFloatingElement();!t||!r||(this._cleanupAutoUpdate?.(),this._cleanupAutoUpdate=n(t,r,()=>this.updatePosition(e)))}async reposition(e,t){return this._config.getTriggerElement()?(this.stopPositioning(),await this.updatePosition(e),t()?(this.startPositioning(e),`repositioned`):`stale`):`no-anchor`}stopPositioning(){this._cleanupAutoUpdate?.(),this._cleanupAutoUpdate=void 0}handlePointerLeave(e,t){let n=this._config.getFloatingElement(),r=this._config.getTriggerElement();if(!n||!r){t(`exit`);return}let i=e.clientX,a=e.clientY,o,d=e=>{h(),t(e)},f=()=>clearTimeout(o),p=()=>{clearTimeout(o),o=setTimeout(()=>d(`settled`),s)},m=e=>{let{clientX:t,clientY:o}=e,s=[t,o],m=n.getBoundingClientRect(),h=r.getBoundingClientRect(),g=this._currentPlacement.split(`-`)[0];if(l(s,m)){f();return}if(l(s,h)){f();return}if(g===`top`&&a>=h.bottom-1||g===`bottom`&&a<=h.top+1||g===`left`&&i>=h.right-1||g===`right`&&i<=h.left+1){d(`exit`);return}if(c(s,u(i,a,m,h,this._currentPlacement))){p();return}d(`exit`)},h=()=>{clearTimeout(o),document.removeEventListener(`pointermove`,m)};document.addEventListener(`pointermove`,m),p(),this._cleanupSafePolygon?.(),this._cleanupSafePolygon=h}cleanupSafePolygon(){this._cleanupSafePolygon?.(),this._cleanupSafePolygon=void 0}addTriggerListeners(e){let t=this._config.getTriggerElement();t&&(this._handlers=e,this._triggerElement=t,e.onPointerEnter&&t.addEventListener(`pointerenter`,e.onPointerEnter),e.onPointerLeave&&t.addEventListener(`pointerleave`,e.onPointerLeave),e.onFocusIn&&t.addEventListener(`focusin`,e.onFocusIn),e.onFocusOut&&t.addEventListener(`focusout`,e.onFocusOut),e.onClick&&t.addEventListener(`click`,e.onClick),e.onKeyDown&&t.addEventListener(`keydown`,e.onKeyDown))}removeTriggerListeners(e){let t=e??this._triggerElement,n=this._handlers;!t||!n||(n.onPointerEnter&&t.removeEventListener(`pointerenter`,n.onPointerEnter),n.onPointerLeave&&t.removeEventListener(`pointerleave`,n.onPointerLeave),n.onFocusIn&&t.removeEventListener(`focusin`,n.onFocusIn),n.onFocusOut&&t.removeEventListener(`focusout`,n.onFocusOut),n.onClick&&t.removeEventListener(`click`,n.onClick),n.onKeyDown&&t.removeEventListener(`keydown`,n.onKeyDown),e||(this._handlers=void 0,this._triggerElement=void 0))}async animateShow(e,t,n){let r=f?.matches?0:t;e.getAnimations().forEach(e=>e.cancel()),e.animate(n??[{opacity:0,scale:.96},{opacity:1,scale:1}],{duration:r,fill:`forwards`})}async animateHide(e,t,n){let r=f?.matches?0:t;await e.animate(n??[{opacity:1,scale:1},{opacity:0,scale:.96}],{duration:r,fill:`forwards`}).finished}};export{p as PopoverController};
2
2
  //# sourceMappingURL=popover.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"popover.js","names":[],"sources":["../../../src/html/shared/controllers/popover.ts"],"sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from 'lit';\nimport {\n computePosition,\n autoUpdate,\n offset,\n flip,\n shift,\n arrow,\n size,\n type Placement,\n} from '@floating-ui/dom';\n\n// --- Pure geometry helpers ---\n\ntype Point = [number, number];\n\n/**\n * How long the pointer may rest, in the corridor between the trigger and the\n * bubble, before the safe polygon gives up and hides. The polygon is only\n * re-evaluated on `pointermove`, so a pointer that leaves and immediately stops\n * emits no further event to close on — this fallback guarantees the tooltip\n * still hides. Kept short so a genuine pause while travelling toward the bubble\n * re-arms it without a visible flicker. It is *not* armed while the pointer sits\n * on the bubble or the trigger themselves — those are stable \"stay open\" states.\n */\nconst SAFE_POLYGON_REST_HIDE_MS = 200;\n\n/** Check if a point is inside a polygon using ray-casting. */\nfunction isPointInPolygon([px, py]: Point, polygon: Point[]) {\n let inside = false;\n for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\n const [xi, yi] = polygon[i];\n const [xj, yj] = polygon[j];\n if (yi >= py !== yj >= py && px <= ((xj - xi) * (py - yi)) / (yj - yi) + xi) {\n inside = !inside;\n }\n }\n return inside;\n}\n\n/** Check if a point is inside a rect. */\nfunction isPointInRect([px, py]: Point, rect: DOMRect) {\n return px >= rect.left && px <= rect.right && py >= rect.top && py <= rect.bottom;\n}\n\n/**\n * Build a safe polygon (triangle/quadrilateral) from cursor to floating element edges.\n * Adapted from floating-ui/safePolygon.\n * @see https://github.com/floating-ui/floating-ui/blob/master/packages/react/src/safePolygon.ts\n */\nfunction getSafePolygon(\n cursorX: number,\n cursorY: number,\n rect: DOMRect,\n refRect: DOMRect,\n placement: string,\n): Point[] {\n const side = placement.split('-')[0] as 'top' | 'bottom' | 'left' | 'right';\n const buffer = 2;\n\n const isFloatingWider = rect.width > refRect.width;\n const isFloatingTaller = rect.height > refRect.height;\n\n const cursorLeaveFromRight = cursorX > rect.right - rect.width / 2;\n const cursorLeaveFromBottom = cursorY > rect.bottom - rect.height / 2;\n\n switch (side) {\n case 'top': {\n const c1: Point = [\n isFloatingWider\n ? cursorX + buffer / 2\n : cursorLeaveFromRight\n ? cursorX + buffer * 4\n : cursorX - buffer * 4,\n cursorY + buffer + 1,\n ];\n const c2: Point = [\n isFloatingWider\n ? cursorX - buffer / 2\n : cursorLeaveFromRight\n ? cursorX + buffer * 4\n : cursorX - buffer * 4,\n cursorY + buffer + 1,\n ];\n return [\n c1,\n c2,\n [\n rect.left,\n cursorLeaveFromRight\n ? rect.bottom - buffer\n : isFloatingWider\n ? rect.bottom - buffer\n : rect.top,\n ],\n [\n rect.right,\n cursorLeaveFromRight\n ? isFloatingWider\n ? rect.bottom - buffer\n : rect.top\n : rect.bottom - buffer,\n ],\n ];\n }\n case 'bottom': {\n const c1: Point = [\n isFloatingWider\n ? cursorX + buffer / 2\n : cursorLeaveFromRight\n ? cursorX + buffer * 4\n : cursorX - buffer * 4,\n cursorY - buffer,\n ];\n const c2: Point = [\n isFloatingWider\n ? cursorX - buffer / 2\n : cursorLeaveFromRight\n ? cursorX + buffer * 4\n : cursorX - buffer * 4,\n cursorY - buffer,\n ];\n return [\n c1,\n c2,\n [\n rect.left,\n cursorLeaveFromRight\n ? rect.top + buffer\n : isFloatingWider\n ? rect.top + buffer\n : rect.bottom,\n ],\n [\n rect.right,\n cursorLeaveFromRight\n ? isFloatingWider\n ? rect.top + buffer\n : rect.bottom\n : rect.top + buffer,\n ],\n ];\n }\n case 'left': {\n const c1: Point = [\n cursorX + buffer + 1,\n isFloatingTaller\n ? cursorY + buffer / 2\n : cursorLeaveFromBottom\n ? cursorY + buffer * 4\n : cursorY - buffer * 4,\n ];\n const c2: Point = [\n cursorX + buffer + 1,\n isFloatingTaller\n ? cursorY - buffer / 2\n : cursorLeaveFromBottom\n ? cursorY + buffer * 4\n : cursorY - buffer * 4,\n ];\n return [\n [\n cursorLeaveFromBottom\n ? rect.right - buffer\n : isFloatingTaller\n ? rect.right - buffer\n : rect.left,\n rect.top,\n ],\n [\n cursorLeaveFromBottom\n ? isFloatingTaller\n ? rect.right - buffer\n : rect.left\n : rect.right - buffer,\n rect.bottom,\n ],\n c1,\n c2,\n ];\n }\n case 'right': {\n const c1: Point = [\n cursorX - buffer,\n isFloatingTaller\n ? cursorY + buffer / 2\n : cursorLeaveFromBottom\n ? cursorY + buffer * 4\n : cursorY - buffer * 4,\n ];\n const c2: Point = [\n cursorX - buffer,\n isFloatingTaller\n ? cursorY - buffer / 2\n : cursorLeaveFromBottom\n ? cursorY + buffer * 4\n : cursorY - buffer * 4,\n ];\n return [\n [\n cursorLeaveFromBottom\n ? rect.left + buffer\n : isFloatingTaller\n ? rect.left + buffer\n : rect.right,\n rect.top,\n ],\n [\n cursorLeaveFromBottom\n ? isFloatingTaller\n ? rect.left + buffer\n : rect.right\n : rect.left + buffer,\n rect.bottom,\n ],\n c1,\n c2,\n ];\n }\n default:\n return [];\n }\n}\n\n// --- Constants ---\n\nconst STATIC_SIDE = { top: 'bottom', right: 'left', bottom: 'top', left: 'right' } as const;\n\nconst reducedMotion =\n typeof window !== 'undefined' ? window.matchMedia('(prefers-reduced-motion: reduce)') : undefined;\n\n// --- Controller ---\n\nexport interface PopoverControllerConfig {\n getTriggerElement: () => HTMLElement | null;\n getFloatingElement: () => HTMLElement | null;\n getArrowElement: () => HTMLElement | null;\n onPlacementChange?: (placement: string) => void;\n}\n\nexport interface PositionOptions {\n placement: Placement;\n distance: number;\n /** Offset along the cross axis, in pixels (e.g. to align a submenu's first item with its trigger). */\n skidding?: number;\n fullWidth?: boolean;\n /** Floor the floating element's width at the trigger's width (`'trigger'`). */\n minWidth?: 'trigger';\n}\n\ntype TriggerHandlers = {\n onPointerEnter?: (e: PointerEvent) => void;\n onPointerLeave?: (e: PointerEvent) => void;\n onFocusIn?: (e: FocusEvent) => void;\n onFocusOut?: (e: FocusEvent) => void;\n onClick?: (e: MouseEvent) => void;\n onKeyDown?: (e: KeyboardEvent) => void;\n};\n\nexport class PopoverController implements ReactiveController {\n private _host: ReactiveControllerHost & HTMLElement;\n private _config: PopoverControllerConfig;\n private _cleanupAutoUpdate?: () => void;\n private _cleanupSafePolygon?: () => void;\n private _handlers?: TriggerHandlers;\n private _triggerElement?: HTMLElement | null;\n private _currentPlacement = '';\n\n constructor(host: ReactiveControllerHost & HTMLElement, config: PopoverControllerConfig) {\n this._host = host;\n this._config = config;\n host.addController(this);\n }\n\n hostConnected() {}\n\n hostDisconnected() {\n this.stopPositioning();\n this.cleanupSafePolygon();\n this.removeTriggerListeners();\n }\n\n get currentPlacement() {\n return this._currentPlacement;\n }\n\n // --- Positioning ---\n\n async updatePosition(options: PositionOptions) {\n const trigger = this._config.getTriggerElement();\n const floating = this._config.getFloatingElement();\n const arrowEl = this._config.getArrowElement();\n if (!trigger || !floating) return;\n\n const middleware = [\n offset({ mainAxis: options.distance, crossAxis: options.skidding ?? 0 }),\n // Size before flip/shift so the adopted floor feeds collision detection.\n ...(options.minWidth === 'trigger'\n ? [\n size({\n apply({ rects, elements }) {\n elements.floating.style.minWidth = `${rects.reference.width}px`;\n },\n }),\n ]\n : []),\n flip(),\n shift(),\n ...(arrowEl ? [arrow({ element: arrowEl, padding: 8 })] : []),\n ];\n\n const { x, y, placement, middlewareData } = await computePosition(trigger, floating, {\n placement: options.placement,\n strategy: 'fixed',\n middleware,\n });\n\n Object.assign(floating.style, {\n position: 'fixed',\n left: options.fullWidth ? '0px' : `${x}px`,\n top: `${y}px`,\n width: options.fullWidth ? '100vw' : '',\n });\n\n // The `size` middleware (above) sets minWidth when syncing; clear it otherwise.\n if (options.minWidth !== 'trigger') floating.style.minWidth = '';\n\n if (placement !== this._currentPlacement) {\n this._currentPlacement = placement;\n this._config.onPlacementChange?.(placement);\n }\n\n if (middlewareData.arrow && arrowEl) {\n const { x: ax, y: ay } = middlewareData.arrow;\n const side = STATIC_SIDE[placement.split('-')[0] as keyof typeof STATIC_SIDE];\n Object.assign(arrowEl.style, {\n left: ax != null ? `${ax}px` : '',\n top: ay != null ? `${ay}px` : '',\n [side]: `${-arrowEl.offsetWidth / 2}px`,\n });\n }\n }\n\n startPositioning(options: PositionOptions) {\n const trigger = this._config.getTriggerElement();\n const floating = this._config.getFloatingElement();\n if (!trigger || !floating) return;\n\n this._cleanupAutoUpdate?.();\n this._cleanupAutoUpdate = autoUpdate(trigger, floating, () => this.updatePosition(options));\n }\n\n stopPositioning() {\n this._cleanupAutoUpdate?.();\n this._cleanupAutoUpdate = undefined;\n }\n\n // --- Safe polygon ---\n\n handlePointerLeave(e: PointerEvent, onHide: (reason: 'exit' | 'settled') => void) {\n const floating = this._config.getFloatingElement();\n const trigger = this._config.getTriggerElement();\n if (!floating || !trigger) {\n onHide('exit');\n return;\n }\n\n const cursorX = e.clientX;\n const cursorY = e.clientY;\n\n // Armed only while the pointer sits in the corridor between the trigger and\n // the bubble: there we cannot tell \"paused mid-travel\" from \"gone\" without\n // waiting, so if no further `pointermove` re-evaluates the polygon the\n // pointer has settled and we hide. On the bubble or the trigger themselves\n // the answer is unambiguous — stay open — so the timer is cleared, never\n // armed (otherwise a pointer resting on the bubble to read it would hide).\n let fallback: ReturnType<typeof setTimeout> | undefined;\n\n const hide = (reason: 'exit' | 'settled') => {\n cleanup();\n onHide(reason);\n };\n\n // Stable \"stay open\" surface (bubble or trigger): cancel any pending rest-hide.\n const stayOpen = () => clearTimeout(fallback);\n\n // Transient corridor: keep open, but hide if the pointer comes to rest here.\n const armRestHide = () => {\n clearTimeout(fallback);\n fallback = setTimeout(() => hide('settled'), SAFE_POLYGON_REST_HIDE_MS);\n };\n\n const onMove = (moveEvent: PointerEvent) => {\n const { clientX: mx, clientY: my } = moveEvent;\n const cursor: Point = [mx, my];\n const rect = floating.getBoundingClientRect();\n const refRect = trigger.getBoundingClientRect();\n const side = this._currentPlacement.split('-')[0];\n\n if (isPointInRect(cursor, rect)) {\n stayOpen();\n return;\n }\n if (isPointInRect(cursor, refRect)) {\n stayOpen();\n return;\n }\n\n if (\n (side === 'top' && cursorY >= refRect.bottom - 1) ||\n (side === 'bottom' && cursorY <= refRect.top + 1) ||\n (side === 'left' && cursorX >= refRect.right - 1) ||\n (side === 'right' && cursorX <= refRect.left + 1)\n ) {\n hide('exit');\n return;\n }\n\n const polygon = getSafePolygon(cursorX, cursorY, rect, refRect, this._currentPlacement);\n if (isPointInPolygon(cursor, polygon)) {\n armRestHide();\n return;\n }\n\n hide('exit');\n };\n\n const cleanup = () => {\n clearTimeout(fallback);\n document.removeEventListener('pointermove', onMove);\n };\n\n document.addEventListener('pointermove', onMove);\n // Arm immediately: a leave with no subsequent move (flick-and-stop) lands in\n // the corridor and must still hide once the pointer has settled.\n armRestHide();\n\n this._cleanupSafePolygon?.();\n this._cleanupSafePolygon = cleanup;\n }\n\n cleanupSafePolygon() {\n this._cleanupSafePolygon?.();\n this._cleanupSafePolygon = undefined;\n }\n\n // --- Trigger listener management ---\n\n addTriggerListeners(handlers: TriggerHandlers) {\n const trigger = this._config.getTriggerElement();\n if (!trigger) return;\n\n this._handlers = handlers;\n this._triggerElement = trigger;\n\n if (handlers.onPointerEnter)\n trigger.addEventListener('pointerenter', handlers.onPointerEnter as EventListener);\n if (handlers.onPointerLeave)\n trigger.addEventListener('pointerleave', handlers.onPointerLeave as EventListener);\n if (handlers.onFocusIn)\n trigger.addEventListener('focusin', handlers.onFocusIn as EventListener);\n if (handlers.onFocusOut)\n trigger.addEventListener('focusout', handlers.onFocusOut as EventListener);\n if (handlers.onClick) trigger.addEventListener('click', handlers.onClick as EventListener);\n if (handlers.onKeyDown)\n trigger.addEventListener('keydown', handlers.onKeyDown as EventListener);\n }\n\n removeTriggerListeners(triggerOverride?: HTMLElement | null) {\n const trigger = triggerOverride ?? this._triggerElement;\n const handlers = this._handlers;\n if (!trigger || !handlers) return;\n\n if (handlers.onPointerEnter)\n trigger.removeEventListener('pointerenter', handlers.onPointerEnter as EventListener);\n if (handlers.onPointerLeave)\n trigger.removeEventListener('pointerleave', handlers.onPointerLeave as EventListener);\n if (handlers.onFocusIn)\n trigger.removeEventListener('focusin', handlers.onFocusIn as EventListener);\n if (handlers.onFocusOut)\n trigger.removeEventListener('focusout', handlers.onFocusOut as EventListener);\n if (handlers.onClick) trigger.removeEventListener('click', handlers.onClick as EventListener);\n if (handlers.onKeyDown)\n trigger.removeEventListener('keydown', handlers.onKeyDown as EventListener);\n\n if (!triggerOverride) {\n this._handlers = undefined;\n this._triggerElement = undefined;\n }\n }\n\n // --- Animation ---\n\n async animateShow(el: HTMLElement, duration: number, keyframes?: Keyframe[]) {\n const d = reducedMotion?.matches ? 0 : duration;\n el.getAnimations().forEach((a) => a.cancel());\n el.animate(\n keyframes ?? [\n { opacity: 0, scale: 0.96 },\n { opacity: 1, scale: 1 },\n ],\n { duration: d, fill: 'forwards' },\n );\n }\n\n async animateHide(el: HTMLElement, duration: number, keyframes?: Keyframe[]) {\n const d = reducedMotion?.matches ? 0 : duration;\n const anim = el.animate(\n keyframes ?? [\n { opacity: 1, scale: 1 },\n { opacity: 0, scale: 0.96 },\n ],\n { duration: d, fill: 'forwards' },\n );\n await anim.finished;\n }\n}\n"],"mappings":"yFAyBA,IAAM,EAA4B,IAGlC,SAAS,EAAiB,CAAC,EAAI,GAAY,EAAkB,CAC3D,IAAI,EAAS,GACb,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,OAAS,EAAG,EAAI,EAAQ,OAAQ,EAAI,IAAK,CACnE,GAAM,CAAC,EAAI,GAAM,EAAQ,GACnB,CAAC,EAAI,GAAM,EAAQ,GACrB,GAAM,GAAO,GAAM,GAAM,IAAQ,EAAK,IAAO,EAAK,IAAQ,EAAK,GAAM,IACvE,EAAS,CAAC,EAEd,CACA,OAAO,CACT,CAGA,SAAS,EAAc,CAAC,EAAI,GAAY,EAAe,CACrD,OAAO,GAAM,EAAK,MAAQ,GAAM,EAAK,OAAS,GAAM,EAAK,KAAO,GAAM,EAAK,MAC7E,CAOA,SAAS,EACP,EACA,EACA,EACA,EACA,EACS,CACT,IAAM,EAAO,EAAU,MAAM,GAAG,CAAC,CAAC,GAG5B,EAAkB,EAAK,MAAQ,EAAQ,MACvC,EAAmB,EAAK,OAAS,EAAQ,OAEzC,EAAuB,EAAU,EAAK,MAAQ,EAAK,MAAQ,EAC3D,EAAwB,EAAU,EAAK,OAAS,EAAK,OAAS,EAEpE,OAAQ,EAAR,CACE,IAAK,MAiBH,MAAO,CACL,CAhBA,EACI,EAAU,EAAS,EACnB,EACE,EAAU,EACV,EAAU,EAChB,EAAU,EAAS,CAWnB,EACA,CATA,EACI,EAAU,EAAS,EACnB,EACE,EAAU,EACV,EAAU,EAChB,EAAU,EAAS,CAInB,EACA,CACE,EAAK,KACL,GAEI,EADA,EAAK,OAAS,EAGZ,EAAK,GACb,EACA,CACE,EAAK,MACL,EACI,EACE,EAAK,OAAS,EACd,EAAK,IACP,EAAK,OAAS,CACpB,CACF,EAEF,IAAK,SAiBH,MAAO,CACL,CAhBA,EACI,EAAU,EAAS,EACnB,EACE,EAAU,EACV,EAAU,EAChB,EAAU,CAWV,EACA,CATA,EACI,EAAU,EAAS,EACnB,EACE,EAAU,EACV,EAAU,EAChB,EAAU,CAIV,EACA,CACE,EAAK,KACL,GAEI,EADA,EAAK,IAAM,EAGT,EAAK,MACb,EACA,CACE,EAAK,MACL,EACI,EACE,EAAK,IAAM,EACX,EAAK,OACP,EAAK,IAAM,CACjB,CACF,EAEF,IAAK,OAAQ,CACX,IAAM,EAAY,CAChB,EAAU,EAAS,EACnB,EACI,EAAU,EAAS,EACnB,EACE,EAAU,EACV,EAAU,CAClB,EACM,EAAY,CAChB,EAAU,EAAS,EACnB,EACI,EAAU,EAAS,EACnB,EACE,EAAU,EACV,EAAU,CAClB,EACA,MAAO,CACL,CACE,GAEI,EADA,EAAK,MAAQ,EAGX,EAAK,KACX,EAAK,GACP,EACA,CACE,EACI,EACE,EAAK,MAAQ,EACb,EAAK,KACP,EAAK,MAAQ,EACjB,EAAK,MACP,EACA,EACA,CACF,CACF,CACA,IAAK,QAAS,CACZ,IAAM,EAAY,CAChB,EAAU,EACV,EACI,EAAU,EAAS,EACnB,EACE,EAAU,EACV,EAAU,CAClB,EACM,EAAY,CAChB,EAAU,EACV,EACI,EAAU,EAAS,EACnB,EACE,EAAU,EACV,EAAU,CAClB,EACA,MAAO,CACL,CACE,GAEI,EADA,EAAK,KAAO,EAGV,EAAK,MACX,EAAK,GACP,EACA,CACE,EACI,EACE,EAAK,KAAO,EACZ,EAAK,MACP,EAAK,KAAO,EAChB,EAAK,MACP,EACA,EACA,CACF,CACF,CACA,QACE,MAAO,CAAC,CACZ,CACF,CAIA,IAAM,EAAc,CAAE,IAAK,SAAU,MAAO,OAAQ,OAAQ,MAAO,KAAM,OAAQ,EAE3E,EACJ,OAAO,OAAW,IAAc,OAAO,WAAW,kCAAkC,EAAI,IAAA,GA8B7E,EAAb,KAA6D,CAS3D,YAAY,EAA4C,EAAiC,wBAF7D,GAG1B,KAAK,MAAQ,EACb,KAAK,QAAU,EACf,EAAK,cAAc,IAAI,CACzB,CAEA,eAAgB,CAAC,CAEjB,kBAAmB,CACjB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,CAC9B,CAEA,IAAI,kBAAmB,CACrB,OAAO,KAAK,iBACd,CAIA,MAAM,eAAe,EAA0B,CAC7C,IAAM,EAAU,KAAK,QAAQ,kBAAkB,EACzC,EAAW,KAAK,QAAQ,mBAAmB,EAC3C,EAAU,KAAK,QAAQ,gBAAgB,EAC7C,GAAI,CAAC,GAAW,CAAC,EAAU,OAE3B,IAAM,EAAa,CACjB,EAAO,CAAE,SAAU,EAAQ,SAAU,UAAW,EAAQ,UAAY,CAAE,CAAC,EAEvE,GAAI,EAAQ,WAAa,UACrB,CACE,EAAK,CACH,MAAM,CAAE,QAAO,YAAY,CACzB,EAAS,SAAS,MAAM,SAAW,GAAG,EAAM,UAAU,MAAM,GAC9D,CACF,CAAC,CACH,EACA,CAAC,EACL,EAAK,EACL,EAAM,EACN,GAAI,EAAU,CAAC,EAAM,CAAE,QAAS,EAAS,QAAS,CAAE,CAAC,CAAC,EAAI,CAAC,CAC7D,EAEM,CAAE,IAAG,IAAG,YAAW,kBAAmB,MAAM,EAAgB,EAAS,EAAU,CACnF,UAAW,EAAQ,UACnB,SAAU,QACV,YACF,CAAC,EAiBD,GAfA,OAAO,OAAO,EAAS,MAAO,CAC5B,SAAU,QACV,KAAM,EAAQ,UAAY,MAAQ,GAAG,EAAE,IACvC,IAAK,GAAG,EAAE,IACV,MAAO,EAAQ,UAAY,QAAU,EACvC,CAAC,EAGG,EAAQ,WAAa,YAAW,EAAS,MAAM,SAAW,IAE1D,IAAc,KAAK,oBACrB,KAAK,kBAAoB,EACzB,KAAK,QAAQ,oBAAoB,CAAS,GAGxC,EAAe,OAAS,EAAS,CACnC,GAAM,CAAE,EAAG,EAAI,EAAG,GAAO,EAAe,MAClC,EAAO,EAAY,EAAU,MAAM,GAAG,CAAC,CAAC,IAC9C,OAAO,OAAO,EAAQ,MAAO,CAC3B,KAAM,GAAM,KAAmB,GAAZ,GAAG,EAAG,IACzB,IAAK,GAAM,KAAmB,GAAZ,GAAG,EAAG,KACvB,GAAO,GAAG,CAAC,EAAQ,YAAc,EAAE,GACtC,CAAC,CACH,CACF,CAEA,iBAAiB,EAA0B,CACzC,IAAM,EAAU,KAAK,QAAQ,kBAAkB,EACzC,EAAW,KAAK,QAAQ,mBAAmB,EAC7C,CAAC,GAAW,CAAC,IAEjB,KAAK,qBAAqB,EAC1B,KAAK,mBAAqB,EAAW,EAAS,MAAgB,KAAK,eAAe,CAAO,CAAC,EAC5F,CAEA,iBAAkB,CAChB,KAAK,qBAAqB,EAC1B,KAAK,mBAAqB,IAAA,EAC5B,CAIA,mBAAmB,EAAiB,EAA8C,CAChF,IAAM,EAAW,KAAK,QAAQ,mBAAmB,EAC3C,EAAU,KAAK,QAAQ,kBAAkB,EAC/C,GAAI,CAAC,GAAY,CAAC,EAAS,CACzB,EAAO,MAAM,EACb,MACF,CAEA,IAAM,EAAU,EAAE,QACZ,EAAU,EAAE,QAQd,EAEE,EAAQ,GAA+B,CAC3C,EAAQ,EACR,EAAO,CAAM,CACf,EAGM,MAAiB,aAAa,CAAQ,EAGtC,MAAoB,CACxB,aAAa,CAAQ,EACrB,EAAW,eAAiB,EAAK,SAAS,EAAG,CAAyB,CACxE,EAEM,EAAU,GAA4B,CAC1C,GAAM,CAAE,QAAS,EAAI,QAAS,GAAO,EAC/B,EAAgB,CAAC,EAAI,CAAE,EACvB,EAAO,EAAS,sBAAsB,EACtC,EAAU,EAAQ,sBAAsB,EACxC,EAAO,KAAK,kBAAkB,MAAM,GAAG,CAAC,CAAC,GAE/C,GAAI,EAAc,EAAQ,CAAI,EAAG,CAC/B,EAAS,EACT,MACF,CACA,GAAI,EAAc,EAAQ,CAAO,EAAG,CAClC,EAAS,EACT,MACF,CAEA,GACG,IAAS,OAAS,GAAW,EAAQ,OAAS,GAC9C,IAAS,UAAY,GAAW,EAAQ,IAAM,GAC9C,IAAS,QAAU,GAAW,EAAQ,MAAQ,GAC9C,IAAS,SAAW,GAAW,EAAQ,KAAO,EAC/C,CACA,EAAK,MAAM,EACX,MACF,CAGA,GAAI,EAAiB,EADL,EAAe,EAAS,EAAS,EAAM,EAAS,KAAK,iBACxC,CAAO,EAAG,CACrC,EAAY,EACZ,MACF,CAEA,EAAK,MAAM,CACb,EAEM,MAAgB,CACpB,aAAa,CAAQ,EACrB,SAAS,oBAAoB,cAAe,CAAM,CACpD,EAEA,SAAS,iBAAiB,cAAe,CAAM,EAG/C,EAAY,EAEZ,KAAK,sBAAsB,EAC3B,KAAK,oBAAsB,CAC7B,CAEA,oBAAqB,CACnB,KAAK,sBAAsB,EAC3B,KAAK,oBAAsB,IAAA,EAC7B,CAIA,oBAAoB,EAA2B,CAC7C,IAAM,EAAU,KAAK,QAAQ,kBAAkB,EAC1C,IAEL,KAAK,UAAY,EACjB,KAAK,gBAAkB,EAEnB,EAAS,gBACX,EAAQ,iBAAiB,eAAgB,EAAS,cAA+B,EAC/E,EAAS,gBACX,EAAQ,iBAAiB,eAAgB,EAAS,cAA+B,EAC/E,EAAS,WACX,EAAQ,iBAAiB,UAAW,EAAS,SAA0B,EACrE,EAAS,YACX,EAAQ,iBAAiB,WAAY,EAAS,UAA2B,EACvE,EAAS,SAAS,EAAQ,iBAAiB,QAAS,EAAS,OAAwB,EACrF,EAAS,WACX,EAAQ,iBAAiB,UAAW,EAAS,SAA0B,EAC3E,CAEA,uBAAuB,EAAsC,CAC3D,IAAM,EAAU,GAAmB,KAAK,gBAClC,EAAW,KAAK,UAClB,CAAC,GAAW,CAAC,IAEb,EAAS,gBACX,EAAQ,oBAAoB,eAAgB,EAAS,cAA+B,EAClF,EAAS,gBACX,EAAQ,oBAAoB,eAAgB,EAAS,cAA+B,EAClF,EAAS,WACX,EAAQ,oBAAoB,UAAW,EAAS,SAA0B,EACxE,EAAS,YACX,EAAQ,oBAAoB,WAAY,EAAS,UAA2B,EAC1E,EAAS,SAAS,EAAQ,oBAAoB,QAAS,EAAS,OAAwB,EACxF,EAAS,WACX,EAAQ,oBAAoB,UAAW,EAAS,SAA0B,EAEvE,IACH,KAAK,UAAY,IAAA,GACjB,KAAK,gBAAkB,IAAA,IAE3B,CAIA,MAAM,YAAY,EAAiB,EAAkB,EAAwB,CAC3E,IAAM,EAAI,GAAe,QAAU,EAAI,EACvC,EAAG,cAAc,CAAC,CAAC,QAAS,GAAM,EAAE,OAAO,CAAC,EAC5C,EAAG,QACD,GAAa,CACX,CAAE,QAAS,EAAG,MAAO,GAAK,EAC1B,CAAE,QAAS,EAAG,MAAO,CAAE,CACzB,EACA,CAAE,SAAU,EAAG,KAAM,UAAW,CAClC,CACF,CAEA,MAAM,YAAY,EAAiB,EAAkB,EAAwB,CAC3E,IAAM,EAAI,GAAe,QAAU,EAAI,EAQvC,MAPa,EAAG,QACd,GAAa,CACX,CAAE,QAAS,EAAG,MAAO,CAAE,EACvB,CAAE,QAAS,EAAG,MAAO,GAAK,CAC5B,EACA,CAAE,SAAU,EAAG,KAAM,UAAW,CAE5B,CAAA,CAAK,QACb,CACF"}
1
+ {"version":3,"file":"popover.js","names":[],"sources":["../../../src/html/shared/controllers/popover.ts"],"sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from 'lit';\nimport {\n computePosition,\n autoUpdate,\n offset,\n flip,\n shift,\n arrow,\n size,\n type Placement,\n} from '@floating-ui/dom';\n\n// --- Pure geometry helpers ---\n\ntype Point = [number, number];\n\n/**\n * How long the pointer may rest, in the corridor between the trigger and the\n * bubble, before the safe polygon gives up and hides. The polygon is only\n * re-evaluated on `pointermove`, so a pointer that leaves and immediately stops\n * emits no further event to close on — this fallback guarantees the tooltip\n * still hides. Kept short so a genuine pause while travelling toward the bubble\n * re-arms it without a visible flicker. It is *not* armed while the pointer sits\n * on the bubble or the trigger themselves — those are stable \"stay open\" states.\n */\nconst SAFE_POLYGON_REST_HIDE_MS = 200;\n\n/** Check if a point is inside a polygon using ray-casting. */\nfunction isPointInPolygon([px, py]: Point, polygon: Point[]) {\n let inside = false;\n for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\n const [xi, yi] = polygon[i];\n const [xj, yj] = polygon[j];\n if (yi >= py !== yj >= py && px <= ((xj - xi) * (py - yi)) / (yj - yi) + xi) {\n inside = !inside;\n }\n }\n return inside;\n}\n\n/** Check if a point is inside a rect. */\nfunction isPointInRect([px, py]: Point, rect: DOMRect) {\n return px >= rect.left && px <= rect.right && py >= rect.top && py <= rect.bottom;\n}\n\n/**\n * Build a safe polygon (triangle/quadrilateral) from cursor to floating element edges.\n * Adapted from floating-ui/safePolygon.\n * @see https://github.com/floating-ui/floating-ui/blob/master/packages/react/src/safePolygon.ts\n */\nfunction getSafePolygon(\n cursorX: number,\n cursorY: number,\n rect: DOMRect,\n refRect: DOMRect,\n placement: string,\n): Point[] {\n const side = placement.split('-')[0] as 'top' | 'bottom' | 'left' | 'right';\n const buffer = 2;\n\n const isFloatingWider = rect.width > refRect.width;\n const isFloatingTaller = rect.height > refRect.height;\n\n const cursorLeaveFromRight = cursorX > rect.right - rect.width / 2;\n const cursorLeaveFromBottom = cursorY > rect.bottom - rect.height / 2;\n\n switch (side) {\n case 'top': {\n const c1: Point = [\n isFloatingWider\n ? cursorX + buffer / 2\n : cursorLeaveFromRight\n ? cursorX + buffer * 4\n : cursorX - buffer * 4,\n cursorY + buffer + 1,\n ];\n const c2: Point = [\n isFloatingWider\n ? cursorX - buffer / 2\n : cursorLeaveFromRight\n ? cursorX + buffer * 4\n : cursorX - buffer * 4,\n cursorY + buffer + 1,\n ];\n return [\n c1,\n c2,\n [\n rect.left,\n cursorLeaveFromRight\n ? rect.bottom - buffer\n : isFloatingWider\n ? rect.bottom - buffer\n : rect.top,\n ],\n [\n rect.right,\n cursorLeaveFromRight\n ? isFloatingWider\n ? rect.bottom - buffer\n : rect.top\n : rect.bottom - buffer,\n ],\n ];\n }\n case 'bottom': {\n const c1: Point = [\n isFloatingWider\n ? cursorX + buffer / 2\n : cursorLeaveFromRight\n ? cursorX + buffer * 4\n : cursorX - buffer * 4,\n cursorY - buffer,\n ];\n const c2: Point = [\n isFloatingWider\n ? cursorX - buffer / 2\n : cursorLeaveFromRight\n ? cursorX + buffer * 4\n : cursorX - buffer * 4,\n cursorY - buffer,\n ];\n return [\n c1,\n c2,\n [\n rect.left,\n cursorLeaveFromRight\n ? rect.top + buffer\n : isFloatingWider\n ? rect.top + buffer\n : rect.bottom,\n ],\n [\n rect.right,\n cursorLeaveFromRight\n ? isFloatingWider\n ? rect.top + buffer\n : rect.bottom\n : rect.top + buffer,\n ],\n ];\n }\n case 'left': {\n const c1: Point = [\n cursorX + buffer + 1,\n isFloatingTaller\n ? cursorY + buffer / 2\n : cursorLeaveFromBottom\n ? cursorY + buffer * 4\n : cursorY - buffer * 4,\n ];\n const c2: Point = [\n cursorX + buffer + 1,\n isFloatingTaller\n ? cursorY - buffer / 2\n : cursorLeaveFromBottom\n ? cursorY + buffer * 4\n : cursorY - buffer * 4,\n ];\n return [\n [\n cursorLeaveFromBottom\n ? rect.right - buffer\n : isFloatingTaller\n ? rect.right - buffer\n : rect.left,\n rect.top,\n ],\n [\n cursorLeaveFromBottom\n ? isFloatingTaller\n ? rect.right - buffer\n : rect.left\n : rect.right - buffer,\n rect.bottom,\n ],\n c1,\n c2,\n ];\n }\n case 'right': {\n const c1: Point = [\n cursorX - buffer,\n isFloatingTaller\n ? cursorY + buffer / 2\n : cursorLeaveFromBottom\n ? cursorY + buffer * 4\n : cursorY - buffer * 4,\n ];\n const c2: Point = [\n cursorX - buffer,\n isFloatingTaller\n ? cursorY - buffer / 2\n : cursorLeaveFromBottom\n ? cursorY + buffer * 4\n : cursorY - buffer * 4,\n ];\n return [\n [\n cursorLeaveFromBottom\n ? rect.left + buffer\n : isFloatingTaller\n ? rect.left + buffer\n : rect.right,\n rect.top,\n ],\n [\n cursorLeaveFromBottom\n ? isFloatingTaller\n ? rect.left + buffer\n : rect.right\n : rect.left + buffer,\n rect.bottom,\n ],\n c1,\n c2,\n ];\n }\n default:\n return [];\n }\n}\n\n// --- Constants ---\n\nconst STATIC_SIDE = { top: 'bottom', right: 'left', bottom: 'top', left: 'right' } as const;\n\nconst reducedMotion =\n typeof window !== 'undefined' ? window.matchMedia('(prefers-reduced-motion: reduce)') : undefined;\n\n// --- Controller ---\n\nexport interface PopoverControllerConfig {\n getTriggerElement: () => HTMLElement | null;\n getFloatingElement: () => HTMLElement | null;\n getArrowElement: () => HTMLElement | null;\n onPlacementChange?: (placement: string) => void;\n}\n\n/**\n * Outcome of {@link PopoverController.reposition}. `stale` means the host\n * closed or retargeted again while `computePosition` was in flight — the caller\n * must not act on it, because whatever superseded it owns the element now.\n */\nexport type RepositionResult = 'repositioned' | 'no-anchor' | 'stale';\n\nexport interface PositionOptions {\n placement: Placement;\n distance: number;\n /** Offset along the cross axis, in pixels (e.g. to align a submenu's first item with its trigger). */\n skidding?: number;\n fullWidth?: boolean;\n /** Floor the floating element's width at the trigger's width (`'trigger'`). */\n minWidth?: 'trigger';\n}\n\ntype TriggerHandlers = {\n onPointerEnter?: (e: PointerEvent) => void;\n onPointerLeave?: (e: PointerEvent) => void;\n onFocusIn?: (e: FocusEvent) => void;\n onFocusOut?: (e: FocusEvent) => void;\n onClick?: (e: MouseEvent) => void;\n onKeyDown?: (e: KeyboardEvent) => void;\n};\n\nexport class PopoverController implements ReactiveController {\n private _host: ReactiveControllerHost & HTMLElement;\n private _config: PopoverControllerConfig;\n private _cleanupAutoUpdate?: () => void;\n private _cleanupSafePolygon?: () => void;\n private _handlers?: TriggerHandlers;\n private _triggerElement?: HTMLElement | null;\n private _currentPlacement = '';\n\n constructor(host: ReactiveControllerHost & HTMLElement, config: PopoverControllerConfig) {\n this._host = host;\n this._config = config;\n host.addController(this);\n }\n\n hostConnected() {}\n\n hostDisconnected() {\n this.stopPositioning();\n this.cleanupSafePolygon();\n // The floating element is leaving the tree, so the state we wrote on the\n // trigger describes nothing any more — drop the attribute rather than\n // leave a resting value claiming a relationship that no longer exists.\n this.releaseTrigger(null);\n this.removeTriggerListeners();\n }\n\n // --- Trigger ARIA state ---\n\n private _markedTrigger: HTMLElement | null = null;\n private _markedAttribute: string | null = null;\n\n /**\n * Write an ARIA attribute on the current trigger, remembering the element\n * written to and settling the previously marked one at `resting`.\n *\n * Remembering matters: re-resolving the trigger from `for` at teardown time\n * is wrong the moment `for` moved in between — the attribute gets stripped\n * from the new anchor, which never had it, and stays on the old one for good.\n */\n markTrigger(attribute: string, value: string, resting: string | null) {\n const next = this._config.getTriggerElement();\n if (this._markedTrigger === next && this._markedAttribute === attribute) return;\n this._settleMarked(resting);\n next?.setAttribute(attribute, value);\n this._markedTrigger = next;\n this._markedAttribute = attribute;\n }\n\n /**\n * Undo `markTrigger` on the element that actually received it. Pass a resting\n * value when the trigger keeps its relationship in a closed state\n * (`aria-expanded=\"false\"`), or `null` to remove the attribute outright.\n */\n releaseTrigger(value: string | null) {\n this._settleMarked(value);\n this._markedTrigger = null;\n this._markedAttribute = null;\n }\n\n private _settleMarked(value: string | null) {\n const el = this._markedTrigger;\n const attribute = this._markedAttribute;\n if (!el || !attribute) return;\n if (value === null) el.removeAttribute(attribute);\n else el.setAttribute(attribute, value);\n }\n\n get currentPlacement() {\n return this._currentPlacement;\n }\n\n // --- Positioning ---\n\n async updatePosition(options: PositionOptions) {\n const trigger = this._config.getTriggerElement();\n const floating = this._config.getFloatingElement();\n const arrowEl = this._config.getArrowElement();\n if (!trigger || !floating) return;\n\n const middleware = [\n offset({ mainAxis: options.distance, crossAxis: options.skidding ?? 0 }),\n // Size before flip/shift so the adopted floor feeds collision detection.\n ...(options.minWidth === 'trigger'\n ? [\n size({\n apply({ rects, elements }) {\n elements.floating.style.minWidth = `${rects.reference.width}px`;\n },\n }),\n ]\n : []),\n flip(),\n shift(),\n ...(arrowEl ? [arrow({ element: arrowEl, padding: 8 })] : []),\n ];\n\n const { x, y, placement, middlewareData } = await computePosition(trigger, floating, {\n placement: options.placement,\n strategy: 'fixed',\n middleware,\n });\n\n Object.assign(floating.style, {\n position: 'fixed',\n left: options.fullWidth ? '0px' : `${x}px`,\n top: `${y}px`,\n width: options.fullWidth ? '100vw' : '',\n });\n\n // The `size` middleware (above) sets minWidth when syncing; clear it otherwise.\n if (options.minWidth !== 'trigger') floating.style.minWidth = '';\n\n if (placement !== this._currentPlacement) {\n this._currentPlacement = placement;\n this._config.onPlacementChange?.(placement);\n }\n\n if (middlewareData.arrow && arrowEl) {\n const { x: ax, y: ay } = middlewareData.arrow;\n const side = STATIC_SIDE[placement.split('-')[0] as keyof typeof STATIC_SIDE];\n Object.assign(arrowEl.style, {\n left: ax != null ? `${ax}px` : '',\n top: ay != null ? `${ay}px` : '',\n [side]: `${-arrowEl.offsetWidth / 2}px`,\n });\n }\n }\n\n startPositioning(options: PositionOptions) {\n const trigger = this._config.getTriggerElement();\n const floating = this._config.getFloatingElement();\n if (!trigger || !floating) return;\n\n this._cleanupAutoUpdate?.();\n this._cleanupAutoUpdate = autoUpdate(trigger, floating, () => this.updatePosition(options));\n }\n\n /**\n * Re-anchor an already-open floating element onto the current trigger.\n *\n * `startPositioning`'s `autoUpdate` observers are bound to whichever element\n * was the trigger when it ran, so they never see a retarget — the loop keeps\n * watching the old anchor while `updatePosition` resolves the new one. Tear\n * it down before recomputing, or the stale cleanup leaks and two loops drive\n * the same floating element.\n *\n * `isCurrent` is re-checked after the await, which is a real gap:\n * `computePosition` is async, so the host may have closed or retargeted again\n * meanwhile. Re-arming `autoUpdate` past that point would strand a loop on a\n * hidden element — the host's close path has already run its own\n * `stopPositioning` and will never run another.\n */\n async reposition(options: PositionOptions, isCurrent: () => boolean): Promise<RepositionResult> {\n if (!this._config.getTriggerElement()) return 'no-anchor';\n this.stopPositioning();\n await this.updatePosition(options);\n if (!isCurrent()) return 'stale';\n this.startPositioning(options);\n return 'repositioned';\n }\n\n stopPositioning() {\n this._cleanupAutoUpdate?.();\n this._cleanupAutoUpdate = undefined;\n }\n\n // --- Safe polygon ---\n\n handlePointerLeave(e: PointerEvent, onHide: (reason: 'exit' | 'settled') => void) {\n const floating = this._config.getFloatingElement();\n const trigger = this._config.getTriggerElement();\n if (!floating || !trigger) {\n onHide('exit');\n return;\n }\n\n const cursorX = e.clientX;\n const cursorY = e.clientY;\n\n // Armed only while the pointer sits in the corridor between the trigger and\n // the bubble: there we cannot tell \"paused mid-travel\" from \"gone\" without\n // waiting, so if no further `pointermove` re-evaluates the polygon the\n // pointer has settled and we hide. On the bubble or the trigger themselves\n // the answer is unambiguous — stay open — so the timer is cleared, never\n // armed (otherwise a pointer resting on the bubble to read it would hide).\n let fallback: ReturnType<typeof setTimeout> | undefined;\n\n const hide = (reason: 'exit' | 'settled') => {\n cleanup();\n onHide(reason);\n };\n\n // Stable \"stay open\" surface (bubble or trigger): cancel any pending rest-hide.\n const stayOpen = () => clearTimeout(fallback);\n\n // Transient corridor: keep open, but hide if the pointer comes to rest here.\n const armRestHide = () => {\n clearTimeout(fallback);\n fallback = setTimeout(() => hide('settled'), SAFE_POLYGON_REST_HIDE_MS);\n };\n\n const onMove = (moveEvent: PointerEvent) => {\n const { clientX: mx, clientY: my } = moveEvent;\n const cursor: Point = [mx, my];\n const rect = floating.getBoundingClientRect();\n const refRect = trigger.getBoundingClientRect();\n const side = this._currentPlacement.split('-')[0];\n\n if (isPointInRect(cursor, rect)) {\n stayOpen();\n return;\n }\n if (isPointInRect(cursor, refRect)) {\n stayOpen();\n return;\n }\n\n if (\n (side === 'top' && cursorY >= refRect.bottom - 1) ||\n (side === 'bottom' && cursorY <= refRect.top + 1) ||\n (side === 'left' && cursorX >= refRect.right - 1) ||\n (side === 'right' && cursorX <= refRect.left + 1)\n ) {\n hide('exit');\n return;\n }\n\n const polygon = getSafePolygon(cursorX, cursorY, rect, refRect, this._currentPlacement);\n if (isPointInPolygon(cursor, polygon)) {\n armRestHide();\n return;\n }\n\n hide('exit');\n };\n\n const cleanup = () => {\n clearTimeout(fallback);\n document.removeEventListener('pointermove', onMove);\n };\n\n document.addEventListener('pointermove', onMove);\n // Arm immediately: a leave with no subsequent move (flick-and-stop) lands in\n // the corridor and must still hide once the pointer has settled.\n armRestHide();\n\n this._cleanupSafePolygon?.();\n this._cleanupSafePolygon = cleanup;\n }\n\n cleanupSafePolygon() {\n this._cleanupSafePolygon?.();\n this._cleanupSafePolygon = undefined;\n }\n\n // --- Trigger listener management ---\n\n addTriggerListeners(handlers: TriggerHandlers) {\n const trigger = this._config.getTriggerElement();\n if (!trigger) return;\n\n this._handlers = handlers;\n this._triggerElement = trigger;\n\n if (handlers.onPointerEnter)\n trigger.addEventListener('pointerenter', handlers.onPointerEnter as EventListener);\n if (handlers.onPointerLeave)\n trigger.addEventListener('pointerleave', handlers.onPointerLeave as EventListener);\n if (handlers.onFocusIn)\n trigger.addEventListener('focusin', handlers.onFocusIn as EventListener);\n if (handlers.onFocusOut)\n trigger.addEventListener('focusout', handlers.onFocusOut as EventListener);\n if (handlers.onClick) trigger.addEventListener('click', handlers.onClick as EventListener);\n if (handlers.onKeyDown)\n trigger.addEventListener('keydown', handlers.onKeyDown as EventListener);\n }\n\n removeTriggerListeners(triggerOverride?: HTMLElement | null) {\n const trigger = triggerOverride ?? this._triggerElement;\n const handlers = this._handlers;\n if (!trigger || !handlers) return;\n\n if (handlers.onPointerEnter)\n trigger.removeEventListener('pointerenter', handlers.onPointerEnter as EventListener);\n if (handlers.onPointerLeave)\n trigger.removeEventListener('pointerleave', handlers.onPointerLeave as EventListener);\n if (handlers.onFocusIn)\n trigger.removeEventListener('focusin', handlers.onFocusIn as EventListener);\n if (handlers.onFocusOut)\n trigger.removeEventListener('focusout', handlers.onFocusOut as EventListener);\n if (handlers.onClick) trigger.removeEventListener('click', handlers.onClick as EventListener);\n if (handlers.onKeyDown)\n trigger.removeEventListener('keydown', handlers.onKeyDown as EventListener);\n\n if (!triggerOverride) {\n this._handlers = undefined;\n this._triggerElement = undefined;\n }\n }\n\n // --- Animation ---\n\n async animateShow(el: HTMLElement, duration: number, keyframes?: Keyframe[]) {\n const d = reducedMotion?.matches ? 0 : duration;\n el.getAnimations().forEach((a) => a.cancel());\n el.animate(\n keyframes ?? [\n { opacity: 0, scale: 0.96 },\n { opacity: 1, scale: 1 },\n ],\n { duration: d, fill: 'forwards' },\n );\n }\n\n async animateHide(el: HTMLElement, duration: number, keyframes?: Keyframe[]) {\n const d = reducedMotion?.matches ? 0 : duration;\n const anim = el.animate(\n keyframes ?? [\n { opacity: 1, scale: 1 },\n { opacity: 0, scale: 0.96 },\n ],\n { duration: d, fill: 'forwards' },\n );\n await anim.finished;\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{i as e}from"../../chunks/lit.js";var t=e(`.l-button{--_button-size-xs:var(--l-size-control-xs);--_button-size-sm:var(--l-size-control-sm);--_button-size-md:var(--l-size-control-md);--_button-size-lg:var(--l-size-control-lg);--_button-size-xl:var(--l-size-control-xl);--height:var(--_button-size-md);--padding-inline:.625rem;--background-color:var(--l-color-bg-fill-secondary);--background-color-active:color-mix(in oklab, var(--l-color-bg-fill-secondary) 90%, var(--l-color-text-primary));--background-color-hover:var(--background-color-active);--font-size:var(--l-text-sm);--text-color:var(--l-color-text-primary);--text-color-hover:var(--text-color);--border-color:var(--l-color-border);--border-color-hover:color-mix(in oklab, var(--l-color-border) 75%, var(--l-color-text-primary));justify-content:center;place-items:center;gap:calc(var(--spacing) * 2);height:var(--height);padding-inline:var(--padding-inline);border:1px solid var(--border-color);border-radius:calc(var(--height) / 8);background:var(--background-color);color:var(--text-color);font-size:var(--font-size);font-weight:var(--l-font-weight-semibold);-webkit-user-select:none;user-select:none;white-space:nowrap;appearance:none;-webkit-tap-highlight-color:transparent;outline:none;text-decoration:none;transition-property:background-color,color,-webkit-text-decoration,text-decoration,border-color;transition-duration:.15s;display:inline-flex;position:relative}.l-button:not(:disabled):not([aria-disabled=true]){cursor:pointer}.l-button:disabled,.l-button[aria-disabled=true]{cursor:not-allowed;opacity:.4}.l-button:not(:disabled):not([aria-disabled=true]):hover{background-color:var(--background-color-hover);border-color:var(--border-color-hover);color:var(--text-color-hover)}.l-button:not(:disabled):not([aria-disabled=true]):active{background-color:var(--background-color-active)}.l-button:focus-visible{outline:2px solid var(--l-focus-ring);outline-offset:2px}.l-button[data-variant=primary]{--background-color:var(--l-color-bg-fill-brand);--background-color-active:var(--l-color-bg-fill-brand-active);--background-color-hover:var(--l-color-bg-fill-brand-hover);--text-color:var(--l-color-text-on-fill-brand);--text-color-hover:var(--l-color-text-on-fill-brand);--border-color:transparent;--border-color-hover:transparent}.l-button[data-variant=destructive]{--background-color:var(--l-color-bg-fill-danger-soft);--background-color-hover:color-mix(in oklab, var(--l-color-bg-fill-danger-soft) 80%, var(--l-color-text-danger));--background-color-active:color-mix(in oklab, var(--l-color-bg-fill-danger-soft) 65%, var(--l-color-text-danger));--text-color:var(--l-color-text-danger);--text-color-hover:var(--l-color-text-danger);--border-color:transparent;--border-color-hover:transparent}`);export{t as default};
1
+ import{i as e}from"../../chunks/lit.js";var t=e(`.l-button{--_button-size-xs:var(--l-size-control-xs);--_button-size-sm:var(--l-size-control-sm);--_button-size-md:var(--l-size-control-md);--_button-size-lg:var(--l-size-control-lg);--_button-size-xl:var(--l-size-control-xl);--height:var(--_button-size-md);--padding-inline:.625rem;--background-color:var(--l-color-bg-fill-secondary);--background-color-active:color-mix(in oklab, var(--l-color-bg-fill-secondary) 90%, var(--l-color-text-primary));--background-color-hover:var(--background-color-active);--font-size:var(--l-text-sm);--text-color:var(--l-color-text-primary);--text-color-hover:var(--text-color);--border-color:var(--l-color-border);--border-color-hover:color-mix(in oklab, var(--l-color-border) 75%, var(--l-color-text-primary));justify-content:center;place-items:center;gap:var(--l-spacing-2);height:var(--height);padding-inline:var(--padding-inline);border:1px solid var(--border-color);border-radius:calc(var(--height) / 8);background:var(--background-color);color:var(--text-color);font-size:var(--font-size);font-weight:var(--l-font-weight-semibold);-webkit-user-select:none;user-select:none;white-space:nowrap;appearance:none;-webkit-tap-highlight-color:transparent;outline:none;text-decoration:none;transition-property:background-color,color,-webkit-text-decoration,text-decoration,border-color;transition-duration:.15s;display:inline-flex;position:relative}.l-button:not(:disabled):not([aria-disabled=true]){cursor:pointer}.l-button:disabled,.l-button[aria-disabled=true]{cursor:not-allowed;opacity:.4}.l-button:not(:disabled):not([aria-disabled=true]):hover{background-color:var(--background-color-hover);border-color:var(--border-color-hover);color:var(--text-color-hover)}.l-button:not(:disabled):not([aria-disabled=true]):active{background-color:var(--background-color-active)}.l-button:focus-visible{outline:2px solid var(--l-focus-ring);outline-offset:2px}.l-button[data-variant=primary]{--background-color:var(--l-color-bg-fill-brand);--background-color-active:var(--l-color-bg-fill-brand-active);--background-color-hover:var(--l-color-bg-fill-brand-hover);--text-color:var(--l-color-text-on-fill-brand);--text-color-hover:var(--l-color-text-on-fill-brand);--border-color:transparent;--border-color-hover:transparent}.l-button[data-variant=destructive]{--background-color:var(--l-color-bg-fill-danger-soft);--background-color-hover:color-mix(in oklab, var(--l-color-bg-fill-danger-soft) 80%, var(--l-color-text-danger));--background-color-active:color-mix(in oklab, var(--l-color-bg-fill-danger-soft) 65%, var(--l-color-text-danger));--text-color:var(--l-color-text-danger);--text-color-hover:var(--l-color-text-danger);--border-color:transparent;--border-color-hover:transparent}.l-button[aria-pressed=true]{--background-color:var(--background-color-active);--background-color-hover:color-mix(in oklab, var(--background-color-active) 88%, var(--text-color));--border-color:var(--border-color-hover)}.l-button:has(input:checked){--background-color:var(--background-color-active);--background-color-hover:color-mix(in oklab, var(--background-color-active) 88%, var(--text-color));--border-color:var(--border-color-hover)}`);export{t as default};
2
2
  //# sourceMappingURL=button-core.styles.js.map