@vielzeug/refine 26.10.6 → 26.10.9

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 (326) hide show
  1. package/README.md +6 -0
  2. package/dist/accordion-item.cjs +2 -2
  3. package/dist/accordion-item.cjs.map +1 -1
  4. package/dist/accordion-item.js +6 -6
  5. package/dist/accordion-item.js.map +1 -1
  6. package/dist/accordion.cjs +2 -2
  7. package/dist/accordion.cjs.map +1 -1
  8. package/dist/accordion.js +2 -2
  9. package/dist/accordion.js.map +1 -1
  10. package/dist/alert.cjs +2 -2
  11. package/dist/alert.cjs.map +1 -1
  12. package/dist/alert.js +2 -2
  13. package/dist/alert.js.map +1 -1
  14. package/dist/avatar.cjs +3 -3
  15. package/dist/avatar.cjs.map +1 -1
  16. package/dist/avatar.js +2 -2
  17. package/dist/avatar.js.map +1 -1
  18. package/dist/badge.cjs +2 -2
  19. package/dist/badge.cjs.map +1 -1
  20. package/dist/badge.js +2 -2
  21. package/dist/badge.js.map +1 -1
  22. package/dist/box.cjs +2 -2
  23. package/dist/box.cjs.map +1 -1
  24. package/dist/box.js +2 -2
  25. package/dist/box.js.map +1 -1
  26. package/dist/button-group.cjs +2 -2
  27. package/dist/button-group.cjs.map +1 -1
  28. package/dist/button-group.js +2 -2
  29. package/dist/button-group.js.map +1 -1
  30. package/dist/button.cjs +9 -9
  31. package/dist/button.cjs.map +1 -1
  32. package/dist/button.js +8 -8
  33. package/dist/button.js.map +1 -1
  34. package/dist/card.cjs +2 -2
  35. package/dist/card.cjs.map +1 -1
  36. package/dist/card.js +2 -2
  37. package/dist/card.js.map +1 -1
  38. package/dist/carousel.cjs +6 -6
  39. package/dist/carousel.cjs.map +1 -1
  40. package/dist/carousel.js +6 -6
  41. package/dist/carousel.js.map +1 -1
  42. package/dist/chat-message.cjs +4 -4
  43. package/dist/chat-message.cjs.map +1 -1
  44. package/dist/chat-message.js +4 -4
  45. package/dist/chat-message.js.map +1 -1
  46. package/dist/checkbox-group.cjs +1 -1
  47. package/dist/checkbox-group.cjs.map +1 -1
  48. package/dist/checkbox-group.js +1 -1
  49. package/dist/checkbox-group.js.map +1 -1
  50. package/dist/checkbox.cjs +1 -1
  51. package/dist/checkbox.cjs.map +1 -1
  52. package/dist/checkbox.js +1 -1
  53. package/dist/checkbox.js.map +1 -1
  54. package/dist/chip.cjs +8 -8
  55. package/dist/chip.cjs.map +1 -1
  56. package/dist/chip.js +8 -8
  57. package/dist/chip.js.map +1 -1
  58. package/dist/combobox.cjs +8 -8
  59. package/dist/combobox.cjs.map +1 -1
  60. package/dist/combobox.js +11 -11
  61. package/dist/combobox.js.map +1 -1
  62. package/dist/content/avatar/avatar.d.ts +2 -1
  63. package/dist/content/avatar/avatar.d.ts.map +1 -1
  64. package/dist/content/card/card.d.ts +6 -3
  65. package/dist/content/card/card.d.ts.map +1 -1
  66. package/dist/content/carousel/carousel.d.ts +5 -3
  67. package/dist/content/carousel/carousel.d.ts.map +1 -1
  68. package/dist/content/chat-message/chat-message.d.ts +2 -1
  69. package/dist/content/chat-message/chat-message.d.ts.map +1 -1
  70. package/dist/content/copy-command/copy-command.d.ts +1 -1
  71. package/dist/content/copy-command/copy-command.d.ts.map +1 -1
  72. package/dist/content/keyboard-key/keyboard-key.d.ts +2 -1
  73. package/dist/content/keyboard-key/keyboard-key.d.ts.map +1 -1
  74. package/dist/content/list/list.d.ts +2 -1
  75. package/dist/content/list/list.d.ts.map +1 -1
  76. package/dist/content/marquee/marquee.d.ts +3 -2
  77. package/dist/content/marquee/marquee.d.ts.map +1 -1
  78. package/dist/content/pagination/pagination.d.ts +1 -1
  79. package/dist/content/pagination/pagination.d.ts.map +1 -1
  80. package/dist/content/qr-code/qr-code.d.ts +0 -41
  81. package/dist/content/qr-code/qr-code.d.ts.map +1 -1
  82. package/dist/content/separator/separator.d.ts +1 -1
  83. package/dist/content/separator/separator.d.ts.map +1 -1
  84. package/dist/content/stepper/step.d.ts +1 -1
  85. package/dist/content/stepper/step.d.ts.map +1 -1
  86. package/dist/content/table/table.d.ts +2 -1
  87. package/dist/content/table/table.d.ts.map +1 -1
  88. package/dist/content/text/text.d.ts +13 -6
  89. package/dist/content/text/text.d.ts.map +1 -1
  90. package/dist/cookie-banner.cjs +1 -1
  91. package/dist/cookie-banner.cjs.map +1 -1
  92. package/dist/cookie-banner.js +1 -1
  93. package/dist/cookie-banner.js.map +1 -1
  94. package/dist/copy-command.cjs +6 -6
  95. package/dist/copy-command.cjs.map +1 -1
  96. package/dist/copy-command.js +7 -7
  97. package/dist/copy-command.js.map +1 -1
  98. package/dist/core/checkable.cjs +1 -1
  99. package/dist/core/checkable.cjs.map +1 -1
  100. package/dist/core/checkable.d.ts +6 -7
  101. package/dist/core/checkable.d.ts.map +1 -1
  102. package/dist/core/checkable.js +1 -1
  103. package/dist/core/checkable.js.map +1 -1
  104. package/dist/core/choice-field.cjs +1 -1
  105. package/dist/core/choice-field.cjs.map +1 -1
  106. package/dist/core/choice-field.d.ts +6 -6
  107. package/dist/core/choice-field.d.ts.map +1 -1
  108. package/dist/core/choice-field.js +1 -1
  109. package/dist/core/choice-field.js.map +1 -1
  110. package/dist/core/text-field.cjs +1 -1
  111. package/dist/core/text-field.cjs.map +1 -1
  112. package/dist/core/text-field.d.ts +8 -8
  113. package/dist/core/text-field.d.ts.map +1 -1
  114. package/dist/core/text-field.js +1 -1
  115. package/dist/core/text-field.js.map +1 -1
  116. package/dist/counter.cjs +2 -2
  117. package/dist/counter.cjs.map +1 -1
  118. package/dist/counter.js +2 -2
  119. package/dist/counter.js.map +1 -1
  120. package/dist/custom-elements.json +5184 -5047
  121. package/dist/datagrid.cjs +47 -47
  122. package/dist/datagrid.cjs.map +1 -1
  123. package/dist/datagrid.js +51 -51
  124. package/dist/datagrid.js.map +1 -1
  125. package/dist/date-picker.cjs +9 -9
  126. package/dist/date-picker.cjs.map +1 -1
  127. package/dist/date-picker.js +11 -11
  128. package/dist/date-picker.js.map +1 -1
  129. package/dist/dialog.cjs +3 -3
  130. package/dist/dialog.cjs.map +1 -1
  131. package/dist/dialog.js +3 -3
  132. package/dist/dialog.js.map +1 -1
  133. package/dist/disclosure/accordion/accordion.d.ts +4 -2
  134. package/dist/disclosure/accordion/accordion.d.ts.map +1 -1
  135. package/dist/disclosure/accordion-item/accordion-item.d.ts +1 -1
  136. package/dist/disclosure/accordion-item/accordion-item.d.ts.map +1 -1
  137. package/dist/disclosure/tab-item/tab-item.d.ts +1 -1
  138. package/dist/disclosure/tab-item/tab-item.d.ts.map +1 -1
  139. package/dist/disclosure/tabs/tabs.d.ts +1 -1
  140. package/dist/disclosure/tabs/tabs.d.ts.map +1 -1
  141. package/dist/drawer.cjs +4 -4
  142. package/dist/drawer.cjs.map +1 -1
  143. package/dist/drawer.js +2 -2
  144. package/dist/drawer.js.map +1 -1
  145. package/dist/feedback/alert/alert.d.ts +2 -1
  146. package/dist/feedback/alert/alert.d.ts.map +1 -1
  147. package/dist/feedback/badge/badge.d.ts +7 -4
  148. package/dist/feedback/badge/badge.d.ts.map +1 -1
  149. package/dist/feedback/chip/chip.d.ts +7 -5
  150. package/dist/feedback/chip/chip.d.ts.map +1 -1
  151. package/dist/grid-item.cjs +2 -2
  152. package/dist/grid-item.cjs.map +1 -1
  153. package/dist/grid-item.js +2 -2
  154. package/dist/grid-item.js.map +1 -1
  155. package/dist/grid.cjs +2 -2
  156. package/dist/grid.cjs.map +1 -1
  157. package/dist/grid.js +2 -2
  158. package/dist/grid.js.map +1 -1
  159. package/dist/input.cjs +7 -7
  160. package/dist/input.cjs.map +1 -1
  161. package/dist/input.js +8 -8
  162. package/dist/input.js.map +1 -1
  163. package/dist/inputs/button/button.d.ts +3 -2
  164. package/dist/inputs/button/button.d.ts.map +1 -1
  165. package/dist/inputs/button-group/button-group.d.ts +1 -37
  166. package/dist/inputs/button-group/button-group.d.ts.map +1 -1
  167. package/dist/inputs/combobox/combobox.types.d.ts +2 -2
  168. package/dist/inputs/combobox/combobox.types.d.ts.map +1 -1
  169. package/dist/inputs/counter/counter.d.ts +3 -2
  170. package/dist/inputs/counter/counter.d.ts.map +1 -1
  171. package/dist/inputs/datagrid/datagrid-model.cjs +1 -1
  172. package/dist/inputs/datagrid/datagrid-model.cjs.map +1 -1
  173. package/dist/inputs/datagrid/datagrid-model.d.ts +2 -1
  174. package/dist/inputs/datagrid/datagrid-model.d.ts.map +1 -1
  175. package/dist/inputs/datagrid/datagrid-model.js +1 -1
  176. package/dist/inputs/datagrid/datagrid-model.js.map +1 -1
  177. package/dist/inputs/datagrid/datagrid.d.ts +4 -2
  178. package/dist/inputs/datagrid/datagrid.d.ts.map +1 -1
  179. package/dist/inputs/date-picker/date-picker.d.ts +3 -2
  180. package/dist/inputs/date-picker/date-picker.d.ts.map +1 -1
  181. package/dist/inputs/input/input.d.ts +5 -3
  182. package/dist/inputs/input/input.d.ts.map +1 -1
  183. package/dist/inputs/message-composer/message-composer.d.ts +3 -2
  184. package/dist/inputs/message-composer/message-composer.d.ts.map +1 -1
  185. package/dist/inputs/number-input/number-input.d.ts +1 -1
  186. package/dist/inputs/number-input/number-input.d.ts.map +1 -1
  187. package/dist/inputs/otp-input/otp-input.d.ts +3 -2
  188. package/dist/inputs/otp-input/otp-input.d.ts.map +1 -1
  189. package/dist/inputs/query-bar/query-bar.d.ts.map +1 -1
  190. package/dist/inputs/select/select.d.ts +2 -2
  191. package/dist/inputs/select/select.d.ts.map +1 -1
  192. package/dist/inputs/textarea/textarea.d.ts +4 -3
  193. package/dist/inputs/textarea/textarea.d.ts.map +1 -1
  194. package/dist/inputs/time-picker/time-picker.d.ts +2 -2
  195. package/dist/inputs/time-picker/time-picker.d.ts.map +1 -1
  196. package/dist/keyboard-key.cjs +3 -3
  197. package/dist/keyboard-key.cjs.map +1 -1
  198. package/dist/keyboard-key.js +3 -3
  199. package/dist/keyboard-key.js.map +1 -1
  200. package/dist/layout/box/box.d.ts +6 -3
  201. package/dist/layout/box/box.d.ts.map +1 -1
  202. package/dist/layout/grid/grid.d.ts +11 -6
  203. package/dist/layout/grid/grid.d.ts.map +1 -1
  204. package/dist/layout/grid-item/grid-item.d.ts +8 -4
  205. package/dist/layout/grid-item/grid-item.d.ts.map +1 -1
  206. package/dist/layout/navbar/navbar.d.ts +4 -2
  207. package/dist/layout/navbar/navbar.d.ts.map +1 -1
  208. package/dist/layout/sidebar/sidebar.d.ts +2 -1
  209. package/dist/layout/sidebar/sidebar.d.ts.map +1 -1
  210. package/dist/list-item.cjs +1 -1
  211. package/dist/list-item.cjs.map +1 -1
  212. package/dist/list-item.js +1 -1
  213. package/dist/list-item.js.map +1 -1
  214. package/dist/list.cjs +2 -2
  215. package/dist/list.cjs.map +1 -1
  216. package/dist/list.js +2 -2
  217. package/dist/list.js.map +1 -1
  218. package/dist/marquee.cjs +3 -3
  219. package/dist/marquee.cjs.map +1 -1
  220. package/dist/marquee.js +3 -3
  221. package/dist/marquee.js.map +1 -1
  222. package/dist/menu.cjs +3 -3
  223. package/dist/menu.cjs.map +1 -1
  224. package/dist/menu.js +3 -3
  225. package/dist/menu.js.map +1 -1
  226. package/dist/message-composer.cjs +10 -10
  227. package/dist/message-composer.cjs.map +1 -1
  228. package/dist/message-composer.js +7 -7
  229. package/dist/message-composer.js.map +1 -1
  230. package/dist/navbar.cjs +10 -10
  231. package/dist/navbar.cjs.map +1 -1
  232. package/dist/navbar.js +13 -13
  233. package/dist/navbar.js.map +1 -1
  234. package/dist/number-input.cjs +3 -3
  235. package/dist/number-input.cjs.map +1 -1
  236. package/dist/number-input.js +4 -4
  237. package/dist/number-input.js.map +1 -1
  238. package/dist/otp-input.cjs +4 -4
  239. package/dist/otp-input.cjs.map +1 -1
  240. package/dist/otp-input.js +4 -4
  241. package/dist/otp-input.js.map +1 -1
  242. package/dist/overlay/dialog/dialog.d.ts +7 -4
  243. package/dist/overlay/dialog/dialog.d.ts.map +1 -1
  244. package/dist/overlay/drawer/drawer.d.ts +4 -2
  245. package/dist/overlay/drawer/drawer.d.ts.map +1 -1
  246. package/dist/overlay/menu/menu.d.ts +2 -1
  247. package/dist/overlay/menu/menu.d.ts.map +1 -1
  248. package/dist/pagination.cjs +9 -9
  249. package/dist/pagination.cjs.map +1 -1
  250. package/dist/pagination.js +9 -9
  251. package/dist/pagination.js.map +1 -1
  252. package/dist/qr-code.cjs +2 -2
  253. package/dist/qr-code.cjs.map +1 -1
  254. package/dist/qr-code.js +2 -2
  255. package/dist/qr-code.js.map +1 -1
  256. package/dist/query-bar.cjs +7 -7
  257. package/dist/query-bar.cjs.map +1 -1
  258. package/dist/query-bar.js +8 -8
  259. package/dist/query-bar.js.map +1 -1
  260. package/dist/radio-group.cjs +1 -1
  261. package/dist/radio-group.cjs.map +1 -1
  262. package/dist/radio-group.js +1 -1
  263. package/dist/radio-group.js.map +1 -1
  264. package/dist/refine.cjs +123 -123
  265. package/dist/refine.cjs.map +1 -1
  266. package/dist/refine.iife.js +123 -123
  267. package/dist/refine.iife.js.map +1 -1
  268. package/dist/refine.js +124 -124
  269. package/dist/refine.js.map +1 -1
  270. package/dist/select.cjs +13 -13
  271. package/dist/select.cjs.map +1 -1
  272. package/dist/select.js +15 -15
  273. package/dist/select.js.map +1 -1
  274. package/dist/separator.cjs +4 -4
  275. package/dist/separator.cjs.map +1 -1
  276. package/dist/separator.js +4 -4
  277. package/dist/separator.js.map +1 -1
  278. package/dist/shared/prop-bundles.cjs +1 -1
  279. package/dist/shared/prop-bundles.cjs.map +1 -1
  280. package/dist/shared/prop-bundles.d.ts +7 -8
  281. package/dist/shared/prop-bundles.d.ts.map +1 -1
  282. package/dist/shared/prop-bundles.js +1 -1
  283. package/dist/shared/prop-bundles.js.map +1 -1
  284. package/dist/shared/types.cjs +2 -0
  285. package/dist/shared/types.cjs.map +1 -0
  286. package/dist/shared/types.d.ts +27 -9
  287. package/dist/shared/types.d.ts.map +1 -1
  288. package/dist/shared/types.js +2 -0
  289. package/dist/shared/types.js.map +1 -0
  290. package/dist/sidebar.cjs +7 -7
  291. package/dist/sidebar.cjs.map +1 -1
  292. package/dist/sidebar.js +4 -4
  293. package/dist/sidebar.js.map +1 -1
  294. package/dist/step.cjs +10 -10
  295. package/dist/step.cjs.map +1 -1
  296. package/dist/step.js +10 -10
  297. package/dist/step.js.map +1 -1
  298. package/dist/tab-item.cjs +4 -4
  299. package/dist/tab-item.cjs.map +1 -1
  300. package/dist/tab-item.js +5 -5
  301. package/dist/tab-item.js.map +1 -1
  302. package/dist/table.cjs +2 -2
  303. package/dist/table.cjs.map +1 -1
  304. package/dist/table.js +2 -2
  305. package/dist/table.js.map +1 -1
  306. package/dist/tabs.cjs +2 -2
  307. package/dist/tabs.cjs.map +1 -1
  308. package/dist/tabs.js +4 -4
  309. package/dist/tabs.js.map +1 -1
  310. package/dist/text.cjs +2 -2
  311. package/dist/text.cjs.map +1 -1
  312. package/dist/text.js +2 -2
  313. package/dist/text.js.map +1 -1
  314. package/dist/textarea.cjs +7 -7
  315. package/dist/textarea.cjs.map +1 -1
  316. package/dist/textarea.js +7 -7
  317. package/dist/textarea.js.map +1 -1
  318. package/dist/time-picker.cjs +7 -7
  319. package/dist/time-picker.cjs.map +1 -1
  320. package/dist/time-picker.js +7 -7
  321. package/dist/time-picker.js.map +1 -1
  322. package/dist/tooltip.cjs +1 -1
  323. package/dist/tooltip.cjs.map +1 -1
  324. package/dist/tooltip.js +1 -1
  325. package/dist/tooltip.js.map +1 -1
  326. package/package.json +13 -13
