luxen-ui 0.23.0 → 0.24.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 (173) hide show
  1. package/cdn/custom-elements.json +203 -170
  2. package/cdn/elements/combobox/combobox.d.ts +6 -1
  3. package/cdn/elements/combobox/combobox.d.ts.map +1 -1
  4. package/cdn/elements/combobox/combobox.js +19 -17
  5. package/cdn/elements/combobox/combobox.js.map +1 -1
  6. package/cdn/elements/dropdown/dropdown.d.ts +6 -7
  7. package/cdn/elements/dropdown/dropdown.d.ts.map +1 -1
  8. package/cdn/elements/dropdown/dropdown.js +2 -2
  9. package/cdn/elements/dropdown/dropdown.js.map +1 -1
  10. package/cdn/elements/dropdown-item/dropdown-item.d.ts +0 -1
  11. package/cdn/elements/dropdown-item/dropdown-item.d.ts.map +1 -1
  12. package/cdn/elements/dropdown-item/dropdown-item.js +2 -2
  13. package/cdn/elements/dropdown-item/dropdown-item.js.map +1 -1
  14. package/cdn/elements/dropdown-label/dropdown-label.js +1 -1
  15. package/cdn/elements/dropdown-label/dropdown-label.js.map +1 -1
  16. package/cdn/elements/form-field/form-field.d.ts +1 -1
  17. package/cdn/elements/form-field/form-field.js.map +1 -1
  18. package/cdn/elements/input/input.meta.d.ts +2 -0
  19. package/cdn/elements/input/input.meta.d.ts.map +1 -1
  20. package/cdn/elements/input-otp/input-otp.d.ts +3 -3
  21. package/cdn/elements/input-otp/input-otp.js.map +1 -1
  22. package/cdn/elements/popover/popover.d.ts +3 -4
  23. package/cdn/elements/popover/popover.d.ts.map +1 -1
  24. package/cdn/elements/popover/popover.js +1 -1
  25. package/cdn/elements/popover/popover.js.map +1 -1
  26. package/cdn/elements/select/select.d.ts +6 -1
  27. package/cdn/elements/select/select.d.ts.map +1 -1
  28. package/cdn/elements/select/select.js +8 -7
  29. package/cdn/elements/select/select.js.map +1 -1
  30. package/cdn/elements/stories-viewer/stories-viewer.d.ts.map +1 -1
  31. package/cdn/elements/stories-viewer/stories-viewer.js +4 -4
  32. package/cdn/elements/stories-viewer/stories-viewer.js.map +1 -1
  33. package/cdn/elements/tooltip/tooltip.d.ts +0 -1
  34. package/cdn/elements/tooltip/tooltip.d.ts.map +1 -1
  35. package/cdn/elements/tooltip/tooltip.js +1 -1
  36. package/cdn/elements/tooltip/tooltip.js.map +1 -1
  37. package/cdn/shared/controllers/listbox-nav.d.ts +15 -1
  38. package/cdn/shared/controllers/listbox-nav.d.ts.map +1 -1
  39. package/cdn/shared/controllers/listbox-nav.js +1 -1
  40. package/cdn/shared/controllers/listbox-nav.js.map +1 -1
  41. package/cdn/shared/controllers/popover.d.ts +10 -2
  42. package/cdn/shared/controllers/popover.d.ts.map +1 -1
  43. package/cdn/shared/controllers/popover.js +1 -1
  44. package/cdn/shared/controllers/popover.js.map +1 -1
  45. package/cdn/shared/styles/checkbox-appearance.styles.js +1 -1
  46. package/cdn/shared/styles/checkbox-appearance.styles.js.map +1 -1
  47. package/cdn/shared/styles/menu-panel.styles.d.ts +8 -0
  48. package/cdn/shared/styles/menu-panel.styles.d.ts.map +1 -0
  49. package/cdn/shared/styles/menu-panel.styles.js +2 -0
  50. package/cdn/shared/styles/menu-panel.styles.js.map +1 -0
  51. package/cdn/shared/styles/picker-core.styles.d.ts +9 -0
  52. package/cdn/shared/styles/picker-core.styles.d.ts.map +1 -0
  53. package/cdn/shared/styles/picker-core.styles.js +2 -0
  54. package/cdn/shared/styles/picker-core.styles.js.map +1 -0
  55. package/cdn/shared/styles/select-item-suffix.styles.js +1 -1
  56. package/cdn/shared/styles/select-item-suffix.styles.js.map +1 -1
  57. package/cdn/shared/styles/select-item.styles.d.ts +8 -0
  58. package/cdn/shared/styles/select-item.styles.d.ts.map +1 -0
  59. package/cdn/shared/styles/select-item.styles.js +2 -0
  60. package/cdn/shared/styles/select-item.styles.js.map +1 -0
  61. package/cdn/shared/styles/visually-hidden.styles.d.ts +8 -0
  62. package/cdn/shared/styles/visually-hidden.styles.d.ts.map +1 -0
  63. package/cdn/shared/styles/visually-hidden.styles.js +2 -0
  64. package/cdn/shared/styles/visually-hidden.styles.js.map +1 -0
  65. package/cdn/standalone.css +365 -196
  66. package/cdn/standalone.js +148 -75
  67. package/cdn/standalone.js.map +1 -1
  68. package/cdn/styles/base.css +30 -20
  69. package/cdn/styles/elements/checkbox.css +2 -2
  70. package/cdn/styles/elements/form-field.css +16 -9
  71. package/cdn/styles/elements/input-otp.css +16 -9
  72. package/cdn/styles/elements/input-stepper/default.css +4 -4
  73. package/cdn/styles/elements/input-stepper/pill.css +4 -4
  74. package/cdn/styles/elements/input-stepper/rounded.css +3 -3
  75. package/cdn/styles/elements/input.css +82 -27
  76. package/cdn/styles/elements/radio.css +2 -2
  77. package/cdn/styles/elements/select.css +137 -80
  78. package/cdn/styles/elements/switch.css +3 -3
  79. package/cdn/styles/elements/textarea.css +5 -5
  80. package/cdn/styles/preset.css +92 -49
  81. package/cdn/styles/tokens/forms.css +33 -30
  82. package/cdn/styles/tokens.css +62 -29
  83. package/dist/css/base.css +30 -20
  84. package/dist/css/elements/checkbox.css +2 -2
  85. package/dist/css/elements/form-field.css +16 -9
  86. package/dist/css/elements/input-otp.css +16 -9
  87. package/dist/css/elements/input-stepper/default.css +4 -4
  88. package/dist/css/elements/input-stepper/pill.css +4 -4
  89. package/dist/css/elements/input-stepper/rounded.css +3 -3
  90. package/dist/css/elements/input.css +82 -27
  91. package/dist/css/elements/radio.css +2 -2
  92. package/dist/css/elements/select.css +137 -80
  93. package/dist/css/elements/switch.css +3 -3
  94. package/dist/css/elements/textarea.css +5 -5
  95. package/dist/css/preset.css +92 -49
  96. package/dist/css/tokens/forms.css +33 -30
  97. package/dist/css/tokens.css +62 -29
  98. package/dist/custom-elements.json +203 -170
  99. package/dist/elements/combobox/combobox.css +30 -187
  100. package/dist/elements/combobox/combobox.d.ts +6 -1
  101. package/dist/elements/combobox/combobox.d.ts.map +1 -1
  102. package/dist/elements/combobox/combobox.js +48 -20
  103. package/dist/elements/dropdown/dropdown.css +7 -30
  104. package/dist/elements/dropdown/dropdown.d.ts +6 -7
  105. package/dist/elements/dropdown/dropdown.d.ts.map +1 -1
  106. package/dist/elements/dropdown/dropdown.js +17 -14
  107. package/dist/elements/dropdown-item/dropdown-item.css +7 -36
  108. package/dist/elements/dropdown-item/dropdown-item.d.ts +0 -1
  109. package/dist/elements/dropdown-item/dropdown-item.d.ts.map +1 -1
  110. package/dist/elements/dropdown-item/dropdown-item.js +4 -7
  111. package/dist/elements/dropdown-label/dropdown-label.css +3 -3
  112. package/dist/elements/form-field/form-field.d.ts +1 -1
  113. package/dist/elements/form-field/form-field.js +2 -2
  114. package/dist/elements/input/input.meta.d.ts +2 -0
  115. package/dist/elements/input/input.meta.d.ts.map +1 -1
  116. package/dist/elements/input/input.meta.js +2 -0
  117. package/dist/elements/input-otp/input-otp.d.ts +3 -3
  118. package/dist/elements/input-otp/input-otp.js +3 -3
  119. package/dist/elements/popover/popover.css +4 -4
  120. package/dist/elements/popover/popover.d.ts +3 -4
  121. package/dist/elements/popover/popover.d.ts.map +1 -1
  122. package/dist/elements/popover/popover.js +5 -9
  123. package/dist/elements/select/select.css +24 -260
  124. package/dist/elements/select/select.d.ts +6 -1
  125. package/dist/elements/select/select.d.ts.map +1 -1
  126. package/dist/elements/select/select.js +27 -9
  127. package/dist/elements/stories-viewer/stories-viewer.css +0 -12
  128. package/dist/elements/stories-viewer/stories-viewer.d.ts.map +1 -1
  129. package/dist/elements/stories-viewer/stories-viewer.js +4 -3
  130. package/dist/elements/tooltip/tooltip.d.ts +0 -1
  131. package/dist/elements/tooltip/tooltip.d.ts.map +1 -1
  132. package/dist/elements/tooltip/tooltip.js +2 -6
  133. package/dist/metadata/combobox.json +16 -1
  134. package/dist/metadata/dropdown.json +10 -10
  135. package/dist/metadata/form-field.json +1 -1
  136. package/dist/metadata/index.json +57 -22
  137. package/dist/metadata/input-otp.json +3 -3
  138. package/dist/metadata/input.json +5 -0
  139. package/dist/metadata/popover.json +5 -5
  140. package/dist/metadata/select.json +16 -1
  141. package/dist/shared/controllers/listbox-nav.d.ts +15 -1
  142. package/dist/shared/controllers/listbox-nav.d.ts.map +1 -1
  143. package/dist/shared/controllers/listbox-nav.js +32 -1
  144. package/dist/shared/controllers/popover.d.ts +10 -2
  145. package/dist/shared/controllers/popover.d.ts.map +1 -1
  146. package/dist/shared/controllers/popover.js +16 -2
  147. package/dist/shared/styles/checkbox-appearance.css +2 -2
  148. package/dist/shared/styles/menu-panel.css +37 -0
  149. package/dist/shared/styles/menu-panel.styles.d.ts +8 -0
  150. package/dist/shared/styles/menu-panel.styles.d.ts.map +1 -0
  151. package/dist/shared/styles/menu-panel.styles.js +8 -0
  152. package/dist/shared/styles/picker-core.css +250 -0
  153. package/dist/shared/styles/picker-core.styles.d.ts +9 -0
  154. package/dist/shared/styles/picker-core.styles.d.ts.map +1 -0
  155. package/dist/shared/styles/picker-core.styles.js +9 -0
  156. package/dist/shared/styles/select-item-suffix.css +4 -4
  157. package/dist/shared/styles/select-item.css +98 -0
  158. package/dist/shared/styles/select-item.styles.d.ts +8 -0
  159. package/dist/shared/styles/select-item.styles.d.ts.map +1 -0
  160. package/dist/shared/styles/select-item.styles.js +8 -0
  161. package/dist/shared/styles/visually-hidden.css +27 -0
  162. package/dist/shared/styles/visually-hidden.styles.d.ts +8 -0
  163. package/dist/shared/styles/visually-hidden.styles.d.ts.map +1 -0
  164. package/dist/shared/styles/visually-hidden.styles.js +8 -0
  165. package/dist/templates/elements/combobox.md +26 -1
  166. package/dist/templates/elements/disclosure.md +2 -0
  167. package/dist/templates/elements/dropdown.md +6 -6
  168. package/dist/templates/elements/form-field.md +2 -2
  169. package/dist/templates/elements/input-otp.md +3 -3
  170. package/dist/templates/elements/input.md +1 -0
  171. package/dist/templates/elements/popover.md +3 -3
  172. package/dist/templates/elements/select.md +30 -1
  173. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"combobox.js","names":["unsafeCSS","rawStyles","hostStyles","suffixStyles","nothing","unsafeHTML","_decorate","property","state","r","r$1","__decorate","o","A","host_styles_default","select_item_suffix_styles_default","styles","defaultFilter","query","label","normalize","item","kw","ComboboxChangeEvent","value","ComboboxInputEvent","Combobox","LuxenFormAssociatedElement","_args","LocalizeController","uniqueId","RichHighlighter","PopoverController","ListboxNavController","i","DatalistObserverController","#_value_accessor_storage","#_placeholder_accessor_storage","#_label_accessor_storage","#_size_accessor_storage","#_withClear_accessor_storage","#_allowCustom_accessor_storage","#_highlight_accessor_storage","#_placement_accessor_storage","#__open_accessor_storage","#__query_accessor_storage","#__items_accessor_storage","next","changed","state","list","opts","rich","richOptionLabel","adoptRichContentStyles","active","displayed","untouched","input","panel","filtered","highlightText","markup","activeId","activeIndex","invalid","status"],"sources":["../../../src/html/elements/combobox/combobox.css?inline","../../../src/html/elements/combobox/combobox.ts"],"sourcesContent":[":host {\n --height: var(--l-form-control-height);\n --border-radius: var(--l-form-control-border-radius);\n --background: var(--l-color-surface-overlay);\n\n display: block;\n inline-size: 100%;\n}\n\n:host([size='xs']) {\n --height: var(--l-size-control-xs);\n}\n:host([size='sm']) {\n --height: var(--l-size-control-sm);\n}\n:host([size='lg']) {\n --height: var(--l-size-control-lg);\n}\n:host([size='xl']) {\n --height: var(--l-size-control-xl);\n}\n\n:host([disabled]) {\n opacity: 0.5;\n pointer-events: none;\n}\n\n.base {\n position: relative;\n}\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅲🅾🅽🆃🆁🅾🅻 (trigger)\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n.control {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n block-size: var(--height);\n padding-inline: var(--l-form-control-padding-inline);\n border: var(--l-form-control-border-width) solid var(--l-form-control-border-color);\n border-radius: var(--border-radius);\n background-color: var(--l-form-control-background-color);\n cursor: text;\n}\n\n@media (hover: hover) {\n .control:hover {\n border-color: var(--l-form-control-border-color-hover);\n }\n}\n\n.control:has(.input:focus-visible) {\n border-color: var(--l-focus-ring);\n box-shadow: 0 0 0 3px color-mix(in oklab, var(--l-focus-ring) 22%, transparent);\n}\n\n.input {\n flex: 1;\n min-inline-size: 0;\n border: 0;\n background: transparent;\n color: var(--l-form-control-value-color);\n font: inherit;\n outline: none;\n}\n\n.input::placeholder {\n color: var(--l-form-control-placeholder-color);\n}\n\n.chevron {\n flex: none;\n inline-size: 1em;\n block-size: 1em;\n background-color: currentColor;\n color: var(--l-form-control-placeholder-color);\n mask: var(--_chevron-icon) center / contain no-repeat;\n transition: rotate 0.15s ease;\n}\n\n/* ≥24px hit area (WCAG 2.5.8) with a 1em icon centred inside. */\n.clear {\n flex: none;\n inline-size: 1.5rem;\n block-size: 1.5rem;\n border: 0;\n padding: 0;\n cursor: pointer;\n background-color: currentColor;\n color: var(--l-form-control-placeholder-color);\n mask: var(--_x-icon) center / 1em no-repeat;\n}\n\n@media (hover: hover) {\n .clear:hover {\n color: var(--l-form-control-value-color);\n }\n}\n\n.control:has(.input[aria-expanded='true']) .chevron {\n rotate: 180deg;\n}\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅿🅰🅽🅴🅻 (popover listbox)\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n.panel {\n inset: auto;\n margin: 0;\n padding: 0;\n border: 1px solid var(--l-color-border-overlay);\n border-radius: var(--border-radius);\n background-color: var(--background);\n box-shadow: var(--l-shadow-md);\n overflow: hidden;\n opacity: 0;\n scale: 0.98;\n transition:\n opacity 0.12s ease,\n scale 0.12s ease,\n display 0.12s allow-discrete,\n overlay 0.12s allow-discrete;\n}\n\n.panel:popover-open {\n opacity: 1;\n scale: 1;\n}\n\n@starting-style {\n .panel:popover-open {\n opacity: 0;\n scale: 0.98;\n }\n}\n\n.listbox {\n margin: 0;\n padding: 0.25rem;\n list-style: none;\n max-block-size: 16rem;\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n.option {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n min-block-size: 2rem;\n padding-block: 0.25rem;\n padding-inline: 0.5rem var(--height);\n position: relative;\n border-radius: calc(var(--border-radius) - 2px);\n color: var(--l-color-text-primary);\n cursor: pointer;\n}\n\n.option[aria-selected='true'] {\n background-color: var(--l-color-bg-state-hover);\n}\n\n.option[aria-disabled='true'] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n/* Checkmark on the currently committed value. */\n.option[data-current]::after {\n content: '';\n position: absolute;\n inset-inline-end: 0.5rem;\n inline-size: 1em;\n block-size: 1em;\n background-color: var(--l-form-control-activated-color);\n mask: var(--_check-icon) center / contain no-repeat;\n}\n\n.option-label {\n flex: 1;\n min-inline-size: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* The part of an option that matches the search query. Scoped to the marks the\n * highlight adds, so a `<mark>` authored in a rich option keeps its own look. */\nmark.match {\n border-radius: 0.2em;\n background-color: var(--l-form-control-match-background-color);\n color: inherit;\n font-weight: var(--l-form-control-match-font-weight);\n box-decoration-break: clone;\n}\n\n/* Rich options — same authoring classes as l-select, styled inside the shadow. */\n.option-rich {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex: 1;\n min-inline-size: 0;\n}\n\n.option-rich .l-select-item-media {\n flex: none;\n display: inline-flex;\n align-items: center;\n font-size: 1.25em;\n}\n\n.option-rich .l-select-item-text {\n display: flex;\n flex-direction: column;\n min-inline-size: 0;\n}\n\n.option-rich .l-select-item-title {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.option-rich .l-select-item-description {\n font-size: 0.85em;\n color: var(--l-color-text-tertiary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.empty {\n padding: 0.5rem;\n color: var(--l-color-text-tertiary);\n text-align: center;\n}\n\n.visually-hidden {\n position: absolute;\n inline-size: 1px;\n block-size: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅸🅲🅾🅽🆂\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n:host {\n --_chevron-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E\");\n --_x-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6 6 18M6 6l12 12'/%3E%3C/svg%3E\");\n --_check-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E\");\n}\n\n@media (prefers-reduced-motion: reduce) {\n .panel,\n .chevron {\n transition-duration: 0ms;\n }\n}\n","import { html, nothing, unsafeCSS } from 'lit';\nimport type { TemplateResult } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { LuxenFormAssociatedElement } from '../../shared/luxen-form-associated-element.js';\nimport { PopoverController } from '../../shared/controllers/popover.js';\nimport { ListboxNavController } from '../../shared/controllers/listbox-nav.js';\nimport { DatalistObserverController } from '../../shared/controllers/datalist-observer.js';\nimport { LocalizeController } from '../../shared/localize.js';\nimport { RichHighlighter, highlightText, normalize } from '../../shared/highlight.js';\nimport { uniqueId } from '../../registry.js';\nimport { adoptRichContentStyles, richOptionLabel } from '../../shared/rich-option.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport suffixStyles from '../../shared/styles/select-item-suffix.styles.js';\nimport rawStyles from './combobox.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\nexport type ComboboxSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\n/** A single option read from the `<datalist>`. */\nexport interface ComboboxItem {\n value: string;\n /** Plain text used for filtering and the input display. */\n label: string;\n disabled: boolean;\n /** Rich inner markup (from a non-empty `<option>`), projected into the row. */\n html?: string;\n}\n\n/** Default filter: accent/case-insensitive, every space-separated keyword must match. */\nexport type ComboboxFilter = (item: ComboboxItem, query: string) => boolean;\n\nconst defaultFilter: ComboboxFilter = (item, query) => {\n if (!query) return true;\n const label = normalize(item.label);\n return normalize(query)\n .split(/\\s+/)\n .filter(Boolean)\n .every((kw) => label.includes(kw));\n};\n\n/** Fired when an option is committed (selected). Bubbles; not composed. */\nexport class ComboboxChangeEvent extends Event {\n readonly value: string;\n constructor(value: string) {\n super('change', { bubbles: true, composed: false, cancelable: false });\n this.value = value;\n }\n}\n\n/** Fired as the user types (filter query changes). Bubbles; not composed. */\nexport class ComboboxInputEvent extends Event {\n /** The current query text. */\n readonly value: string;\n constructor(value: string) {\n super('input', { bubbles: true, composed: false, cancelable: false });\n this.value = value;\n }\n}\n\ninterface ComboboxEventMap {\n change: ComboboxChangeEvent;\n input: ComboboxInputEvent;\n}\n\n/**\n * A text input paired with a filterable list of options (the ARIA combobox\n * pattern). Options are authored as a native `<datalist>` of `<option>` —\n * the same authoring surface as `l-select`. Form-associated.\n *\n * @summary A searchable text input with a filtered list of options.\n *\n * @example\n * ```html\n * <l-combobox label=\"Country\" name=\"country\" placeholder=\"Search…\">\n * <datalist>\n * <option value=\"us\">United States</option>\n * <option value=\"fr\">France</option>\n * </datalist>\n * </l-combobox>\n * ```\n *\n * @event change - Fired when an option is selected. Bubbles. Not cancelable. Properties: `value: string`.\n * @event input - Fired as the user types. Bubbles. Not cancelable. Properties: `value: string` (the query).\n * @event show - Fired before the list opens. Cancelable.\n * @event hide - Fired before the list closes. Cancelable.\n *\n * @csspart base - The host wrapper.\n * @csspart control - The input control row (input + clear + chevron).\n * @csspart input - The text input.\n * @csspart panel - The floating popover panel.\n * @csspart listbox - The options container.\n * @csspart option - Each option row.\n * @csspart empty - The \"no results\" message.\n *\n * @cssproperty [--height] - Control height. Defaults to the form-control height.\n * @cssproperty [--border-radius] - Control + panel radius.\n * @cssproperty [--background] - Panel background.\n *\n * @cssClass .l-select-item-media - Rich option: leading image or icon.\n * @cssClass .l-select-item-text - Rich option: column holding the title and the description.\n * @cssClass .l-select-item-title - Rich option: title, truncated with an ellipsis. Used as the label when the option has no `label`.\n * @cssClass .l-select-item-description - Rich option: secondary line, shown in the listbox only.\n * @cssClass .l-select-item-suffix - Rich option: trailing badge, count or status icon on the title's line. Kept out of the input and the filter.\n *\n * @customElement l-combobox\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below; no uninitialized properties.\nexport class Combobox extends LuxenFormAssociatedElement {\n // Rich option markup is copied into the shadow root, out of reach of the\n // global stylesheets: suffixStyles lays out the `.l-select-item-suffix` zone,\n // and `_readItems` adopts the styles of the light-DOM elements (l-badge) the\n // options actually contain.\n static override styles = [hostStyles, suffixStyles, styles];\n\n private _localize = new LocalizeController(this);\n private readonly _listId = uniqueId('combobox-list');\n private readonly _richHighlighter = new RichHighlighter();\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._controlEl,\n getFloatingElement: () => this._panelEl,\n getArrowElement: () => null,\n });\n\n private _nav = new ListboxNavController(this, {\n listboxId: this._listId,\n getCount: () => this._filtered.length,\n isDisabled: (i) => this._filtered[i]?.disabled ?? false,\n isOpen: () => this._open,\n open: () => this.show(),\n getOptionElements: () => this.shadowRoot?.querySelectorAll<HTMLElement>('.option'),\n });\n\n // The `<datalist>` is light DOM the consumer owns and rewrites in place;\n // re-read it so the input tracks the options it was given.\n private _options = new DatalistObserverController(this, () => this._syncItems());\n\n /** Selected value (the chosen option's `value`). */\n @property({ reflect: true })\n accessor value = '';\n\n /** Placeholder text in the input. */\n @property()\n accessor placeholder = '';\n\n /** Accessible label for the input. */\n @property()\n accessor label = '';\n\n /** Control size. */\n @property({ reflect: true })\n accessor size: ComboboxSize = 'md';\n\n /** Show a button to clear the value. */\n @property({ type: Boolean, reflect: true, attribute: 'with-clear' })\n accessor withClear = false;\n\n /** Accept a typed value that matches no option. */\n @property({ type: Boolean, reflect: true, attribute: 'allow-custom' })\n accessor allowCustom = false;\n\n /**\n * Wrap the matching substring of each option in `<mark>` — the label of a\n * plain option, the title and description of a rich one.\n */\n @property({ type: Boolean })\n accessor highlight = true;\n\n /** Panel placement relative to the control. */\n @property()\n accessor placement: Placement = 'bottom-start';\n\n /** Override the option filter. `(item, query) => boolean`. */\n filter: ComboboxFilter = defaultFilter;\n\n @state() private accessor _open = false;\n @state() private accessor _query = '';\n @state() private accessor _items: ComboboxItem[] = [];\n\n private get _controlEl() {\n return this.shadowRoot?.querySelector<HTMLElement>('.control') ?? null;\n }\n private get _panelEl() {\n return this.shadowRoot?.querySelector<HTMLElement>('.panel') ?? null;\n }\n private get _inputEl() {\n return this.shadowRoot?.querySelector<HTMLInputElement>('.input') ?? null;\n }\n\n override connectedCallback() {\n super.connectedCallback();\n // Sync rather than plain-read: the observer is disconnected while detached,\n // so options may have been re-labelled behind our back and the input — which\n // holds its own text, not a render of `_items` — would stay on the old one.\n this._syncItems();\n this._defaultFormValue = this.value;\n this._syncFormValue(this.value);\n }\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('value')) {\n this._syncFormValue(this.value);\n this._updateValidity();\n if (!this._open) this._resetDisplay();\n }\n }\n\n override formResetCallback() {\n this.value = this._defaultFormValue;\n this._query = '';\n this._nav.reset();\n super.formResetCallback();\n this._resetDisplay();\n }\n\n override formStateRestoreCallback(state: string) {\n this.value = state;\n this._syncFormValue(state);\n }\n\n // --- Items (read from the light-DOM <datalist>) ---\n\n private _readItems() {\n const list = this.querySelector('datalist');\n const opts = list ? Array.from(list.options) : [];\n this._items = opts.map((o) => {\n const rich = o.childElementCount > 0;\n const label = richOptionLabel(o);\n return { value: o.value, label, disabled: o.disabled, html: rich ? o.innerHTML : undefined };\n });\n adoptRichContentStyles(this.shadowRoot, this._items);\n }\n\n /**\n * Re-read the options after the light DOM changed underneath us.\n *\n * The input carries its own text rather than a render of `_items`, so it has\n * to be refreshed by hand — but only when it is still showing the label we put\n * there. Anything else in it is the user's (a query typed while the panel\n * stayed closed, because a consumer vetoed `show`) and must survive. A closed\n * panel is not on its own proof that nobody is typing.\n *\n * Open, the input is theirs by definition; what needs care there is the active\n * option, addressed by position in `_filtered` — re-anchor it on the option the\n * user had highlighted, or a list that grows or shrinks commits a different one.\n */\n private _syncItems() {\n const active = this._open ? this._filtered[this._nav.activeIndex] : undefined;\n const displayed = this._inputEl?.value;\n const untouched = displayed === undefined || displayed === this._labelForValue(this.value);\n this._readItems();\n if (!this._open) {\n if (untouched) this._resetDisplay();\n return;\n }\n this._nav.setActive(active ? this._filtered.findIndex((i) => i.value === active.value) : -1);\n }\n\n private _labelForValue(value: string): string {\n return this._items.find((i) => i.value === value)?.label ?? (this.allowCustom ? value : '');\n }\n\n private get _filtered(): ComboboxItem[] {\n return this._items.filter((i) => this.filter(i, this._query));\n }\n\n private _resetDisplay() {\n const input = this._inputEl;\n if (input) input.value = this._labelForValue(this.value);\n }\n\n // --- Validity ---\n\n private _updateValidity() {\n if (this.required && !this.value) {\n this.setValidity(\n { valueMissing: true },\n this._localize.term('selectOption'),\n this._inputEl ?? undefined,\n );\n } else {\n this.setValidity({});\n }\n }\n\n // --- Open / close ---\n\n show() {\n if (this._open || this.disabled) return;\n this._readItems();\n if (!this.dispatchEvent(new Event('show', { cancelable: true }))) return;\n this._open = true;\n // Pre-activate the chosen option so a screen reader announces the current\n // selection when the list opens (only when not actively filtering).\n if (this.value && !this._query) {\n this._nav.setActive(this._filtered.findIndex((i) => i.value === this.value));\n }\n this.updateComplete.then(() => {\n const panel = this._panelEl;\n panel?.showPopover?.();\n this._floating.startPositioning({\n placement: this.placement,\n distance: 4,\n minWidth: 'trigger',\n });\n });\n }\n\n hide() {\n if (!this._open) return;\n if (!this.dispatchEvent(new Event('hide', { cancelable: true }))) return;\n this._open = false;\n this._nav.reset();\n const panel = this._panelEl;\n if (panel?.matches?.(':popover-open')) panel.hidePopover?.();\n this._floating.stopPositioning();\n this._resetDisplay();\n }\n\n // --- Selection ---\n\n private _commit(item: ComboboxItem) {\n if (item.disabled) return;\n this.value = item.value;\n this._query = '';\n this.hasInteracted = true;\n const input = this._inputEl;\n if (input) input.value = item.label;\n this._syncFormValue(this.value);\n this._updateValidity();\n this.dispatchEvent(new ComboboxChangeEvent(this.value));\n this.hide();\n input?.focus();\n }\n\n private _commitActiveOrCustom() {\n const filtered = this._filtered;\n const active = this._nav.activeIndex;\n if (active >= 0 && filtered[active]) {\n this._commit(filtered[active]);\n return;\n }\n const input = this._inputEl;\n if (this.allowCustom && input) {\n this.value = input.value;\n this._syncFormValue(this.value);\n this._updateValidity();\n this.dispatchEvent(new ComboboxChangeEvent(this.value));\n this.hide();\n }\n }\n\n private _clear() {\n this.value = '';\n this._query = '';\n this._nav.reset();\n if (this._inputEl) this._inputEl.value = '';\n this._syncFormValue('');\n this._updateValidity();\n this.dispatchEvent(new ComboboxChangeEvent(''));\n this._inputEl?.focus();\n }\n\n // --- Keyboard (navigation delegated to ListboxNavController) ---\n\n private _onKeyDown = (e: KeyboardEvent) => {\n // Arrow Up/Down move aria-activedescendant. Home/End are left to the input's\n // text cursor (APG editable combobox).\n if (this._nav.onKeyDown(e)) return;\n switch (e.key) {\n case 'Enter':\n if (this._open) {\n e.preventDefault();\n this._commitActiveOrCustom();\n }\n break;\n case 'Escape':\n if (this._open) {\n e.preventDefault();\n this.hide();\n } else if (this.value || this._inputEl?.value) {\n this._clear();\n }\n break;\n case 'Tab':\n if (this._open && this._nav.activeIndex >= 0) this._commitActiveOrCustom();\n break;\n default:\n break;\n }\n };\n\n private _onInput = (e: Event) => {\n this._query = (e.target as HTMLInputElement).value;\n this._nav.reset();\n if (!this._open) this.show();\n this.dispatchEvent(new ComboboxInputEvent(this._query));\n };\n\n private _onControlPointerDown = (e: PointerEvent) => {\n // Toggle on clicking the control chrome (not while typing).\n if ((e.target as HTMLElement).closest('.clear')) return;\n if (this._open) return;\n this.show();\n };\n\n private _onFocusOut = (e: FocusEvent) => {\n const next = e.relatedTarget as Node | null;\n if (next && this.shadowRoot?.contains(next)) return;\n this.hasInteracted = true;\n this._updateValidity();\n this.hide();\n };\n\n // --- Render ---\n\n private _highlight(label: string): TemplateResult | string {\n return this.highlight ? highlightText(label, this._query) : label;\n }\n\n private _highlightRich(markup: string): string {\n return this.highlight ? this._richHighlighter.highlight(markup, this._query) : markup;\n }\n\n override render() {\n const filtered = this._filtered;\n const activeId = this._nav.activeDescendant;\n const activeIndex = this._nav.activeIndex;\n const invalid = this.required && !this.value && this.hasInteracted;\n const status = !this._open\n ? ''\n : filtered.length === 0\n ? this._localize.term('noResults')\n : `${filtered.length} ${this._localize.term('suggestions')}`;\n\n return html`\n <div\n class=\"base\"\n part=\"base\"\n @focusout=${this._onFocusOut}\n >\n <div\n class=\"control\"\n part=\"control\"\n @pointerdown=${this._onControlPointerDown}\n >\n <slot name=\"prefix\"></slot>\n <input\n class=\"input\"\n part=\"input\"\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n .placeholder=${this.placeholder}\n ?disabled=${this.disabled}\n aria-label=${this.label || nothing}\n aria-autocomplete=\"list\"\n aria-haspopup=\"listbox\"\n aria-expanded=${this._open ? 'true' : 'false'}\n aria-controls=${this._listId}\n aria-activedescendant=${activeId ?? nothing}\n aria-required=${this.required ? 'true' : nothing}\n aria-invalid=${invalid ? 'true' : nothing}\n @input=${this._onInput}\n @keydown=${this._onKeyDown}\n />\n ${\n this.withClear && (this.value || this._query)\n ? html`<button\n class=\"clear\"\n type=\"button\"\n tabindex=\"-1\"\n aria-label=${this._localize.term('clear')}\n @click=${this._clear}\n ></button>`\n : nothing\n }\n <span\n class=\"chevron\"\n aria-hidden=\"true\"\n ></span>\n </div>\n\n <div\n class=\"panel\"\n part=\"panel\"\n popover=\"manual\"\n >\n <ul\n class=\"listbox\"\n part=\"listbox\"\n role=\"listbox\"\n id=${this._listId}\n aria-label=${this.label || this._localize.term('suggestions')}\n >\n ${filtered.map(\n (item, i) =>\n html`<li\n class=\"option\"\n part=\"option\"\n id=${this._nav.optionId(i)}\n role=\"option\"\n aria-selected=${i === activeIndex ? 'true' : 'false'}\n aria-disabled=${item.disabled ? 'true' : nothing}\n data-current=${item.value === this.value ? '' : nothing}\n @pointerdown=${(e: Event) => e.preventDefault()}\n @click=${() => this._commit(item)}\n >\n ${\n item.html\n ? html`<span class=\"option-rich\"\n >${unsafeHTML(this._highlightRich(item.html))}</span\n >`\n : html`<span class=\"option-label\">${this._highlight(item.label)}</span>`\n }\n </li>`,\n )}\n </ul>\n ${\n filtered.length === 0\n ? html`<div\n class=\"empty\"\n part=\"empty\"\n >\n ${this._localize.term('noResults')}\n </div>`\n : nothing\n }\n </div>\n\n <div\n class=\"visually-hidden\"\n aria-live=\"polite\"\n >\n ${status}\n </div>\n </div>\n `;\n }\n}\n\n// Typed addEventListener for the colliding `change` / `input` names.\nexport interface Combobox {\n addEventListener<K extends keyof ComboboxEventMap>(\n type: K,\n listener: (this: Combobox, ev: ComboboxEventMap[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 ComboboxEventMap>(\n type: K,\n listener: (this: Combobox, ev: ComboboxEventMap[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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"}