@@ -1 +1 @@
1
- {"version":3,"file":"marquee.js","names":[],"sources":["../src/content/marquee/marquee.ts"],"sourcesContent":["import { define, getHost, html, onMounted, prop } from '@vielzeug/ore';\nimport { watch } from '@vielzeug/ripple';\n\nimport type { ThemeColor } from '../../types';\n\nimport componentStyles from './marquee.css?inline';\n\nexport { BUTTON_TAG } from '../../inputs/button/button';\nexport { ICON_TAG } from '../icon/icon';\nexport type MarqueeDirection = 'left' | 'right';\n\nexport type OreMarqueeProps = {\n /** Theme color for the navigation controls. */\n color?: ThemeColor;\n /** Scroll direction. Defaults to `'left'`. */\n direction?: MarqueeDirection;\n /** Duration in seconds for one complete ticker cycle. Defaults to `20`. */\n duration?: number;\n /** Pause the ticker while it is hovered. Defaults to `false`. */\n 'pause-on-hover'?: boolean;\n /** Show previous/next navigation controls. Defaults to `true`. */\n 'show-controls'?: boolean;\n};\n\n/**\n * A CSS-driven, continuously scrolling ticker.\n *\n * Direct child elements are duplicated internally for a seamless loop. Cloned\n * content is inert and hidden from assistive technology, so author interactive\n * content only in the original children.\n *\n * @element ore-marquee\n *\n * @attr {string} color - Theme color for the navigation controls.\n * @attr {number} duration - Duration in seconds for one ticker cycle (default: 20)\n * @attr {string} direction - 'left' (default) | 'right'\n * @attr {boolean} pause-on-hover - Pause while hovered (default: false)\n * @attr {boolean} show-controls - Show previous/next controls (default: true)\n *\n * @cssprop --marquee-gap - Gap between ticker items (default: var(--size-4))\n *\n * @part track - The animated ticker track.\n * @example\n * ```html\n * <ore-marquee duration=\"24\" pause-on-hover>\n * <span>Free shipping</span>\n * <span>New arrivals</span>\n * </ore-marquee>\n * ```\n */\nexport const MARQUEE_TAG = 'ore-marquee' as const;\n\ndefine<OreMarqueeProps>(MARQUEE_TAG, {\n props: {\n color: prop.string<ThemeColor>(),\n direction: prop.string<MarqueeDirection>('left'),\n duration: prop.number(20),\n 'pause-on-hover': prop.bool(false),\n 'show-controls': prop.bool(true),\n },\n setup(props) {\n const el = getHost();\n let activeIndex = 0;\n let clones: HTMLElement[] = [];\n let items: HTMLElement[] = [];\n let layoutFrame: number | null = null;\n let loopWidth = 0;\n let resizeObserver: ResizeObserver | null = null;\n let track: HTMLElement | null = null;\n\n const seekTo = (index: number): void => {\n if (!track || items.length === 0) return;\n\n activeIndex = (index + items.length) % items.length;\n\n const item = items[activeIndex];\n\n if (!item || loopWidth === 0) return;\n\n const duration = props.duration.value ?? 20;\n const delay = -((item.offsetLeft / loopWidth) * duration);\n\n track.style.setProperty('animation-delay', `${delay}s`);\n };\n\n watch(\n props.duration,\n (duration) => {\n el.style.setProperty('--_marquee-duration', `${duration ?? 20}s`);\n },\n { immediate: true },\n );\n\n onMounted(() => {\n track = el.shadowRoot?.querySelector<HTMLElement>('.track') ?? null;\n\n const slot = el.shadowRoot?.querySelector<HTMLSlotElement>('slot') ?? null;\n\n const removeClones = (): void => {\n clones.forEach((clone) => {\n clone.remove();\n });\n clones = [];\n };\n\n const appendCloneSet = (): void => {\n clones.push(\n ...items.map((item) => {\n const clone = item.cloneNode(true) as HTMLElement;\n\n clone.setAttribute('aria-hidden', 'true');\n clone.setAttribute('data-marquee-clone', '');\n clone.setAttribute('inert', '');\n el.appendChild(clone);\n\n return clone;\n }),\n );\n };\n\n const syncLoop = (): void => {\n if (!track || items.length === 0) return;\n\n const marqueeTrack = track;\n\n if (layoutFrame !== null) cancelAnimationFrame(layoutFrame);\n\n removeClones();\n loopWidth = 0;\n marqueeTrack.style.removeProperty('--_marquee-translate');\n appendCloneSet();\n\n layoutFrame = requestAnimationFrame(() => {\n layoutFrame = null;\n\n const firstClone = clones[0];\n const firstItem = items[0];\n\n if (!firstClone || !firstItem) return;\n\n loopWidth = firstClone.offsetLeft - firstItem.offsetLeft;\n\n if (loopWidth <= 0) return;\n\n const requiredSets = Math.max(2, Math.ceil((el.clientWidth + loopWidth) / loopWidth));\n\n for (let set = 2; set < requiredSets; set += 1) appendCloneSet();\n\n marqueeTrack.style.setProperty('--_marquee-translate', `-${loopWidth}px`);\n });\n };\n\n const refreshItems = (): void => {\n const nextItems = (slot?.assignedElements() ?? []).filter(\n (child): child is HTMLElement => child instanceof HTMLElement && !child.hasAttribute('data-marquee-clone'),\n );\n const itemsChanged =\n nextItems.length !== items.length || nextItems.some((item, index) => item !== items[index]);\n\n if (!itemsChanged) return;\n\n items = nextItems;\n activeIndex = Math.min(activeIndex, Math.max(items.length - 1, 0));\n syncLoop();\n };\n\n resizeObserver = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(syncLoop);\n resizeObserver?.observe(el);\n slot?.addEventListener('slotchange', refreshItems);\n refreshItems();\n\n return () => {\n if (layoutFrame !== null) cancelAnimationFrame(layoutFrame);\n\n resizeObserver?.disconnect();\n slot?.removeEventListener('slotchange', refreshItems);\n removeClones();\n track = null;\n items = [];\n loopWidth = 0;\n };\n });\n\n return html`\n <div class=\"track\" part=\"track\" aria-live=\"off\">\n <slot></slot>\n </div>\n ${() =>\n props['show-controls'].value !== false\n ? html`\n <div class=\"controls\" part=\"controls\">\n <ore-button\n class=\"nav-btn prev-btn\"\n part=\"prev-btn\"\n variant=\"ghost\"\n color=${() => props.color.value}\n rounded\n icon-only\n label=\"Previous item\"\n @click=${(event: Event) => {\n event.stopPropagation();\n seekTo(activeIndex - 1);\n }}>\n <ore-icon name=\"chevron-left\" size=\"16\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n </ore-button>\n <ore-button\n class=\"nav-btn next-btn\"\n part=\"next-btn\"\n variant=\"ghost\"\n color=${() => props.color.value}\n rounded\n icon-only\n label=\"Next item\"\n @click=${(event: Event) => {\n event.stopPropagation();\n seekTo(activeIndex + 1);\n }}>\n <ore-icon name=\"chevron-right\" size=\"16\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n </ore-button>\n </div>\n `\n : null}\n `;\n },\n styles: [componentStyles],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"marquee.js","names":[],"sources":["../src/content/marquee/marquee.ts"],"sourcesContent":["import { define, getHost, html, onMounted, prop } from '@vielzeug/ore';\nimport { watch } from '@vielzeug/ripple';\n\nimport { THEME_COLORS, type ThemeColor } from '../../types';\n\nimport componentStyles from './marquee.css?inline';\n\nexport { BUTTON_TAG } from '../../inputs/button/button';\nexport { ICON_TAG } from '../icon/icon';\nexport const MARQUEE_DIRECTIONS = ['left', 'right'] as const;\nexport type MarqueeDirection = (typeof MARQUEE_DIRECTIONS)[number];\n\nexport type OreMarqueeProps = {\n /** Theme color for the navigation controls. */\n color?: ThemeColor;\n /** Scroll direction. Defaults to `'left'`. */\n direction?: MarqueeDirection;\n /** Duration in seconds for one complete ticker cycle. Defaults to `20`. */\n duration?: number;\n /** Pause the ticker while it is hovered. Defaults to `false`. */\n 'pause-on-hover'?: boolean;\n /** Show previous/next navigation controls. Defaults to `true`. */\n 'show-controls'?: boolean;\n};\n\n/**\n * A CSS-driven, continuously scrolling ticker.\n *\n * Direct child elements are duplicated internally for a seamless loop. Cloned\n * content is inert and hidden from assistive technology, so author interactive\n * content only in the original children.\n *\n * @element ore-marquee\n *\n * @attr {string} color - Theme color for the navigation controls.\n * @attr {number} duration - Duration in seconds for one ticker cycle (default: 20)\n * @attr {string} direction - 'left' (default) | 'right'\n * @attr {boolean} pause-on-hover - Pause while hovered (default: false)\n * @attr {boolean} show-controls - Show previous/next controls (default: true)\n *\n * @cssprop --marquee-gap - Gap between ticker items (default: var(--size-4))\n *\n * @part track - The animated ticker track.\n * @example\n * ```html\n * <ore-marquee duration=\"24\" pause-on-hover>\n * <span>Free shipping</span>\n * <span>New arrivals</span>\n * </ore-marquee>\n * ```\n */\nexport const MARQUEE_TAG = 'ore-marquee' as const;\n\ndefine<OreMarqueeProps>(MARQUEE_TAG, {\n props: {\n color: prop.oneOf(THEME_COLORS),\n direction: prop.oneOf(MARQUEE_DIRECTIONS, 'left'),\n duration: prop.number(20),\n 'pause-on-hover': prop.bool(false),\n 'show-controls': prop.bool(true),\n },\n setup(props) {\n const el = getHost();\n let activeIndex = 0;\n let clones: HTMLElement[] = [];\n let items: HTMLElement[] = [];\n let layoutFrame: number | null = null;\n let loopWidth = 0;\n let resizeObserver: ResizeObserver | null = null;\n let track: HTMLElement | null = null;\n\n const seekTo = (index: number): void => {\n if (!track || items.length === 0) return;\n\n activeIndex = (index + items.length) % items.length;\n\n const item = items[activeIndex];\n\n if (!item || loopWidth === 0) return;\n\n const duration = props.duration.value ?? 20;\n const delay = -((item.offsetLeft / loopWidth) * duration);\n\n track.style.setProperty('animation-delay', `${delay}s`);\n };\n\n watch(\n props.duration,\n (duration) => {\n el.style.setProperty('--_marquee-duration', `${duration ?? 20}s`);\n },\n { immediate: true },\n );\n\n onMounted(() => {\n track = el.shadowRoot?.querySelector<HTMLElement>('.track') ?? null;\n\n const slot = el.shadowRoot?.querySelector<HTMLSlotElement>('slot') ?? null;\n\n const removeClones = (): void => {\n clones.forEach((clone) => {\n clone.remove();\n });\n clones = [];\n };\n\n const appendCloneSet = (): void => {\n clones.push(\n ...items.map((item) => {\n const clone = item.cloneNode(true) as HTMLElement;\n\n clone.setAttribute('aria-hidden', 'true');\n clone.setAttribute('data-marquee-clone', '');\n clone.setAttribute('inert', '');\n el.appendChild(clone);\n\n return clone;\n }),\n );\n };\n\n const syncLoop = (): void => {\n if (!track || items.length === 0) return;\n\n const marqueeTrack = track;\n\n if (layoutFrame !== null) cancelAnimationFrame(layoutFrame);\n\n removeClones();\n loopWidth = 0;\n marqueeTrack.style.removeProperty('--_marquee-translate');\n appendCloneSet();\n\n layoutFrame = requestAnimationFrame(() => {\n layoutFrame = null;\n\n const firstClone = clones[0];\n const firstItem = items[0];\n\n if (!firstClone || !firstItem) return;\n\n loopWidth = firstClone.offsetLeft - firstItem.offsetLeft;\n\n if (loopWidth <= 0) return;\n\n const requiredSets = Math.max(2, Math.ceil((el.clientWidth + loopWidth) / loopWidth));\n\n for (let set = 2; set < requiredSets; set += 1) appendCloneSet();\n\n marqueeTrack.style.setProperty('--_marquee-translate', `-${loopWidth}px`);\n });\n };\n\n const refreshItems = (): void => {\n const nextItems = (slot?.assignedElements() ?? []).filter(\n (child): child is HTMLElement => child instanceof HTMLElement && !child.hasAttribute('data-marquee-clone'),\n );\n const itemsChanged =\n nextItems.length !== items.length || nextItems.some((item, index) => item !== items[index]);\n\n if (!itemsChanged) return;\n\n items = nextItems;\n activeIndex = Math.min(activeIndex, Math.max(items.length - 1, 0));\n syncLoop();\n };\n\n resizeObserver = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(syncLoop);\n resizeObserver?.observe(el);\n slot?.addEventListener('slotchange', refreshItems);\n refreshItems();\n\n return () => {\n if (layoutFrame !== null) cancelAnimationFrame(layoutFrame);\n\n resizeObserver?.disconnect();\n slot?.removeEventListener('slotchange', refreshItems);\n removeClones();\n track = null;\n items = [];\n loopWidth = 0;\n };\n });\n\n return html`\n <div class=\"track\" part=\"track\" aria-live=\"off\">\n <slot></slot>\n </div>\n ${() =>\n props['show-controls'].value !== false\n ? html`\n <div class=\"controls\" part=\"controls\">\n <ore-button\n class=\"nav-btn prev-btn\"\n part=\"prev-btn\"\n variant=\"ghost\"\n color=${() => props.color.value}\n rounded\n icon-only\n label=\"Previous item\"\n @click=${(event: Event) => {\n event.stopPropagation();\n seekTo(activeIndex - 1);\n }}>\n <ore-icon name=\"chevron-left\" size=\"16\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n </ore-button>\n <ore-button\n class=\"nav-btn next-btn\"\n part=\"next-btn\"\n variant=\"ghost\"\n color=${() => props.color.value}\n rounded\n icon-only\n label=\"Next item\"\n @click=${(event: Event) => {\n event.stopPropagation();\n seekTo(activeIndex + 1);\n }}>\n <ore-icon name=\"chevron-right\" size=\"16\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n </ore-button>\n </div>\n `\n : null}\n `;\n },\n styles: [componentStyles],\n});\n"],"mappings":"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"}
package/dist/menu.cjs CHANGED
@@ -1,4 +1,4 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/shape.css.cjs"),t=require("./styles/mixins/theme.css.cjs"),n=require("./core/keyboard.cjs"),r=require("./core/ore.cjs"),i=require("./core/overlay.cjs"),a=require("./core/nav.cjs"),o=require("./core/positioner.cjs"),s=require("./core/parse.cjs"),c=require("./shared/prop-bundles.cjs"),l=require("./shared/size-presets.cjs"),u=require("./overlay/menu/menu.css.cjs"),d=require("./overlay/menu/menu-item.css.cjs"),f=require("./overlay/menu/menu-separator.css.cjs");let p=require("@vielzeug/ripple"),m=require("@vielzeug/ore"),h=require("@vielzeug/focus");var g=`ore-menu-item`;(0,m.define)(g,{props:{checked:m.prop.bool(!1),disabled:m.prop.bool(!1),type:m.prop.string(),value:m.prop.string()},setup(e){let isCheckable=()=>e.type.value===`checkbox`||e.type.value===`radio`,isChecked=()=>isCheckable()&&e.checked.value,itemRole=()=>e.type.value===`checkbox`?`menuitemcheckbox`:e.type.value===`radio`?`menuitemradio`:`menuitem`,itemClass=()=>{let t=e.type.value;return[`item`,t===`checkbox`?`is-checkbox`:``,t===`radio`?`is-radio`:``,isChecked()?`is-checked`:``].filter(Boolean).join(` `)};return isCheckable()?m.html`
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/shape.css.cjs"),t=require("./styles/mixins/theme.css.cjs"),n=require("./core/keyboard.cjs"),r=require("./core/ore.cjs"),i=require("./core/overlay.cjs"),a=require("./core/nav.cjs"),o=require("./core/positioner.cjs"),s=require("./core/parse.cjs"),c=require("./shared/prop-bundles.cjs"),l=require("./shared/size-presets.cjs"),u=require("./overlay/menu/menu.css.cjs"),d=require("./overlay/menu/menu-item.css.cjs"),f=require("./overlay/menu/menu-separator.css.cjs");let p=require("@vielzeug/ripple"),m=require("@vielzeug/ore"),h=require("@vielzeug/focus");var g=[`checkbox`,`radio`],_=`ore-menu-item`;(0,m.define)(_,{props:{checked:m.prop.bool(!1),disabled:m.prop.bool(!1),type:m.prop.oneOf(g),value:m.prop.string()},setup(e){let isCheckable=()=>e.type.value===`checkbox`||e.type.value===`radio`,isChecked=()=>isCheckable()&&e.checked.value,itemRole=()=>e.type.value===`checkbox`?`menuitemcheckbox`:e.type.value===`radio`?`menuitemradio`:`menuitem`,itemClass=()=>{let t=e.type.value;return[`item`,t===`checkbox`?`is-checkbox`:``,t===`radio`?`is-radio`:``,isChecked()?`is-checked`:``].filter(Boolean).join(` `)};return isCheckable()?m.html`
2
2
  <div
3
3
  class="${itemClass}"
4
4
  part="item"
@@ -15,7 +15,7 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
15
15
  <span class="icon-slot" part="icon-slot"><slot name="icon"></slot></span>
16
16
  <span class="item-label" part="item-label"><slot></slot></span>
17
17
  </div>
18
- `},styles:[t.colorThemeMixin,d.default]});var _=`ore-menu-separator`;(0,m.define)(_,{setup(){return m.html``},styles:[f.default]});var isCheckableItemType=e=>e===`checkbox`||e===`radio`,v=`ore-menu`;(0,m.define)(v,{props:{...c.sizableBundle,...c.disablableBundle,"default-open":m.prop.bool(!1),open:{default:void 0,parse:s.parseOptionalBool},placement:m.prop.oneOf([`bottom`,`bottom-start`,`bottom-end`,`top`,`top-start`,`top-end`],`bottom-start`)},setup(e){let t=(0,m.getHost)(),s=(0,m.useEmit)(),c=(0,m.useSlots)(),l=m.watchEffect,u=(0,m.createId)(`menu`),d=(0,p.computed)(()=>!!e.disabled.value),f=r.lifecycleSignal(m.onCleanup),g=null,_=null,cleanupTrigger=null;function getItems(){return Array.from(t.querySelectorAll(`ore-menu-item:not([disabled])`))}function getItemFocusable(e){return e?e.shadowRoot?.querySelector(`[role^="menuitem"]`)??e:null}function getFocusedItemIndex(){return getItems().findIndex(e=>{let t=getItemFocusable(e);return e===document.activeElement||t===document.activeElement})}let v=(0,p.signal)(!1),y=null,b=o.createDropdownPositioner({getFloating:()=>_,getOffsetPx:()=>4,getPlacement:()=>e.placement.value??`bottom-start`,getReference:()=>g,matchWidth:!1,onPlacementChange:e=>_?.setAttribute(`data-placement`,e),padding:6,useClippingAncestor:!1}),x=a.createListControl({disabled:(0,p.computed)(()=>!v.value),getItems,isItemDisabled:e=>e.hasAttribute(`disabled`),onNavigate:({item:e})=>{getItemFocusable(e)?.focus()},signal:f}),close=(e=`programmatic`,t=!0)=>{v.value&&(v.value=!1,x.reset(),y?.(),y=null,t&&(0,h.restoreFocus)(()=>g),s(`open-change`,{open:!1,reason:e}))},open=(e=`programmatic`)=>{d.value||v.value||(v.value=!0,b.update(),y=b.startAutoUpdate?.()??null,s(`open-change`,{open:!0,reason:e}))},toggle=()=>{v.value?close(`trigger`):open(`click`)};i.createOutsidePointerDismissal({getTargets:()=>[t,_],isActive:()=>v.value,onDismiss:()=>close(`outsideClick`),signal:f});let S=!1;(0,p.watch)(e.open,t=>{t===void 0?S?close(`programmatic`):e[`default-open`].value&&open(`programmatic`):t?open(`programmatic`):close(`programmatic`),S=!0},{immediate:!0}),f.addEventListener(`abort`,()=>{v.value&&(v.value=!1,x.reset(),y?.(),y=null)},{once:!0});let activateItem=e=>{let n=e.getAttribute(`type`),r=isCheckableItemType(n);if(n===`checkbox`)e.toggleAttribute(`checked`,!e.hasAttribute(`checked`));else if(n===`radio`)for(let n of t.querySelectorAll(`ore-menu-item[type="radio"]`))n.toggleAttribute(`checked`,n===e);let i=e.getAttribute(`value`)??``,a=r?e.hasAttribute(`checked`):void 0;s(`select`,{checked:a,value:i}),r||close(`programmatic`)},C=n.createInteraction({keys:[`Enter`,` `,`ArrowDown`],onPress:()=>{open(`keyboard`),requestAnimationFrame(()=>x.set(0))}}),w=n.createInteraction({onPress:()=>{let e=x.getActiveItem();e&&activateItem(e)}});function handleMenuKeydown(e){if(d.value)return;if(!v.value){C.handleKeydown(e);return}let t=getFocusedItemIndex();if(t>=0&&x.set(t),e.key===`Escape`){e.preventDefault(),close(`escape`);return}if(!x.handleKeydown(e)){if(e.key===` `||e.key===`Enter`){w.handleKeydown(e);return}e.key===`Tab`&&close(`programmatic`)}}(0,m.bind)({on:{click:e=>{let t=e.composedPath();if(!v.value)return;let n=t.find(e=>e instanceof HTMLElement&&e.tagName===`ORE-MENU-ITEM`)??e.target?.closest(`ore-menu-item`)??null;n&&!n.hasAttribute(`disabled`)&&activateItem(n)}}}),l(()=>{let e=v.value;_&&_.toggleAttribute(`data-open`,e)});function resolveTrigger(){if(cleanupTrigger?.(),cleanupTrigger=null,g=c.elements(`trigger`).value?.[0]??null,!g)return;let e=[],t=(0,m.bind)({attr:{"aria-controls":()=>u,"aria-disabled":()=>d.value,"aria-expanded":()=>String(v.value),"aria-haspopup":`menu`}},{target:g}),onTriggerClick=e=>{e.stopPropagation(),!d.value&&toggle()},onTriggerKeydown=e=>{handleMenuKeydown(e)};g.addEventListener(`click`,onTriggerClick),g.addEventListener(`keydown`,onTriggerKeydown),e.push(()=>g?.removeEventListener(`click`,onTriggerClick)),e.push(()=>g?.removeEventListener(`keydown`,onTriggerKeydown)),cleanupTrigger=()=>{t();for(let t of e)t()}}return(0,p.watch)(c.elements(`trigger`),resolveTrigger,{immediate:!0}),(0,m.onMounted)(()=>(resolveTrigger(),()=>{cleanupTrigger?.(),cleanupTrigger=null})),m.html`
18
+ `},styles:[t.colorThemeMixin,d.default]});var v=`ore-menu-separator`;(0,m.define)(v,{setup(){return m.html``},styles:[f.default]});var isCheckableItemType=e=>e===`checkbox`||e===`radio`,y=`ore-menu`;(0,m.define)(y,{props:{...c.sizableBundle,...c.disablableBundle,"default-open":m.prop.bool(!1),open:{default:void 0,parse:s.parseOptionalBool},placement:m.prop.oneOf([`bottom`,`bottom-start`,`bottom-end`,`top`,`top-start`,`top-end`],`bottom-start`)},setup(e){let t=(0,m.getHost)(),s=(0,m.useEmit)(),c=(0,m.useSlots)(),l=m.watchEffect,u=(0,m.createId)(`menu`),d=(0,p.computed)(()=>!!e.disabled.value),f=r.lifecycleSignal(m.onCleanup),g=null,_=null,cleanupTrigger=null;function getItems(){return Array.from(t.querySelectorAll(`ore-menu-item:not([disabled])`))}function getItemFocusable(e){return e?e.shadowRoot?.querySelector(`[role^="menuitem"]`)??e:null}function getFocusedItemIndex(){return getItems().findIndex(e=>{let t=getItemFocusable(e);return e===document.activeElement||t===document.activeElement})}let v=(0,p.signal)(!1),y=null,b=o.createDropdownPositioner({getFloating:()=>_,getOffsetPx:()=>4,getPlacement:()=>e.placement.value??`bottom-start`,getReference:()=>g,matchWidth:!1,onPlacementChange:e=>_?.setAttribute(`data-placement`,e),padding:6,useClippingAncestor:!1}),x=a.createListControl({disabled:(0,p.computed)(()=>!v.value),getItems,isItemDisabled:e=>e.hasAttribute(`disabled`),onNavigate:({item:e})=>{getItemFocusable(e)?.focus()},signal:f}),close=(e=`programmatic`,t=!0)=>{v.value&&(v.value=!1,x.reset(),y?.(),y=null,t&&(0,h.restoreFocus)(()=>g),s(`open-change`,{open:!1,reason:e}))},open=(e=`programmatic`)=>{d.value||v.value||(v.value=!0,b.update(),y=b.startAutoUpdate?.()??null,s(`open-change`,{open:!0,reason:e}))},toggle=()=>{v.value?close(`trigger`):open(`click`)};i.createOutsidePointerDismissal({getTargets:()=>[t,_],isActive:()=>v.value,onDismiss:()=>close(`outsideClick`),signal:f});let S=!1;(0,p.watch)(e.open,t=>{t===void 0?S?close(`programmatic`):e[`default-open`].value&&open(`programmatic`):t?open(`programmatic`):close(`programmatic`),S=!0},{immediate:!0}),f.addEventListener(`abort`,()=>{v.value&&(v.value=!1,x.reset(),y?.(),y=null)},{once:!0});let activateItem=e=>{let n=e.getAttribute(`type`),r=isCheckableItemType(n);if(n===`checkbox`)e.toggleAttribute(`checked`,!e.hasAttribute(`checked`));else if(n===`radio`)for(let n of t.querySelectorAll(`ore-menu-item[type="radio"]`))n.toggleAttribute(`checked`,n===e);let i=e.getAttribute(`value`)??``,a=r?e.hasAttribute(`checked`):void 0;s(`select`,{checked:a,value:i}),r||close(`programmatic`)},C=n.createInteraction({keys:[`Enter`,` `,`ArrowDown`],onPress:()=>{open(`keyboard`),requestAnimationFrame(()=>x.set(0))}}),w=n.createInteraction({onPress:()=>{let e=x.getActiveItem();e&&activateItem(e)}});function handleMenuKeydown(e){if(d.value)return;if(!v.value){C.handleKeydown(e);return}let t=getFocusedItemIndex();if(t>=0&&x.set(t),e.key===`Escape`){e.preventDefault(),close(`escape`);return}if(!x.handleKeydown(e)){if(e.key===` `||e.key===`Enter`){w.handleKeydown(e);return}e.key===`Tab`&&close(`programmatic`)}}(0,m.bind)({on:{click:e=>{let t=e.composedPath();if(!v.value)return;let n=t.find(e=>e instanceof HTMLElement&&e.tagName===`ORE-MENU-ITEM`)??e.target?.closest(`ore-menu-item`)??null;n&&!n.hasAttribute(`disabled`)&&activateItem(n)}}}),l(()=>{let e=v.value;_&&_.toggleAttribute(`data-open`,e)});function resolveTrigger(){if(cleanupTrigger?.(),cleanupTrigger=null,g=c.elements(`trigger`).value?.[0]??null,!g)return;let e=[],t=(0,m.bind)({attr:{"aria-controls":()=>u,"aria-disabled":()=>d.value,"aria-expanded":()=>String(v.value),"aria-haspopup":`menu`}},{target:g}),onTriggerClick=e=>{e.stopPropagation(),!d.value&&toggle()},onTriggerKeydown=e=>{handleMenuKeydown(e)};g.addEventListener(`click`,onTriggerClick),g.addEventListener(`keydown`,onTriggerKeydown),e.push(()=>g?.removeEventListener(`click`,onTriggerClick)),e.push(()=>g?.removeEventListener(`keydown`,onTriggerKeydown)),cleanupTrigger=()=>{t();for(let t of e)t()}}return(0,p.watch)(c.elements(`trigger`),resolveTrigger,{immediate:!0}),(0,m.onMounted)(()=>(resolveTrigger(),()=>{cleanupTrigger?.(),cleanupTrigger=null})),m.html`
19
19
  <slot name="trigger"></slot>
20
20
  <div
21
21
  class="menu-panel"
@@ -27,5 +27,5 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
27
27
  ref="${e=>{_=e,_?.toggleAttribute(`data-open`,v.value)}}">
28
28
  <slot></slot>
29
29
  </div>
30
- `},styles:[u.default,e.sizeVariantMixin(l.MENU_SIZE_PRESET),t.forcedColorsMixin]}),exports.MENU_ITEM_TAG=g,exports.MENU_TAG=v,exports.SEPARATOR_TAG=_;
30
+ `},styles:[u.default,e.sizeVariantMixin(l.MENU_SIZE_PRESET),t.forcedColorsMixin]}),exports.MENU_ITEM_TAG=_,exports.MENU_TAG=y,exports.ORE_MENU_ITEM_TYPES=g,exports.SEPARATOR_TAG=v;
31
31
  //# sourceMappingURL=menu.cjs.map
package/dist/menu.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"menu.cjs","names":[],"sources":["../src/overlay/menu/menu.ts"],"sourcesContent":["import { restoreFocus } from '@vielzeug/focus';\nimport type { Placement } from '@vielzeug/orbit';\nimport {\n bind,\n createId,\n define,\n getHost,\n html,\n onCleanup,\n onMounted,\n prop,\n useEmit,\n useSlots,\n watchEffect,\n} from '@vielzeug/ore';\nimport { computed, watch as rippleWatch, signal } from '@vielzeug/ripple';\nimport {\n createDropdownPositioner,\n createInteraction,\n createListControl,\n createOutsidePointerDismissal,\n type DropdownCloseReason,\n lifecycleSignal,\n type OverlayOpenChangeDetail,\n type OverlayOpenReason,\n parseOptionalBool,\n} from '../../core';\nimport { disablableBundle, MENU_SIZE_PRESET, sizableBundle } from '../../shared';\nimport { colorThemeMixin, forcedColorsMixin, sizeVariantMixin } from '../../styles';\nimport type { ComponentSize } from '../../types';\nimport componentStyles from './menu.css?inline';\nimport menuItemStyles from './menu-item.css?inline';\nimport menuSeparatorStyles from './menu-separator.css?inline';\n\n// ── Types ─────────────────────────────────────────────────────────────\n\nexport interface MenuSelectDetail {\n checked?: boolean;\n value: string;\n}\n\nexport type OreMenuItemType = 'checkbox' | 'radio';\n\nexport type OreMenuEvents = {\n 'open-change': OverlayOpenChangeDetail;\n select: MenuSelectDetail;\n};\n\nexport type OreMenuItemProps = {\n checked?: boolean;\n disabled?: boolean;\n type?: OreMenuItemType;\n value?: string;\n};\n\nexport type OreMenuProps = {\n /** Initial uncontrolled open state. Ignored when `open` is set. */\n 'default-open'?: boolean;\n disabled?: boolean;\n /** Controlled open state. */\n open?: boolean;\n placement?: 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end';\n size?: ComponentSize;\n};\n\n// ── Styles ─────────────────────────────────────────────────────────────\n\n// ── Menu Item Component ─────────────────────────────────────────────────────────────\n\n/**\n * A selectable action item used inside `<ore-menu>`.\n *\n * @element ore-menu-item\n *\n * @attr {boolean} checked - Checked state for `checkbox` and `radio` item types\n * @attr {boolean} disabled - Disables selection and pointer interaction\n * @attr {'checkbox'|'radio'} type - Optional checkable menu item mode\n * @attr {string} value - Value emitted by parent menu on selection\n *\n * @slot - Item label/content\n * @slot icon - Optional leading icon content\n *\n * @cssprop --menu-item-hover-bg - Background on hover\n * @cssprop --menu-item-focus-color - Text color when keyboard-focused\n * @cssprop --menu-item-focus-bg - Background when keyboard-focused\n * @cssprop --menu-item-selection-bg - Background for checkbox/radio items (unselected)\n * @cssprop --menu-item-checked-color - Text color when checked\n * @cssprop --menu-item-checked-bg - Background when checked\n *\n * @part item - Root item container element.\n * @part item-label - Label text container.\n * @part icon-slot - Leading icon slot container.\n *\n * @example\n * ```html\n * <ore-menu-item value=\"edit\">Edit</ore-menu-item>\n * <ore-menu-item value=\"delete\" disabled>Delete</ore-menu-item>\n * <ore-menu-item type=\"checkbox\" value=\"wrap\" checked>Word wrap</ore-menu-item>\n * <ore-menu-item type=\"radio\" value=\"left\">Align left</ore-menu-item>\n * ```\n */\nexport const MENU_ITEM_TAG = 'ore-menu-item' as const;\ndefine<OreMenuItemProps>(MENU_ITEM_TAG, {\n props: {\n checked: prop.bool(false),\n disabled: prop.bool(false),\n type: prop.string<OreMenuItemType>(),\n value: prop.string(),\n },\n setup(props) {\n const isCheckable = () => props.type.value === 'checkbox' || props.type.value === 'radio';\n const isChecked = () => isCheckable() && props.checked.value;\n const itemRole = () => {\n if (props.type.value === 'checkbox') return 'menuitemcheckbox';\n\n if (props.type.value === 'radio') return 'menuitemradio';\n\n return 'menuitem';\n };\n const itemClass = () => {\n const type = props.type.value;\n\n return [\n 'item',\n type === 'checkbox' ? 'is-checkbox' : '',\n type === 'radio' ? 'is-radio' : '',\n isChecked() ? 'is-checked' : '',\n ]\n .filter(Boolean)\n .join(' ');\n };\n\n return isCheckable()\n ? html`\n <div\n class=\"${itemClass}\"\n part=\"item\"\n tabindex=\"-1\"\n role=\"${itemRole}\"\n aria-checked=\"${() => String(isChecked())}\"\n aria-disabled=\"${props.disabled}\">\n <span class=\"item-check\" aria-hidden=\"true\"></span>\n <span class=\"icon-slot\" part=\"icon-slot\"><slot name=\"icon\"></slot></span>\n <span class=\"item-label\" part=\"item-label\"><slot></slot></span>\n </div>\n `\n : html`\n <div class=\"item\" part=\"item\" tabindex=\"-1\" role=\"menuitem\" aria-disabled=\"${props.disabled}\">\n <span class=\"icon-slot\" part=\"icon-slot\"><slot name=\"icon\"></slot></span>\n <span class=\"item-label\" part=\"item-label\"><slot></slot></span>\n </div>\n `;\n },\n styles: [colorThemeMixin, menuItemStyles],\n});\n\n// ── Menu Separator ─────────────────────────────────────────────────────────────\n\n/**\n * Visual separator used to group menu items inside `<ore-menu>`.\n *\n * @element ore-menu-separator\n *\n * @example\n * ```html\n * <ore-menu-item value=\"cut\">Cut</ore-menu-item>\n * <ore-menu-separator></ore-menu-separator>\n * <ore-menu-item value=\"paste\">Paste</ore-menu-item>\n * ```\n */\nexport const SEPARATOR_TAG = 'ore-menu-separator' as const;\ndefine(SEPARATOR_TAG, {\n setup() {\n return html``;\n },\n styles: [menuSeparatorStyles],\n});\n\n// ── Menu Component ─────────────────────────────────────────────────────────────\n\nconst isCheckableItemType = (value: string | null): value is OreMenuItemType =>\n value === 'checkbox' || value === 'radio';\n\n/**\n * Action dropdown menu triggered by a slotted trigger element.\n *\n * @element ore-menu\n * @element ore-menu-item - Clickable menu option (place in default slot)\n * @element ore-menu-separator - Visual divider between menu groups\n *\n * @attr {boolean} disabled - Disables opening and keyboard interaction\n * @attr {boolean} open - Controlled open state\n * @attr {boolean} default-open - Initial uncontrolled open state\n * @attr {string} placement - Panel placement: 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end' (default: 'bottom-start')\n * @attr {string} size - Size: 'sm' | 'md' | 'lg'\n *\n * @fires open-change - Fired when the menu state changes. detail: { open, reason }\n * @fires select - Fired when an item is selected. detail: { value: string, checked?: boolean }\n *\n * @slot trigger - Trigger element that toggles menu visibility\n * @slot - Menu content (`<ore-menu-item>` and `<ore-menu-separator>`)\n *\n * @part panel - Floating menu panel container\n *\n * @cssprop --menu-panel-bg - Background of the floating panel\n * @cssprop --menu-panel-border-color - Border color of the floating panel\n * @cssprop --menu-panel-shadow - Box shadow of the floating panel\n * @cssprop --menu-panel-blur - Backdrop blur amount for the floating panel\n * @cssprop --menu-panel-min-width - Minimum width of the floating panel\n * @cssprop --menu-panel-max-height - Maximum height of the floating panel before it scrolls\n * @cssprop --menu-panel-radius - Border radius of the floating panel\n *\n * @example\n * ```html\n * <ore-menu>\n * <button slot=\"trigger\">Actions</button>\n * <ore-menu-item value=\"edit\">Edit</ore-menu-item>\n * <ore-menu-item value=\"delete\">Delete</ore-menu-item>\n * </ore-menu>\n * ```\n */\nexport const MENU_TAG = 'ore-menu' as const;\ndefine<OreMenuProps>(MENU_TAG, {\n props: {\n ...sizableBundle,\n ...disablableBundle,\n 'default-open': prop.bool(false),\n open: { default: undefined as boolean | undefined, parse: parseOptionalBool },\n placement: prop.oneOf(\n ['bottom', 'bottom-start', 'bottom-end', 'top', 'top-start', 'top-end'] as const,\n 'bottom-start',\n ),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreMenuEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const menuId = createId('menu');\n const isDisabled = computed(() => Boolean(props.disabled.value));\n const abortSignal = lifecycleSignal(onCleanup);\n let triggerEl: HTMLElement | null = null;\n let panelEl: HTMLElement | null = null;\n let cleanupTrigger: (() => void) | null = null;\n\n // ── Helpers ───────────────────────────────────────────────────────────────\n function getItems(): HTMLElement[] {\n return Array.from(el.querySelectorAll<HTMLElement>('ore-menu-item:not([disabled])'));\n }\n\n function getItemFocusable(item: HTMLElement | null | undefined): HTMLElement | null {\n if (!item) return null;\n\n return item.shadowRoot?.querySelector<HTMLElement>('[role^=\"menuitem\"]') ?? item;\n }\n\n function getFocusedItemIndex(): number {\n const items = getItems();\n\n return items.findIndex((item) => {\n const focusable = getItemFocusable(item);\n\n return item === document.activeElement || focusable === document.activeElement;\n });\n }\n\n const isOpenSignal = signal(false);\n let stopPositioning: (() => void) | null = null;\n const positioner = createDropdownPositioner({\n getFloating: () => panelEl,\n getOffsetPx: () => 4,\n getPlacement: () => (props.placement.value ?? 'bottom-start') as Placement,\n getReference: () => triggerEl,\n matchWidth: false,\n // menu.css keys the panel's transform-origin off [data-placement]; the positioner reports the\n // resolved (RTL-mirrored, flipped) placement here so that rule finally has something to match.\n onPlacementChange: (placement) => panelEl?.setAttribute('data-placement', placement),\n padding: 6,\n useClippingAncestor: false,\n });\n const list = createListControl<HTMLElement>({\n disabled: computed(() => !isOpenSignal.value),\n getItems: getItems,\n isItemDisabled: (item) => item.hasAttribute('disabled'),\n onNavigate: ({ item }) => {\n getItemFocusable(item)?.focus();\n },\n signal: abortSignal,\n });\n\n const close = (reason: DropdownCloseReason = 'programmatic', shouldRestoreFocus = true): void => {\n if (!isOpenSignal.value) return;\n\n isOpenSignal.value = false;\n list.reset();\n stopPositioning?.();\n stopPositioning = null;\n\n if (shouldRestoreFocus) restoreFocus(() => triggerEl);\n\n emit('open-change', { open: false, reason });\n };\n\n const open = (reason: OverlayOpenReason = 'programmatic'): void => {\n if (isDisabled.value || isOpenSignal.value) return;\n\n isOpenSignal.value = true;\n positioner.update();\n stopPositioning = positioner.startAutoUpdate?.() ?? null;\n emit('open-change', { open: true, reason });\n };\n\n const toggle = (): void => {\n if (isOpenSignal.value) close('trigger');\n else open('click');\n };\n\n createOutsidePointerDismissal({\n getTargets: () => [el, panelEl],\n isActive: () => isOpenSignal.value,\n onDismiss: () => close('outsideClick'),\n signal: abortSignal,\n });\n\n let initialized = false;\n\n rippleWatch(\n props.open,\n (value) => {\n if (value === undefined) {\n if (!initialized) {\n if (props['default-open'].value) open('programmatic');\n } else {\n close('programmatic');\n }\n } else if (value) {\n open('programmatic');\n } else {\n close('programmatic');\n }\n\n initialized = true;\n },\n { immediate: true },\n );\n abortSignal.addEventListener(\n 'abort',\n () => {\n if (!isOpenSignal.value) return;\n\n isOpenSignal.value = false;\n list.reset();\n stopPositioning?.();\n stopPositioning = null;\n },\n { once: true },\n );\n\n const activateItem = (item: HTMLElement): void => {\n const type = item.getAttribute('type');\n const isCheckable = isCheckableItemType(type);\n\n if (type === 'checkbox') {\n item.toggleAttribute('checked', !item.hasAttribute('checked'));\n } else if (type === 'radio') {\n for (const radio of el.querySelectorAll<HTMLElement>('ore-menu-item[type=\"radio\"]')) {\n radio.toggleAttribute('checked', radio === item);\n }\n }\n\n const value = item.getAttribute('value') ?? '';\n const checked = isCheckable ? item.hasAttribute('checked') : undefined;\n\n emit('select', { checked, value });\n\n if (!isCheckable) {\n close('programmatic');\n }\n };\n\n const openFromKeyboardPress = createInteraction({\n keys: ['Enter', ' ', 'ArrowDown'],\n onPress: () => {\n open('keyboard');\n requestAnimationFrame(() => list.set(0));\n },\n });\n\n const activateFocusedFromKeyboardPress = createInteraction({\n onPress: () => {\n const focused = list.getActiveItem();\n\n if (focused) activateItem(focused);\n },\n });\n\n // ── Keyboard Navigation ───────────────────────────────────────────────────\n function handleMenuKeydown(e: KeyboardEvent) {\n if (isDisabled.value) return;\n\n const open = isOpenSignal.value;\n\n // When closed: open on Enter / Space / ArrowDown\n if (!open) {\n openFromKeyboardPress.handleKeydown(e);\n\n return;\n }\n\n const currentFocusedIndex = getFocusedItemIndex();\n\n if (currentFocusedIndex >= 0) list.set(currentFocusedIndex);\n\n if (e.key === 'Escape') {\n e.preventDefault();\n close('escape');\n\n return;\n }\n\n if (list.handleKeydown(e)) return;\n\n // When open: navigate and activate\n if (e.key === ' ' || e.key === 'Enter') {\n activateFocusedFromKeyboardPress.handleKeydown(e);\n\n return;\n }\n\n if (e.key === 'Tab') {\n close('programmatic');\n }\n }\n\n // ── Lifecycle ─────────────────────────────────────────────────────────────\n bind({\n on: {\n click: (e: MouseEvent) => {\n const path = e.composedPath();\n\n if (!isOpenSignal.value) return;\n\n const itemFromPath = path.find(\n (node): node is HTMLElement => node instanceof HTMLElement && node.tagName === 'ORE-MENU-ITEM',\n );\n const item = itemFromPath ?? (e.target as HTMLElement | null)?.closest<HTMLElement>('ore-menu-item') ?? null;\n\n if (!item || item.hasAttribute('disabled')) return;\n\n activateItem(item);\n },\n },\n });\n\n watch(() => {\n const open = isOpenSignal.value;\n\n if (!panelEl) return;\n\n panelEl.toggleAttribute('data-open', open);\n });\n\n function resolveTrigger() {\n cleanupTrigger?.();\n cleanupTrigger = null;\n\n const assigned = slots.elements('trigger').value;\n\n triggerEl = (assigned?.[0] as HTMLElement | undefined) ?? null;\n\n if (!triggerEl) return;\n\n const cleanups: Array<() => void> = [];\n const removeAria = bind(\n {\n attr: {\n 'aria-controls': () => menuId,\n 'aria-disabled': () => isDisabled.value,\n 'aria-expanded': () => String(isOpenSignal.value),\n 'aria-haspopup': 'menu',\n },\n },\n { target: triggerEl },\n );\n\n const onTriggerClick = (event: MouseEvent) => {\n event.stopPropagation();\n\n if (isDisabled.value) return;\n\n toggle();\n };\n const onTriggerKeydown = (event: KeyboardEvent) => {\n handleMenuKeydown(event);\n };\n\n triggerEl.addEventListener('click', onTriggerClick);\n triggerEl.addEventListener('keydown', onTriggerKeydown);\n cleanups.push(() => triggerEl?.removeEventListener('click', onTriggerClick));\n cleanups.push(() => triggerEl?.removeEventListener('keydown', onTriggerKeydown));\n\n cleanupTrigger = () => {\n removeAria();\n\n for (const cleanup of cleanups) cleanup();\n };\n }\n\n rippleWatch(slots.elements('trigger'), resolveTrigger, { immediate: true });\n\n onMounted(() => {\n resolveTrigger();\n\n return () => {\n cleanupTrigger?.();\n cleanupTrigger = null;\n };\n });\n\n return html`\n <slot name=\"trigger\"></slot>\n <div\n class=\"menu-panel\"\n part=\"panel\"\n id=\"${menuId}\"\n role=\"menu\"\n aria-orientation=\"vertical\"\n @keydown=\"${handleMenuKeydown}\"\n ref=\"${(el: HTMLElement | null) => {\n panelEl = el;\n panelEl?.toggleAttribute('data-open', isOpenSignal.value);\n }}\">\n <slot></slot>\n </div>\n `;\n },\n styles: [componentStyles, sizeVariantMixin(MENU_SIZE_PRESET), forcedColorsMixin],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"menu.cjs","names":[],"sources":["../src/overlay/menu/menu.ts"],"sourcesContent":["import { restoreFocus } from '@vielzeug/focus';\nimport type { Placement } from '@vielzeug/orbit';\nimport {\n bind,\n createId,\n define,\n getHost,\n html,\n onCleanup,\n onMounted,\n prop,\n useEmit,\n useSlots,\n watchEffect,\n} from '@vielzeug/ore';\nimport { computed, watch as rippleWatch, signal } from '@vielzeug/ripple';\nimport {\n createDropdownPositioner,\n createInteraction,\n createListControl,\n createOutsidePointerDismissal,\n type DropdownCloseReason,\n lifecycleSignal,\n type OverlayOpenChangeDetail,\n type OverlayOpenReason,\n parseOptionalBool,\n} from '../../core';\nimport { disablableBundle, MENU_SIZE_PRESET, sizableBundle } from '../../shared';\nimport { colorThemeMixin, forcedColorsMixin, sizeVariantMixin } from '../../styles';\nimport type { ComponentSize } from '../../types';\nimport componentStyles from './menu.css?inline';\nimport menuItemStyles from './menu-item.css?inline';\nimport menuSeparatorStyles from './menu-separator.css?inline';\n\n// ── Types ─────────────────────────────────────────────────────────────\n\nexport interface MenuSelectDetail {\n checked?: boolean;\n value: string;\n}\n\nexport const ORE_MENU_ITEM_TYPES = ['checkbox', 'radio'] as const;\nexport type OreMenuItemType = (typeof ORE_MENU_ITEM_TYPES)[number];\n\nexport type OreMenuEvents = {\n 'open-change': OverlayOpenChangeDetail;\n select: MenuSelectDetail;\n};\n\nexport type OreMenuItemProps = {\n checked?: boolean;\n disabled?: boolean;\n type?: OreMenuItemType;\n value?: string;\n};\n\nexport type OreMenuProps = {\n /** Initial uncontrolled open state. Ignored when `open` is set. */\n 'default-open'?: boolean;\n disabled?: boolean;\n /** Controlled open state. */\n open?: boolean;\n placement?: 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end';\n size?: ComponentSize;\n};\n\n// ── Styles ─────────────────────────────────────────────────────────────\n\n// ── Menu Item Component ─────────────────────────────────────────────────────────────\n\n/**\n * A selectable action item used inside `<ore-menu>`.\n *\n * @element ore-menu-item\n *\n * @attr {boolean} checked - Checked state for `checkbox` and `radio` item types\n * @attr {boolean} disabled - Disables selection and pointer interaction\n * @attr {'checkbox'|'radio'} type - Optional checkable menu item mode\n * @attr {string} value - Value emitted by parent menu on selection\n *\n * @slot - Item label/content\n * @slot icon - Optional leading icon content\n *\n * @cssprop --menu-item-hover-bg - Background on hover\n * @cssprop --menu-item-focus-color - Text color when keyboard-focused\n * @cssprop --menu-item-focus-bg - Background when keyboard-focused\n * @cssprop --menu-item-selection-bg - Background for checkbox/radio items (unselected)\n * @cssprop --menu-item-checked-color - Text color when checked\n * @cssprop --menu-item-checked-bg - Background when checked\n *\n * @part item - Root item container element.\n * @part item-label - Label text container.\n * @part icon-slot - Leading icon slot container.\n *\n * @example\n * ```html\n * <ore-menu-item value=\"edit\">Edit</ore-menu-item>\n * <ore-menu-item value=\"delete\" disabled>Delete</ore-menu-item>\n * <ore-menu-item type=\"checkbox\" value=\"wrap\" checked>Word wrap</ore-menu-item>\n * <ore-menu-item type=\"radio\" value=\"left\">Align left</ore-menu-item>\n * ```\n */\nexport const MENU_ITEM_TAG = 'ore-menu-item' as const;\ndefine<OreMenuItemProps>(MENU_ITEM_TAG, {\n props: {\n checked: prop.bool(false),\n disabled: prop.bool(false),\n type: prop.oneOf(ORE_MENU_ITEM_TYPES),\n value: prop.string(),\n },\n setup(props) {\n const isCheckable = () => props.type.value === 'checkbox' || props.type.value === 'radio';\n const isChecked = () => isCheckable() && props.checked.value;\n const itemRole = () => {\n if (props.type.value === 'checkbox') return 'menuitemcheckbox';\n\n if (props.type.value === 'radio') return 'menuitemradio';\n\n return 'menuitem';\n };\n const itemClass = () => {\n const type = props.type.value;\n\n return [\n 'item',\n type === 'checkbox' ? 'is-checkbox' : '',\n type === 'radio' ? 'is-radio' : '',\n isChecked() ? 'is-checked' : '',\n ]\n .filter(Boolean)\n .join(' ');\n };\n\n return isCheckable()\n ? html`\n <div\n class=\"${itemClass}\"\n part=\"item\"\n tabindex=\"-1\"\n role=\"${itemRole}\"\n aria-checked=\"${() => String(isChecked())}\"\n aria-disabled=\"${props.disabled}\">\n <span class=\"item-check\" aria-hidden=\"true\"></span>\n <span class=\"icon-slot\" part=\"icon-slot\"><slot name=\"icon\"></slot></span>\n <span class=\"item-label\" part=\"item-label\"><slot></slot></span>\n </div>\n `\n : html`\n <div class=\"item\" part=\"item\" tabindex=\"-1\" role=\"menuitem\" aria-disabled=\"${props.disabled}\">\n <span class=\"icon-slot\" part=\"icon-slot\"><slot name=\"icon\"></slot></span>\n <span class=\"item-label\" part=\"item-label\"><slot></slot></span>\n </div>\n `;\n },\n styles: [colorThemeMixin, menuItemStyles],\n});\n\n// ── Menu Separator ─────────────────────────────────────────────────────────────\n\n/**\n * Visual separator used to group menu items inside `<ore-menu>`.\n *\n * @element ore-menu-separator\n *\n * @example\n * ```html\n * <ore-menu-item value=\"cut\">Cut</ore-menu-item>\n * <ore-menu-separator></ore-menu-separator>\n * <ore-menu-item value=\"paste\">Paste</ore-menu-item>\n * ```\n */\nexport const SEPARATOR_TAG = 'ore-menu-separator' as const;\ndefine(SEPARATOR_TAG, {\n setup() {\n return html``;\n },\n styles: [menuSeparatorStyles],\n});\n\n// ── Menu Component ─────────────────────────────────────────────────────────────\n\nconst isCheckableItemType = (value: string | null): value is OreMenuItemType =>\n value === 'checkbox' || value === 'radio';\n\n/**\n * Action dropdown menu triggered by a slotted trigger element.\n *\n * @element ore-menu\n * @element ore-menu-item - Clickable menu option (place in default slot)\n * @element ore-menu-separator - Visual divider between menu groups\n *\n * @attr {boolean} disabled - Disables opening and keyboard interaction\n * @attr {boolean} open - Controlled open state\n * @attr {boolean} default-open - Initial uncontrolled open state\n * @attr {string} placement - Panel placement: 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end' (default: 'bottom-start')\n * @attr {string} size - Size: 'sm' | 'md' | 'lg'\n *\n * @fires open-change - Fired when the menu state changes. detail: { open, reason }\n * @fires select - Fired when an item is selected. detail: { value: string, checked?: boolean }\n *\n * @slot trigger - Trigger element that toggles menu visibility\n * @slot - Menu content (`<ore-menu-item>` and `<ore-menu-separator>`)\n *\n * @part panel - Floating menu panel container\n *\n * @cssprop --menu-panel-bg - Background of the floating panel\n * @cssprop --menu-panel-border-color - Border color of the floating panel\n * @cssprop --menu-panel-shadow - Box shadow of the floating panel\n * @cssprop --menu-panel-blur - Backdrop blur amount for the floating panel\n * @cssprop --menu-panel-min-width - Minimum width of the floating panel\n * @cssprop --menu-panel-max-height - Maximum height of the floating panel before it scrolls\n * @cssprop --menu-panel-radius - Border radius of the floating panel\n *\n * @example\n * ```html\n * <ore-menu>\n * <button slot=\"trigger\">Actions</button>\n * <ore-menu-item value=\"edit\">Edit</ore-menu-item>\n * <ore-menu-item value=\"delete\">Delete</ore-menu-item>\n * </ore-menu>\n * ```\n */\nexport const MENU_TAG = 'ore-menu' as const;\ndefine<OreMenuProps>(MENU_TAG, {\n props: {\n ...sizableBundle,\n ...disablableBundle,\n 'default-open': prop.bool(false),\n open: { default: undefined as boolean | undefined, parse: parseOptionalBool },\n placement: prop.oneOf(\n ['bottom', 'bottom-start', 'bottom-end', 'top', 'top-start', 'top-end'] as const,\n 'bottom-start',\n ),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreMenuEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const menuId = createId('menu');\n const isDisabled = computed(() => Boolean(props.disabled.value));\n const abortSignal = lifecycleSignal(onCleanup);\n let triggerEl: HTMLElement | null = null;\n let panelEl: HTMLElement | null = null;\n let cleanupTrigger: (() => void) | null = null;\n\n // ── Helpers ───────────────────────────────────────────────────────────────\n function getItems(): HTMLElement[] {\n return Array.from(el.querySelectorAll<HTMLElement>('ore-menu-item:not([disabled])'));\n }\n\n function getItemFocusable(item: HTMLElement | null | undefined): HTMLElement | null {\n if (!item) return null;\n\n return item.shadowRoot?.querySelector<HTMLElement>('[role^=\"menuitem\"]') ?? item;\n }\n\n function getFocusedItemIndex(): number {\n const items = getItems();\n\n return items.findIndex((item) => {\n const focusable = getItemFocusable(item);\n\n return item === document.activeElement || focusable === document.activeElement;\n });\n }\n\n const isOpenSignal = signal(false);\n let stopPositioning: (() => void) | null = null;\n const positioner = createDropdownPositioner({\n getFloating: () => panelEl,\n getOffsetPx: () => 4,\n getPlacement: () => (props.placement.value ?? 'bottom-start') as Placement,\n getReference: () => triggerEl,\n matchWidth: false,\n // menu.css keys the panel's transform-origin off [data-placement]; the positioner reports the\n // resolved (RTL-mirrored, flipped) placement here so that rule finally has something to match.\n onPlacementChange: (placement) => panelEl?.setAttribute('data-placement', placement),\n padding: 6,\n useClippingAncestor: false,\n });\n const list = createListControl<HTMLElement>({\n disabled: computed(() => !isOpenSignal.value),\n getItems: getItems,\n isItemDisabled: (item) => item.hasAttribute('disabled'),\n onNavigate: ({ item }) => {\n getItemFocusable(item)?.focus();\n },\n signal: abortSignal,\n });\n\n const close = (reason: DropdownCloseReason = 'programmatic', shouldRestoreFocus = true): void => {\n if (!isOpenSignal.value) return;\n\n isOpenSignal.value = false;\n list.reset();\n stopPositioning?.();\n stopPositioning = null;\n\n if (shouldRestoreFocus) restoreFocus(() => triggerEl);\n\n emit('open-change', { open: false, reason });\n };\n\n const open = (reason: OverlayOpenReason = 'programmatic'): void => {\n if (isDisabled.value || isOpenSignal.value) return;\n\n isOpenSignal.value = true;\n positioner.update();\n stopPositioning = positioner.startAutoUpdate?.() ?? null;\n emit('open-change', { open: true, reason });\n };\n\n const toggle = (): void => {\n if (isOpenSignal.value) close('trigger');\n else open('click');\n };\n\n createOutsidePointerDismissal({\n getTargets: () => [el, panelEl],\n isActive: () => isOpenSignal.value,\n onDismiss: () => close('outsideClick'),\n signal: abortSignal,\n });\n\n let initialized = false;\n\n rippleWatch(\n props.open,\n (value) => {\n if (value === undefined) {\n if (!initialized) {\n if (props['default-open'].value) open('programmatic');\n } else {\n close('programmatic');\n }\n } else if (value) {\n open('programmatic');\n } else {\n close('programmatic');\n }\n\n initialized = true;\n },\n { immediate: true },\n );\n abortSignal.addEventListener(\n 'abort',\n () => {\n if (!isOpenSignal.value) return;\n\n isOpenSignal.value = false;\n list.reset();\n stopPositioning?.();\n stopPositioning = null;\n },\n { once: true },\n );\n\n const activateItem = (item: HTMLElement): void => {\n const type = item.getAttribute('type');\n const isCheckable = isCheckableItemType(type);\n\n if (type === 'checkbox') {\n item.toggleAttribute('checked', !item.hasAttribute('checked'));\n } else if (type === 'radio') {\n for (const radio of el.querySelectorAll<HTMLElement>('ore-menu-item[type=\"radio\"]')) {\n radio.toggleAttribute('checked', radio === item);\n }\n }\n\n const value = item.getAttribute('value') ?? '';\n const checked = isCheckable ? item.hasAttribute('checked') : undefined;\n\n emit('select', { checked, value });\n\n if (!isCheckable) {\n close('programmatic');\n }\n };\n\n const openFromKeyboardPress = createInteraction({\n keys: ['Enter', ' ', 'ArrowDown'],\n onPress: () => {\n open('keyboard');\n requestAnimationFrame(() => list.set(0));\n },\n });\n\n const activateFocusedFromKeyboardPress = createInteraction({\n onPress: () => {\n const focused = list.getActiveItem();\n\n if (focused) activateItem(focused);\n },\n });\n\n // ── Keyboard Navigation ───────────────────────────────────────────────────\n function handleMenuKeydown(e: KeyboardEvent) {\n if (isDisabled.value) return;\n\n const open = isOpenSignal.value;\n\n // When closed: open on Enter / Space / ArrowDown\n if (!open) {\n openFromKeyboardPress.handleKeydown(e);\n\n return;\n }\n\n const currentFocusedIndex = getFocusedItemIndex();\n\n if (currentFocusedIndex >= 0) list.set(currentFocusedIndex);\n\n if (e.key === 'Escape') {\n e.preventDefault();\n close('escape');\n\n return;\n }\n\n if (list.handleKeydown(e)) return;\n\n // When open: navigate and activate\n if (e.key === ' ' || e.key === 'Enter') {\n activateFocusedFromKeyboardPress.handleKeydown(e);\n\n return;\n }\n\n if (e.key === 'Tab') {\n close('programmatic');\n }\n }\n\n // ── Lifecycle ─────────────────────────────────────────────────────────────\n bind({\n on: {\n click: (e: MouseEvent) => {\n const path = e.composedPath();\n\n if (!isOpenSignal.value) return;\n\n const itemFromPath = path.find(\n (node): node is HTMLElement => node instanceof HTMLElement && node.tagName === 'ORE-MENU-ITEM',\n );\n const item = itemFromPath ?? (e.target as HTMLElement | null)?.closest<HTMLElement>('ore-menu-item') ?? null;\n\n if (!item || item.hasAttribute('disabled')) return;\n\n activateItem(item);\n },\n },\n });\n\n watch(() => {\n const open = isOpenSignal.value;\n\n if (!panelEl) return;\n\n panelEl.toggleAttribute('data-open', open);\n });\n\n function resolveTrigger() {\n cleanupTrigger?.();\n cleanupTrigger = null;\n\n const assigned = slots.elements('trigger').value;\n\n triggerEl = (assigned?.[0] as HTMLElement | undefined) ?? null;\n\n if (!triggerEl) return;\n\n const cleanups: Array<() => void> = [];\n const removeAria = bind(\n {\n attr: {\n 'aria-controls': () => menuId,\n 'aria-disabled': () => isDisabled.value,\n 'aria-expanded': () => String(isOpenSignal.value),\n 'aria-haspopup': 'menu',\n },\n },\n { target: triggerEl },\n );\n\n const onTriggerClick = (event: MouseEvent) => {\n event.stopPropagation();\n\n if (isDisabled.value) return;\n\n toggle();\n };\n const onTriggerKeydown = (event: KeyboardEvent) => {\n handleMenuKeydown(event);\n };\n\n triggerEl.addEventListener('click', onTriggerClick);\n triggerEl.addEventListener('keydown', onTriggerKeydown);\n cleanups.push(() => triggerEl?.removeEventListener('click', onTriggerClick));\n cleanups.push(() => triggerEl?.removeEventListener('keydown', onTriggerKeydown));\n\n cleanupTrigger = () => {\n removeAria();\n\n for (const cleanup of cleanups) cleanup();\n };\n }\n\n rippleWatch(slots.elements('trigger'), resolveTrigger, { immediate: true });\n\n onMounted(() => {\n resolveTrigger();\n\n return () => {\n cleanupTrigger?.();\n cleanupTrigger = null;\n };\n });\n\n return html`\n <slot name=\"trigger\"></slot>\n <div\n class=\"menu-panel\"\n part=\"panel\"\n id=\"${menuId}\"\n role=\"menu\"\n aria-orientation=\"vertical\"\n @keydown=\"${handleMenuKeydown}\"\n ref=\"${(el: HTMLElement | null) => {\n panelEl = el;\n panelEl?.toggleAttribute('data-open', isOpenSignal.value);\n }}\">\n <slot></slot>\n </div>\n `;\n },\n styles: [componentStyles, sizeVariantMixin(MENU_SIZE_PRESET), forcedColorsMixin],\n});\n"],"mappings":"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"}
package/dist/menu.js CHANGED
@@ -1,4 +1,4 @@
1
- import{sizeVariantMixin as e}from"./styles/mixins/shape.css.js";import{colorThemeMixin as t,forcedColorsMixin as n}from"./styles/mixins/theme.css.js";import{createInteraction as r}from"./core/keyboard.js";import{lifecycleSignal as i}from"./core/ore.js";import{createOutsidePointerDismissal as a}from"./core/overlay.js";import{createListControl as o}from"./core/nav.js";import{createDropdownPositioner as s}from"./core/positioner.js";import{parseOptionalBool as c}from"./core/parse.js";import{disablableBundle as l,sizableBundle as u}from"./shared/prop-bundles.js";import{MENU_SIZE_PRESET as d}from"./shared/size-presets.js";import f from"./overlay/menu/menu.css.js";import p from"./overlay/menu/menu-item.css.js";import m from"./overlay/menu/menu-separator.css.js";import{computed as h,signal as g,watch as _}from"@vielzeug/ripple";import{bind as v,createId as y,define as b,getHost as x,html as S,onCleanup as C,onMounted as w,prop as T,useEmit as E,useSlots as D,watchEffect as O}from"@vielzeug/ore";import{restoreFocus as k}from"@vielzeug/focus";var A=`ore-menu-item`;b(A,{props:{checked:T.bool(!1),disabled:T.bool(!1),type:T.string(),value:T.string()},setup(e){let isCheckable=()=>e.type.value===`checkbox`||e.type.value===`radio`,isChecked=()=>isCheckable()&&e.checked.value,itemRole=()=>e.type.value===`checkbox`?`menuitemcheckbox`:e.type.value===`radio`?`menuitemradio`:`menuitem`,itemClass=()=>{let t=e.type.value;return[`item`,t===`checkbox`?`is-checkbox`:``,t===`radio`?`is-radio`:``,isChecked()?`is-checked`:``].filter(Boolean).join(` `)};return isCheckable()?S`
1
+ import{sizeVariantMixin as e}from"./styles/mixins/shape.css.js";import{colorThemeMixin as t,forcedColorsMixin as n}from"./styles/mixins/theme.css.js";import{createInteraction as r}from"./core/keyboard.js";import{lifecycleSignal as i}from"./core/ore.js";import{createOutsidePointerDismissal as a}from"./core/overlay.js";import{createListControl as o}from"./core/nav.js";import{createDropdownPositioner as s}from"./core/positioner.js";import{parseOptionalBool as c}from"./core/parse.js";import{disablableBundle as l,sizableBundle as u}from"./shared/prop-bundles.js";import{MENU_SIZE_PRESET as d}from"./shared/size-presets.js";import f from"./overlay/menu/menu.css.js";import p from"./overlay/menu/menu-item.css.js";import m from"./overlay/menu/menu-separator.css.js";import{computed as h,signal as g,watch as _}from"@vielzeug/ripple";import{bind as v,createId as y,define as b,getHost as x,html as S,onCleanup as C,onMounted as w,prop as T,useEmit as E,useSlots as D,watchEffect as O}from"@vielzeug/ore";import{restoreFocus as k}from"@vielzeug/focus";var A=[`checkbox`,`radio`],j=`ore-menu-item`;b(j,{props:{checked:T.bool(!1),disabled:T.bool(!1),type:T.oneOf(A),value:T.string()},setup(e){let isCheckable=()=>e.type.value===`checkbox`||e.type.value===`radio`,isChecked=()=>isCheckable()&&e.checked.value,itemRole=()=>e.type.value===`checkbox`?`menuitemcheckbox`:e.type.value===`radio`?`menuitemradio`:`menuitem`,itemClass=()=>{let t=e.type.value;return[`item`,t===`checkbox`?`is-checkbox`:``,t===`radio`?`is-radio`:``,isChecked()?`is-checked`:``].filter(Boolean).join(` `)};return isCheckable()?S`
2
2
  <div
3
3
  class="${itemClass}"
4
4
  part="item"
@@ -15,7 +15,7 @@ import{sizeVariantMixin as e}from"./styles/mixins/shape.css.js";import{colorThem
15
15
  <span class="icon-slot" part="icon-slot"><slot name="icon"></slot></span>
16
16
  <span class="item-label" part="item-label"><slot></slot></span>
17
17
  </div>
18
- `},styles:[t,p]});var j=`ore-menu-separator`;b(j,{setup(){return S``},styles:[m]});var isCheckableItemType=e=>e===`checkbox`||e===`radio`,M=`ore-menu`;b(M,{props:{...u,...l,"default-open":T.bool(!1),open:{default:void 0,parse:c},placement:T.oneOf([`bottom`,`bottom-start`,`bottom-end`,`top`,`top-start`,`top-end`],`bottom-start`)},setup(e){let t=x(),n=E(),c=D(),l=O,u=y(`menu`),d=h(()=>!!e.disabled.value),f=i(C),p=null,m=null,cleanupTrigger=null;function getItems(){return Array.from(t.querySelectorAll(`ore-menu-item:not([disabled])`))}function getItemFocusable(e){return e?e.shadowRoot?.querySelector(`[role^="menuitem"]`)??e:null}function getFocusedItemIndex(){return getItems().findIndex(e=>{let t=getItemFocusable(e);return e===document.activeElement||t===document.activeElement})}let b=g(!1),T=null,A=s({getFloating:()=>m,getOffsetPx:()=>4,getPlacement:()=>e.placement.value??`bottom-start`,getReference:()=>p,matchWidth:!1,onPlacementChange:e=>m?.setAttribute(`data-placement`,e),padding:6,useClippingAncestor:!1}),j=o({disabled:h(()=>!b.value),getItems,isItemDisabled:e=>e.hasAttribute(`disabled`),onNavigate:({item:e})=>{getItemFocusable(e)?.focus()},signal:f}),close=(e=`programmatic`,t=!0)=>{b.value&&(b.value=!1,j.reset(),T?.(),T=null,t&&k(()=>p),n(`open-change`,{open:!1,reason:e}))},open=(e=`programmatic`)=>{d.value||b.value||(b.value=!0,A.update(),T=A.startAutoUpdate?.()??null,n(`open-change`,{open:!0,reason:e}))},toggle=()=>{b.value?close(`trigger`):open(`click`)};a({getTargets:()=>[t,m],isActive:()=>b.value,onDismiss:()=>close(`outsideClick`),signal:f});let M=!1;_(e.open,t=>{t===void 0?M?close(`programmatic`):e[`default-open`].value&&open(`programmatic`):t?open(`programmatic`):close(`programmatic`),M=!0},{immediate:!0}),f.addEventListener(`abort`,()=>{b.value&&(b.value=!1,j.reset(),T?.(),T=null)},{once:!0});let activateItem=e=>{let r=e.getAttribute(`type`),i=isCheckableItemType(r);if(r===`checkbox`)e.toggleAttribute(`checked`,!e.hasAttribute(`checked`));else if(r===`radio`)for(let n of t.querySelectorAll(`ore-menu-item[type="radio"]`))n.toggleAttribute(`checked`,n===e);let a=e.getAttribute(`value`)??``,o=i?e.hasAttribute(`checked`):void 0;n(`select`,{checked:o,value:a}),i||close(`programmatic`)},N=r({keys:[`Enter`,` `,`ArrowDown`],onPress:()=>{open(`keyboard`),requestAnimationFrame(()=>j.set(0))}}),P=r({onPress:()=>{let e=j.getActiveItem();e&&activateItem(e)}});function handleMenuKeydown(e){if(d.value)return;if(!b.value){N.handleKeydown(e);return}let t=getFocusedItemIndex();if(t>=0&&j.set(t),e.key===`Escape`){e.preventDefault(),close(`escape`);return}if(!j.handleKeydown(e)){if(e.key===` `||e.key===`Enter`){P.handleKeydown(e);return}e.key===`Tab`&&close(`programmatic`)}}v({on:{click:e=>{let t=e.composedPath();if(!b.value)return;let n=t.find(e=>e instanceof HTMLElement&&e.tagName===`ORE-MENU-ITEM`)??e.target?.closest(`ore-menu-item`)??null;n&&!n.hasAttribute(`disabled`)&&activateItem(n)}}}),l(()=>{let e=b.value;m&&m.toggleAttribute(`data-open`,e)});function resolveTrigger(){if(cleanupTrigger?.(),cleanupTrigger=null,p=c.elements(`trigger`).value?.[0]??null,!p)return;let e=[],t=v({attr:{"aria-controls":()=>u,"aria-disabled":()=>d.value,"aria-expanded":()=>String(b.value),"aria-haspopup":`menu`}},{target:p}),onTriggerClick=e=>{e.stopPropagation(),!d.value&&toggle()},onTriggerKeydown=e=>{handleMenuKeydown(e)};p.addEventListener(`click`,onTriggerClick),p.addEventListener(`keydown`,onTriggerKeydown),e.push(()=>p?.removeEventListener(`click`,onTriggerClick)),e.push(()=>p?.removeEventListener(`keydown`,onTriggerKeydown)),cleanupTrigger=()=>{t();for(let t of e)t()}}return _(c.elements(`trigger`),resolveTrigger,{immediate:!0}),w(()=>(resolveTrigger(),()=>{cleanupTrigger?.(),cleanupTrigger=null})),S`
18
+ `},styles:[t,p]});var M=`ore-menu-separator`;b(M,{setup(){return S``},styles:[m]});var isCheckableItemType=e=>e===`checkbox`||e===`radio`,N=`ore-menu`;b(N,{props:{...u,...l,"default-open":T.bool(!1),open:{default:void 0,parse:c},placement:T.oneOf([`bottom`,`bottom-start`,`bottom-end`,`top`,`top-start`,`top-end`],`bottom-start`)},setup(e){let t=x(),n=E(),c=D(),l=O,u=y(`menu`),d=h(()=>!!e.disabled.value),f=i(C),p=null,m=null,cleanupTrigger=null;function getItems(){return Array.from(t.querySelectorAll(`ore-menu-item:not([disabled])`))}function getItemFocusable(e){return e?e.shadowRoot?.querySelector(`[role^="menuitem"]`)??e:null}function getFocusedItemIndex(){return getItems().findIndex(e=>{let t=getItemFocusable(e);return e===document.activeElement||t===document.activeElement})}let b=g(!1),T=null,A=s({getFloating:()=>m,getOffsetPx:()=>4,getPlacement:()=>e.placement.value??`bottom-start`,getReference:()=>p,matchWidth:!1,onPlacementChange:e=>m?.setAttribute(`data-placement`,e),padding:6,useClippingAncestor:!1}),j=o({disabled:h(()=>!b.value),getItems,isItemDisabled:e=>e.hasAttribute(`disabled`),onNavigate:({item:e})=>{getItemFocusable(e)?.focus()},signal:f}),close=(e=`programmatic`,t=!0)=>{b.value&&(b.value=!1,j.reset(),T?.(),T=null,t&&k(()=>p),n(`open-change`,{open:!1,reason:e}))},open=(e=`programmatic`)=>{d.value||b.value||(b.value=!0,A.update(),T=A.startAutoUpdate?.()??null,n(`open-change`,{open:!0,reason:e}))},toggle=()=>{b.value?close(`trigger`):open(`click`)};a({getTargets:()=>[t,m],isActive:()=>b.value,onDismiss:()=>close(`outsideClick`),signal:f});let M=!1;_(e.open,t=>{t===void 0?M?close(`programmatic`):e[`default-open`].value&&open(`programmatic`):t?open(`programmatic`):close(`programmatic`),M=!0},{immediate:!0}),f.addEventListener(`abort`,()=>{b.value&&(b.value=!1,j.reset(),T?.(),T=null)},{once:!0});let activateItem=e=>{let r=e.getAttribute(`type`),i=isCheckableItemType(r);if(r===`checkbox`)e.toggleAttribute(`checked`,!e.hasAttribute(`checked`));else if(r===`radio`)for(let n of t.querySelectorAll(`ore-menu-item[type="radio"]`))n.toggleAttribute(`checked`,n===e);let a=e.getAttribute(`value`)??``,o=i?e.hasAttribute(`checked`):void 0;n(`select`,{checked:o,value:a}),i||close(`programmatic`)},N=r({keys:[`Enter`,` `,`ArrowDown`],onPress:()=>{open(`keyboard`),requestAnimationFrame(()=>j.set(0))}}),P=r({onPress:()=>{let e=j.getActiveItem();e&&activateItem(e)}});function handleMenuKeydown(e){if(d.value)return;if(!b.value){N.handleKeydown(e);return}let t=getFocusedItemIndex();if(t>=0&&j.set(t),e.key===`Escape`){e.preventDefault(),close(`escape`);return}if(!j.handleKeydown(e)){if(e.key===` `||e.key===`Enter`){P.handleKeydown(e);return}e.key===`Tab`&&close(`programmatic`)}}v({on:{click:e=>{let t=e.composedPath();if(!b.value)return;let n=t.find(e=>e instanceof HTMLElement&&e.tagName===`ORE-MENU-ITEM`)??e.target?.closest(`ore-menu-item`)??null;n&&!n.hasAttribute(`disabled`)&&activateItem(n)}}}),l(()=>{let e=b.value;m&&m.toggleAttribute(`data-open`,e)});function resolveTrigger(){if(cleanupTrigger?.(),cleanupTrigger=null,p=c.elements(`trigger`).value?.[0]??null,!p)return;let e=[],t=v({attr:{"aria-controls":()=>u,"aria-disabled":()=>d.value,"aria-expanded":()=>String(b.value),"aria-haspopup":`menu`}},{target:p}),onTriggerClick=e=>{e.stopPropagation(),!d.value&&toggle()},onTriggerKeydown=e=>{handleMenuKeydown(e)};p.addEventListener(`click`,onTriggerClick),p.addEventListener(`keydown`,onTriggerKeydown),e.push(()=>p?.removeEventListener(`click`,onTriggerClick)),e.push(()=>p?.removeEventListener(`keydown`,onTriggerKeydown)),cleanupTrigger=()=>{t();for(let t of e)t()}}return _(c.elements(`trigger`),resolveTrigger,{immediate:!0}),w(()=>(resolveTrigger(),()=>{cleanupTrigger?.(),cleanupTrigger=null})),S`
19
19
  <slot name="trigger"></slot>
20
20
  <div
21
21
  class="menu-panel"
@@ -27,5 +27,5 @@ import{sizeVariantMixin as e}from"./styles/mixins/shape.css.js";import{colorThem
27
27
  ref="${e=>{m=e,m?.toggleAttribute(`data-open`,b.value)}}">
28
28
  <slot></slot>
29
29
  </div>
30
- `},styles:[f,e(d),n]});export{A as MENU_ITEM_TAG,M as MENU_TAG,j as SEPARATOR_TAG};
30
+ `},styles:[f,e(d),n]});export{j as MENU_ITEM_TAG,N as MENU_TAG,A as ORE_MENU_ITEM_TYPES,M as SEPARATOR_TAG};
31
31
  //# sourceMappingURL=menu.js.map
package/dist/menu.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"menu.js","names":[],"sources":["../src/overlay/menu/menu.ts"],"sourcesContent":["import { restoreFocus } from '@vielzeug/focus';\nimport type { Placement } from '@vielzeug/orbit';\nimport {\n bind,\n createId,\n define,\n getHost,\n html,\n onCleanup,\n onMounted,\n prop,\n useEmit,\n useSlots,\n watchEffect,\n} from '@vielzeug/ore';\nimport { computed, watch as rippleWatch, signal } from '@vielzeug/ripple';\nimport {\n createDropdownPositioner,\n createInteraction,\n createListControl,\n createOutsidePointerDismissal,\n type DropdownCloseReason,\n lifecycleSignal,\n type OverlayOpenChangeDetail,\n type OverlayOpenReason,\n parseOptionalBool,\n} from '../../core';\nimport { disablableBundle, MENU_SIZE_PRESET, sizableBundle } from '../../shared';\nimport { colorThemeMixin, forcedColorsMixin, sizeVariantMixin } from '../../styles';\nimport type { ComponentSize } from '../../types';\nimport componentStyles from './menu.css?inline';\nimport menuItemStyles from './menu-item.css?inline';\nimport menuSeparatorStyles from './menu-separator.css?inline';\n\n// ── Types ─────────────────────────────────────────────────────────────\n\nexport interface MenuSelectDetail {\n checked?: boolean;\n value: string;\n}\n\nexport type OreMenuItemType = 'checkbox' | 'radio';\n\nexport type OreMenuEvents = {\n 'open-change': OverlayOpenChangeDetail;\n select: MenuSelectDetail;\n};\n\nexport type OreMenuItemProps = {\n checked?: boolean;\n disabled?: boolean;\n type?: OreMenuItemType;\n value?: string;\n};\n\nexport type OreMenuProps = {\n /** Initial uncontrolled open state. Ignored when `open` is set. */\n 'default-open'?: boolean;\n disabled?: boolean;\n /** Controlled open state. */\n open?: boolean;\n placement?: 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end';\n size?: ComponentSize;\n};\n\n// ── Styles ─────────────────────────────────────────────────────────────\n\n// ── Menu Item Component ─────────────────────────────────────────────────────────────\n\n/**\n * A selectable action item used inside `<ore-menu>`.\n *\n * @element ore-menu-item\n *\n * @attr {boolean} checked - Checked state for `checkbox` and `radio` item types\n * @attr {boolean} disabled - Disables selection and pointer interaction\n * @attr {'checkbox'|'radio'} type - Optional checkable menu item mode\n * @attr {string} value - Value emitted by parent menu on selection\n *\n * @slot - Item label/content\n * @slot icon - Optional leading icon content\n *\n * @cssprop --menu-item-hover-bg - Background on hover\n * @cssprop --menu-item-focus-color - Text color when keyboard-focused\n * @cssprop --menu-item-focus-bg - Background when keyboard-focused\n * @cssprop --menu-item-selection-bg - Background for checkbox/radio items (unselected)\n * @cssprop --menu-item-checked-color - Text color when checked\n * @cssprop --menu-item-checked-bg - Background when checked\n *\n * @part item - Root item container element.\n * @part item-label - Label text container.\n * @part icon-slot - Leading icon slot container.\n *\n * @example\n * ```html\n * <ore-menu-item value=\"edit\">Edit</ore-menu-item>\n * <ore-menu-item value=\"delete\" disabled>Delete</ore-menu-item>\n * <ore-menu-item type=\"checkbox\" value=\"wrap\" checked>Word wrap</ore-menu-item>\n * <ore-menu-item type=\"radio\" value=\"left\">Align left</ore-menu-item>\n * ```\n */\nexport const MENU_ITEM_TAG = 'ore-menu-item' as const;\ndefine<OreMenuItemProps>(MENU_ITEM_TAG, {\n props: {\n checked: prop.bool(false),\n disabled: prop.bool(false),\n type: prop.string<OreMenuItemType>(),\n value: prop.string(),\n },\n setup(props) {\n const isCheckable = () => props.type.value === 'checkbox' || props.type.value === 'radio';\n const isChecked = () => isCheckable() && props.checked.value;\n const itemRole = () => {\n if (props.type.value === 'checkbox') return 'menuitemcheckbox';\n\n if (props.type.value === 'radio') return 'menuitemradio';\n\n return 'menuitem';\n };\n const itemClass = () => {\n const type = props.type.value;\n\n return [\n 'item',\n type === 'checkbox' ? 'is-checkbox' : '',\n type === 'radio' ? 'is-radio' : '',\n isChecked() ? 'is-checked' : '',\n ]\n .filter(Boolean)\n .join(' ');\n };\n\n return isCheckable()\n ? html`\n <div\n class=\"${itemClass}\"\n part=\"item\"\n tabindex=\"-1\"\n role=\"${itemRole}\"\n aria-checked=\"${() => String(isChecked())}\"\n aria-disabled=\"${props.disabled}\">\n <span class=\"item-check\" aria-hidden=\"true\"></span>\n <span class=\"icon-slot\" part=\"icon-slot\"><slot name=\"icon\"></slot></span>\n <span class=\"item-label\" part=\"item-label\"><slot></slot></span>\n </div>\n `\n : html`\n <div class=\"item\" part=\"item\" tabindex=\"-1\" role=\"menuitem\" aria-disabled=\"${props.disabled}\">\n <span class=\"icon-slot\" part=\"icon-slot\"><slot name=\"icon\"></slot></span>\n <span class=\"item-label\" part=\"item-label\"><slot></slot></span>\n </div>\n `;\n },\n styles: [colorThemeMixin, menuItemStyles],\n});\n\n// ── Menu Separator ─────────────────────────────────────────────────────────────\n\n/**\n * Visual separator used to group menu items inside `<ore-menu>`.\n *\n * @element ore-menu-separator\n *\n * @example\n * ```html\n * <ore-menu-item value=\"cut\">Cut</ore-menu-item>\n * <ore-menu-separator></ore-menu-separator>\n * <ore-menu-item value=\"paste\">Paste</ore-menu-item>\n * ```\n */\nexport const SEPARATOR_TAG = 'ore-menu-separator' as const;\ndefine(SEPARATOR_TAG, {\n setup() {\n return html``;\n },\n styles: [menuSeparatorStyles],\n});\n\n// ── Menu Component ─────────────────────────────────────────────────────────────\n\nconst isCheckableItemType = (value: string | null): value is OreMenuItemType =>\n value === 'checkbox' || value === 'radio';\n\n/**\n * Action dropdown menu triggered by a slotted trigger element.\n *\n * @element ore-menu\n * @element ore-menu-item - Clickable menu option (place in default slot)\n * @element ore-menu-separator - Visual divider between menu groups\n *\n * @attr {boolean} disabled - Disables opening and keyboard interaction\n * @attr {boolean} open - Controlled open state\n * @attr {boolean} default-open - Initial uncontrolled open state\n * @attr {string} placement - Panel placement: 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end' (default: 'bottom-start')\n * @attr {string} size - Size: 'sm' | 'md' | 'lg'\n *\n * @fires open-change - Fired when the menu state changes. detail: { open, reason }\n * @fires select - Fired when an item is selected. detail: { value: string, checked?: boolean }\n *\n * @slot trigger - Trigger element that toggles menu visibility\n * @slot - Menu content (`<ore-menu-item>` and `<ore-menu-separator>`)\n *\n * @part panel - Floating menu panel container\n *\n * @cssprop --menu-panel-bg - Background of the floating panel\n * @cssprop --menu-panel-border-color - Border color of the floating panel\n * @cssprop --menu-panel-shadow - Box shadow of the floating panel\n * @cssprop --menu-panel-blur - Backdrop blur amount for the floating panel\n * @cssprop --menu-panel-min-width - Minimum width of the floating panel\n * @cssprop --menu-panel-max-height - Maximum height of the floating panel before it scrolls\n * @cssprop --menu-panel-radius - Border radius of the floating panel\n *\n * @example\n * ```html\n * <ore-menu>\n * <button slot=\"trigger\">Actions</button>\n * <ore-menu-item value=\"edit\">Edit</ore-menu-item>\n * <ore-menu-item value=\"delete\">Delete</ore-menu-item>\n * </ore-menu>\n * ```\n */\nexport const MENU_TAG = 'ore-menu' as const;\ndefine<OreMenuProps>(MENU_TAG, {\n props: {\n ...sizableBundle,\n ...disablableBundle,\n 'default-open': prop.bool(false),\n open: { default: undefined as boolean | undefined, parse: parseOptionalBool },\n placement: prop.oneOf(\n ['bottom', 'bottom-start', 'bottom-end', 'top', 'top-start', 'top-end'] as const,\n 'bottom-start',\n ),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreMenuEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const menuId = createId('menu');\n const isDisabled = computed(() => Boolean(props.disabled.value));\n const abortSignal = lifecycleSignal(onCleanup);\n let triggerEl: HTMLElement | null = null;\n let panelEl: HTMLElement | null = null;\n let cleanupTrigger: (() => void) | null = null;\n\n // ── Helpers ───────────────────────────────────────────────────────────────\n function getItems(): HTMLElement[] {\n return Array.from(el.querySelectorAll<HTMLElement>('ore-menu-item:not([disabled])'));\n }\n\n function getItemFocusable(item: HTMLElement | null | undefined): HTMLElement | null {\n if (!item) return null;\n\n return item.shadowRoot?.querySelector<HTMLElement>('[role^=\"menuitem\"]') ?? item;\n }\n\n function getFocusedItemIndex(): number {\n const items = getItems();\n\n return items.findIndex((item) => {\n const focusable = getItemFocusable(item);\n\n return item === document.activeElement || focusable === document.activeElement;\n });\n }\n\n const isOpenSignal = signal(false);\n let stopPositioning: (() => void) | null = null;\n const positioner = createDropdownPositioner({\n getFloating: () => panelEl,\n getOffsetPx: () => 4,\n getPlacement: () => (props.placement.value ?? 'bottom-start') as Placement,\n getReference: () => triggerEl,\n matchWidth: false,\n // menu.css keys the panel's transform-origin off [data-placement]; the positioner reports the\n // resolved (RTL-mirrored, flipped) placement here so that rule finally has something to match.\n onPlacementChange: (placement) => panelEl?.setAttribute('data-placement', placement),\n padding: 6,\n useClippingAncestor: false,\n });\n const list = createListControl<HTMLElement>({\n disabled: computed(() => !isOpenSignal.value),\n getItems: getItems,\n isItemDisabled: (item) => item.hasAttribute('disabled'),\n onNavigate: ({ item }) => {\n getItemFocusable(item)?.focus();\n },\n signal: abortSignal,\n });\n\n const close = (reason: DropdownCloseReason = 'programmatic', shouldRestoreFocus = true): void => {\n if (!isOpenSignal.value) return;\n\n isOpenSignal.value = false;\n list.reset();\n stopPositioning?.();\n stopPositioning = null;\n\n if (shouldRestoreFocus) restoreFocus(() => triggerEl);\n\n emit('open-change', { open: false, reason });\n };\n\n const open = (reason: OverlayOpenReason = 'programmatic'): void => {\n if (isDisabled.value || isOpenSignal.value) return;\n\n isOpenSignal.value = true;\n positioner.update();\n stopPositioning = positioner.startAutoUpdate?.() ?? null;\n emit('open-change', { open: true, reason });\n };\n\n const toggle = (): void => {\n if (isOpenSignal.value) close('trigger');\n else open('click');\n };\n\n createOutsidePointerDismissal({\n getTargets: () => [el, panelEl],\n isActive: () => isOpenSignal.value,\n onDismiss: () => close('outsideClick'),\n signal: abortSignal,\n });\n\n let initialized = false;\n\n rippleWatch(\n props.open,\n (value) => {\n if (value === undefined) {\n if (!initialized) {\n if (props['default-open'].value) open('programmatic');\n } else {\n close('programmatic');\n }\n } else if (value) {\n open('programmatic');\n } else {\n close('programmatic');\n }\n\n initialized = true;\n },\n { immediate: true },\n );\n abortSignal.addEventListener(\n 'abort',\n () => {\n if (!isOpenSignal.value) return;\n\n isOpenSignal.value = false;\n list.reset();\n stopPositioning?.();\n stopPositioning = null;\n },\n { once: true },\n );\n\n const activateItem = (item: HTMLElement): void => {\n const type = item.getAttribute('type');\n const isCheckable = isCheckableItemType(type);\n\n if (type === 'checkbox') {\n item.toggleAttribute('checked', !item.hasAttribute('checked'));\n } else if (type === 'radio') {\n for (const radio of el.querySelectorAll<HTMLElement>('ore-menu-item[type=\"radio\"]')) {\n radio.toggleAttribute('checked', radio === item);\n }\n }\n\n const value = item.getAttribute('value') ?? '';\n const checked = isCheckable ? item.hasAttribute('checked') : undefined;\n\n emit('select', { checked, value });\n\n if (!isCheckable) {\n close('programmatic');\n }\n };\n\n const openFromKeyboardPress = createInteraction({\n keys: ['Enter', ' ', 'ArrowDown'],\n onPress: () => {\n open('keyboard');\n requestAnimationFrame(() => list.set(0));\n },\n });\n\n const activateFocusedFromKeyboardPress = createInteraction({\n onPress: () => {\n const focused = list.getActiveItem();\n\n if (focused) activateItem(focused);\n },\n });\n\n // ── Keyboard Navigation ───────────────────────────────────────────────────\n function handleMenuKeydown(e: KeyboardEvent) {\n if (isDisabled.value) return;\n\n const open = isOpenSignal.value;\n\n // When closed: open on Enter / Space / ArrowDown\n if (!open) {\n openFromKeyboardPress.handleKeydown(e);\n\n return;\n }\n\n const currentFocusedIndex = getFocusedItemIndex();\n\n if (currentFocusedIndex >= 0) list.set(currentFocusedIndex);\n\n if (e.key === 'Escape') {\n e.preventDefault();\n close('escape');\n\n return;\n }\n\n if (list.handleKeydown(e)) return;\n\n // When open: navigate and activate\n if (e.key === ' ' || e.key === 'Enter') {\n activateFocusedFromKeyboardPress.handleKeydown(e);\n\n return;\n }\n\n if (e.key === 'Tab') {\n close('programmatic');\n }\n }\n\n // ── Lifecycle ─────────────────────────────────────────────────────────────\n bind({\n on: {\n click: (e: MouseEvent) => {\n const path = e.composedPath();\n\n if (!isOpenSignal.value) return;\n\n const itemFromPath = path.find(\n (node): node is HTMLElement => node instanceof HTMLElement && node.tagName === 'ORE-MENU-ITEM',\n );\n const item = itemFromPath ?? (e.target as HTMLElement | null)?.closest<HTMLElement>('ore-menu-item') ?? null;\n\n if (!item || item.hasAttribute('disabled')) return;\n\n activateItem(item);\n },\n },\n });\n\n watch(() => {\n const open = isOpenSignal.value;\n\n if (!panelEl) return;\n\n panelEl.toggleAttribute('data-open', open);\n });\n\n function resolveTrigger() {\n cleanupTrigger?.();\n cleanupTrigger = null;\n\n const assigned = slots.elements('trigger').value;\n\n triggerEl = (assigned?.[0] as HTMLElement | undefined) ?? null;\n\n if (!triggerEl) return;\n\n const cleanups: Array<() => void> = [];\n const removeAria = bind(\n {\n attr: {\n 'aria-controls': () => menuId,\n 'aria-disabled': () => isDisabled.value,\n 'aria-expanded': () => String(isOpenSignal.value),\n 'aria-haspopup': 'menu',\n },\n },\n { target: triggerEl },\n );\n\n const onTriggerClick = (event: MouseEvent) => {\n event.stopPropagation();\n\n if (isDisabled.value) return;\n\n toggle();\n };\n const onTriggerKeydown = (event: KeyboardEvent) => {\n handleMenuKeydown(event);\n };\n\n triggerEl.addEventListener('click', onTriggerClick);\n triggerEl.addEventListener('keydown', onTriggerKeydown);\n cleanups.push(() => triggerEl?.removeEventListener('click', onTriggerClick));\n cleanups.push(() => triggerEl?.removeEventListener('keydown', onTriggerKeydown));\n\n cleanupTrigger = () => {\n removeAria();\n\n for (const cleanup of cleanups) cleanup();\n };\n }\n\n rippleWatch(slots.elements('trigger'), resolveTrigger, { immediate: true });\n\n onMounted(() => {\n resolveTrigger();\n\n return () => {\n cleanupTrigger?.();\n cleanupTrigger = null;\n };\n });\n\n return html`\n <slot name=\"trigger\"></slot>\n <div\n class=\"menu-panel\"\n part=\"panel\"\n id=\"${menuId}\"\n role=\"menu\"\n aria-orientation=\"vertical\"\n @keydown=\"${handleMenuKeydown}\"\n ref=\"${(el: HTMLElement | null) => {\n panelEl = el;\n panelEl?.toggleAttribute('data-open', isOpenSignal.value);\n }}\">\n <slot></slot>\n </div>\n `;\n },\n styles: [componentStyles, sizeVariantMixin(MENU_SIZE_PRESET), forcedColorsMixin],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"menu.js","names":[],"sources":["../src/overlay/menu/menu.ts"],"sourcesContent":["import { restoreFocus } from '@vielzeug/focus';\nimport type { Placement } from '@vielzeug/orbit';\nimport {\n bind,\n createId,\n define,\n getHost,\n html,\n onCleanup,\n onMounted,\n prop,\n useEmit,\n useSlots,\n watchEffect,\n} from '@vielzeug/ore';\nimport { computed, watch as rippleWatch, signal } from '@vielzeug/ripple';\nimport {\n createDropdownPositioner,\n createInteraction,\n createListControl,\n createOutsidePointerDismissal,\n type DropdownCloseReason,\n lifecycleSignal,\n type OverlayOpenChangeDetail,\n type OverlayOpenReason,\n parseOptionalBool,\n} from '../../core';\nimport { disablableBundle, MENU_SIZE_PRESET, sizableBundle } from '../../shared';\nimport { colorThemeMixin, forcedColorsMixin, sizeVariantMixin } from '../../styles';\nimport type { ComponentSize } from '../../types';\nimport componentStyles from './menu.css?inline';\nimport menuItemStyles from './menu-item.css?inline';\nimport menuSeparatorStyles from './menu-separator.css?inline';\n\n// ── Types ─────────────────────────────────────────────────────────────\n\nexport interface MenuSelectDetail {\n checked?: boolean;\n value: string;\n}\n\nexport const ORE_MENU_ITEM_TYPES = ['checkbox', 'radio'] as const;\nexport type OreMenuItemType = (typeof ORE_MENU_ITEM_TYPES)[number];\n\nexport type OreMenuEvents = {\n 'open-change': OverlayOpenChangeDetail;\n select: MenuSelectDetail;\n};\n\nexport type OreMenuItemProps = {\n checked?: boolean;\n disabled?: boolean;\n type?: OreMenuItemType;\n value?: string;\n};\n\nexport type OreMenuProps = {\n /** Initial uncontrolled open state. Ignored when `open` is set. */\n 'default-open'?: boolean;\n disabled?: boolean;\n /** Controlled open state. */\n open?: boolean;\n placement?: 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end';\n size?: ComponentSize;\n};\n\n// ── Styles ─────────────────────────────────────────────────────────────\n\n// ── Menu Item Component ─────────────────────────────────────────────────────────────\n\n/**\n * A selectable action item used inside `<ore-menu>`.\n *\n * @element ore-menu-item\n *\n * @attr {boolean} checked - Checked state for `checkbox` and `radio` item types\n * @attr {boolean} disabled - Disables selection and pointer interaction\n * @attr {'checkbox'|'radio'} type - Optional checkable menu item mode\n * @attr {string} value - Value emitted by parent menu on selection\n *\n * @slot - Item label/content\n * @slot icon - Optional leading icon content\n *\n * @cssprop --menu-item-hover-bg - Background on hover\n * @cssprop --menu-item-focus-color - Text color when keyboard-focused\n * @cssprop --menu-item-focus-bg - Background when keyboard-focused\n * @cssprop --menu-item-selection-bg - Background for checkbox/radio items (unselected)\n * @cssprop --menu-item-checked-color - Text color when checked\n * @cssprop --menu-item-checked-bg - Background when checked\n *\n * @part item - Root item container element.\n * @part item-label - Label text container.\n * @part icon-slot - Leading icon slot container.\n *\n * @example\n * ```html\n * <ore-menu-item value=\"edit\">Edit</ore-menu-item>\n * <ore-menu-item value=\"delete\" disabled>Delete</ore-menu-item>\n * <ore-menu-item type=\"checkbox\" value=\"wrap\" checked>Word wrap</ore-menu-item>\n * <ore-menu-item type=\"radio\" value=\"left\">Align left</ore-menu-item>\n * ```\n */\nexport const MENU_ITEM_TAG = 'ore-menu-item' as const;\ndefine<OreMenuItemProps>(MENU_ITEM_TAG, {\n props: {\n checked: prop.bool(false),\n disabled: prop.bool(false),\n type: prop.oneOf(ORE_MENU_ITEM_TYPES),\n value: prop.string(),\n },\n setup(props) {\n const isCheckable = () => props.type.value === 'checkbox' || props.type.value === 'radio';\n const isChecked = () => isCheckable() && props.checked.value;\n const itemRole = () => {\n if (props.type.value === 'checkbox') return 'menuitemcheckbox';\n\n if (props.type.value === 'radio') return 'menuitemradio';\n\n return 'menuitem';\n };\n const itemClass = () => {\n const type = props.type.value;\n\n return [\n 'item',\n type === 'checkbox' ? 'is-checkbox' : '',\n type === 'radio' ? 'is-radio' : '',\n isChecked() ? 'is-checked' : '',\n ]\n .filter(Boolean)\n .join(' ');\n };\n\n return isCheckable()\n ? html`\n <div\n class=\"${itemClass}\"\n part=\"item\"\n tabindex=\"-1\"\n role=\"${itemRole}\"\n aria-checked=\"${() => String(isChecked())}\"\n aria-disabled=\"${props.disabled}\">\n <span class=\"item-check\" aria-hidden=\"true\"></span>\n <span class=\"icon-slot\" part=\"icon-slot\"><slot name=\"icon\"></slot></span>\n <span class=\"item-label\" part=\"item-label\"><slot></slot></span>\n </div>\n `\n : html`\n <div class=\"item\" part=\"item\" tabindex=\"-1\" role=\"menuitem\" aria-disabled=\"${props.disabled}\">\n <span class=\"icon-slot\" part=\"icon-slot\"><slot name=\"icon\"></slot></span>\n <span class=\"item-label\" part=\"item-label\"><slot></slot></span>\n </div>\n `;\n },\n styles: [colorThemeMixin, menuItemStyles],\n});\n\n// ── Menu Separator ─────────────────────────────────────────────────────────────\n\n/**\n * Visual separator used to group menu items inside `<ore-menu>`.\n *\n * @element ore-menu-separator\n *\n * @example\n * ```html\n * <ore-menu-item value=\"cut\">Cut</ore-menu-item>\n * <ore-menu-separator></ore-menu-separator>\n * <ore-menu-item value=\"paste\">Paste</ore-menu-item>\n * ```\n */\nexport const SEPARATOR_TAG = 'ore-menu-separator' as const;\ndefine(SEPARATOR_TAG, {\n setup() {\n return html``;\n },\n styles: [menuSeparatorStyles],\n});\n\n// ── Menu Component ─────────────────────────────────────────────────────────────\n\nconst isCheckableItemType = (value: string | null): value is OreMenuItemType =>\n value === 'checkbox' || value === 'radio';\n\n/**\n * Action dropdown menu triggered by a slotted trigger element.\n *\n * @element ore-menu\n * @element ore-menu-item - Clickable menu option (place in default slot)\n * @element ore-menu-separator - Visual divider between menu groups\n *\n * @attr {boolean} disabled - Disables opening and keyboard interaction\n * @attr {boolean} open - Controlled open state\n * @attr {boolean} default-open - Initial uncontrolled open state\n * @attr {string} placement - Panel placement: 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end' (default: 'bottom-start')\n * @attr {string} size - Size: 'sm' | 'md' | 'lg'\n *\n * @fires open-change - Fired when the menu state changes. detail: { open, reason }\n * @fires select - Fired when an item is selected. detail: { value: string, checked?: boolean }\n *\n * @slot trigger - Trigger element that toggles menu visibility\n * @slot - Menu content (`<ore-menu-item>` and `<ore-menu-separator>`)\n *\n * @part panel - Floating menu panel container\n *\n * @cssprop --menu-panel-bg - Background of the floating panel\n * @cssprop --menu-panel-border-color - Border color of the floating panel\n * @cssprop --menu-panel-shadow - Box shadow of the floating panel\n * @cssprop --menu-panel-blur - Backdrop blur amount for the floating panel\n * @cssprop --menu-panel-min-width - Minimum width of the floating panel\n * @cssprop --menu-panel-max-height - Maximum height of the floating panel before it scrolls\n * @cssprop --menu-panel-radius - Border radius of the floating panel\n *\n * @example\n * ```html\n * <ore-menu>\n * <button slot=\"trigger\">Actions</button>\n * <ore-menu-item value=\"edit\">Edit</ore-menu-item>\n * <ore-menu-item value=\"delete\">Delete</ore-menu-item>\n * </ore-menu>\n * ```\n */\nexport const MENU_TAG = 'ore-menu' as const;\ndefine<OreMenuProps>(MENU_TAG, {\n props: {\n ...sizableBundle,\n ...disablableBundle,\n 'default-open': prop.bool(false),\n open: { default: undefined as boolean | undefined, parse: parseOptionalBool },\n placement: prop.oneOf(\n ['bottom', 'bottom-start', 'bottom-end', 'top', 'top-start', 'top-end'] as const,\n 'bottom-start',\n ),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreMenuEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const menuId = createId('menu');\n const isDisabled = computed(() => Boolean(props.disabled.value));\n const abortSignal = lifecycleSignal(onCleanup);\n let triggerEl: HTMLElement | null = null;\n let panelEl: HTMLElement | null = null;\n let cleanupTrigger: (() => void) | null = null;\n\n // ── Helpers ───────────────────────────────────────────────────────────────\n function getItems(): HTMLElement[] {\n return Array.from(el.querySelectorAll<HTMLElement>('ore-menu-item:not([disabled])'));\n }\n\n function getItemFocusable(item: HTMLElement | null | undefined): HTMLElement | null {\n if (!item) return null;\n\n return item.shadowRoot?.querySelector<HTMLElement>('[role^=\"menuitem\"]') ?? item;\n }\n\n function getFocusedItemIndex(): number {\n const items = getItems();\n\n return items.findIndex((item) => {\n const focusable = getItemFocusable(item);\n\n return item === document.activeElement || focusable === document.activeElement;\n });\n }\n\n const isOpenSignal = signal(false);\n let stopPositioning: (() => void) | null = null;\n const positioner = createDropdownPositioner({\n getFloating: () => panelEl,\n getOffsetPx: () => 4,\n getPlacement: () => (props.placement.value ?? 'bottom-start') as Placement,\n getReference: () => triggerEl,\n matchWidth: false,\n // menu.css keys the panel's transform-origin off [data-placement]; the positioner reports the\n // resolved (RTL-mirrored, flipped) placement here so that rule finally has something to match.\n onPlacementChange: (placement) => panelEl?.setAttribute('data-placement', placement),\n padding: 6,\n useClippingAncestor: false,\n });\n const list = createListControl<HTMLElement>({\n disabled: computed(() => !isOpenSignal.value),\n getItems: getItems,\n isItemDisabled: (item) => item.hasAttribute('disabled'),\n onNavigate: ({ item }) => {\n getItemFocusable(item)?.focus();\n },\n signal: abortSignal,\n });\n\n const close = (reason: DropdownCloseReason = 'programmatic', shouldRestoreFocus = true): void => {\n if (!isOpenSignal.value) return;\n\n isOpenSignal.value = false;\n list.reset();\n stopPositioning?.();\n stopPositioning = null;\n\n if (shouldRestoreFocus) restoreFocus(() => triggerEl);\n\n emit('open-change', { open: false, reason });\n };\n\n const open = (reason: OverlayOpenReason = 'programmatic'): void => {\n if (isDisabled.value || isOpenSignal.value) return;\n\n isOpenSignal.value = true;\n positioner.update();\n stopPositioning = positioner.startAutoUpdate?.() ?? null;\n emit('open-change', { open: true, reason });\n };\n\n const toggle = (): void => {\n if (isOpenSignal.value) close('trigger');\n else open('click');\n };\n\n createOutsidePointerDismissal({\n getTargets: () => [el, panelEl],\n isActive: () => isOpenSignal.value,\n onDismiss: () => close('outsideClick'),\n signal: abortSignal,\n });\n\n let initialized = false;\n\n rippleWatch(\n props.open,\n (value) => {\n if (value === undefined) {\n if (!initialized) {\n if (props['default-open'].value) open('programmatic');\n } else {\n close('programmatic');\n }\n } else if (value) {\n open('programmatic');\n } else {\n close('programmatic');\n }\n\n initialized = true;\n },\n { immediate: true },\n );\n abortSignal.addEventListener(\n 'abort',\n () => {\n if (!isOpenSignal.value) return;\n\n isOpenSignal.value = false;\n list.reset();\n stopPositioning?.();\n stopPositioning = null;\n },\n { once: true },\n );\n\n const activateItem = (item: HTMLElement): void => {\n const type = item.getAttribute('type');\n const isCheckable = isCheckableItemType(type);\n\n if (type === 'checkbox') {\n item.toggleAttribute('checked', !item.hasAttribute('checked'));\n } else if (type === 'radio') {\n for (const radio of el.querySelectorAll<HTMLElement>('ore-menu-item[type=\"radio\"]')) {\n radio.toggleAttribute('checked', radio === item);\n }\n }\n\n const value = item.getAttribute('value') ?? '';\n const checked = isCheckable ? item.hasAttribute('checked') : undefined;\n\n emit('select', { checked, value });\n\n if (!isCheckable) {\n close('programmatic');\n }\n };\n\n const openFromKeyboardPress = createInteraction({\n keys: ['Enter', ' ', 'ArrowDown'],\n onPress: () => {\n open('keyboard');\n requestAnimationFrame(() => list.set(0));\n },\n });\n\n const activateFocusedFromKeyboardPress = createInteraction({\n onPress: () => {\n const focused = list.getActiveItem();\n\n if (focused) activateItem(focused);\n },\n });\n\n // ── Keyboard Navigation ───────────────────────────────────────────────────\n function handleMenuKeydown(e: KeyboardEvent) {\n if (isDisabled.value) return;\n\n const open = isOpenSignal.value;\n\n // When closed: open on Enter / Space / ArrowDown\n if (!open) {\n openFromKeyboardPress.handleKeydown(e);\n\n return;\n }\n\n const currentFocusedIndex = getFocusedItemIndex();\n\n if (currentFocusedIndex >= 0) list.set(currentFocusedIndex);\n\n if (e.key === 'Escape') {\n e.preventDefault();\n close('escape');\n\n return;\n }\n\n if (list.handleKeydown(e)) return;\n\n // When open: navigate and activate\n if (e.key === ' ' || e.key === 'Enter') {\n activateFocusedFromKeyboardPress.handleKeydown(e);\n\n return;\n }\n\n if (e.key === 'Tab') {\n close('programmatic');\n }\n }\n\n // ── Lifecycle ─────────────────────────────────────────────────────────────\n bind({\n on: {\n click: (e: MouseEvent) => {\n const path = e.composedPath();\n\n if (!isOpenSignal.value) return;\n\n const itemFromPath = path.find(\n (node): node is HTMLElement => node instanceof HTMLElement && node.tagName === 'ORE-MENU-ITEM',\n );\n const item = itemFromPath ?? (e.target as HTMLElement | null)?.closest<HTMLElement>('ore-menu-item') ?? null;\n\n if (!item || item.hasAttribute('disabled')) return;\n\n activateItem(item);\n },\n },\n });\n\n watch(() => {\n const open = isOpenSignal.value;\n\n if (!panelEl) return;\n\n panelEl.toggleAttribute('data-open', open);\n });\n\n function resolveTrigger() {\n cleanupTrigger?.();\n cleanupTrigger = null;\n\n const assigned = slots.elements('trigger').value;\n\n triggerEl = (assigned?.[0] as HTMLElement | undefined) ?? null;\n\n if (!triggerEl) return;\n\n const cleanups: Array<() => void> = [];\n const removeAria = bind(\n {\n attr: {\n 'aria-controls': () => menuId,\n 'aria-disabled': () => isDisabled.value,\n 'aria-expanded': () => String(isOpenSignal.value),\n 'aria-haspopup': 'menu',\n },\n },\n { target: triggerEl },\n );\n\n const onTriggerClick = (event: MouseEvent) => {\n event.stopPropagation();\n\n if (isDisabled.value) return;\n\n toggle();\n };\n const onTriggerKeydown = (event: KeyboardEvent) => {\n handleMenuKeydown(event);\n };\n\n triggerEl.addEventListener('click', onTriggerClick);\n triggerEl.addEventListener('keydown', onTriggerKeydown);\n cleanups.push(() => triggerEl?.removeEventListener('click', onTriggerClick));\n cleanups.push(() => triggerEl?.removeEventListener('keydown', onTriggerKeydown));\n\n cleanupTrigger = () => {\n removeAria();\n\n for (const cleanup of cleanups) cleanup();\n };\n }\n\n rippleWatch(slots.elements('trigger'), resolveTrigger, { immediate: true });\n\n onMounted(() => {\n resolveTrigger();\n\n return () => {\n cleanupTrigger?.();\n cleanupTrigger = null;\n };\n });\n\n return html`\n <slot name=\"trigger\"></slot>\n <div\n class=\"menu-panel\"\n part=\"panel\"\n id=\"${menuId}\"\n role=\"menu\"\n aria-orientation=\"vertical\"\n @keydown=\"${handleMenuKeydown}\"\n ref=\"${(el: HTMLElement | null) => {\n panelEl = el;\n panelEl?.toggleAttribute('data-open', isOpenSignal.value);\n }}\">\n <slot></slot>\n </div>\n `;\n },\n styles: [componentStyles, sizeVariantMixin(MENU_SIZE_PRESET), forcedColorsMixin],\n});\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./inputs/shared/native-field-event.cjs"),t=require("./styles/mixins/accessibility.css.cjs"),n=require("./styles/mixins/field-variants.css.cjs"),r=require("./styles/mixins/shape.css.cjs"),i=require("./styles/mixins/states.css.cjs"),a=require("./styles/mixins/theme.css.cjs"),o=require("./core/auto-resize.cjs"),s=require("./core/composer.cjs"),c=require("./core/ore.cjs"),l=require("./core/ref-callback.cjs"),u=require("./core/text-field.cjs"),d=require("./shared/prop-bundles.cjs"),f=require("./shared/size-presets.cjs"),p=require("./icon.cjs"),m=require("./inputs/shared/field-binding.cjs"),h=require("./inputs/shared/templates.cjs"),g=require("./button.cjs"),_=require("./inputs/message-composer/message-composer.css.cjs");let v=require("@vielzeug/ripple"),y=require("@vielzeug/ore");var b=`Send message`,x=`arrow-up`,S=`ore-message-composer`;(0,y.define)(S,{formAssociated:!0,props:{...d.themableBundle,...d.sizableBundle,...d.disablableBundle,...d.loadableBundle,"clear-on-send":y.prop.bool(!0),error:y.prop.string(),fullwidth:y.prop.bool(!1),helper:y.prop.string(),label:y.prop.string(),maxlength:y.prop.json(void 0),name:y.prop.string(),placeholder:y.prop.string(),readonly:y.prop.bool(!1),ref:y.prop.data(),required:y.prop.bool(!1),rows:y.prop.json(void 0),"send-icon":y.prop.string(),"send-label":y.prop.string(),"send-shortcut":y.prop.oneOf([`enter`,`mod+enter`],`enter`),success:y.prop.bool(!1),value:y.prop.string(),variant:y.prop.string()},setup(t){let n=(0,y.getHost)(),r=(0,y.useEmit)(),i=(0,y.useSlots)(),a=t.disabled,d=c.lifecycleSignal(y.onCleanup),f=(0,y.ref)(),p=o.createAutoResize(),g=u.createTextField({disabled:a,error:t.error,helper:t.helper,maxLength:t.maxlength,onInput:(t,r)=>{e.setFieldValue(n,r),e.dispatchNativeFieldEvent(n,`input`)},prefix:`composer`,readonly:t.readonly,required:t.required,signal:d,value:t.value});e.defineFieldValue(n,()=>g.value.value,e=>{g.value.value=e}),g.attachFormField((0,y.useField)({disabled:g.disabled,onReset:()=>{g.reset(),p.recompute()},toFormValue:e=>e,validationMessage:g.validationMessage,validity:g.validity,value:g.value}));function attemptSend(e){r(`send`,{originalEvent:e,value:g.value.value.trim()})&&t[`clear-on-send`].value!==!1&&(g.clear(),p.recompute(),f.value?.focus())}let _=s.createComposerControl({disabled:a,loading:(0,v.computed)(()=>!!t.loading.value),onSend:attemptSend,sendShortcut:t[`send-shortcut`],value:g.value});return(0,y.onElement)(f,e=>{let n=g.wire(e),r=p.wire(e),i=l.bindRefCallback(t.ref,e),handleKeydown=e=>_.handleKeydown(e);return e.addEventListener(`keydown`,handleKeydown),()=>{e.removeEventListener(`keydown`,handleKeydown),n(),r(),i()}}),(0,y.bind)({attr:{error:m.errorAttr(g.errorText),size:t.size,success:()=>t.success.value&&!g.errorText.value?!0:void 0,variant:t.variant}}),y.html`
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./inputs/shared/native-field-event.cjs"),t=require("./styles/mixins/accessibility.css.cjs"),n=require("./styles/mixins/field-variants.css.cjs"),r=require("./styles/mixins/shape.css.cjs"),i=require("./styles/mixins/states.css.cjs"),a=require("./styles/mixins/theme.css.cjs"),o=require("./core/auto-resize.cjs"),s=require("./core/composer.cjs"),c=require("./core/ore.cjs"),l=require("./core/ref-callback.cjs"),u=require("./core/text-field.cjs"),d=require("./shared/types.cjs"),f=require("./shared/prop-bundles.cjs"),p=require("./shared/size-presets.cjs"),m=require("./icon.cjs"),h=require("./inputs/shared/field-binding.cjs"),g=require("./inputs/shared/templates.cjs"),_=require("./button.cjs"),v=require("./inputs/message-composer/message-composer.css.cjs");let y=require("@vielzeug/ripple"),b=require("@vielzeug/ore");var x=`Send message`,S=`arrow-up`,C=`ore-message-composer`;(0,b.define)(C,{formAssociated:!0,props:{...f.themableBundle,...f.sizableBundle,...f.disablableBundle,...f.loadableBundle,"clear-on-send":b.prop.bool(!0),error:b.prop.string(),fullwidth:b.prop.bool(!1),helper:b.prop.string(),label:b.prop.string(),maxlength:b.prop.json(void 0),name:b.prop.string(),placeholder:b.prop.string(),readonly:b.prop.bool(!1),ref:b.prop.data(),required:b.prop.bool(!1),rows:b.prop.json(void 0),"send-icon":b.prop.string(),"send-label":b.prop.string(),"send-shortcut":b.prop.oneOf([`enter`,`mod+enter`],`enter`),success:b.prop.bool(!1),value:b.prop.string(),variant:b.prop.oneOf(d.FIELD_VARIANTS)},setup(t){let n=(0,b.getHost)(),r=(0,b.useEmit)(),i=(0,b.useSlots)(),a=t.disabled,d=c.lifecycleSignal(b.onCleanup),f=(0,b.ref)(),p=o.createAutoResize(),m=u.createTextField({disabled:a,error:t.error,helper:t.helper,maxLength:t.maxlength,onInput:(t,r)=>{e.setFieldValue(n,r),e.dispatchNativeFieldEvent(n,`input`)},prefix:`composer`,readonly:t.readonly,required:t.required,signal:d,value:t.value});e.defineFieldValue(n,()=>m.value.value,e=>{m.value.value=e}),m.attachFormField((0,b.useField)({disabled:m.disabled,onReset:()=>{m.reset(),p.recompute()},toFormValue:e=>e,validity:m.validity,value:m.value}));function attemptSend(e){r(`send`,{originalEvent:e,value:m.value.value.trim()})&&t[`clear-on-send`].value!==!1&&(m.clear(),p.recompute(),f.value?.focus())}let _=s.createComposerControl({disabled:a,loading:(0,y.computed)(()=>!!t.loading.value),onSend:attemptSend,sendShortcut:t[`send-shortcut`],value:m.value});return(0,b.onElement)(f,e=>{let n=m.wire(e),r=p.wire(e),i=l.bindRefCallback(t.ref,e),handleKeydown=e=>_.handleKeydown(e);return e.addEventListener(`keydown`,handleKeydown),()=>{e.removeEventListener(`keydown`,handleKeydown),n(),r(),i()}}),(0,b.bind)({attr:{error:h.errorAttr(m.errorText),size:t.size,success:()=>t.success.value&&!m.errorText.value?!0:void 0,variant:t.variant}}),b.html`
2
2
  <div class="composer" part="composer">
3
3
  <textarea
4
4
  class="field"
@@ -11,13 +11,13 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
11
11
  ?disabled="${a}"
12
12
  ?readonly="${()=>!!t.readonly.value}"
13
13
  ?required="${()=>!!t.required.value}"
14
- value="${(0,y.live)(g.value)}"
14
+ value="${(0,b.model)(m.value)}"
15
15
  aria-label="${()=>t.label.value||`Message`}"
16
16
  aria-keyshortcuts="${_.keyShortcutsHint}"
17
- aria-describedby="${g.ariaDescribedBy}"
18
- aria-errormessage="${g.ariaErrorMessage}"
19
- aria-invalid="${g.ariaInvalid}"></textarea>
20
- ${h.renderStatusIcon(g.errorText)} ${h.renderFieldStatusRegion(g)}
17
+ aria-describedby="${m.ariaDescribedBy}"
18
+ aria-errormessage="${m.ariaErrorMessage}"
19
+ aria-invalid="${m.ariaInvalid}"></textarea>
20
+ ${g.renderStatusIcon(m.errorText)} ${g.renderFieldStatusRegion(m)}
21
21
  <div class="toolbar" part="toolbar">
22
22
  <div class="toolbar-start" part="toolbar-start">
23
23
  <slot name="prefix"></slot>
@@ -25,7 +25,7 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
25
25
  <div class="toolbar-end" part="toolbar-end">
26
26
  <slot name="suffix"></slot>
27
27
  <slot name="send"></slot>
28
- ${()=>i.has(`send`).value?``:y.html`
28
+ ${()=>i.has(`send`).value?``:b.html`
29
29
  <ore-button
30
30
  class="send-btn"
31
31
  part="send-button"
@@ -34,15 +34,15 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
34
34
  variant="solid"
35
35
  color="${()=>t.color.value||`primary`}"
36
36
  size="${t.size}"
37
- label="${()=>t[`send-label`].value||b}"
37
+ label="${()=>t[`send-label`].value||x}"
38
38
  ?loading="${()=>!!t.loading.value}"
39
39
  ?disabled="${()=>!_.canSend.value}"
40
40
  @click="${e=>_.send(e)}">
41
- <ore-icon name="${()=>t[`send-icon`].value||x}" size="16"></ore-icon>
41
+ <ore-icon name="${()=>t[`send-icon`].value||S}" size="16"></ore-icon>
42
42
  </ore-button>
43
43
  `}
44
44
  </div>
45
45
  </div>
46
46
  </div>
47
- `},shadow:{delegatesFocus:!0},styles:[a.colorThemeMixin,t.coarsePointerMixin,t.reducedMotionMixin,i.disabledLoadingMixin,a.forcedColorsMixin,a.forcedColorsFocusMixin(`.field`),r.sizeVariantMixin(f.MESSAGE_COMPOSER_SIZE_PRESET),_.default,n.fieldVariantMixin({container:`.composer`,text:`.field`,tokenPrefix:`message-composer`})]}),exports.BUTTON_TAG=g.BUTTON_TAG,exports.ICON_TAG=p.ICON_TAG,exports.MESSAGE_COMPOSER_TAG=S;
47
+ `},shadow:{delegatesFocus:!0},styles:[a.colorThemeMixin,t.coarsePointerMixin,t.reducedMotionMixin,i.disabledLoadingMixin,a.forcedColorsMixin,a.forcedColorsFocusMixin(`.field`),r.sizeVariantMixin(p.MESSAGE_COMPOSER_SIZE_PRESET),v.default,n.fieldVariantMixin({container:`.composer`,text:`.field`,tokenPrefix:`message-composer`})]}),exports.BUTTON_TAG=_.BUTTON_TAG,exports.ICON_TAG=m.ICON_TAG,exports.MESSAGE_COMPOSER_TAG=C;
48
48
  //# sourceMappingURL=message-composer.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"message-composer.cjs","names":[],"sources":["../src/inputs/message-composer/message-composer.ts"],"sourcesContent":["import {\n bind,\n define,\n getHost,\n html,\n live,\n onCleanup,\n onElement,\n prop,\n ref,\n useEmit,\n useField,\n useSlots,\n} from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport {\n bindRefCallback,\n createAutoResize,\n createComposerControl,\n createTextField,\n lifecycleSignal,\n type SendShortcut,\n} from '../../core';\nimport {\n disablableBundle,\n loadableBundle,\n MESSAGE_COMPOSER_SIZE_PRESET,\n sizableBundle,\n themableBundle,\n} from '../../shared';\nimport {\n coarsePointerMixin,\n colorThemeMixin,\n disabledLoadingMixin,\n fieldVariantMixin,\n forcedColorsFocusMixin,\n forcedColorsMixin,\n reducedMotionMixin,\n sizeVariantMixin,\n} from '../../styles';\nimport type { ComponentSize, ThemeColor, VisualVariant } from '../../types';\nimport { errorAttr } from '../shared/field-binding';\nimport { defineFieldValue, dispatchNativeFieldEvent, setFieldValue } from '../shared/native-field-event';\nimport { renderFieldStatusRegion, renderStatusIcon } from '../shared/templates';\n\nimport componentStyles from './message-composer.css?inline';\n\nexport { ICON_TAG } from '../../content/icon/icon';\nexport type { SendShortcut } from '../../core';\nexport { BUTTON_TAG } from '../button/button';\n\nconst DEFAULT_SEND_LABEL = 'Send message';\nconst SEND_ICON = 'arrow-up';\n\n/** Events emitted by the message composer */\nexport type OreMessageComposerEvents = {\n input: InputEvent;\n /**\n * Fired for a send attempt (the resolved `send-shortcut` or the send button) while the\n * composer isn't blank/disabled/loading. Cancelable: call `preventDefault()` to keep the\n * current text in the field instead of the default clear + refocus.\n */\n send: { originalEvent: KeyboardEvent | MouseEvent; value: string };\n};\n\n/** Message composer properties */\nexport type OreMessageComposerProps = {\n /** Clear the value and refocus after a non-cancelled `send` (default `true`) */\n 'clear-on-send'?: boolean;\n /** Theme color (card focus ring + default send button color) */\n color?: ThemeColor;\n /** Disable the whole composer (field, slots, and send action) */\n disabled?: boolean;\n /** Error message shown below the field */\n error?: string;\n /** Stretch the composer to the full width of its container */\n fullwidth?: boolean;\n /** Helper text shown below the field */\n helper?: string;\n /** Accessible name for the field: never rendered visually, only as `aria-label` */\n label?: string;\n /** Blocks further sends (e.g. a send is already in flight) without disabling editing */\n loading?: boolean;\n /** Maximum character count; shows a counter */\n maxlength?: number;\n /** Form field name */\n name?: string;\n /** Placeholder text */\n placeholder?: string;\n /** Make the field read-only */\n readonly?: boolean;\n /**\n * JS-only callback fired with the inner `<textarea>` element when it mounts, and with\n * `null` when it unmounts. Set as a JS property: `composer.ref = (el) => { ... }`.\n */\n ref?: ((el: HTMLTextAreaElement | null) => void) | null;\n /** Require a non-blank value for native form validation */\n required?: boolean;\n /** Number of visible rows before auto-resize grows the field */\n rows?: number;\n /** Lucide icon name for the default send button (default `'arrow-up'`) */\n 'send-icon'?: string;\n /** Accessible label for the send button (default `'Send message'`) */\n 'send-label'?: string;\n /** Keyboard shortcut that sends: 'enter' (Shift+Enter for a newline) or 'mod+enter' (Enter always inserts a newline; Ctrl/Cmd+Enter sends) */\n 'send-shortcut'?: SendShortcut;\n /** Component size */\n size?: ComponentSize;\n /**\n * Shows an inline green check icon inside the field to confirm the value has\n * passed validation. Ignored while `error` is set: an error always wins.\n */\n success?: boolean;\n /** Current text value */\n value?: string;\n /** Visual variant of the card: same variant set as `ore-textarea`, applied to the card. */\n variant?: Exclude<VisualVariant, 'frost' | 'text'>;\n};\n\n/**\n * A message/comment composer: a card with an auto-resizing field on top and a toolbar row\n * below it, built directly on the same core primitives as `ore-textarea` (`createTextField`,\n * `createAutoResize`) rather than composing `ore-textarea` itself. Nesting a fully-styled sibling\n * component and suppressing most of its chrome (as an earlier version of this component did) is\n * a leaky composition: this owns its single `<textarea>` outright, so there's exactly one\n * implementation of the field's appearance to reason about, not two fighting each other.\n *\n * Handles the send gesture (Enter to send, Shift+Enter for a newline, or a Ctrl/Cmd+Enter\n * alternative), IME-safe composition, and clear-and-refocus after sending.\n *\n * @element ore-message-composer\n *\n * @attr {string} value - Current text value\n * @attr {string} placeholder - Placeholder text\n * @attr {string} label - Accessible name for the field (not rendered visually)\n * @attr {string} name - Form field name\n * @attr {string} helper - Helper text shown below the field\n * @attr {string} error - Error message shown below the field\n * @attr {boolean} success - Show an inline success check icon (suppressed while `error` is set)\n * @attr {boolean} disabled - Disable the whole composer\n * @attr {boolean} readonly - Read-only mode\n * @attr {boolean} required - Require a non-blank value for native form validation\n * @attr {boolean} loading - Blocks further sends without disabling editing\n * @attr {number} maxlength - Max character count; shows a counter\n * @attr {number} rows - Visible rows before auto-resize grows the field (default 1)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} variant - Visual variant of the card: 'solid' | 'flat' | 'bordered' | 'outline' | 'ghost'\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {boolean} fullwidth - Stretch to the full width of the container\n * @attr {string} send-icon - Lucide icon name for the default send button (default 'arrow-up')\n * @attr {string} send-label - Accessible label for the send button (default 'Send message')\n * @attr {string} send-shortcut - 'enter' (default) | 'mod+enter'\n * @attr {boolean} clear-on-send - Clear the value and refocus after a non-cancelled send (default true)\n *\n * @fires input - Fired on every keystroke. detail: { value, originalEvent }\n * @fires send - Fired on a send attempt. Cancelable. detail: { value, originalEvent }\n *\n * @slot prefix - Content at the start of the toolbar row (e.g. an attach menu)\n * @slot suffix - Content at the end of the toolbar row, before the send button (e.g. a model picker)\n * @slot send - Replaces the default send button entirely: the only supported way to customize it\n *\n * @cssprop --message-composer-bg - Card background color\n * @cssprop --message-composer-border-color - Card border color\n * @cssprop --message-composer-radius - Card border radius\n * @cssprop --message-composer-padding - Card inner padding\n * @cssprop --message-composer-gap - Gap between the field/toolbar rows and between toolbar items\n * @cssprop --message-composer-placeholder-color - Field placeholder text color\n * @cssprop --message-composer-min-height - Minimum field height (default one line)\n * @cssprop --message-composer-hover-bg - Card background on hover (flat/ghost variants)\n * @cssprop --message-composer-hover-border-color - Card border on hover (flat/bordered variants)\n * @cssprop --message-composer-focus-bg - Card background when focused (flat variant)\n * @cssprop --message-composer-focus-border-color - Card border when focused (flat variant)\n *\n * @part composer - Root card container\n * @part field - The native `<textarea>` element\n * @part status-icon - The inline error/success icon shown inside the field\n * @part helper - Helper text element\n * @part error - Error text element (`role=\"alert\"`)\n * @part toolbar - Toolbar row below the field\n * @part toolbar-start - Toolbar group holding the `prefix` slot\n * @part toolbar-end - Toolbar group holding the `suffix` slot and the send button\n * @part send-button - The default send button (absent when the `send` slot is used)\n *\n * @example\n * ```html\n * <ore-message-composer placeholder=\"Message…\" send-shortcut=\"mod+enter\"></ore-message-composer>\n * ```\n */\nexport const MESSAGE_COMPOSER_TAG = 'ore-message-composer' as const;\ndefine<OreMessageComposerProps>(MESSAGE_COMPOSER_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n ...loadableBundle,\n 'clear-on-send': prop.bool(true),\n error: prop.string(),\n fullwidth: prop.bool(false),\n helper: prop.string(),\n label: prop.string(),\n maxlength: prop.json(undefined as number | undefined),\n name: prop.string(),\n placeholder: prop.string(),\n readonly: prop.bool(false),\n ref: prop.data<((el: HTMLTextAreaElement | null) => void) | null>(),\n required: prop.bool(false),\n rows: prop.json(undefined as number | undefined),\n 'send-icon': prop.string(),\n 'send-label': prop.string(),\n 'send-shortcut': prop.oneOf(['enter', 'mod+enter'] as const, 'enter'),\n success: prop.bool(false),\n value: prop.string(),\n variant: prop.string<Exclude<VisualVariant, 'frost' | 'text'>>(),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreMessageComposerEvents>();\n const slots = useSlots<'prefix' | 'send' | 'suffix'>();\n\n const isDisabled = props.disabled;\n\n const abortSignal = lifecycleSignal(onCleanup);\n const textareaRef = ref<HTMLTextAreaElement>();\n const autoResize = createAutoResize();\n\n const tf = createTextField({\n disabled: isDisabled,\n error: props.error,\n helper: props.helper,\n maxLength: props.maxlength,\n onInput: (_event, value) => {\n setFieldValue(el, value);\n dispatchNativeFieldEvent(el, 'input');\n },\n prefix: 'composer',\n readonly: props.readonly,\n required: props.required,\n signal: abortSignal,\n value: props.value,\n });\n\n defineFieldValue(\n el,\n () => tf.value.value,\n (value) => {\n tf.value.value = value;\n },\n );\n\n // Directly form-associated now that the field is owned here rather than borrowed from a\n // nested `ore-textarea` (which previously registered with the ancestor `<form>` on this\n // component's behalf).\n tf.attachFormField(\n useField<string>({\n disabled: tf.disabled,\n onReset: () => {\n tf.reset();\n autoResize.recompute();\n },\n toFormValue: (v) => v,\n validationMessage: tf.validationMessage,\n validity: tf.validity,\n value: tf.value,\n }),\n );\n\n function attemptSend(event: KeyboardEvent | MouseEvent): void {\n const notPrevented = emit('send', { originalEvent: event, value: tf.value.value.trim() });\n\n // `preventDefault()` on `send` means \"keep the current text: skip the default clear\n // + refocus\" (see the event's own doc comment): both halves of that default are gated on\n // the same condition, not just the clear.\n if (notPrevented && props['clear-on-send'].value !== false) {\n tf.clear();\n autoResize.recompute();\n textareaRef.value?.focus();\n }\n }\n\n const composer = createComposerControl({\n disabled: isDisabled,\n loading: computed(() => Boolean(props.loading.value)),\n onSend: attemptSend,\n sendShortcut: props['send-shortcut'],\n value: tf.value,\n });\n\n onElement(textareaRef, (textareaEl) => {\n const unwireField = tf.wire(textareaEl);\n const unwireAutoResize = autoResize.wire(textareaEl);\n const unwireRef = bindRefCallback(props.ref, textareaEl);\n const handleKeydown = (e: KeyboardEvent) => composer.handleKeydown(e);\n\n textareaEl.addEventListener('keydown', handleKeydown);\n\n return () => {\n textareaEl.removeEventListener('keydown', handleKeydown);\n unwireField();\n unwireAutoResize();\n unwireRef();\n };\n });\n\n bind({\n attr: {\n error: errorAttr(tf.errorText),\n size: props.size,\n // Reflects `success` only once `error` is confirmed empty: keeps the two host\n // attributes mutually exclusive even if a consumer sets both props at once.\n success: () => (props.success.value && !tf.errorText.value ? true : undefined),\n variant: props.variant,\n },\n });\n\n return html`\n <div class=\"composer\" part=\"composer\">\n <textarea\n class=\"field\"\n part=\"field\"\n ref=\"${textareaRef}\"\n rows=\"${() => props.rows.value ?? 1}\"\n name=\"${() => props.name.value ?? ''}\"\n placeholder=\"${() => props.placeholder.value ?? 'Message…'}\"\n maxlength=\"${() => props.maxlength.value}\"\n ?disabled=\"${isDisabled}\"\n ?readonly=\"${() => Boolean(props.readonly.value)}\"\n ?required=\"${() => Boolean(props.required.value)}\"\n value=\"${live(tf.value)}\"\n aria-label=\"${() => props.label.value || 'Message'}\"\n aria-keyshortcuts=\"${composer.keyShortcutsHint}\"\n aria-describedby=\"${tf.ariaDescribedBy}\"\n aria-errormessage=\"${tf.ariaErrorMessage}\"\n aria-invalid=\"${tf.ariaInvalid}\"></textarea>\n ${renderStatusIcon(tf.errorText)} ${renderFieldStatusRegion(tf)}\n <div class=\"toolbar\" part=\"toolbar\">\n <div class=\"toolbar-start\" part=\"toolbar-start\">\n <slot name=\"prefix\"></slot>\n </div>\n <div class=\"toolbar-end\" part=\"toolbar-end\">\n <slot name=\"suffix\"></slot>\n <slot name=\"send\"></slot>\n ${() =>\n slots.has('send').value\n ? ''\n : html`\n <ore-button\n class=\"send-btn\"\n part=\"send-button\"\n type=\"button\"\n icon-only\n variant=\"solid\"\n color=\"${() => props.color.value || 'primary'}\"\n size=\"${props.size}\"\n label=\"${() => props['send-label'].value || DEFAULT_SEND_LABEL}\"\n ?loading=\"${() => Boolean(props.loading.value)}\"\n ?disabled=\"${() => !composer.canSend.value}\"\n @click=\"${(e: MouseEvent) => composer.send(e)}\">\n <ore-icon name=\"${() => props['send-icon'].value || SEND_ICON}\" size=\"16\"></ore-icon>\n </ore-button>\n `}\n </div>\n </div>\n </div>\n `;\n },\n shadow: { delegatesFocus: true },\n styles: [\n colorThemeMixin,\n coarsePointerMixin,\n reducedMotionMixin,\n disabledLoadingMixin,\n forcedColorsMixin,\n forcedColorsFocusMixin('.field'),\n sizeVariantMixin(MESSAGE_COMPOSER_SIZE_PRESET),\n componentStyles,\n // Must come after `componentStyles`: see `ore-input`'s identical ordering note for why\n // (`@layer` precedence is fixed by which layer name is *first* referenced across this whole\n // array; `componentStyles` establishes `refine.base`, which this mixin's `refine.variants`\n // rules need to win over).\n fieldVariantMixin({ container: '.composer', text: '.field', tokenPrefix: 'message-composer' }),\n ],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"message-composer.cjs","names":[],"sources":["../src/inputs/message-composer/message-composer.ts"],"sourcesContent":["import {\n bind,\n define,\n getHost,\n html,\n model,\n onCleanup,\n onElement,\n prop,\n ref,\n useEmit,\n useField,\n useSlots,\n} from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport {\n bindRefCallback,\n createAutoResize,\n createComposerControl,\n createTextField,\n lifecycleSignal,\n type SendShortcut,\n} from '../../core';\nimport {\n disablableBundle,\n loadableBundle,\n MESSAGE_COMPOSER_SIZE_PRESET,\n sizableBundle,\n themableBundle,\n} from '../../shared';\nimport {\n coarsePointerMixin,\n colorThemeMixin,\n disabledLoadingMixin,\n fieldVariantMixin,\n forcedColorsFocusMixin,\n forcedColorsMixin,\n reducedMotionMixin,\n sizeVariantMixin,\n} from '../../styles';\nimport type { ComponentSize, ThemeColor } from '../../types';\nimport { FIELD_VARIANTS, type FieldVariant } from '../../types';\nimport { errorAttr } from '../shared/field-binding';\nimport { defineFieldValue, dispatchNativeFieldEvent, setFieldValue } from '../shared/native-field-event';\nimport { renderFieldStatusRegion, renderStatusIcon } from '../shared/templates';\n\nimport componentStyles from './message-composer.css?inline';\n\nexport { ICON_TAG } from '../../content/icon/icon';\nexport type { SendShortcut } from '../../core';\nexport { BUTTON_TAG } from '../button/button';\n\nconst DEFAULT_SEND_LABEL = 'Send message';\nconst SEND_ICON = 'arrow-up';\n\n/** Events emitted by the message composer */\nexport type OreMessageComposerEvents = {\n input: InputEvent;\n /**\n * Fired for a send attempt (the resolved `send-shortcut` or the send button) while the\n * composer isn't blank/disabled/loading. Cancelable: call `preventDefault()` to keep the\n * current text in the field instead of the default clear + refocus.\n */\n send: { originalEvent: KeyboardEvent | MouseEvent; value: string };\n};\n\n/** Message composer properties */\nexport type OreMessageComposerProps = {\n /** Clear the value and refocus after a non-cancelled `send` (default `true`) */\n 'clear-on-send'?: boolean;\n /** Theme color (card focus ring + default send button color) */\n color?: ThemeColor;\n /** Disable the whole composer (field, slots, and send action) */\n disabled?: boolean;\n /** Error message shown below the field */\n error?: string;\n /** Stretch the composer to the full width of its container */\n fullwidth?: boolean;\n /** Helper text shown below the field */\n helper?: string;\n /** Accessible name for the field: never rendered visually, only as `aria-label` */\n label?: string;\n /** Blocks further sends (e.g. a send is already in flight) without disabling editing */\n loading?: boolean;\n /** Maximum character count; shows a counter */\n maxlength?: number;\n /** Form field name */\n name?: string;\n /** Placeholder text */\n placeholder?: string;\n /** Make the field read-only */\n readonly?: boolean;\n /**\n * JS-only callback fired with the inner `<textarea>` element when it mounts, and with\n * `null` when it unmounts. Set as a JS property: `composer.ref = (el) => { ... }`.\n */\n ref?: ((el: HTMLTextAreaElement | null) => void) | null;\n /** Require a non-blank value for native form validation */\n required?: boolean;\n /** Number of visible rows before auto-resize grows the field */\n rows?: number;\n /** Lucide icon name for the default send button (default `'arrow-up'`) */\n 'send-icon'?: string;\n /** Accessible label for the send button (default `'Send message'`) */\n 'send-label'?: string;\n /** Keyboard shortcut that sends: 'enter' (Shift+Enter for a newline) or 'mod+enter' (Enter always inserts a newline; Ctrl/Cmd+Enter sends) */\n 'send-shortcut'?: SendShortcut;\n /** Component size */\n size?: ComponentSize;\n /**\n * Shows an inline green check icon inside the field to confirm the value has\n * passed validation. Ignored while `error` is set: an error always wins.\n */\n success?: boolean;\n /** Current text value */\n value?: string;\n /** Visual variant of the card: same variant set as `ore-textarea`, applied to the card. */\n variant?: FieldVariant;\n};\n\n/**\n * A message/comment composer: a card with an auto-resizing field on top and a toolbar row\n * below it, built directly on the same core primitives as `ore-textarea` (`createTextField`,\n * `createAutoResize`) rather than composing `ore-textarea` itself. Nesting a fully-styled sibling\n * component and suppressing most of its chrome (as an earlier version of this component did) is\n * a leaky composition: this owns its single `<textarea>` outright, so there's exactly one\n * implementation of the field's appearance to reason about, not two fighting each other.\n *\n * Handles the send gesture (Enter to send, Shift+Enter for a newline, or a Ctrl/Cmd+Enter\n * alternative), IME-safe composition, and clear-and-refocus after sending.\n *\n * @element ore-message-composer\n *\n * @attr {string} value - Current text value\n * @attr {string} placeholder - Placeholder text\n * @attr {string} label - Accessible name for the field (not rendered visually)\n * @attr {string} name - Form field name\n * @attr {string} helper - Helper text shown below the field\n * @attr {string} error - Error message shown below the field\n * @attr {boolean} success - Show an inline success check icon (suppressed while `error` is set)\n * @attr {boolean} disabled - Disable the whole composer\n * @attr {boolean} readonly - Read-only mode\n * @attr {boolean} required - Require a non-blank value for native form validation\n * @attr {boolean} loading - Blocks further sends without disabling editing\n * @attr {number} maxlength - Max character count; shows a counter\n * @attr {number} rows - Visible rows before auto-resize grows the field (default 1)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} variant - Visual variant of the card: 'solid' | 'flat' | 'bordered' | 'outline' | 'ghost'\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {boolean} fullwidth - Stretch to the full width of the container\n * @attr {string} send-icon - Lucide icon name for the default send button (default 'arrow-up')\n * @attr {string} send-label - Accessible label for the send button (default 'Send message')\n * @attr {string} send-shortcut - 'enter' (default) | 'mod+enter'\n * @attr {boolean} clear-on-send - Clear the value and refocus after a non-cancelled send (default true)\n *\n * @fires input - Fired on every keystroke. detail: { value, originalEvent }\n * @fires send - Fired on a send attempt. Cancelable. detail: { value, originalEvent }\n *\n * @slot prefix - Content at the start of the toolbar row (e.g. an attach menu)\n * @slot suffix - Content at the end of the toolbar row, before the send button (e.g. a model picker)\n * @slot send - Replaces the default send button entirely: the only supported way to customize it\n *\n * @cssprop --message-composer-bg - Card background color\n * @cssprop --message-composer-border-color - Card border color\n * @cssprop --message-composer-radius - Card border radius\n * @cssprop --message-composer-padding - Card inner padding\n * @cssprop --message-composer-gap - Gap between the field/toolbar rows and between toolbar items\n * @cssprop --message-composer-placeholder-color - Field placeholder text color\n * @cssprop --message-composer-min-height - Minimum field height (default one line)\n * @cssprop --message-composer-hover-bg - Card background on hover (flat/ghost variants)\n * @cssprop --message-composer-hover-border-color - Card border on hover (flat/bordered variants)\n * @cssprop --message-composer-focus-bg - Card background when focused (flat variant)\n * @cssprop --message-composer-focus-border-color - Card border when focused (flat variant)\n *\n * @part composer - Root card container\n * @part field - The native `<textarea>` element\n * @part status-icon - The inline error/success icon shown inside the field\n * @part helper - Helper text element\n * @part error - Error text element (`role=\"alert\"`)\n * @part toolbar - Toolbar row below the field\n * @part toolbar-start - Toolbar group holding the `prefix` slot\n * @part toolbar-end - Toolbar group holding the `suffix` slot and the send button\n * @part send-button - The default send button (absent when the `send` slot is used)\n *\n * @example\n * ```html\n * <ore-message-composer placeholder=\"Message…\" send-shortcut=\"mod+enter\"></ore-message-composer>\n * ```\n */\nexport const MESSAGE_COMPOSER_TAG = 'ore-message-composer' as const;\ndefine<OreMessageComposerProps>(MESSAGE_COMPOSER_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n ...loadableBundle,\n 'clear-on-send': prop.bool(true),\n error: prop.string(),\n fullwidth: prop.bool(false),\n helper: prop.string(),\n label: prop.string(),\n maxlength: prop.json(undefined as number | undefined),\n name: prop.string(),\n placeholder: prop.string(),\n readonly: prop.bool(false),\n ref: prop.data<((el: HTMLTextAreaElement | null) => void) | null>(),\n required: prop.bool(false),\n rows: prop.json(undefined as number | undefined),\n 'send-icon': prop.string(),\n 'send-label': prop.string(),\n 'send-shortcut': prop.oneOf(['enter', 'mod+enter'] as const, 'enter'),\n success: prop.bool(false),\n value: prop.string(),\n variant: prop.oneOf(FIELD_VARIANTS),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreMessageComposerEvents>();\n const slots = useSlots<'prefix' | 'send' | 'suffix'>();\n\n const isDisabled = props.disabled;\n\n const abortSignal = lifecycleSignal(onCleanup);\n const textareaRef = ref<HTMLTextAreaElement>();\n const autoResize = createAutoResize();\n\n const tf = createTextField({\n disabled: isDisabled,\n error: props.error,\n helper: props.helper,\n maxLength: props.maxlength,\n onInput: (_event, value) => {\n setFieldValue(el, value);\n dispatchNativeFieldEvent(el, 'input');\n },\n prefix: 'composer',\n readonly: props.readonly,\n required: props.required,\n signal: abortSignal,\n value: props.value,\n });\n\n defineFieldValue(\n el,\n () => tf.value.value,\n (value) => {\n tf.value.value = value;\n },\n );\n\n // Directly form-associated now that the field is owned here rather than borrowed from a\n // nested `ore-textarea` (which previously registered with the ancestor `<form>` on this\n // component's behalf).\n tf.attachFormField(\n useField<string>({\n disabled: tf.disabled,\n onReset: () => {\n tf.reset();\n autoResize.recompute();\n },\n toFormValue: (v) => v,\n validity: tf.validity,\n value: tf.value,\n }),\n );\n\n function attemptSend(event: KeyboardEvent | MouseEvent): void {\n const notPrevented = emit('send', { originalEvent: event, value: tf.value.value.trim() });\n\n // `preventDefault()` on `send` means \"keep the current text: skip the default clear\n // + refocus\" (see the event's own doc comment): both halves of that default are gated on\n // the same condition, not just the clear.\n if (notPrevented && props['clear-on-send'].value !== false) {\n tf.clear();\n autoResize.recompute();\n textareaRef.value?.focus();\n }\n }\n\n const composer = createComposerControl({\n disabled: isDisabled,\n loading: computed(() => Boolean(props.loading.value)),\n onSend: attemptSend,\n sendShortcut: props['send-shortcut'],\n value: tf.value,\n });\n\n onElement(textareaRef, (textareaEl) => {\n const unwireField = tf.wire(textareaEl);\n const unwireAutoResize = autoResize.wire(textareaEl);\n const unwireRef = bindRefCallback(props.ref, textareaEl);\n const handleKeydown = (e: KeyboardEvent) => composer.handleKeydown(e);\n\n textareaEl.addEventListener('keydown', handleKeydown);\n\n return () => {\n textareaEl.removeEventListener('keydown', handleKeydown);\n unwireField();\n unwireAutoResize();\n unwireRef();\n };\n });\n\n bind({\n attr: {\n error: errorAttr(tf.errorText),\n size: props.size,\n // Reflects `success` only once `error` is confirmed empty: keeps the two host\n // attributes mutually exclusive even if a consumer sets both props at once.\n success: () => (props.success.value && !tf.errorText.value ? true : undefined),\n variant: props.variant,\n },\n });\n\n return html`\n <div class=\"composer\" part=\"composer\">\n <textarea\n class=\"field\"\n part=\"field\"\n ref=\"${textareaRef}\"\n rows=\"${() => props.rows.value ?? 1}\"\n name=\"${() => props.name.value ?? ''}\"\n placeholder=\"${() => props.placeholder.value ?? 'Message…'}\"\n maxlength=\"${() => props.maxlength.value}\"\n ?disabled=\"${isDisabled}\"\n ?readonly=\"${() => Boolean(props.readonly.value)}\"\n ?required=\"${() => Boolean(props.required.value)}\"\n value=\"${model(tf.value)}\"\n aria-label=\"${() => props.label.value || 'Message'}\"\n aria-keyshortcuts=\"${composer.keyShortcutsHint}\"\n aria-describedby=\"${tf.ariaDescribedBy}\"\n aria-errormessage=\"${tf.ariaErrorMessage}\"\n aria-invalid=\"${tf.ariaInvalid}\"></textarea>\n ${renderStatusIcon(tf.errorText)} ${renderFieldStatusRegion(tf)}\n <div class=\"toolbar\" part=\"toolbar\">\n <div class=\"toolbar-start\" part=\"toolbar-start\">\n <slot name=\"prefix\"></slot>\n </div>\n <div class=\"toolbar-end\" part=\"toolbar-end\">\n <slot name=\"suffix\"></slot>\n <slot name=\"send\"></slot>\n ${() =>\n slots.has('send').value\n ? ''\n : html`\n <ore-button\n class=\"send-btn\"\n part=\"send-button\"\n type=\"button\"\n icon-only\n variant=\"solid\"\n color=\"${() => props.color.value || 'primary'}\"\n size=\"${props.size}\"\n label=\"${() => props['send-label'].value || DEFAULT_SEND_LABEL}\"\n ?loading=\"${() => Boolean(props.loading.value)}\"\n ?disabled=\"${() => !composer.canSend.value}\"\n @click=\"${(e: MouseEvent) => composer.send(e)}\">\n <ore-icon name=\"${() => props['send-icon'].value || SEND_ICON}\" size=\"16\"></ore-icon>\n </ore-button>\n `}\n </div>\n </div>\n </div>\n `;\n },\n shadow: { delegatesFocus: true },\n styles: [\n colorThemeMixin,\n coarsePointerMixin,\n reducedMotionMixin,\n disabledLoadingMixin,\n forcedColorsMixin,\n forcedColorsFocusMixin('.field'),\n sizeVariantMixin(MESSAGE_COMPOSER_SIZE_PRESET),\n componentStyles,\n // Must come after `componentStyles`: see `ore-input`'s identical ordering note for why\n // (`@layer` precedence is fixed by which layer name is *first* referenced across this whole\n // array; `componentStyles` establishes `refine.base`, which this mixin's `refine.variants`\n // rules need to win over).\n fieldVariantMixin({ container: '.composer', text: '.field', tokenPrefix: 'message-composer' }),\n ],\n});\n"],"mappings":"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"}