1
+ {"version":3,"file":"combobox.js","names":["unsafeCSS","rawStyles","hostStyles","visuallyHiddenStyles","suffixStyles","pickerCoreStyles","nothing","unsafeHTML","_decorate","property","state","r","r$1","__decorate","o","A","host_styles_default","visually_hidden_styles_default","select_item_suffix_styles_default","picker_core_styles_default","styles","defaultFilter","query","label","normalize","item","kw","ComboboxChangeEvent","value","ComboboxInputEvent","Combobox","LuxenFormAssociatedElement","_args","LocalizeController","uniqueId","RichHighlighter","PopoverController","ListboxNavController","i","DatalistObserverController","#_value_accessor_storage","#_placeholder_accessor_storage","#_label_accessor_storage","#_size_accessor_storage","#_withClear_accessor_storage","#_allowCustom_accessor_storage","#_highlight_accessor_storage","#_placement_accessor_storage","#__open_accessor_storage","#__query_accessor_storage","#__items_accessor_storage","next","changed","state","list","opts","rich","richOptionLabel","adoptRichContentStyles","active","displayed","untouched","input","panel","filtered","highlightText","markup","activeId","activeIndex","invalid","status","cls"],"sources":["../../../src/html/elements/combobox/combobox.css?inline","../../../src/html/elements/combobox/combobox.ts"],"sourcesContent":["/* Knobs, size scale, field frame, chevron, clear, listbox popover and the\n shared option bits come from shared/styles/picker-core.css (loaded first).\n This file holds what is specific to the editable combobox. */\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅲🅾🅽🆃🆁🅾🅻 (trigger)\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n.control {\n gap: 0.25rem;\n block-size: var(--height);\n cursor: text;\n}\n\n.input {\n flex: 1;\n min-inline-size: 0;\n border: 0;\n background: transparent;\n color: var(--l-form-control-value-color);\n font: inherit;\n outline: none;\n}\n\n.input::placeholder {\n color: var(--l-form-control-placeholder-color);\n}\n\n/* The clear button and the chevron sit side by side, square against square —\n the row's gap only separates them from the input. */\n.actions {\n flex: none;\n display: flex;\n align-self: stretch;\n align-items: center;\n}\n\n/* The chevron toggles the list (see _onControlPointerDown). */\n.chevron {\n cursor: pointer;\n}\n\n@media (hover: hover) {\n .chevron:hover {\n color: var(--l-form-control-value-color);\n }\n}\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅾🅿🆃🅸🅾🅽🆂\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n.option {\n display: flex;\n align-items: center;\n gap: var(--l-menu-item-gap);\n min-block-size: var(--l-menu-item-min-height);\n padding-block: var(--l-menu-item-padding-block);\n /* Inline-end room for the checkmark of the committed value. It sits at the\n end, not in an inline-start column like l-select: a reserved start column\n would indent every row even when nothing is selected yet. */\n padding-inline: var(--l-menu-item-padding-inline) calc(var(--l-menu-item-padding-inline) + 1.5rem);\n position: relative;\n border-radius: var(--l-menu-item-border-radius);\n color: var(--l-color-text-primary);\n cursor: pointer;\n}\n\n/* The active option takes the same tint as a hovered l-dropdown-item. The\n mouse moves the active option too (ListboxNavController.onPointerMove), so\n hover and keyboard share this single highlight instead of tinting two rows. */\n.option[aria-selected='true'] {\n background-color: var(--l-color-bg-state-hover);\n}\n\n/* Checkmark on the currently committed value. */\n.option[data-current]::after {\n content: '';\n position: absolute;\n inset-inline-end: 0.5rem;\n inline-size: 1em;\n block-size: 1em;\n background-color: var(--l-form-control-activated-color);\n mask: var(--_check-icon) center / contain no-repeat;\n}\n\n/* Rich options — same authoring classes as l-select, copied straight into the\n row, so an option is rich by its content alone. */\n.option .l-select-item-media {\n flex: none;\n display: inline-flex;\n align-items: center;\n font-size: 1.25em;\n}\n\n.option .l-select-item-text {\n display: flex;\n flex-direction: column;\n min-inline-size: 0;\n}\n\n.option .l-select-item-title {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.option .l-select-item-description {\n font-size: 0.85em;\n color: var(--l-color-text-tertiary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n","import { html, nothing, unsafeCSS } from 'lit';\nimport type { TemplateResult } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { LuxenFormAssociatedElement } from '../../shared/luxen-form-associated-element.js';\nimport { PopoverController } from '../../shared/controllers/popover.js';\nimport { ListboxNavController } from '../../shared/controllers/listbox-nav.js';\nimport { DatalistObserverController } from '../../shared/controllers/datalist-observer.js';\nimport { LocalizeController } from '../../shared/localize.js';\nimport { RichHighlighter, highlightText, normalize } from '../../shared/highlight.js';\nimport { cls, uniqueId } from '../../registry.js';\nimport { adoptRichContentStyles, richOptionLabel } from '../../shared/rich-option.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport visuallyHiddenStyles from '../../shared/styles/visually-hidden.styles.js';\nimport suffixStyles from '../../shared/styles/select-item-suffix.styles.js';\nimport pickerCoreStyles from '../../shared/styles/picker-core.styles.js';\nimport rawStyles from './combobox.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\nexport type ComboboxSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\n/** A single option read from the `<datalist>`. */\nexport interface ComboboxItem {\n value: string;\n /** Plain text used for filtering and the input display. */\n label: string;\n disabled: boolean;\n /** Rich inner markup (from a non-empty `<option>`), projected into the row. */\n html?: string;\n}\n\n/** Default filter: accent/case-insensitive, every space-separated keyword must match. */\nexport type ComboboxFilter = (item: ComboboxItem, query: string) => boolean;\n\nconst defaultFilter: ComboboxFilter = (item, query) => {\n if (!query) return true;\n const label = normalize(item.label);\n return normalize(query)\n .split(/\\s+/)\n .filter(Boolean)\n .every((kw) => label.includes(kw));\n};\n\n/** Fired when an option is committed (selected). Bubbles; not composed. */\nexport class ComboboxChangeEvent extends Event {\n readonly value: string;\n constructor(value: string) {\n super('change', { bubbles: true, composed: false, cancelable: false });\n this.value = value;\n }\n}\n\n/** Fired as the user types (filter query changes). Bubbles; not composed. */\nexport class ComboboxInputEvent extends Event {\n /** The current query text. */\n readonly value: string;\n constructor(value: string) {\n super('input', { bubbles: true, composed: false, cancelable: false });\n this.value = value;\n }\n}\n\ninterface ComboboxEventMap {\n change: ComboboxChangeEvent;\n input: ComboboxInputEvent;\n}\n\n/**\n * A text input paired with a filterable list of options (the ARIA combobox\n * pattern). Options are authored as a native `<datalist>` of `<option>` —\n * the same authoring surface as `l-select`. Form-associated.\n *\n * @summary A searchable text input with a filtered list of options.\n *\n * @example\n * ```html\n * <l-combobox label=\"Country\" name=\"country\" placeholder=\"Search…\">\n * <datalist>\n * <option value=\"us\">United States</option>\n * <option value=\"fr\">France</option>\n * </datalist>\n * </l-combobox>\n * ```\n *\n * @event change - Fired when an option is selected. Bubbles. Not cancelable. Properties: `value: string`.\n * @event input - Fired as the user types. Bubbles. Not cancelable. Properties: `value: string` (the query).\n * @event show - Fired before the list opens. Cancelable.\n * @event hide - Fired before the list closes. Cancelable.\n *\n * @csspart base - The host wrapper.\n * @csspart control - The input control row (input + clear + chevron).\n * @csspart input - The text input.\n * @csspart panel - The floating popover panel.\n * @csspart listbox - The options container.\n * @csspart option - Each option row.\n * @csspart empty - The \"no results\" message.\n *\n * @cssproperty [--height=var(--l-form-control-height)] - Control height. The clear button and the chevron\n * each take a square of this height (borders excluded), so their hit area and spacing follow it.\n * @cssproperty [--l-form-control-icon-size=1em] - Glyph size of the clear button and the chevron (global form-control\n * token; set it here or on any ancestor). Changes the glyph only — the hit area follows `--height`.\n * @cssproperty --chevron-icon - Chevron glyph as a `url()`. Masked, so its color comes from the control, not the image.\n * @cssproperty --clear-icon - Clear button glyph as a `url()`.\n * @cssproperty [--border-radius] - Control + panel radius.\n * @cssproperty [--background] - Panel background.\n *\n * @cssClass .l-select-item-media - Rich option: leading image or icon.\n * @cssClass .l-select-item-text - Rich option: column holding the title and the description.\n * @cssClass .l-select-item-title - Rich option: title, truncated with an ellipsis. Used as the label when the option has no `label`.\n * @cssClass .l-select-item-description - Rich option: secondary line, shown in the listbox only.\n * @cssClass .l-select-item-suffix - Rich option: trailing badge, count or status icon on the title's line. Kept out of the input and the filter.\n *\n * @customElement l-combobox\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below; no uninitialized properties.\nexport class Combobox extends LuxenFormAssociatedElement {\n // Rich option markup is copied into the shadow root, out of reach of the\n // global stylesheets: suffixStyles lays out the `.l-select-item-suffix` zone,\n // and `_readItems` adopts the styles of the light-DOM elements (l-badge) the\n // options actually contain.\n static override styles = [\n hostStyles,\n visuallyHiddenStyles,\n suffixStyles,\n pickerCoreStyles,\n styles,\n ];\n\n private _localize = new LocalizeController(this);\n private readonly _listId = uniqueId('combobox-list');\n private readonly _richHighlighter = new RichHighlighter();\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._controlEl,\n getFloatingElement: () => this._panelEl,\n getArrowElement: () => null,\n });\n\n private _nav = new ListboxNavController(this, {\n listboxId: this._listId,\n getCount: () => this._filtered.length,\n isDisabled: (i) => this._filtered[i]?.disabled ?? false,\n isOpen: () => this._open,\n open: () => this.show(),\n getOptionElements: () => this.shadowRoot?.querySelectorAll<HTMLElement>('.option'),\n });\n\n // The `<datalist>` is light DOM the consumer owns and rewrites in place;\n // re-read it so the input tracks the options it was given.\n private _options = new DatalistObserverController(this, () => this._syncItems());\n\n /** Selected value (the chosen option's `value`). */\n @property({ reflect: true })\n accessor value = '';\n\n /** Placeholder text in the input. */\n @property()\n accessor placeholder = '';\n\n /** Accessible label for the input. */\n @property()\n accessor label = '';\n\n /** Control size. */\n @property({ reflect: true })\n accessor size: ComboboxSize = 'md';\n\n /** Show a button to clear the value. */\n @property({ type: Boolean, reflect: true, attribute: 'with-clear' })\n accessor withClear = false;\n\n /** Accept a typed value that matches no option. */\n @property({ type: Boolean, reflect: true, attribute: 'allow-custom' })\n accessor allowCustom = false;\n\n /**\n * Wrap the matching substring of each option in `<mark>` — the label of a\n * plain option, the title and description of a rich one.\n */\n @property({ type: Boolean })\n accessor highlight = true;\n\n /** Panel placement relative to the control. */\n @property()\n accessor placement: Placement = 'bottom-start';\n\n /** Override the option filter. `(item, query) => boolean`. */\n filter: ComboboxFilter = defaultFilter;\n\n @state() private accessor _open = false;\n @state() private accessor _query = '';\n @state() private accessor _items: ComboboxItem[] = [];\n\n private get _controlEl() {\n return this.shadowRoot?.querySelector<HTMLElement>('.control') ?? null;\n }\n private get _panelEl() {\n return this.shadowRoot?.querySelector<HTMLElement>('.panel') ?? null;\n }\n private get _inputEl() {\n return this.shadowRoot?.querySelector<HTMLInputElement>('.input') ?? null;\n }\n\n override connectedCallback() {\n super.connectedCallback();\n // Sync rather than plain-read: the observer is disconnected while detached,\n // so options may have been re-labelled behind our back and the input — which\n // holds its own text, not a render of `_items` — would stay on the old one.\n this._syncItems();\n this._defaultFormValue = this.value;\n this._syncFormValue(this.value);\n }\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('value')) {\n this._syncFormValue(this.value);\n this._updateValidity();\n if (!this._open) this._resetDisplay();\n }\n }\n\n override formResetCallback() {\n this.value = this._defaultFormValue;\n this._query = '';\n this._nav.reset();\n super.formResetCallback();\n this._resetDisplay();\n }\n\n override formStateRestoreCallback(state: string) {\n this.value = state;\n this._syncFormValue(state);\n }\n\n // --- Items (read from the light-DOM <datalist>) ---\n\n private _readItems() {\n const list = this.querySelector('datalist');\n const opts = list ? Array.from(list.options) : [];\n this._items = opts.map((o) => {\n const rich = o.childElementCount > 0;\n const label = richOptionLabel(o);\n return { value: o.value, label, disabled: o.disabled, html: rich ? o.innerHTML : undefined };\n });\n adoptRichContentStyles(this.shadowRoot, this._items);\n }\n\n /**\n * Re-read the options after the light DOM changed underneath us.\n *\n * The input carries its own text rather than a render of `_items`, so it has\n * to be refreshed by hand — but only when it is still showing the label we put\n * there. Anything else in it is the user's (a query typed while the panel\n * stayed closed, because a consumer vetoed `show`) and must survive. A closed\n * panel is not on its own proof that nobody is typing.\n *\n * Open, the input is theirs by definition; what needs care there is the active\n * option, addressed by position in `_filtered` — re-anchor it on the option the\n * user had highlighted, or a list that grows or shrinks commits a different one.\n */\n private _syncItems() {\n const active = this._open ? this._filtered[this._nav.activeIndex] : undefined;\n const displayed = this._inputEl?.value;\n const untouched = displayed === undefined || displayed === this._labelForValue(this.value);\n this._readItems();\n if (!this._open) {\n if (untouched) this._resetDisplay();\n return;\n }\n this._nav.setActive(active ? this._filtered.findIndex((i) => i.value === active.value) : -1);\n }\n\n private _labelForValue(value: string): string {\n return this._items.find((i) => i.value === value)?.label ?? (this.allowCustom ? value : '');\n }\n\n private get _filtered(): ComboboxItem[] {\n return this._items.filter((i) => this.filter(i, this._query));\n }\n\n private _resetDisplay() {\n const input = this._inputEl;\n if (input) input.value = this._labelForValue(this.value);\n }\n\n // --- Validity ---\n\n private _updateValidity() {\n if (this.required && !this.value) {\n this.setValidity(\n { valueMissing: true },\n this._localize.term('selectOption'),\n this._inputEl ?? undefined,\n );\n } else {\n this.setValidity({});\n }\n }\n\n // --- Open / close ---\n\n show() {\n if (this._open || this.disabled) return;\n this._readItems();\n if (!this.dispatchEvent(new Event('show', { cancelable: true }))) return;\n this._open = true;\n // Pre-activate the chosen option so a screen reader announces the current\n // selection when the list opens (only when not actively filtering).\n if (this.value && !this._query) {\n this._nav.setActive(this._filtered.findIndex((i) => i.value === this.value));\n }\n this.updateComplete.then(() => {\n const panel = this._panelEl;\n panel?.showPopover?.();\n this._floating.startPositioning({\n placement: this.placement,\n distance: 4,\n minWidth: 'trigger',\n });\n });\n }\n\n hide() {\n if (!this._open) return;\n if (!this.dispatchEvent(new Event('hide', { cancelable: true }))) return;\n this._open = false;\n this._nav.reset();\n const panel = this._panelEl;\n if (panel?.matches?.(':popover-open')) panel.hidePopover?.();\n this._floating.stopPositioning();\n this._resetDisplay();\n }\n\n // --- Selection ---\n\n private _commit(item: ComboboxItem) {\n if (item.disabled) return;\n this.value = item.value;\n this._query = '';\n this.hasInteracted = true;\n const input = this._inputEl;\n if (input) input.value = item.label;\n this._syncFormValue(this.value);\n this._updateValidity();\n this.dispatchEvent(new ComboboxChangeEvent(this.value));\n this.hide();\n input?.focus();\n }\n\n private _commitActiveOrCustom() {\n const filtered = this._filtered;\n const active = this._nav.activeIndex;\n if (active >= 0 && filtered[active]) {\n this._commit(filtered[active]);\n return;\n }\n const input = this._inputEl;\n if (this.allowCustom && input) {\n this.value = input.value;\n this._syncFormValue(this.value);\n this._updateValidity();\n this.dispatchEvent(new ComboboxChangeEvent(this.value));\n this.hide();\n }\n }\n\n private _clear() {\n this.value = '';\n this._query = '';\n this._nav.reset();\n if (this._inputEl) this._inputEl.value = '';\n this._syncFormValue('');\n this._updateValidity();\n this.dispatchEvent(new ComboboxChangeEvent(''));\n this._inputEl?.focus();\n }\n\n // --- Keyboard (navigation delegated to ListboxNavController) ---\n\n private _onKeyDown = (e: KeyboardEvent) => {\n // Arrow Up/Down move aria-activedescendant. Home/End are left to the input's\n // text cursor (APG editable combobox).\n if (this._nav.onKeyDown(e)) return;\n switch (e.key) {\n case 'Enter':\n if (this._open) {\n e.preventDefault();\n this._commitActiveOrCustom();\n }\n break;\n case 'Escape':\n if (this._open) {\n e.preventDefault();\n this.hide();\n } else if (this.value || this._inputEl?.value) {\n this._clear();\n }\n break;\n case 'Tab':\n if (this._open && this._nav.activeIndex >= 0) this._commitActiveOrCustom();\n break;\n default:\n break;\n }\n };\n\n private _onInput = (e: Event) => {\n this._query = (e.target as HTMLInputElement).value;\n this._nav.reset();\n if (!this._open) this.show();\n this.dispatchEvent(new ComboboxInputEvent(this._query));\n };\n\n private _onControlPointerDown = (e: PointerEvent) => {\n // Toggle on clicking the control chrome (not while typing).\n if ((e.target as HTMLElement).closest('.clear-button')) return;\n // A click on the chrome (chevron, padding, prefix) focuses the input, like a\n // native select. Otherwise focus stays on the page, the input never sees a\n // focusout, and an outside click or Tab can no longer close the list.\n if (e.target !== this._inputEl) {\n e.preventDefault();\n this._inputEl?.focus();\n }\n // The chevron toggles, like a native select's arrow: it closes an open list.\n // Anywhere else on the control only opens it, so clicking into the text to\n // move the caret never dismisses the suggestions.\n if (this._open) {\n if ((e.target as HTMLElement).closest('.chevron')) this.hide();\n return;\n }\n this.show();\n };\n\n private _onFocusOut = (e: FocusEvent) => {\n const next = e.relatedTarget as Node | null;\n if (next && this.shadowRoot?.contains(next)) return;\n this.hasInteracted = true;\n this._updateValidity();\n this.hide();\n };\n\n // --- Render ---\n\n private _highlight(label: string): TemplateResult | string {\n return this.highlight ? highlightText(label, this._query) : label;\n }\n\n private _highlightRich(markup: string): string {\n return this.highlight ? this._richHighlighter.highlight(markup, this._query) : markup;\n }\n\n override render() {\n const filtered = this._filtered;\n const activeId = this._nav.activeDescendant;\n const activeIndex = this._nav.activeIndex;\n const invalid = this.required && !this.value && this.hasInteracted;\n const status = !this._open\n ? ''\n : filtered.length === 0\n ? this._localize.term('noResults')\n : `${filtered.length} ${this._localize.term('suggestions')}`;\n\n return html`\n <div\n class=\"base\"\n part=\"base\"\n @focusout=${this._onFocusOut}\n >\n <div\n class=\"control\"\n part=\"control\"\n @pointerdown=${this._onControlPointerDown}\n >\n <slot name=\"prefix\"></slot>\n <input\n class=\"input\"\n part=\"input\"\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n .placeholder=${this.placeholder}\n ?disabled=${this.disabled}\n aria-label=${this.label || nothing}\n aria-autocomplete=\"list\"\n aria-haspopup=\"listbox\"\n aria-expanded=${this._open ? 'true' : 'false'}\n aria-controls=${this._listId}\n aria-activedescendant=${activeId ?? nothing}\n aria-required=${this.required ? 'true' : nothing}\n aria-invalid=${invalid ? 'true' : nothing}\n @input=${this._onInput}\n @keydown=${this._onKeyDown}\n />\n <span class=\"actions\">\n ${\n this.withClear && (this.value || this._query)\n ? html`<button\n class=\"clear-button\"\n type=\"button\"\n tabindex=\"-1\"\n aria-label=${this._localize.term('clear')}\n @click=${this._clear}\n ></button>`\n : nothing\n }\n <span\n class=\"chevron\"\n aria-hidden=\"true\"\n ></span>\n </span>\n </div>\n\n <div\n class=\"panel\"\n part=\"panel\"\n popover=\"manual\"\n >\n <ul\n class=\"listbox\"\n part=\"listbox\"\n role=\"listbox\"\n id=${this._listId}\n aria-label=${this.label || this._localize.term('suggestions')}\n @pointermove=${this._nav.onPointerMove}\n @pointerleave=${this._nav.onPointerLeave}\n >\n ${filtered.map(\n (item, i) =>\n html`<li\n class=\"option\"\n part=\"option\"\n id=${this._nav.optionId(i)}\n role=\"option\"\n aria-selected=${i === activeIndex ? 'true' : 'false'}\n aria-disabled=${item.disabled ? 'true' : nothing}\n data-current=${item.value === this.value ? '' : nothing}\n @pointerdown=${(e: Event) => e.preventDefault()}\n @click=${() => this._commit(item)}\n >\n ${\n item.html\n ? unsafeHTML(this._highlightRich(item.html))\n : html`<span class=\"option-label\">${this._highlight(item.label)}</span>`\n }\n </li>`,\n )}\n </ul>\n ${\n filtered.length === 0\n ? html`<div\n class=\"empty\"\n part=\"empty\"\n >\n ${this._localize.term('noResults')}\n </div>`\n : nothing\n }\n </div>\n\n <div\n class=${cls('visually-hidden')}\n aria-live=\"polite\"\n >\n ${status}\n </div>\n </div>\n `;\n }\n}\n\n// Typed addEventListener for the colliding `change` / `input` names.\nexport interface Combobox {\n addEventListener<K extends keyof ComboboxEventMap>(\n type: K,\n listener: (this: Combobox, ev: ComboboxEventMap[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 ComboboxEventMap>(\n type: K,\n listener: (this: Combobox, ev: ComboboxEventMap[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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"}
@@ -20,12 +20,12 @@ interface DropdownEventMap {
20
20
  *
21
21
  * @csspart panel - The floating menu container.
22
22
  *
23
- * @cssproperty --background - Panel background color.
24
- * @cssproperty --border-radius - Panel border radius. Default `8px`.
25
- * @cssproperty --padding - Panel inner padding. Default `0.25rem`. Slotted `<l-divider>` elements bleed by this amount on each side to span the panel edges.
26
- * @cssproperty --shadow - Panel box shadow.
27
- * @cssproperty --show-duration - Show animation duration in ms. Default `150`.
28
- * @cssproperty --hide-duration - Hide animation duration in ms. Default `150`.
23
+ * @cssproperty --background - Panel background color. Default `var(--l-color-surface-overlay)`.
24
+ * @cssproperty --border-radius - Panel border radius. Default `var(--l-popover-border-radius)` (6px).
25
+ * @cssproperty --padding - Panel inner padding. Default `var(--l-popover-padding)` (0.25rem). Slotted `<l-divider>` elements bleed by this amount on each side to span the panel edges.
26
+ * @cssproperty --shadow - Panel box shadow. Default `var(--l-popover-shadow)`.
27
+ * @cssproperty --show-duration - Show animation duration. Default `150ms`.
28
+ * @cssproperty --hide-duration - Hide animation duration. Default `150ms`.
29
29
  *
30
30
  * @event show - Fired before the dropdown opens. Cancelable.
31
31
  * @event after-show - Fired after the open animation completes.
@@ -68,7 +68,6 @@ export declare class Dropdown extends LuxenElement {
68
68
  private _parentItemOf;
69
69
  /** The enabled items of the level that currently holds focus (root level as fallback). */
70
70
  private _currentLevelItems;
71
- private _getDuration;
72
71
  show(): void;
73
72
  hide(): void;
74
73
  toggle(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"dropdown.d.ts","sourceRoot":"","sources":["../../../src/html/elements/dropdown/dropdown.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dropdown.d.ts","sourceRoot":"","sources":["../../../src/html/elements/dropdown/dropdown.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import{i as e}from"../../chunks/lit.js";import{LuxenElement as t}from"../../shared/luxen-element.js";import{r as n,t as r}from"../../chunks/decorate.js";import{a as i}from"../../chunks/lit-html.js";import{PopoverController as a}from"../../shared/controllers/popover.js";import{tagName as o}from"../../registry.js";import{ShowEvent as s}from"../../events/show.js";import{AfterShowEvent as c}from"../../events/after-show.js";import{HideEvent as l}from"../../events/hide.js";import{AfterHideEvent as u}from"../../events/after-hide.js";import d from"../../shared/styles/host.styles.js";var f=e(`:host{--padding:.25rem;--background:var(--l-color-surface-overlay);--border-radius:6px;--shadow:var(--l-shadow-md);--show-duration:150;--hide-duration:150;display:inline-block;position:relative}.trigger{display:contents}[popover]{inset:unset;box-sizing:border-box;width:max-content;padding:var(--padding);border:1px solid var(--l-color-border-overlay);border-radius:var(--border-radius);background:var(--background);color:var(--l-color-text-primary);box-shadow:var(--shadow);margin:0;font-size:.875rem;line-height:1.5;overflow:visible}::slotted(hr){height:1px;margin-block:var(--padding);margin-inline:calc(var(--padding) * -1);background:var(--l-color-divider);border:0}`),p=new Set([`Shift`,`Control`,`Alt`,`AltGraph`,`Meta`]),m=class extends Event{constructor(e){super(`select`,{bubbles:!0,composed:!1,cancelable:!1}),this.item=e}},h=class extends t{constructor(...e){super(...e),this._floating=new a(this,{getTriggerElement:()=>this._triggerEl,getFloatingElement:()=>this._panelEl,getArrowElement:()=>null}),this._typeaheadBuffer=``,this._typeaheadTimeout=0,this.#e=!1,this.#t=`bottom-start`,this.#n=4,this.#r=!1,this._hoverTimer=0,this._hoverItem=null,this._onTriggerSlotChange=()=>{let e=this._triggerEl;if(!e)return;e.setAttribute(`aria-haspopup`,`menu`),e.setAttribute(`aria-expanded`,String(this.open));let t=e.getAttribute(`aria-label`)??e.textContent?.trim()??``,n=this.shadowRoot?.querySelector(`[role="menu"]`);n&&t&&n.setAttribute(`aria-label`,t)},this._onTriggerClick=e=>{this.disabled||(!this.open||this._panelEl?.matches(`:popover-open`))&&(this.toggle(),this.open&&e.detail===0&&requestAnimationFrame(()=>this._focusFirstItem()))},this._onTriggerKeyDown=e=>{if(!this.disabled){if(e.key===`Escape`){if(!this.open)return;e.preventDefault(),this.hide()}else e.key===`ArrowDown`?(e.preventDefault(),this.show(),requestAnimationFrame(()=>this._focusFirstItem())):e.key===`ArrowUp`&&(e.preventDefault(),this.show(),requestAnimationFrame(()=>this._focusLastItem()))}},this._onPanelKeyDown=e=>{switch(p.has(e.key)||this._cancelHoverIntent(),e.key){case`ArrowDown`:e.preventDefault(),this._focusNextItem();break;case`ArrowUp`:e.preventDefault(),this._focusPreviousItem();break;case`Home`:e.preventDefault(),this._focusFirstItem(this._currentLevelItems());break;case`End`:e.preventDefault(),this._focusLastItem(this._currentLevelItems());break;case`ArrowRight`:{let t=this._getCurrentItem();t?.hasSubmenu&&(e.preventDefault(),this._openSubmenu(t,{focus:!0}));break}case`ArrowLeft`:{let t=this._parentItemOf(this._getCurrentItem());t&&(e.preventDefault(),t.closeSubmenu(),this._setActiveItem(t));break}case`Escape`:{e.preventDefault();let t=this._parentItemOf(this._getCurrentItem());t?(t.closeSubmenu(),this._setActiveItem(t)):(this.hide(),this._triggerEl?.focus());break}case`Tab`:this.hide();break;case`Enter`:case` `:e.preventDefault(),this._selectCurrentItem();break;default:e.key.length===1&&!e.ctrlKey&&!e.metaKey&&this._handleTypeahead(e.key)}},this._onItemClick=e=>{let t=this._itemFromEvent(e);t&&!t.disabled&&(t.hasSubmenu?(this._cancelHoverIntent(),t.submenuOpen?t.closeSubmenu():this._openSubmenu(t)):this._selectItem(t))},this._onPanelPointerOver=e=>{if(e.pointerType!==`mouse`)return;let t=this._itemFromEvent(e);t!==this._hoverItem&&(this._cancelHoverIntent(),this._hoverItem=t,t&&!t.disabled&&(this._hoverTimer=window.setTimeout(()=>{this._openSubmenu(t)},100)))},this._onPanelPointerOut=e=>{let t=e.relatedTarget;this._hoverItem&&t instanceof Element&&t.closest(o(`dropdown-item`))===this._hoverItem||this._cancelHoverIntent({forget:!0})},this._onToggle=e=>{if(e.newState!==`closed`||!this.open)return;if(this.dispatchEvent(new l)){this.open=!1;return}let t=this._panelEl;t?.isConnected&&t.showPopover()}}static{this.styles=[d,f]}#e;get open(){return this.#e}set open(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 disabled(){return this.#r}set disabled(e){this.#r=e}#i;get minWidth(){return this.#i}set minWidth(e){this.#i=e}get _triggerEl(){return this.shadowRoot.querySelector(`.trigger slot`)?.assignedElements()[0]??null}get _panelEl(){return this.shadowRoot.querySelector(`[popover]`)}_getItems(){let e=this.shadowRoot.querySelector(`slot:not([name])`);return e?e.assignedElements().filter(e=>e.tagName===o(`dropdown-item`).toUpperCase()&&!e.disabled):[]}_getAllItems(){let e=this.shadowRoot.querySelector(`slot:not([name])`);return e?e.assignedElements().filter(e=>e.tagName===o(`dropdown-item`).toUpperCase()):[]}_getAllItemsDeep(){return[...this.querySelectorAll(o(`dropdown-item`))]}_levelItemsOf(e){let t=e.parentElement;return t&&t.tagName===o(`dropdown-item`).toUpperCase()?t.getSubmenuItems():this._getItems()}_parentItemOf(e){let t=e?.parentElement;return t&&t.tagName===o(`dropdown-item`).toUpperCase()?t:null}_currentLevelItems(){let e=this._getCurrentItem();return e?this._levelItemsOf(e):this._getItems()}_getDuration(e){let t=parseFloat(getComputedStyle(this).getPropertyValue(e));return Number.isNaN(t)?150:t}show(){this.open||this.disabled||this.dispatchEvent(new s)&&(this.open=!0)}hide(){this.open&&this.dispatchEvent(new l)&&(this.open=!1)}toggle(){this.open?this.hide():this.show()}disconnectedCallback(){super.disconnectedCallback(),this._cancelHoverIntent({forget:!0})}updated(e){e.has(`open`)&&this._handleOpenChange()}async _handleOpenChange(){let e=this._panelEl;if(!e)return;let t={placement:this.placement,distance:this.distance,minWidth:this.minWidth};if(this.open){if(e.showPopover(),await this._floating.updatePosition(t),!this.open)return;await this._floating.animateShow(e,this._getDuration(`--show-duration`)),this._floating.startPositioning(t),this._triggerEl?.setAttribute(`aria-expanded`,`true`),this.dispatchEvent(new c)}else this._floating.stopPositioning(),this._closeAllSubmenus(),this._triggerEl?.setAttribute(`aria-expanded`,`false`),await this._floating.animateHide(e,this._getDuration(`--hide-duration`)),e.matches(`:popover-open`)&&e.hidePopover(),this.dispatchEvent(new u)}async _openSubmenu(e,{focus:t=!1}={}){if(!this.open||!this.isConnected)return;let n=!1;for(let t of this._levelItemsOf(e))t!==e&&(t.contains(document.activeElement)&&(n=!0),t.closeSubmenu());if(n&&!t&&this._setActiveItem(e),e.openSubmenu(),t){await e.updateComplete;let t=e.getSubmenuItems()[0];t&&this._setActiveItem(t)}}_cancelHoverIntent({forget:e=!1}={}){clearTimeout(this._hoverTimer),e&&(this._hoverItem=null)}_closeAllSubmenus(){this._cancelHoverIntent({forget:!0});for(let e of this._getAllItems())e.closeSubmenu()}_itemFromEvent(e){let t=e.composedPath(),n=o(`dropdown-item`).toUpperCase(),r=t.find(e=>e instanceof Element&&e.tagName===n)??null;if(!r)return null;let i=r.shadowRoot?.querySelector(`.item`);if(i&&!t.includes(i)){let e=r.shadowRoot?.querySelector(`.submenu`);if(e&&t.includes(e))return null}return r}_setActiveItem(e){let t=e.shadowRoot.querySelector(`.item`);if(t){for(let e of this._getAllItemsDeep())e.shadowRoot.querySelector(`.item`)?.setAttribute(`tabindex`,`-1`);t.setAttribute(`tabindex`,`0`),t.focus();for(let t of this._levelItemsOf(e))t!==e&&t.closeSubmenu()}}_focusFirstItem(e=this._getItems()){e.length&&this._setActiveItem(e[0])}_focusLastItem(e=this._getItems()){e.length&&this._setActiveItem(e[e.length-1])}_getCurrentItem(){return this._getAllItemsDeep().find(e=>{let t=e.shadowRoot.querySelector(`.item`);return t?.getAttribute(`tabindex`)===`0`&&e.shadowRoot.activeElement===t})??null}_focusNextItem(){let e=this._currentLevelItems(),t=this._getCurrentItem(),n=e[((t?e.indexOf(t):-1)+1)%e.length];n&&this._setActiveItem(n)}_focusPreviousItem(){let e=this._currentLevelItems(),t=this._getCurrentItem(),n=e[((t?e.indexOf(t):0)-1+e.length)%e.length];n&&this._setActiveItem(n)}_handleTypeahead(e){clearTimeout(this._typeaheadTimeout),this._typeaheadBuffer+=e.toLowerCase(),this._typeaheadTimeout=window.setTimeout(()=>{this._typeaheadBuffer=``},500);let t=this._currentLevelItems().find(e=>e.getTextLabel().toLowerCase().startsWith(this._typeaheadBuffer));t&&this._setActiveItem(t)}_selectCurrentItem(){let e=this._getCurrentItem();e&&(e.hasSubmenu?this._openSubmenu(e,{focus:!0}):this._selectItem(e))}_selectItem(e){e.type===`checkbox`&&(e.checked=!e.checked),this.dispatchEvent(new m(e)),e.type!==`checkbox`&&(this.hide(),this._triggerEl?.focus())}render(){return i`
1
+ import{i as e}from"../../chunks/lit.js";import{LuxenElement as t}from"../../shared/luxen-element.js";import{r as n,t as r}from"../../chunks/decorate.js";import{a as i}from"../../chunks/lit-html.js";import{PopoverController as a}from"../../shared/controllers/popover.js";import{tagName as o}from"../../registry.js";import{ShowEvent as s}from"../../events/show.js";import{AfterShowEvent as c}from"../../events/after-show.js";import{HideEvent as l}from"../../events/hide.js";import{AfterHideEvent as u}from"../../events/after-hide.js";import d from"../../shared/styles/host.styles.js";import f from"../../shared/styles/menu-panel.styles.js";var p=e(`:host{--padding:var(--l-popover-padding);--background:var(--l-color-surface-overlay);--border-radius:var(--l-popover-border-radius);--shadow:var(--l-popover-shadow);--show-duration:.15s;--hide-duration:.15s;display:inline-block;position:relative}.trigger{display:contents}`),m=new Set([`Shift`,`Control`,`Alt`,`AltGraph`,`Meta`]),h=class extends Event{constructor(e){super(`select`,{bubbles:!0,composed:!1,cancelable:!1}),this.item=e}},g=class extends t{constructor(...e){super(...e),this._floating=new a(this,{getTriggerElement:()=>this._triggerEl,getFloatingElement:()=>this._panelEl,getArrowElement:()=>null}),this._typeaheadBuffer=``,this._typeaheadTimeout=0,this.#e=!1,this.#t=`bottom-start`,this.#n=4,this.#r=!1,this._hoverTimer=0,this._hoverItem=null,this._onTriggerSlotChange=()=>{let e=this._triggerEl;if(!e)return;e.setAttribute(`aria-haspopup`,`menu`),e.setAttribute(`aria-expanded`,String(this.open));let t=e.getAttribute(`aria-label`)??e.textContent?.trim()??``,n=this.shadowRoot?.querySelector(`[role="menu"]`);n&&t&&n.setAttribute(`aria-label`,t)},this._onTriggerClick=e=>{this.disabled||(!this.open||this._panelEl?.matches(`:popover-open`))&&(this.toggle(),this.open&&e.detail===0&&requestAnimationFrame(()=>this._focusFirstItem()))},this._onTriggerKeyDown=e=>{if(!this.disabled){if(e.key===`Escape`){if(!this.open)return;e.preventDefault(),this.hide()}else e.key===`ArrowDown`?(e.preventDefault(),this.show(),requestAnimationFrame(()=>this._focusFirstItem())):e.key===`ArrowUp`&&(e.preventDefault(),this.show(),requestAnimationFrame(()=>this._focusLastItem()))}},this._onPanelKeyDown=e=>{switch(m.has(e.key)||this._cancelHoverIntent(),e.key){case`ArrowDown`:e.preventDefault(),this._focusNextItem();break;case`ArrowUp`:e.preventDefault(),this._focusPreviousItem();break;case`Home`:e.preventDefault(),this._focusFirstItem(this._currentLevelItems());break;case`End`:e.preventDefault(),this._focusLastItem(this._currentLevelItems());break;case`ArrowRight`:{let t=this._getCurrentItem();t?.hasSubmenu&&(e.preventDefault(),this._openSubmenu(t,{focus:!0}));break}case`ArrowLeft`:{let t=this._parentItemOf(this._getCurrentItem());t&&(e.preventDefault(),t.closeSubmenu(),this._setActiveItem(t));break}case`Escape`:{e.preventDefault();let t=this._parentItemOf(this._getCurrentItem());t?(t.closeSubmenu(),this._setActiveItem(t)):(this.hide(),this._triggerEl?.focus());break}case`Tab`:this.hide();break;case`Enter`:case` `:e.preventDefault(),this._selectCurrentItem();break;default:e.key.length===1&&!e.ctrlKey&&!e.metaKey&&this._handleTypeahead(e.key)}},this._onItemClick=e=>{let t=this._itemFromEvent(e);t&&!t.disabled&&(t.hasSubmenu?(this._cancelHoverIntent(),t.submenuOpen?t.closeSubmenu():this._openSubmenu(t)):this._selectItem(t))},this._onPanelPointerOver=e=>{if(e.pointerType!==`mouse`)return;let t=this._itemFromEvent(e);t!==this._hoverItem&&(this._cancelHoverIntent(),this._hoverItem=t,t&&!t.disabled&&(this._hoverTimer=window.setTimeout(()=>{this._openSubmenu(t)},100)))},this._onPanelPointerOut=e=>{let t=e.relatedTarget;this._hoverItem&&t instanceof Element&&t.closest(o(`dropdown-item`))===this._hoverItem||this._cancelHoverIntent({forget:!0})},this._onToggle=e=>{if(e.newState!==`closed`||!this.open)return;if(this.dispatchEvent(new l)){this.open=!1;return}let t=this._panelEl;t?.isConnected&&t.showPopover()}}static{this.styles=[d,f,p]}#e;get open(){return this.#e}set open(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 disabled(){return this.#r}set disabled(e){this.#r=e}#i;get minWidth(){return this.#i}set minWidth(e){this.#i=e}get _triggerEl(){return this.shadowRoot.querySelector(`.trigger slot`)?.assignedElements()[0]??null}get _panelEl(){return this.shadowRoot.querySelector(`[popover]`)}_getItems(){let e=this.shadowRoot.querySelector(`slot:not([name])`);return e?e.assignedElements().filter(e=>e.tagName===o(`dropdown-item`).toUpperCase()&&!e.disabled):[]}_getAllItems(){let e=this.shadowRoot.querySelector(`slot:not([name])`);return e?e.assignedElements().filter(e=>e.tagName===o(`dropdown-item`).toUpperCase()):[]}_getAllItemsDeep(){return[...this.querySelectorAll(o(`dropdown-item`))]}_levelItemsOf(e){let t=e.parentElement;return t&&t.tagName===o(`dropdown-item`).toUpperCase()?t.getSubmenuItems():this._getItems()}_parentItemOf(e){let t=e?.parentElement;return t&&t.tagName===o(`dropdown-item`).toUpperCase()?t:null}_currentLevelItems(){let e=this._getCurrentItem();return e?this._levelItemsOf(e):this._getItems()}show(){this.open||this.disabled||this.dispatchEvent(new s)&&(this.open=!0)}hide(){this.open&&this.dispatchEvent(new l)&&(this.open=!1)}toggle(){this.open?this.hide():this.show()}disconnectedCallback(){super.disconnectedCallback(),this._cancelHoverIntent({forget:!0})}updated(e){let t=e.get(`open`);e.has(`open`)&&(this.open||t!==void 0)&&this._handleOpenChange()}async _handleOpenChange(){let e=this._panelEl;if(!e)return;let t={placement:this.placement,distance:this.distance,minWidth:this.minWidth};if(this.open){if(e.showPopover(),await this._floating.updatePosition(t),!this.open)return;await this._floating.animateShow(e),this._floating.startPositioning(t),this._triggerEl?.setAttribute(`aria-expanded`,`true`),this.dispatchEvent(new c)}else this._floating.stopPositioning(),this._closeAllSubmenus(),this._triggerEl?.setAttribute(`aria-expanded`,`false`),await this._floating.animateHide(e),e.matches(`:popover-open`)&&e.hidePopover(),this.dispatchEvent(new u)}async _openSubmenu(e,{focus:t=!1}={}){if(!this.open||!this.isConnected)return;let n=!1;for(let t of this._levelItemsOf(e))t!==e&&(t.contains(document.activeElement)&&(n=!0),t.closeSubmenu());if(n&&!t&&this._setActiveItem(e),e.openSubmenu(),t){await e.updateComplete;let t=e.getSubmenuItems()[0];t&&this._setActiveItem(t)}}_cancelHoverIntent({forget:e=!1}={}){clearTimeout(this._hoverTimer),e&&(this._hoverItem=null)}_closeAllSubmenus(){this._cancelHoverIntent({forget:!0});for(let e of this._getAllItems())e.closeSubmenu()}_itemFromEvent(e){let t=e.composedPath(),n=o(`dropdown-item`).toUpperCase(),r=t.find(e=>e instanceof Element&&e.tagName===n)??null;if(!r)return null;let i=r.shadowRoot?.querySelector(`.item`);if(i&&!t.includes(i)){let e=r.shadowRoot?.querySelector(`.submenu`);if(e&&t.includes(e))return null}return r}_setActiveItem(e){let t=e.shadowRoot.querySelector(`.item`);if(t){for(let e of this._getAllItemsDeep())e.shadowRoot.querySelector(`.item`)?.setAttribute(`tabindex`,`-1`);t.setAttribute(`tabindex`,`0`),t.focus();for(let t of this._levelItemsOf(e))t!==e&&t.closeSubmenu()}}_focusFirstItem(e=this._getItems()){e.length&&this._setActiveItem(e[0])}_focusLastItem(e=this._getItems()){e.length&&this._setActiveItem(e[e.length-1])}_getCurrentItem(){return this._getAllItemsDeep().find(e=>{let t=e.shadowRoot.querySelector(`.item`);return t?.getAttribute(`tabindex`)===`0`&&e.shadowRoot.activeElement===t})??null}_focusNextItem(){let e=this._currentLevelItems(),t=this._getCurrentItem(),n=e[((t?e.indexOf(t):-1)+1)%e.length];n&&this._setActiveItem(n)}_focusPreviousItem(){let e=this._currentLevelItems(),t=this._getCurrentItem(),n=e[((t?e.indexOf(t):0)-1+e.length)%e.length];n&&this._setActiveItem(n)}_handleTypeahead(e){clearTimeout(this._typeaheadTimeout),this._typeaheadBuffer+=e.toLowerCase(),this._typeaheadTimeout=window.setTimeout(()=>{this._typeaheadBuffer=``},500);let t=this._currentLevelItems().find(e=>e.getTextLabel().toLowerCase().startsWith(this._typeaheadBuffer));t&&this._setActiveItem(t)}_selectCurrentItem(){let e=this._getCurrentItem();e&&(e.hasSubmenu?this._openSubmenu(e,{focus:!0}):this._selectItem(e))}_selectItem(e){e.type===`checkbox`&&(e.checked=!e.checked),this.dispatchEvent(new h(e)),e.type!==`checkbox`&&(this.hide(),this._triggerEl?.focus())}render(){return i`
2
2
  <div
3
3
  class="trigger"
4
4
  @click=${this._onTriggerClick}
@@ -24,5 +24,5 @@ import{i as e}from"../../chunks/lit.js";import{LuxenElement as t}from"../../shar
24
24
  </div>
25
25
  <slot name="footer"></slot>
26
26
  </div>
27
- `}};r([n({type:Boolean,reflect:!0})],h.prototype,`open`,null),r([n()],h.prototype,`placement`,null),r([n({type:Number})],h.prototype,`distance`,null),r([n({type:Boolean,reflect:!0})],h.prototype,`disabled`,null),r([n({attribute:`min-width`})],h.prototype,`minWidth`,null);export{h as Dropdown,m as DropdownSelectEvent};
27
+ `}};r([n({type:Boolean,reflect:!0})],g.prototype,`open`,null),r([n()],g.prototype,`placement`,null),r([n({type:Number})],g.prototype,`distance`,null),r([n({type:Boolean,reflect:!0})],g.prototype,`disabled`,null),r([n({attribute:`min-width`})],g.prototype,`minWidth`,null);export{g as Dropdown,h as DropdownSelectEvent};
28
28
  //# sourceMappingURL=dropdown.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"dropdown.js","names":["unsafeCSS","rawStyles","toggleEvent","hostStyles","_decorate","property","r","__decorate","b","host_styles_default","styles","MODIFIER_KEYS","DropdownSelectEvent","item","Dropdown","LuxenElement","_args","PopoverController","#_open_accessor_storage","#_placement_accessor_storage","#_distance_accessor_storage","#_disabled_accessor_storage","trigger","name","menu","current","parentItem","to","tagName","HideEvent","panel","value","#_minWidth_accessor_storage","menuSlot","el","parent","parsed","prop","ShowEvent","changed","posOpts","AfterShowEvent","AfterHideEvent","stealsFocus","sibling","focus","first","forget","path","itemTag","n","row","submenu","itemEl","i","items","next","prev","key","match"],"sources":["../../../src/html/elements/dropdown/dropdown.css?inline","../../../src/html/elements/dropdown/dropdown.ts"],"sourcesContent":[":host {\n --padding: 0.25rem;\n\n --background: var(--l-color-surface-overlay);\n --border-radius: 6px;\n --shadow: var(--l-shadow-md);\n --show-duration: 150;\n --hide-duration: 150;\n\n display: inline-block;\n position: relative;\n}\n\n.trigger {\n display: contents;\n}\n\n[popover] {\n inset: unset;\n overflow: visible;\n box-sizing: border-box;\n width: max-content;\n padding: var(--padding);\n margin: 0;\n border: 1px solid var(--l-color-border-overlay);\n border-radius: var(--border-radius);\n background: var(--background);\n color: var(--l-color-text-primary);\n box-shadow: var(--shadow);\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n/* Lightweight separator alternative to <l-divider> — same dimensions, no extra import. */\n::slotted(hr) {\n height: 1px;\n margin-block: var(--padding);\n margin-inline: calc(var(--padding) * -1);\n border: 0;\n background: var(--l-color-divider);\n}\n","import { html, 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 { tagName } from '../../registry.js';\nimport type { DropdownItem } from '../dropdown-item/dropdown-item.js';\nimport { ShowEvent, AfterShowEvent, HideEvent, AfterHideEvent } from '../../events/index.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport rawStyles from './dropdown.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/** Keys that decide nothing on their own — pressing one must not drop a pending hover. */\nconst MODIFIER_KEYS = new Set(['Shift', 'Control', 'Alt', 'AltGraph', 'Meta']);\n\n/** Fired when a dropdown item is selected. Bubbles; not composed. */\nexport class DropdownSelectEvent extends Event {\n readonly item: DropdownItem;\n constructor(item: DropdownItem) {\n super('select', { bubbles: true, composed: false, cancelable: false });\n this.item = item;\n }\n}\n\ninterface DropdownEventMap {\n select: DropdownSelectEvent;\n}\n\n/**\n * A dropdown menu anchored to a trigger element.\n *\n * @slot trigger - The element that triggers the dropdown.\n * @slot header - Optional content rendered above the menu items (e.g. a user profile row). Use an `<l-divider>` (or `<hr>`) after it to separate from items.\n * @slot - Menu content (`l-dropdown-item` elements). Drop an `<l-divider>` (or `<hr>`) between items to render a section separator, or an `<l-dropdown-label>` to caption a group of items. Nest items with `slot=\"submenu\"` inside an item to create a submenu.\n * @slot footer - Optional content rendered below the menu items (e.g. a version label or shortcut row). Use an `<l-divider>` (or `<hr>`) before it to separate from items.\n *\n * @csspart panel - The floating menu container.\n *\n * @cssproperty --background - Panel background color.\n * @cssproperty --border-radius - Panel border radius. Default `8px`.\n * @cssproperty --padding - Panel inner padding. Default `0.25rem`. Slotted `<l-divider>` elements bleed by this amount on each side to span the panel edges.\n * @cssproperty --shadow - Panel box shadow.\n * @cssproperty --show-duration - Show animation duration in ms. Default `150`.\n * @cssproperty --hide-duration - Hide animation duration in ms. Default `150`.\n *\n * @event show - Fired before the dropdown opens. Cancelable.\n * @event after-show - Fired after the open animation completes.\n * @event hide - Fired before the dropdown closes. Cancelable.\n * @event after-hide - Fired after the close animation completes.\n * @event select - Fired when an item is selected, including items nested in submenus. Bubbles. Properties: `item: DropdownItem`.\n *\n * @customElement l-dropdown\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below; no uninitialized properties.\nexport class Dropdown extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._triggerEl,\n getFloatingElement: () => this._panelEl,\n getArrowElement: () => null,\n });\n\n private _typeaheadBuffer = '';\n private _typeaheadTimeout = 0;\n\n /** Whether the dropdown is open. */\n @property({ type: Boolean, reflect: true })\n accessor open = false;\n\n /** Preferred placement of the panel. */\n @property()\n accessor placement: Placement = 'bottom-start';\n\n /** Distance in pixels from the trigger. */\n @property({ type: Number })\n accessor distance = 4;\n\n /** Disables the dropdown trigger. */\n @property({ type: Boolean, reflect: true })\n accessor disabled = false;\n\n /**\n * Floor the panel's width at the trigger's width. Set to `trigger` so the\n * panel is never narrower than the trigger; it still grows with its content.\n * Useful for select-like triggers (a date-range or filter button) where the\n * panel should line up with the control. Re-applies if the trigger resizes\n * while open.\n */\n @property({ attribute: 'min-width' })\n accessor minWidth: 'trigger' | undefined;\n\n private get _triggerEl(): HTMLElement | null {\n const slot = this.shadowRoot!.querySelector<HTMLSlotElement>('.trigger slot');\n return (slot?.assignedElements()[0] as HTMLElement) ?? null;\n }\n\n private get _panelEl(): HTMLElement | null {\n return this.shadowRoot!.querySelector<HTMLElement>('[popover]');\n }\n\n private _getItems(): DropdownItem[] {\n const menuSlot = this.shadowRoot!.querySelector<HTMLSlotElement>('slot:not([name])');\n if (!menuSlot) return [];\n return (menuSlot.assignedElements() as DropdownItem[]).filter(\n (el) => el.tagName === tagName('dropdown-item').toUpperCase() && !el.disabled,\n );\n }\n\n private _getAllItems(): DropdownItem[] {\n const menuSlot = this.shadowRoot!.querySelector<HTMLSlotElement>('slot:not([name])');\n if (!menuSlot) return [];\n return (menuSlot.assignedElements() as DropdownItem[]).filter(\n (el) => el.tagName === tagName('dropdown-item').toUpperCase(),\n );\n }\n\n /** Every item in the menu tree, submenu items included (light-DOM descendants). */\n private _getAllItemsDeep(): DropdownItem[] {\n return [...this.querySelectorAll<DropdownItem>(tagName('dropdown-item'))];\n }\n\n /** The enabled items of the menu level `item` belongs to (its siblings, itself included). */\n private _levelItemsOf(item: DropdownItem): DropdownItem[] {\n const parent = item.parentElement;\n if (parent && parent.tagName === tagName('dropdown-item').toUpperCase()) {\n return (parent as DropdownItem).getSubmenuItems();\n }\n return this._getItems();\n }\n\n /** The parent item whose submenu contains `item`, or null at the root level. */\n private _parentItemOf(item: DropdownItem | null): DropdownItem | null {\n const parent = item?.parentElement;\n if (parent && parent.tagName === tagName('dropdown-item').toUpperCase()) {\n return parent as DropdownItem;\n }\n return null;\n }\n\n /** The enabled items of the level that currently holds focus (root level as fallback). */\n private _currentLevelItems(): DropdownItem[] {\n const current = this._getCurrentItem();\n return current ? this._levelItemsOf(current) : this._getItems();\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 // --- Public API ---\n\n show() {\n if (this.open || this.disabled) return;\n if (this.dispatchEvent(new ShowEvent())) this.open = true;\n }\n\n hide() {\n if (!this.open) return;\n if (this.dispatchEvent(new HideEvent())) this.open = false;\n }\n\n toggle() {\n if (this.open) this.hide();\n else this.show();\n }\n\n // --- Lifecycle ---\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._cancelHoverIntent({ forget: true });\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('open')) {\n void this._handleOpenChange();\n }\n }\n\n // --- Open/Close ---\n\n private async _handleOpenChange() {\n const panel = this._panelEl;\n if (!panel) return;\n\n const posOpts = {\n placement: this.placement,\n distance: this.distance,\n minWidth: this.minWidth,\n };\n\n if (this.open) {\n panel.showPopover();\n await this._floating.updatePosition(posOpts);\n if (!this.open) return;\n await this._floating.animateShow(panel, this._getDuration('--show-duration'));\n this._floating.startPositioning(posOpts);\n this._triggerEl?.setAttribute('aria-expanded', 'true');\n this.dispatchEvent(new AfterShowEvent());\n } else {\n this._floating.stopPositioning();\n this._closeAllSubmenus();\n this._triggerEl?.setAttribute('aria-expanded', 'false');\n await this._floating.animateHide(panel, this._getDuration('--hide-duration'));\n if (panel.matches(':popover-open')) panel.hidePopover();\n this.dispatchEvent(new AfterHideEvent());\n }\n }\n\n // --- Submenus ---\n\n private _hoverTimer = 0;\n private _hoverItem: DropdownItem | null = null;\n\n private async _openSubmenu(item: DropdownItem, { focus = false } = {}) {\n // A pending hover timer may fire after the menu closed or unmounted\n if (!this.open || !this.isConnected) return;\n // Only one submenu open per level. If a sibling we're about to close holds\n // the focus (keyboard focus inside it, mouse hovers another row), the panel\n // would vanish under the focus and drop it to <body> — move focus to `item`\n // first so the roving tabindex stays coherent.\n let stealsFocus = false;\n for (const sibling of this._levelItemsOf(item)) {\n if (sibling === item) continue;\n if (sibling.contains(document.activeElement)) stealsFocus = true;\n sibling.closeSubmenu();\n }\n if (stealsFocus && !focus) this._setActiveItem(item);\n item.openSubmenu();\n if (focus) {\n await item.updateComplete;\n const first = item.getSubmenuItems()[0];\n if (first) this._setActiveItem(first);\n }\n }\n\n /**\n * Drop the pending hover-open. A click or a key press is an explicit decision\n * that supersedes it — left armed, the timer would fire afterwards and reopen\n * a submenu the user just closed. By default `_hoverItem` is kept, so staying\n * on the same row doesn't re-arm it; `forget` also clears it, for when the\n * pointer leaves the row or the menu goes away.\n */\n private _cancelHoverIntent({ forget = false } = {}) {\n clearTimeout(this._hoverTimer);\n if (forget) this._hoverItem = null;\n }\n\n private _closeAllSubmenus() {\n this._cancelHoverIntent({ forget: true });\n for (const item of this._getAllItems()) item.closeSubmenu();\n }\n\n /**\n * The item targeted by a pointer event, via the composed path. Returns null\n * for hits inside an item's submenu panel that miss every nested item row\n * (e.g. the panel padding) — those must not read as hits on the parent item.\n */\n private _itemFromEvent(e: Event): DropdownItem | null {\n const path = e.composedPath();\n const itemTag = tagName('dropdown-item').toUpperCase();\n const item =\n path.find((n): n is DropdownItem => n instanceof Element && n.tagName === itemTag) ?? null;\n if (!item) return null;\n const row = item.shadowRoot?.querySelector('.item');\n if (row && !path.includes(row)) {\n // The path reached the item host but not its row. Reject only when the\n // hit actually landed inside this item's own submenu panel (its padding),\n // which must not read as a selection of the parent item.\n //\n // Why not `return null` outright: a click dispatched on the host element\n // — `item.click()`, a synthetic MouseEvent, or a testing tool (Cypress,\n // Playwright) whose coordinate hit-test resolves to the host rather than\n // the inner row — produces a path with neither the row nor the submenu in\n // it. Such programmatic activation should still select the item. Checking\n // for the submenu explicitly (instead of the absent row) is also\n // retargeting-independent, so it holds regardless of which shadow tree the\n // listener sits in.\n const submenu = item.shadowRoot?.querySelector('.submenu');\n if (submenu && path.includes(submenu)) return null;\n }\n return item;\n }\n\n // --- Focus management ---\n\n private _setActiveItem(item: DropdownItem) {\n const itemEl = item.shadowRoot!.querySelector<HTMLElement>('.item');\n if (!itemEl) return;\n\n // Reset all items, submenu items included — one roving tabindex tree-wide\n for (const i of this._getAllItemsDeep()) {\n const el = i.shadowRoot!.querySelector<HTMLElement>('.item');\n el?.setAttribute('tabindex', '-1');\n }\n\n itemEl.setAttribute('tabindex', '0');\n itemEl.focus();\n\n // APG: moving focus within a level collapses any sibling's open submenu, so\n // an unfocused parent never keeps aria-expanded=\"true\". The active item's\n // own submenu (one level down) is untouched — `item` is not its own sibling.\n for (const sibling of this._levelItemsOf(item)) {\n if (sibling !== item) sibling.closeSubmenu();\n }\n }\n\n private _focusFirstItem(items = this._getItems()) {\n if (items.length) this._setActiveItem(items[0]);\n }\n\n private _focusLastItem(items = this._getItems()) {\n if (items.length) this._setActiveItem(items[items.length - 1]);\n }\n\n private _getCurrentItem(): DropdownItem | null {\n return (\n this._getAllItemsDeep().find((item) => {\n const el = item.shadowRoot!.querySelector<HTMLElement>('.item');\n return el?.getAttribute('tabindex') === '0' && item.shadowRoot!.activeElement === el;\n }) ?? null\n );\n }\n\n private _focusNextItem() {\n const items = this._currentLevelItems();\n const current = this._getCurrentItem();\n const index = current ? items.indexOf(current) : -1;\n const next = items[(index + 1) % items.length];\n if (next) this._setActiveItem(next);\n }\n\n private _focusPreviousItem() {\n const items = this._currentLevelItems();\n const current = this._getCurrentItem();\n const index = current ? items.indexOf(current) : 0;\n const prev = items[(index - 1 + items.length) % items.length];\n if (prev) this._setActiveItem(prev);\n }\n\n // --- Typeahead ---\n\n private _handleTypeahead(key: string) {\n clearTimeout(this._typeaheadTimeout);\n this._typeaheadBuffer += key.toLowerCase();\n this._typeaheadTimeout = window.setTimeout(() => {\n this._typeaheadBuffer = '';\n }, 500);\n\n const items = this._currentLevelItems();\n const match = items.find((item) =>\n item.getTextLabel().toLowerCase().startsWith(this._typeaheadBuffer),\n );\n if (match) this._setActiveItem(match);\n }\n\n // --- Event handlers ---\n\n /**\n * Expose menu-button semantics on whatever element is slotted as the trigger,\n * and give the root `role=\"menu\"` panel an accessible name derived from it.\n * Runs on initial assignment and on any runtime trigger swap.\n */\n private _onTriggerSlotChange = () => {\n const trigger = this._triggerEl;\n if (!trigger) return;\n trigger.setAttribute('aria-haspopup', 'menu');\n trigger.setAttribute('aria-expanded', String(this.open));\n\n // Cross-shadow aria-labelledby to the trigger is impossible, so copy its\n // accessible name onto the menu as a plain aria-label.\n const name = trigger.getAttribute('aria-label') ?? trigger.textContent?.trim() ?? '';\n const menu = this.shadowRoot?.querySelector<HTMLElement>('[role=\"menu\"]');\n if (menu && name) menu.setAttribute('aria-label', name);\n };\n\n private _onTriggerClick = (e: MouseEvent) => {\n if (this.disabled) return;\n // The pointerdown that produced this click already light-dismissed the panel:\n // `open` still reads true, but the panel has left the top layer and the\n // queued `toggle` owns the close (and its `hide` event). Closing here too\n // would emit a second one. A keyboard activation never gets here — no\n // light-dismiss, so the panel is still open.\n if (this.open && !this._panelEl?.matches(':popover-open')) return;\n this.toggle();\n // Space/Enter on a native button dispatches click with detail=0; focus the\n // first item so the menu is keyboard-navigable immediately on open.\n if (this.open && e.detail === 0) {\n requestAnimationFrame(() => this._focusFirstItem());\n }\n };\n\n private _onTriggerKeyDown = (e: KeyboardEvent) => {\n if (this.disabled) return;\n\n if (e.key === 'Escape') {\n // A pointer-opened menu leaves focus on the trigger, so `_onPanelKeyDown`\n // never sees the key. The platform's popover light-dismiss normally covers\n // that, but it runs as a close request the page can suppress — any\n // `preventDefault()` on the Escape keydown (a common app-level shortcut\n // handler) leaves the menu stuck open. Close it here, and preventDefault\n // so the close request doesn't race our animated hide.\n if (!this.open) return;\n e.preventDefault();\n this.hide();\n // Focus is already on the trigger — nothing to restore.\n } else if (e.key === 'ArrowDown') {\n e.preventDefault();\n this.show();\n requestAnimationFrame(() => this._focusFirstItem());\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n this.show();\n requestAnimationFrame(() => this._focusLastItem());\n }\n };\n\n private _onPanelKeyDown = (e: KeyboardEvent) => {\n if (!MODIFIER_KEYS.has(e.key)) this._cancelHoverIntent();\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n this._focusNextItem();\n break;\n case 'ArrowUp':\n e.preventDefault();\n this._focusPreviousItem();\n break;\n case 'Home':\n e.preventDefault();\n this._focusFirstItem(this._currentLevelItems());\n break;\n case 'End':\n e.preventDefault();\n this._focusLastItem(this._currentLevelItems());\n break;\n case 'ArrowRight': {\n const current = this._getCurrentItem();\n if (current?.hasSubmenu) {\n e.preventDefault();\n void this._openSubmenu(current, { focus: true });\n }\n break;\n }\n case 'ArrowLeft': {\n const parentItem = this._parentItemOf(this._getCurrentItem());\n if (parentItem) {\n e.preventDefault();\n parentItem.closeSubmenu();\n this._setActiveItem(parentItem);\n }\n break;\n }\n case 'Escape': {\n // Close one level at a time: a submenu first, the menu itself at the root\n e.preventDefault();\n const parentItem = this._parentItemOf(this._getCurrentItem());\n if (parentItem) {\n parentItem.closeSubmenu();\n this._setActiveItem(parentItem);\n } else {\n this.hide();\n this._triggerEl?.focus();\n }\n break;\n }\n case 'Tab':\n // APG: Tab closes the menu (and any open submenus, via the hide path)\n // and lets focus move to the next tab-sequence element naturally — so\n // no preventDefault and no focus return to the trigger.\n this.hide();\n break;\n case 'Enter':\n case ' ':\n e.preventDefault();\n this._selectCurrentItem();\n break;\n default:\n if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) {\n this._handleTypeahead(e.key);\n }\n }\n };\n\n private _onItemClick = (e: Event) => {\n const item = this._itemFromEvent(e);\n if (!item || item.disabled) return;\n if (item.hasSubmenu) {\n // A parent item toggles its submenu instead of selecting\n this._cancelHoverIntent();\n if (item.submenuOpen) item.closeSubmenu();\n else void this._openSubmenu(item);\n return;\n }\n this._selectItem(item);\n };\n\n /** Open submenus on hover after a short intent delay; hovering a sibling closes them. */\n private _onPanelPointerOver = (e: PointerEvent) => {\n // Touch taps fire pointerover too — they must only toggle via click,\n // or the hover timer would reopen a submenu the tap just closed.\n if (e.pointerType !== 'mouse') return;\n const item = this._itemFromEvent(e);\n if (item === this._hoverItem) return;\n this._cancelHoverIntent();\n this._hoverItem = item;\n if (!item || item.disabled) return;\n this._hoverTimer = window.setTimeout(() => {\n void this._openSubmenu(item);\n }, 100);\n };\n\n private _onPanelPointerOut = (e: PointerEvent) => {\n // pointerout also fires between parts of the same row — a slotted icon and\n // the row padding (the related target arrives retargeted to the item host).\n // Forgetting the row there would let the next pointerover re-arm the timer\n // and reopen a submenu a click just closed. Leaving the row is pointerover's\n // call, or this one's once the pointer exits the panel.\n const to = e.relatedTarget;\n if (\n this._hoverItem &&\n to instanceof Element &&\n to.closest(tagName('dropdown-item')) === this._hoverItem\n ) {\n return;\n }\n this._cancelHoverIntent({ forget: true });\n };\n\n private _selectCurrentItem() {\n const current = this._getCurrentItem();\n if (!current) return;\n if (current.hasSubmenu) {\n void this._openSubmenu(current, { focus: true });\n } else {\n this._selectItem(current);\n }\n }\n\n private _selectItem(item: DropdownItem) {\n if (item.type === 'checkbox') {\n item.checked = !item.checked;\n }\n this.dispatchEvent(new DropdownSelectEvent(item));\n if (item.type !== 'checkbox') {\n this.hide();\n this._triggerEl?.focus();\n }\n }\n\n /**\n * Route `popover=\"auto\"` light-dismiss through the same close path as every\n * other user-driven close (trigger click, Escape, item selection), so the\n * cancelable `hide` event fires for it too. Assigning `open` here directly\n * would skip it and desync anything a consumer syncs on `hide`.\n *\n * Driving `open` as a property or attribute still closes the menu with only\n * `after-hide` — `l-dialog` mediates that path from `updated()`, this element\n * does not. A gap worth closing on its own.\n *\n * The platform makes a popover's close non-cancelable (`beforetoggle` is only\n * cancelable when opening), so a vetoed `hide` is honored by putting the panel\n * back. `toggle` is the right hook for that: it is queued after the hide has\n * actually been applied, whereas during `beforetoggle` the panel is still\n * `:popover-open` and `showPopover()` would be a no-op the pending hide undoes.\n */\n private _onToggle = (e: Event) => {\n const toggleEvent = e as ToggleEvent;\n // Our own close path flips `open` before hiding the popover, so this guard\n // keeps it from emitting a second `hide` for the same close.\n if (toggleEvent.newState !== 'closed' || !this.open) return;\n\n if (this.dispatchEvent(new HideEvent())) {\n this.open = false;\n return;\n }\n const panel = this._panelEl;\n if (panel?.isConnected) panel.showPopover();\n };\n\n override render() {\n return html`\n <div\n class=\"trigger\"\n @click=${this._onTriggerClick}\n @keydown=${this._onTriggerKeyDown}\n >\n <slot\n name=\"trigger\"\n @slotchange=${this._onTriggerSlotChange}\n ></slot>\n </div>\n <div\n popover=\"auto\"\n part=\"panel\"\n @keydown=${this._onPanelKeyDown}\n @click=${this._onItemClick}\n @pointerover=${this._onPanelPointerOver}\n @pointerout=${this._onPanelPointerOut}\n @toggle=${this._onToggle}\n >\n <slot name=\"header\"></slot>\n <div role=\"menu\">\n <slot></slot>\n </div>\n <slot name=\"footer\"></slot>\n </div>\n `;\n }\n}\n\n// Types `addEventListener('select', …)` as `DropdownSelectEvent` on this element\n// (the global event map can't be augmented for the colliding name `select`).\n// See the Tabs interface in tabs.ts for the full rationale and the gotchas.\nexport interface Dropdown {\n addEventListener<K extends keyof DropdownEventMap>(\n type: K,\n listener: (this: Dropdown, ev: DropdownEventMap[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 DropdownEventMap>(\n type: K,\n listener: (this: Dropdown, ev: DropdownEventMap[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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"}
1
+ {"version":3,"file":"dropdown.js","names":["unsafeCSS","rawStyles","toggleEvent","hostStyles","menuPanelStyles","_decorate","property","r","__decorate","b","host_styles_default","menu_panel_styles_default","styles","MODIFIER_KEYS","DropdownSelectEvent","item","Dropdown","LuxenElement","_args","PopoverController","#_open_accessor_storage","#_placement_accessor_storage","#_distance_accessor_storage","#_disabled_accessor_storage","trigger","name","menu","current","parentItem","to","tagName","HideEvent","panel","value","#_minWidth_accessor_storage","menuSlot","el","parent","ShowEvent","wasOpen","changed","posOpts","AfterShowEvent","AfterHideEvent","stealsFocus","sibling","focus","first","forget","path","itemTag","n","row","submenu","itemEl","i","items","next","prev","key","match"],"sources":["../../../src/html/elements/dropdown/dropdown.css?inline","../../../src/html/elements/dropdown/dropdown.ts"],"sourcesContent":["/* The panel itself and its <hr> separators: shared/styles/menu-panel.css. */\n\n:host {\n --padding: var(--l-popover-padding);\n\n --background: var(--l-color-surface-overlay);\n --border-radius: var(--l-popover-border-radius);\n --shadow: var(--l-popover-shadow);\n --show-duration: 150ms;\n --hide-duration: 150ms;\n\n display: inline-block;\n position: relative;\n}\n\n.trigger {\n display: contents;\n}\n","import { html, 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 { tagName } from '../../registry.js';\nimport type { DropdownItem } from '../dropdown-item/dropdown-item.js';\nimport { ShowEvent, AfterShowEvent, HideEvent, AfterHideEvent } from '../../events/index.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport menuPanelStyles from '../../shared/styles/menu-panel.styles.js';\nimport rawStyles from './dropdown.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/** Keys that decide nothing on their own — pressing one must not drop a pending hover. */\nconst MODIFIER_KEYS = new Set(['Shift', 'Control', 'Alt', 'AltGraph', 'Meta']);\n\n/** Fired when a dropdown item is selected. Bubbles; not composed. */\nexport class DropdownSelectEvent extends Event {\n readonly item: DropdownItem;\n constructor(item: DropdownItem) {\n super('select', { bubbles: true, composed: false, cancelable: false });\n this.item = item;\n }\n}\n\ninterface DropdownEventMap {\n select: DropdownSelectEvent;\n}\n\n/**\n * A dropdown menu anchored to a trigger element.\n *\n * @slot trigger - The element that triggers the dropdown.\n * @slot header - Optional content rendered above the menu items (e.g. a user profile row). Use an `<l-divider>` (or `<hr>`) after it to separate from items.\n * @slot - Menu content (`l-dropdown-item` elements). Drop an `<l-divider>` (or `<hr>`) between items to render a section separator, or an `<l-dropdown-label>` to caption a group of items. Nest items with `slot=\"submenu\"` inside an item to create a submenu.\n * @slot footer - Optional content rendered below the menu items (e.g. a version label or shortcut row). Use an `<l-divider>` (or `<hr>`) before it to separate from items.\n *\n * @csspart panel - The floating menu container.\n *\n * @cssproperty --background - Panel background color. Default `var(--l-color-surface-overlay)`.\n * @cssproperty --border-radius - Panel border radius. Default `var(--l-popover-border-radius)` (6px).\n * @cssproperty --padding - Panel inner padding. Default `var(--l-popover-padding)` (0.25rem). Slotted `<l-divider>` elements bleed by this amount on each side to span the panel edges.\n * @cssproperty --shadow - Panel box shadow. Default `var(--l-popover-shadow)`.\n * @cssproperty --show-duration - Show animation duration. Default `150ms`.\n * @cssproperty --hide-duration - Hide animation duration. Default `150ms`.\n *\n * @event show - Fired before the dropdown opens. Cancelable.\n * @event after-show - Fired after the open animation completes.\n * @event hide - Fired before the dropdown closes. Cancelable.\n * @event after-hide - Fired after the close animation completes.\n * @event select - Fired when an item is selected, including items nested in submenus. Bubbles. Properties: `item: DropdownItem`.\n *\n * @customElement l-dropdown\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below; no uninitialized properties.\nexport class Dropdown extends LuxenElement {\n static override styles = [hostStyles, menuPanelStyles, styles];\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._triggerEl,\n getFloatingElement: () => this._panelEl,\n getArrowElement: () => null,\n });\n\n private _typeaheadBuffer = '';\n private _typeaheadTimeout = 0;\n\n /** Whether the dropdown is open. */\n @property({ type: Boolean, reflect: true })\n accessor open = false;\n\n /** Preferred placement of the panel. */\n @property()\n accessor placement: Placement = 'bottom-start';\n\n /** Distance in pixels from the trigger. */\n @property({ type: Number })\n accessor distance = 4;\n\n /** Disables the dropdown trigger. */\n @property({ type: Boolean, reflect: true })\n accessor disabled = false;\n\n /**\n * Floor the panel's width at the trigger's width. Set to `trigger` so the\n * panel is never narrower than the trigger; it still grows with its content.\n * Useful for select-like triggers (a date-range or filter button) where the\n * panel should line up with the control. Re-applies if the trigger resizes\n * while open.\n */\n @property({ attribute: 'min-width' })\n accessor minWidth: 'trigger' | undefined;\n\n private get _triggerEl(): HTMLElement | null {\n const slot = this.shadowRoot!.querySelector<HTMLSlotElement>('.trigger slot');\n return (slot?.assignedElements()[0] as HTMLElement) ?? null;\n }\n\n private get _panelEl(): HTMLElement | null {\n return this.shadowRoot!.querySelector<HTMLElement>('[popover]');\n }\n\n private _getItems(): DropdownItem[] {\n const menuSlot = this.shadowRoot!.querySelector<HTMLSlotElement>('slot:not([name])');\n if (!menuSlot) return [];\n return (menuSlot.assignedElements() as DropdownItem[]).filter(\n (el) => el.tagName === tagName('dropdown-item').toUpperCase() && !el.disabled,\n );\n }\n\n private _getAllItems(): DropdownItem[] {\n const menuSlot = this.shadowRoot!.querySelector<HTMLSlotElement>('slot:not([name])');\n if (!menuSlot) return [];\n return (menuSlot.assignedElements() as DropdownItem[]).filter(\n (el) => el.tagName === tagName('dropdown-item').toUpperCase(),\n );\n }\n\n /** Every item in the menu tree, submenu items included (light-DOM descendants). */\n private _getAllItemsDeep(): DropdownItem[] {\n return [...this.querySelectorAll<DropdownItem>(tagName('dropdown-item'))];\n }\n\n /** The enabled items of the menu level `item` belongs to (its siblings, itself included). */\n private _levelItemsOf(item: DropdownItem): DropdownItem[] {\n const parent = item.parentElement;\n if (parent && parent.tagName === tagName('dropdown-item').toUpperCase()) {\n return (parent as DropdownItem).getSubmenuItems();\n }\n return this._getItems();\n }\n\n /** The parent item whose submenu contains `item`, or null at the root level. */\n private _parentItemOf(item: DropdownItem | null): DropdownItem | null {\n const parent = item?.parentElement;\n if (parent && parent.tagName === tagName('dropdown-item').toUpperCase()) {\n return parent as DropdownItem;\n }\n return null;\n }\n\n /** The enabled items of the level that currently holds focus (root level as fallback). */\n private _currentLevelItems(): DropdownItem[] {\n const current = this._getCurrentItem();\n return current ? this._levelItemsOf(current) : this._getItems();\n }\n\n // --- Public API ---\n\n show() {\n if (this.open || this.disabled) return;\n if (this.dispatchEvent(new ShowEvent())) this.open = true;\n }\n\n hide() {\n if (!this.open) return;\n if (this.dispatchEvent(new HideEvent())) this.open = false;\n }\n\n toggle() {\n if (this.open) this.hide();\n else this.show();\n }\n\n // --- Lifecycle ---\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._cancelHoverIntent({ forget: true });\n }\n\n override updated(changed: PropertyValues<this>) {\n // `open` reads as changed on the first update even when it stays false. A\n // menu that was never opened has nothing to close — running the close path\n // there would fire a stray `after-hide` and call into items that may not be\n // upgraded yet (l-dropdown defined before l-dropdown-item), which throws.\n // React to real transitions only.\n const wasOpen = changed.get('open');\n if (changed.has('open') && (this.open || wasOpen !== undefined)) {\n void this._handleOpenChange();\n }\n }\n\n // --- Open/Close ---\n\n private async _handleOpenChange() {\n const panel = this._panelEl;\n if (!panel) return;\n\n const posOpts = {\n placement: this.placement,\n distance: this.distance,\n minWidth: this.minWidth,\n };\n\n if (this.open) {\n panel.showPopover();\n await this._floating.updatePosition(posOpts);\n if (!this.open) return;\n await this._floating.animateShow(panel);\n this._floating.startPositioning(posOpts);\n this._triggerEl?.setAttribute('aria-expanded', 'true');\n this.dispatchEvent(new AfterShowEvent());\n } else {\n this._floating.stopPositioning();\n this._closeAllSubmenus();\n this._triggerEl?.setAttribute('aria-expanded', 'false');\n await this._floating.animateHide(panel);\n if (panel.matches(':popover-open')) panel.hidePopover();\n this.dispatchEvent(new AfterHideEvent());\n }\n }\n\n // --- Submenus ---\n\n private _hoverTimer = 0;\n private _hoverItem: DropdownItem | null = null;\n\n private async _openSubmenu(item: DropdownItem, { focus = false } = {}) {\n // A pending hover timer may fire after the menu closed or unmounted\n if (!this.open || !this.isConnected) return;\n // Only one submenu open per level. If a sibling we're about to close holds\n // the focus (keyboard focus inside it, mouse hovers another row), the panel\n // would vanish under the focus and drop it to <body> — move focus to `item`\n // first so the roving tabindex stays coherent.\n let stealsFocus = false;\n for (const sibling of this._levelItemsOf(item)) {\n if (sibling === item) continue;\n if (sibling.contains(document.activeElement)) stealsFocus = true;\n sibling.closeSubmenu();\n }\n if (stealsFocus && !focus) this._setActiveItem(item);\n item.openSubmenu();\n if (focus) {\n await item.updateComplete;\n const first = item.getSubmenuItems()[0];\n if (first) this._setActiveItem(first);\n }\n }\n\n /**\n * Drop the pending hover-open. A click or a key press is an explicit decision\n * that supersedes it — left armed, the timer would fire afterwards and reopen\n * a submenu the user just closed. By default `_hoverItem` is kept, so staying\n * on the same row doesn't re-arm it; `forget` also clears it, for when the\n * pointer leaves the row or the menu goes away.\n */\n private _cancelHoverIntent({ forget = false } = {}) {\n clearTimeout(this._hoverTimer);\n if (forget) this._hoverItem = null;\n }\n\n private _closeAllSubmenus() {\n this._cancelHoverIntent({ forget: true });\n for (const item of this._getAllItems()) item.closeSubmenu();\n }\n\n /**\n * The item targeted by a pointer event, via the composed path. Returns null\n * for hits inside an item's submenu panel that miss every nested item row\n * (e.g. the panel padding) — those must not read as hits on the parent item.\n */\n private _itemFromEvent(e: Event): DropdownItem | null {\n const path = e.composedPath();\n const itemTag = tagName('dropdown-item').toUpperCase();\n const item =\n path.find((n): n is DropdownItem => n instanceof Element && n.tagName === itemTag) ?? null;\n if (!item) return null;\n const row = item.shadowRoot?.querySelector('.item');\n if (row && !path.includes(row)) {\n // The path reached the item host but not its row. Reject only when the\n // hit actually landed inside this item's own submenu panel (its padding),\n // which must not read as a selection of the parent item.\n //\n // Why not `return null` outright: a click dispatched on the host element\n // — `item.click()`, a synthetic MouseEvent, or a testing tool (Cypress,\n // Playwright) whose coordinate hit-test resolves to the host rather than\n // the inner row — produces a path with neither the row nor the submenu in\n // it. Such programmatic activation should still select the item. Checking\n // for the submenu explicitly (instead of the absent row) is also\n // retargeting-independent, so it holds regardless of which shadow tree the\n // listener sits in.\n const submenu = item.shadowRoot?.querySelector('.submenu');\n if (submenu && path.includes(submenu)) return null;\n }\n return item;\n }\n\n // --- Focus management ---\n\n private _setActiveItem(item: DropdownItem) {\n const itemEl = item.shadowRoot!.querySelector<HTMLElement>('.item');\n if (!itemEl) return;\n\n // Reset all items, submenu items included — one roving tabindex tree-wide\n for (const i of this._getAllItemsDeep()) {\n const el = i.shadowRoot!.querySelector<HTMLElement>('.item');\n el?.setAttribute('tabindex', '-1');\n }\n\n itemEl.setAttribute('tabindex', '0');\n itemEl.focus();\n\n // APG: moving focus within a level collapses any sibling's open submenu, so\n // an unfocused parent never keeps aria-expanded=\"true\". The active item's\n // own submenu (one level down) is untouched — `item` is not its own sibling.\n for (const sibling of this._levelItemsOf(item)) {\n if (sibling !== item) sibling.closeSubmenu();\n }\n }\n\n private _focusFirstItem(items = this._getItems()) {\n if (items.length) this._setActiveItem(items[0]);\n }\n\n private _focusLastItem(items = this._getItems()) {\n if (items.length) this._setActiveItem(items[items.length - 1]);\n }\n\n private _getCurrentItem(): DropdownItem | null {\n return (\n this._getAllItemsDeep().find((item) => {\n const el = item.shadowRoot!.querySelector<HTMLElement>('.item');\n return el?.getAttribute('tabindex') === '0' && item.shadowRoot!.activeElement === el;\n }) ?? null\n );\n }\n\n private _focusNextItem() {\n const items = this._currentLevelItems();\n const current = this._getCurrentItem();\n const index = current ? items.indexOf(current) : -1;\n const next = items[(index + 1) % items.length];\n if (next) this._setActiveItem(next);\n }\n\n private _focusPreviousItem() {\n const items = this._currentLevelItems();\n const current = this._getCurrentItem();\n const index = current ? items.indexOf(current) : 0;\n const prev = items[(index - 1 + items.length) % items.length];\n if (prev) this._setActiveItem(prev);\n }\n\n // --- Typeahead ---\n\n private _handleTypeahead(key: string) {\n clearTimeout(this._typeaheadTimeout);\n this._typeaheadBuffer += key.toLowerCase();\n this._typeaheadTimeout = window.setTimeout(() => {\n this._typeaheadBuffer = '';\n }, 500);\n\n const items = this._currentLevelItems();\n const match = items.find((item) =>\n item.getTextLabel().toLowerCase().startsWith(this._typeaheadBuffer),\n );\n if (match) this._setActiveItem(match);\n }\n\n // --- Event handlers ---\n\n /**\n * Expose menu-button semantics on whatever element is slotted as the trigger,\n * and give the root `role=\"menu\"` panel an accessible name derived from it.\n * Runs on initial assignment and on any runtime trigger swap.\n */\n private _onTriggerSlotChange = () => {\n const trigger = this._triggerEl;\n if (!trigger) return;\n trigger.setAttribute('aria-haspopup', 'menu');\n trigger.setAttribute('aria-expanded', String(this.open));\n\n // Cross-shadow aria-labelledby to the trigger is impossible, so copy its\n // accessible name onto the menu as a plain aria-label.\n const name = trigger.getAttribute('aria-label') ?? trigger.textContent?.trim() ?? '';\n const menu = this.shadowRoot?.querySelector<HTMLElement>('[role=\"menu\"]');\n if (menu && name) menu.setAttribute('aria-label', name);\n };\n\n private _onTriggerClick = (e: MouseEvent) => {\n if (this.disabled) return;\n // The pointerdown that produced this click already light-dismissed the panel:\n // `open` still reads true, but the panel has left the top layer and the\n // queued `toggle` owns the close (and its `hide` event). Closing here too\n // would emit a second one. A keyboard activation never gets here — no\n // light-dismiss, so the panel is still open.\n if (this.open && !this._panelEl?.matches(':popover-open')) return;\n this.toggle();\n // Space/Enter on a native button dispatches click with detail=0; focus the\n // first item so the menu is keyboard-navigable immediately on open.\n if (this.open && e.detail === 0) {\n requestAnimationFrame(() => this._focusFirstItem());\n }\n };\n\n private _onTriggerKeyDown = (e: KeyboardEvent) => {\n if (this.disabled) return;\n\n if (e.key === 'Escape') {\n // A pointer-opened menu leaves focus on the trigger, so `_onPanelKeyDown`\n // never sees the key. The platform's popover light-dismiss normally covers\n // that, but it runs as a close request the page can suppress — any\n // `preventDefault()` on the Escape keydown (a common app-level shortcut\n // handler) leaves the menu stuck open. Close it here, and preventDefault\n // so the close request doesn't race our animated hide.\n if (!this.open) return;\n e.preventDefault();\n this.hide();\n // Focus is already on the trigger — nothing to restore.\n } else if (e.key === 'ArrowDown') {\n e.preventDefault();\n this.show();\n requestAnimationFrame(() => this._focusFirstItem());\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n this.show();\n requestAnimationFrame(() => this._focusLastItem());\n }\n };\n\n private _onPanelKeyDown = (e: KeyboardEvent) => {\n if (!MODIFIER_KEYS.has(e.key)) this._cancelHoverIntent();\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n this._focusNextItem();\n break;\n case 'ArrowUp':\n e.preventDefault();\n this._focusPreviousItem();\n break;\n case 'Home':\n e.preventDefault();\n this._focusFirstItem(this._currentLevelItems());\n break;\n case 'End':\n e.preventDefault();\n this._focusLastItem(this._currentLevelItems());\n break;\n case 'ArrowRight': {\n const current = this._getCurrentItem();\n if (current?.hasSubmenu) {\n e.preventDefault();\n void this._openSubmenu(current, { focus: true });\n }\n break;\n }\n case 'ArrowLeft': {\n const parentItem = this._parentItemOf(this._getCurrentItem());\n if (parentItem) {\n e.preventDefault();\n parentItem.closeSubmenu();\n this._setActiveItem(parentItem);\n }\n break;\n }\n case 'Escape': {\n // Close one level at a time: a submenu first, the menu itself at the root\n e.preventDefault();\n const parentItem = this._parentItemOf(this._getCurrentItem());\n if (parentItem) {\n parentItem.closeSubmenu();\n this._setActiveItem(parentItem);\n } else {\n this.hide();\n this._triggerEl?.focus();\n }\n break;\n }\n case 'Tab':\n // APG: Tab closes the menu (and any open submenus, via the hide path)\n // and lets focus move to the next tab-sequence element naturally — so\n // no preventDefault and no focus return to the trigger.\n this.hide();\n break;\n case 'Enter':\n case ' ':\n e.preventDefault();\n this._selectCurrentItem();\n break;\n default:\n if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) {\n this._handleTypeahead(e.key);\n }\n }\n };\n\n private _onItemClick = (e: Event) => {\n const item = this._itemFromEvent(e);\n if (!item || item.disabled) return;\n if (item.hasSubmenu) {\n // A parent item toggles its submenu instead of selecting\n this._cancelHoverIntent();\n if (item.submenuOpen) item.closeSubmenu();\n else void this._openSubmenu(item);\n return;\n }\n this._selectItem(item);\n };\n\n /** Open submenus on hover after a short intent delay; hovering a sibling closes them. */\n private _onPanelPointerOver = (e: PointerEvent) => {\n // Touch taps fire pointerover too — they must only toggle via click,\n // or the hover timer would reopen a submenu the tap just closed.\n if (e.pointerType !== 'mouse') return;\n const item = this._itemFromEvent(e);\n if (item === this._hoverItem) return;\n this._cancelHoverIntent();\n this._hoverItem = item;\n if (!item || item.disabled) return;\n this._hoverTimer = window.setTimeout(() => {\n void this._openSubmenu(item);\n }, 100);\n };\n\n private _onPanelPointerOut = (e: PointerEvent) => {\n // pointerout also fires between parts of the same row — a slotted icon and\n // the row padding (the related target arrives retargeted to the item host).\n // Forgetting the row there would let the next pointerover re-arm the timer\n // and reopen a submenu a click just closed. Leaving the row is pointerover's\n // call, or this one's once the pointer exits the panel.\n const to = e.relatedTarget;\n if (\n this._hoverItem &&\n to instanceof Element &&\n to.closest(tagName('dropdown-item')) === this._hoverItem\n ) {\n return;\n }\n this._cancelHoverIntent({ forget: true });\n };\n\n private _selectCurrentItem() {\n const current = this._getCurrentItem();\n if (!current) return;\n if (current.hasSubmenu) {\n void this._openSubmenu(current, { focus: true });\n } else {\n this._selectItem(current);\n }\n }\n\n private _selectItem(item: DropdownItem) {\n if (item.type === 'checkbox') {\n item.checked = !item.checked;\n }\n this.dispatchEvent(new DropdownSelectEvent(item));\n if (item.type !== 'checkbox') {\n this.hide();\n this._triggerEl?.focus();\n }\n }\n\n /**\n * Route `popover=\"auto\"` light-dismiss through the same close path as every\n * other user-driven close (trigger click, Escape, item selection), so the\n * cancelable `hide` event fires for it too. Assigning `open` here directly\n * would skip it and desync anything a consumer syncs on `hide`.\n *\n * Driving `open` as a property or attribute still closes the menu with only\n * `after-hide` — `l-dialog` mediates that path from `updated()`, this element\n * does not. A gap worth closing on its own.\n *\n * The platform makes a popover's close non-cancelable (`beforetoggle` is only\n * cancelable when opening), so a vetoed `hide` is honored by putting the panel\n * back. `toggle` is the right hook for that: it is queued after the hide has\n * actually been applied, whereas during `beforetoggle` the panel is still\n * `:popover-open` and `showPopover()` would be a no-op the pending hide undoes.\n */\n private _onToggle = (e: Event) => {\n const toggleEvent = e as ToggleEvent;\n // Our own close path flips `open` before hiding the popover, so this guard\n // keeps it from emitting a second `hide` for the same close.\n if (toggleEvent.newState !== 'closed' || !this.open) return;\n\n if (this.dispatchEvent(new HideEvent())) {\n this.open = false;\n return;\n }\n const panel = this._panelEl;\n if (panel?.isConnected) panel.showPopover();\n };\n\n override render() {\n return html`\n <div\n class=\"trigger\"\n @click=${this._onTriggerClick}\n @keydown=${this._onTriggerKeyDown}\n >\n <slot\n name=\"trigger\"\n @slotchange=${this._onTriggerSlotChange}\n ></slot>\n </div>\n <div\n popover=\"auto\"\n part=\"panel\"\n @keydown=${this._onPanelKeyDown}\n @click=${this._onItemClick}\n @pointerover=${this._onPanelPointerOver}\n @pointerout=${this._onPanelPointerOut}\n @toggle=${this._onToggle}\n >\n <slot name=\"header\"></slot>\n <div role=\"menu\">\n <slot></slot>\n </div>\n <slot name=\"footer\"></slot>\n </div>\n `;\n }\n}\n\n// Types `addEventListener('select', …)` as `DropdownSelectEvent` on this element\n// (the global event map can't be augmented for the colliding name `select`).\n// See the Tabs interface in tabs.ts for the full rationale and the gotchas.\nexport interface Dropdown {\n addEventListener<K extends keyof DropdownEventMap>(\n type: K,\n listener: (this: Dropdown, ev: DropdownEventMap[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 DropdownEventMap>(\n type: K,\n listener: (this: Dropdown, ev: DropdownEventMap[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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"}
@@ -52,7 +52,6 @@ export declare class DropdownItem extends LuxenElement {
52
52
  /** Closes this item's submenu panel, including any open nested submenus. */
53
53
  closeSubmenu(): void;
54
54
  updated(changed: PropertyValues<this>): void;
55
- private _getDuration;
56
55
  private _handleSubmenuOpenChange;
57
56
  private _renderCheck;
58
57
  private _onSubmenuSlotChange;
@@ -1 +1 @@
1
- {"version":3,"file":"dropdown-item.d.ts","sourceRoot":"","sources":["../../../src/html/elements/dropdown-item/dropdown-item.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAU7D;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,YAAa,SAAQ,YAAY;IAC5C,OAAgB,MAAM,4BAAwB;IAE9C,OAAO,CAAC,gBAAgB,CAIrB;IAEH,2CAA2C;IAE3C,QAAQ,CAAC,KAAK,SAAM;IAEpB,yBAAyB;IAEzB,QAAQ,CAAC,QAAQ,UAAS;IAE1B,gDAAgD;IAEhD,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAY;IAEhD,wEAAwE;IAExE,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,SAAS,CAAa;IAExD,4CAA4C;IAE5C,QAAQ,CAAC,OAAO,UAAS;IAEzB,+EAA+E;IAE/E,QAAQ,CAAC,WAAW,UAAS;IAE7B;;;;;OAKG;IAEH,QAAQ,CAAC,cAAc,EAAE,OAAO,GAAG,KAAK,CAAW;IAGnD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC,2DAA2D;IAC3D,IAAI,UAAU,IAAI,OAAO,CAExB;IAEQ,iBAAiB,SAQzB;IAED,OAAO,KAAK,UAAU,GAErB;IAED,2EAA2E;IAC3E,YAAY,IAAI,MAAM,CAQrB;IAED,uFAAuF;IACvF,eAAe,IAAI,YAAY,EAAE,CAMhC;IAED,oEAAoE;IACpE,WAAW,SAGV;IAED,4EAA4E;IAC5E,YAAY,SAGX;IAEQ,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC,QAI7C;IAED,OAAO,CAAC,YAAY;YAKN,wBAAwB;IA2BtC,OAAO,CAAC,YAAY;IAwBpB,OAAO,CAAC,oBAAoB,CAK1B;IAEO,MAAM,oCAsDd;CACF"}
1
+ {"version":3,"file":"dropdown-item.d.ts","sourceRoot":"","sources":["../../../src/html/elements/dropdown-item/dropdown-item.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAW7D;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,YAAa,SAAQ,YAAY;IAC5C,OAAgB,MAAM,4BAAyC;IAE/D,OAAO,CAAC,gBAAgB,CAIrB;IAEH,2CAA2C;IAE3C,QAAQ,CAAC,KAAK,SAAM;IAEpB,yBAAyB;IAEzB,QAAQ,CAAC,QAAQ,UAAS;IAE1B,gDAAgD;IAEhD,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAY;IAEhD,wEAAwE;IAExE,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,SAAS,CAAa;IAExD,4CAA4C;IAE5C,QAAQ,CAAC,OAAO,UAAS;IAEzB,+EAA+E;IAE/E,QAAQ,CAAC,WAAW,UAAS;IAE7B;;;;;OAKG;IAEH,QAAQ,CAAC,cAAc,EAAE,OAAO,GAAG,KAAK,CAAW;IAGnD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC,2DAA2D;IAC3D,IAAI,UAAU,IAAI,OAAO,CAExB;IAEQ,iBAAiB,SAQzB;IAED,OAAO,KAAK,UAAU,GAErB;IAED,2EAA2E;IAC3E,YAAY,IAAI,MAAM,CAQrB;IAED,uFAAuF;IACvF,eAAe,IAAI,YAAY,EAAE,CAMhC;IAED,oEAAoE;IACpE,WAAW,SAGV;IAED,4EAA4E;IAC5E,YAAY,SAGX;IAEQ,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC,QAI7C;YAEa,wBAAwB;IA2BtC,OAAO,CAAC,YAAY;IAwBpB,OAAO,CAAC,oBAAoB,CAK1B;IAEO,MAAM,oCAsDd;CACF"}
@@ -1,4 +1,4 @@
1
- import{i as e}from"../../chunks/lit.js";import{LuxenElement as t}from"../../shared/luxen-element.js";import{n,r,t as i}from"../../chunks/decorate.js";import{a,t as o}from"../../chunks/lit-html.js";import{PopoverController as s}from"../../shared/controllers/popover.js";import{tagName as c}from"../../registry.js";import l from"../../shared/styles/host.styles.js";var u=e(`:host{display:block}:host([disabled]){pointer-events:none;opacity:.5}.item{box-sizing:border-box;cursor:pointer;min-block-size:36px;color:var(--l-color-text-primary);white-space:nowrap;text-align:start;border-radius:4px;outline:none;align-items:center;gap:8px;padding:.375rem .5rem;font-size:.875rem;line-height:1.5;display:flex}.item:focus-visible{background:var(--l-color-bg-state-hover);outline:2px solid var(--l-focus-ring);outline-offset:-2px}@media (hover:hover){.item:hover{background:var(--l-color-bg-state-hover)}}:host([submenu-open]) .item{background:var(--l-color-bg-state-hover)}:host([variant=destructive]) .item{color:var(--l-color-text-danger)}:host([variant=destructive]) .item:focus-visible{background:var(--l-color-bg-fill-danger-soft);outline-color:var(--l-color-text-danger)}@media (hover:hover){:host([variant=destructive]) .item:hover{background:var(--l-color-bg-fill-danger-soft)}}.check{flex-shrink:0;width:16px;display:flex}.submenu-indicator{background-color:currentColor;flex-shrink:0;block-size:16px;inline-size:16px;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6 4l4 4-4 4' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 50%/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6 4l4 4-4 4' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 50%/contain no-repeat}:host(:dir(rtl)) .submenu-indicator{scale:-1 1}.submenu{inset:unset;box-sizing:border-box;width:max-content;padding:var(--padding,.25rem);border:1px solid var(--l-color-border-overlay);border-radius:var(--border-radius,6px);background:var(--background,var(--l-color-surface-overlay));color:var(--l-color-text-primary);box-shadow:var(--shadow,0 4px 6px -1px #00000014, 0 2px 4px -2px #0000000f);margin:0;font-size:.875rem;line-height:1.5;overflow:visible}slot[name=submenu]::slotted(hr){height:1px;margin-block:var(--padding,.25rem);margin-inline:calc(var(--padding,.25rem) * -1);background:var(--l-color-divider);border:0;display:block}:host(:not([checked])) .check svg{visibility:hidden}::slotted([slot=prefix]),::slotted([slot=suffix]){flex-shrink:0;display:flex}.label{flex:1}`),d=class extends t{constructor(...e){super(...e),this._submenuFloating=new s(this,{getTriggerElement:()=>this,getFloatingElement:()=>this._submenuEl,getArrowElement:()=>null}),this.#e=``,this.#t=!1,this.#n=`normal`,this.#r=void 0,this.#i=!1,this.#a=!1,this.#o=`start`,this.#s=!1,this._onSubmenuSlotChange=e=>{let t=e.target;this._hasSubmenu=t.assignedElements().some(e=>e.tagName===c(`dropdown-item`).toUpperCase())}}static{this.styles=[l,u]}#e;get value(){return this.#e}set value(e){this.#e=e}#t;get disabled(){return this.#t}set disabled(e){this.#t=e}#n;get type(){return this.#n}set type(e){this.#n=e}#r;get variant(){return this.#r}set variant(e){this.#r=e}#i;get checked(){return this.#i}set checked(e){this.#i=e}#a;get submenuOpen(){return this.#a}set submenuOpen(e){this.#a=e}#o;get checkPlacement(){return this.#o}set checkPlacement(e){this.#o=e}#s;get _hasSubmenu(){return this.#s}set _hasSubmenu(e){this.#s=e}get hasSubmenu(){return this._hasSubmenu}connectedCallback(){super.connectedCallback(),this.hasAttribute(`role`)||this.setAttribute(`role`,`none`)}get _submenuEl(){return this.shadowRoot.querySelector(`.submenu`)}getTextLabel(){let e=this.shadowRoot?.querySelector(`slot:not([name])`);return e?e.assignedNodes({flatten:!0}).map(e=>e.textContent??``).join(``).trim():(this.textContent??``).trim()}getSubmenuItems(){let e=this.shadowRoot?.querySelector(`slot[name="submenu"]`);return e?e.assignedElements().filter(e=>e.tagName===c(`dropdown-item`).toUpperCase()&&!e.disabled):[]}openSubmenu(){!this._hasSubmenu||this.disabled||this.submenuOpen||(this.submenuOpen=!0)}closeSubmenu(){for(let e of this.getSubmenuItems())e.closeSubmenu();this.submenuOpen&&=!1}updated(e){e.has(`submenuOpen`)&&this._handleSubmenuOpenChange()}_getDuration(e){let t=parseFloat(getComputedStyle(this).getPropertyValue(e));return Number.isNaN(t)?150:t}async _handleSubmenuOpenChange(){let e=this._submenuEl;if(!e)return;let t={placement:getComputedStyle(this).direction===`rtl`?`left-start`:`right-start`,distance:0,skidding:-parseFloat(getComputedStyle(e).paddingTop||`0`)};if(this.submenuOpen){if(!this.isConnected||(e.showPopover(),await this._submenuFloating.updatePosition(t),!this.submenuOpen))return;await this._submenuFloating.animateShow(e,this._getDuration(`--show-duration`)),this._submenuFloating.startPositioning(t)}else{if(!e.matches(`:popover-open`))return;this._submenuFloating.stopPositioning(),await this._submenuFloating.animateHide(e,this._getDuration(`--hide-duration`)),e.matches(`:popover-open`)&&e.hidePopover()}}_renderCheck(){return a`
1
+ import{i as e}from"../../chunks/lit.js";import{LuxenElement as t}from"../../shared/luxen-element.js";import{n,r,t as i}from"../../chunks/decorate.js";import{a,t as o}from"../../chunks/lit-html.js";import{PopoverController as s}from"../../shared/controllers/popover.js";import{tagName as c}from"../../registry.js";import l from"../../shared/styles/host.styles.js";import u from"../../shared/styles/menu-panel.styles.js";var d=e(`:host{display:block}:host([disabled]){pointer-events:none;opacity:.5}.item{align-items:center;gap:var(--l-menu-item-gap);min-block-size:var(--l-menu-item-min-height);padding:var(--l-menu-item-padding-block) var(--l-menu-item-padding-inline);cursor:pointer;border-radius:var(--l-menu-item-border-radius);font-size:var(--l-popover-font-size);line-height:var(--l-popover-line-height);color:var(--l-color-text-primary);white-space:nowrap;text-align:start;outline:none;display:flex}.item:focus-visible{background:var(--l-color-bg-state-hover);outline:2px solid var(--l-focus-ring);outline-offset:-2px}@media (hover:hover){.item:hover{background:var(--l-color-bg-state-hover)}}:host([submenu-open]) .item{background:var(--l-color-bg-state-hover)}:host([variant=destructive]) .item{color:var(--l-color-text-danger)}:host([variant=destructive]) .item:focus-visible{background:var(--l-color-bg-fill-danger-soft);outline-color:var(--l-color-text-danger)}@media (hover:hover){:host([variant=destructive]) .item:hover{background:var(--l-color-bg-fill-danger-soft)}}.check{flex-shrink:0;width:16px;display:flex}.submenu-indicator{background-color:currentColor;flex-shrink:0;block-size:16px;inline-size:16px;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6 4l4 4-4 4' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 50%/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6 4l4 4-4 4' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 50%/contain no-repeat}:host(:dir(rtl)) .submenu-indicator{scale:-1 1}:host(:not([checked])) .check svg{visibility:hidden}::slotted([slot=prefix]),::slotted([slot=suffix]){flex-shrink:0;display:flex}.label{flex:1}`),f=class extends t{constructor(...e){super(...e),this._submenuFloating=new s(this,{getTriggerElement:()=>this,getFloatingElement:()=>this._submenuEl,getArrowElement:()=>null}),this.#e=``,this.#t=!1,this.#n=`normal`,this.#r=void 0,this.#i=!1,this.#a=!1,this.#o=`start`,this.#s=!1,this._onSubmenuSlotChange=e=>{let t=e.target;this._hasSubmenu=t.assignedElements().some(e=>e.tagName===c(`dropdown-item`).toUpperCase())}}static{this.styles=[l,u,d]}#e;get value(){return this.#e}set value(e){this.#e=e}#t;get disabled(){return this.#t}set disabled(e){this.#t=e}#n;get type(){return this.#n}set type(e){this.#n=e}#r;get variant(){return this.#r}set variant(e){this.#r=e}#i;get checked(){return this.#i}set checked(e){this.#i=e}#a;get submenuOpen(){return this.#a}set submenuOpen(e){this.#a=e}#o;get checkPlacement(){return this.#o}set checkPlacement(e){this.#o=e}#s;get _hasSubmenu(){return this.#s}set _hasSubmenu(e){this.#s=e}get hasSubmenu(){return this._hasSubmenu}connectedCallback(){super.connectedCallback(),this.hasAttribute(`role`)||this.setAttribute(`role`,`none`)}get _submenuEl(){return this.shadowRoot.querySelector(`.submenu`)}getTextLabel(){let e=this.shadowRoot?.querySelector(`slot:not([name])`);return e?e.assignedNodes({flatten:!0}).map(e=>e.textContent??``).join(``).trim():(this.textContent??``).trim()}getSubmenuItems(){let e=this.shadowRoot?.querySelector(`slot[name="submenu"]`);return e?e.assignedElements().filter(e=>e.tagName===c(`dropdown-item`).toUpperCase()&&!e.disabled):[]}openSubmenu(){!this._hasSubmenu||this.disabled||this.submenuOpen||(this.submenuOpen=!0)}closeSubmenu(){for(let e of this.getSubmenuItems())e.closeSubmenu();this.submenuOpen&&=!1}updated(e){e.has(`submenuOpen`)&&this._handleSubmenuOpenChange()}async _handleSubmenuOpenChange(){let e=this._submenuEl;if(!e)return;let t={placement:getComputedStyle(this).direction===`rtl`?`left-start`:`right-start`,distance:0,skidding:-parseFloat(getComputedStyle(e).paddingTop||`0`)};if(this.submenuOpen){if(!this.isConnected||(e.showPopover(),await this._submenuFloating.updatePosition(t),!this.submenuOpen))return;await this._submenuFloating.animateShow(e),this._submenuFloating.startPositioning(t)}else{if(!e.matches(`:popover-open`))return;this._submenuFloating.stopPositioning(),await this._submenuFloating.animateHide(e),e.matches(`:popover-open`)&&e.hidePopover()}}_renderCheck(){return a`
2
2
  <span
3
3
  class="check"
4
4
  aria-hidden="true"
@@ -63,5 +63,5 @@ import{i as e}from"../../chunks/lit.js";import{LuxenElement as t}from"../../shar
63
63
  @slotchange=${this._onSubmenuSlotChange}
64
64
  ></slot>
65
65
  </div>
66
- `}};i([r()],d.prototype,`value`,null),i([r({type:Boolean,reflect:!0})],d.prototype,`disabled`,null),i([r()],d.prototype,`type`,null),i([r({reflect:!0})],d.prototype,`variant`,null),i([r({type:Boolean,reflect:!0})],d.prototype,`checked`,null),i([r({type:Boolean,reflect:!0,attribute:`submenu-open`})],d.prototype,`submenuOpen`,null),i([r({reflect:!0,attribute:`check-placement`})],d.prototype,`checkPlacement`,null),i([n()],d.prototype,`_hasSubmenu`,null);export{d as DropdownItem};
66
+ `}};i([r()],f.prototype,`value`,null),i([r({type:Boolean,reflect:!0})],f.prototype,`disabled`,null),i([r()],f.prototype,`type`,null),i([r({reflect:!0})],f.prototype,`variant`,null),i([r({type:Boolean,reflect:!0})],f.prototype,`checked`,null),i([r({type:Boolean,reflect:!0,attribute:`submenu-open`})],f.prototype,`submenuOpen`,null),i([r({reflect:!0,attribute:`check-placement`})],f.prototype,`checkPlacement`,null),i([n()],f.prototype,`_hasSubmenu`,null);export{f as DropdownItem};
67
67
  //# sourceMappingURL=dropdown-item.js.map