@easemate/web-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/README.md +824 -0
  2. package/build/components/code/index.cjs +152 -0
  3. package/build/components/code/index.d.cts +11 -0
  4. package/build/components/code/index.d.ts +11 -0
  5. package/build/components/code/index.js +148 -0
  6. package/build/components/code/utils/highlight-api.cjs +18 -0
  7. package/build/components/code/utils/highlight-api.d.cts +7 -0
  8. package/build/components/code/utils/highlight-api.d.ts +7 -0
  9. package/build/components/code/utils/highlight-api.js +14 -0
  10. package/build/components/code/utils/syntax-grammars.cjs +62 -0
  11. package/build/components/code/utils/syntax-grammars.d.cts +7 -0
  12. package/build/components/code/utils/syntax-grammars.d.ts +7 -0
  13. package/build/components/code/utils/syntax-grammars.js +59 -0
  14. package/build/components/code/utils/syntax-highlighter-theme.cjs +27 -0
  15. package/build/components/code/utils/syntax-highlighter-theme.d.cts +3 -0
  16. package/build/components/code/utils/syntax-highlighter-theme.d.ts +3 -0
  17. package/build/components/code/utils/syntax-highlighter-theme.js +23 -0
  18. package/build/components/code/utils/syntax-highlighter-types.cjs +2 -0
  19. package/build/components/code/utils/syntax-highlighter-types.d.cts +12 -0
  20. package/build/components/code/utils/syntax-highlighter-types.d.ts +12 -0
  21. package/build/components/code/utils/syntax-highlighter-types.js +1 -0
  22. package/build/components/code/utils/syntax-tokenizer.cjs +63 -0
  23. package/build/components/code/utils/syntax-tokenizer.d.cts +3 -0
  24. package/build/components/code/utils/syntax-tokenizer.d.ts +3 -0
  25. package/build/components/code/utils/syntax-tokenizer.js +58 -0
  26. package/build/components/curve/bezier-conversion.cjs +23 -0
  27. package/build/components/curve/bezier-conversion.d.cts +2 -0
  28. package/build/components/curve/bezier-conversion.d.ts +2 -0
  29. package/build/components/curve/bezier-conversion.js +19 -0
  30. package/build/components/curve/canvas-controls.cjs +300 -0
  31. package/build/components/curve/canvas-controls.d.cts +12 -0
  32. package/build/components/curve/canvas-controls.d.ts +12 -0
  33. package/build/components/curve/canvas-controls.js +296 -0
  34. package/build/components/curve/canvas.cjs +1208 -0
  35. package/build/components/curve/canvas.d.cts +24 -0
  36. package/build/components/curve/canvas.d.ts +24 -0
  37. package/build/components/curve/canvas.js +1204 -0
  38. package/build/components/curve/constants.cjs +203 -0
  39. package/build/components/curve/constants.d.cts +23 -0
  40. package/build/components/curve/constants.d.ts +23 -0
  41. package/build/components/curve/constants.js +200 -0
  42. package/build/components/curve/controls.cjs +942 -0
  43. package/build/components/curve/controls.d.cts +37 -0
  44. package/build/components/curve/controls.d.ts +37 -0
  45. package/build/components/curve/controls.js +938 -0
  46. package/build/components/curve/index.cjs +335 -0
  47. package/build/components/curve/index.d.cts +31 -0
  48. package/build/components/curve/index.d.ts +31 -0
  49. package/build/components/curve/index.js +330 -0
  50. package/build/components/curve/output.cjs +141 -0
  51. package/build/components/curve/output.d.cts +19 -0
  52. package/build/components/curve/output.d.ts +19 -0
  53. package/build/components/curve/output.js +137 -0
  54. package/build/components/curve/styles.cjs +493 -0
  55. package/build/components/curve/styles.d.cts +6 -0
  56. package/build/components/curve/styles.d.ts +6 -0
  57. package/build/components/curve/styles.js +490 -0
  58. package/build/components/curve/svg-renderer.cjs +185 -0
  59. package/build/components/curve/svg-renderer.d.cts +9 -0
  60. package/build/components/curve/svg-renderer.d.ts +9 -0
  61. package/build/components/curve/svg-renderer.js +175 -0
  62. package/build/components/curve/toolbar.cjs +368 -0
  63. package/build/components/curve/toolbar.d.cts +26 -0
  64. package/build/components/curve/toolbar.d.ts +26 -0
  65. package/build/components/curve/toolbar.js +364 -0
  66. package/build/components/curve/types.cjs +10 -0
  67. package/build/components/curve/types.d.cts +33 -0
  68. package/build/components/curve/types.d.ts +33 -0
  69. package/build/components/curve/types.js +7 -0
  70. package/build/components/curve/utils.cjs +541 -0
  71. package/build/components/curve/utils.d.cts +33 -0
  72. package/build/components/curve/utils.d.ts +33 -0
  73. package/build/components/curve/utils.js +521 -0
  74. package/build/components/index.cjs +18 -0
  75. package/build/components/index.d.cts +2 -0
  76. package/build/components/index.d.ts +2 -0
  77. package/build/components/index.js +2 -0
  78. package/build/decorators/Component.cjs +127 -0
  79. package/build/decorators/Component.d.cts +28 -0
  80. package/build/decorators/Component.d.ts +28 -0
  81. package/build/decorators/Component.js +123 -0
  82. package/build/decorators/Listen.cjs +154 -0
  83. package/build/decorators/Listen.d.cts +18 -0
  84. package/build/decorators/Listen.d.ts +18 -0
  85. package/build/decorators/Listen.js +151 -0
  86. package/build/decorators/OutsideClick.cjs +64 -0
  87. package/build/decorators/OutsideClick.d.cts +16 -0
  88. package/build/decorators/OutsideClick.d.ts +16 -0
  89. package/build/decorators/OutsideClick.js +59 -0
  90. package/build/decorators/Prop.cjs +273 -0
  91. package/build/decorators/Prop.d.cts +22 -0
  92. package/build/decorators/Prop.d.ts +22 -0
  93. package/build/decorators/Prop.js +270 -0
  94. package/build/decorators/Query.cjs +79 -0
  95. package/build/decorators/Query.d.cts +27 -0
  96. package/build/decorators/Query.d.ts +27 -0
  97. package/build/decorators/Query.js +76 -0
  98. package/build/decorators/Watch.cjs +52 -0
  99. package/build/decorators/Watch.d.cts +11 -0
  100. package/build/decorators/Watch.d.ts +11 -0
  101. package/build/decorators/Watch.js +49 -0
  102. package/build/decorators/index.cjs +15 -0
  103. package/build/decorators/index.d.cts +6 -0
  104. package/build/decorators/index.d.ts +6 -0
  105. package/build/decorators/index.js +6 -0
  106. package/build/elements/button/index.cjs +214 -0
  107. package/build/elements/button/index.d.cts +11 -0
  108. package/build/elements/button/index.d.ts +11 -0
  109. package/build/elements/button/index.js +210 -0
  110. package/build/elements/checkbox/index.cjs +316 -0
  111. package/build/elements/checkbox/index.d.cts +14 -0
  112. package/build/elements/checkbox/index.d.ts +14 -0
  113. package/build/elements/checkbox/index.js +312 -0
  114. package/build/elements/color/index.cjs +154 -0
  115. package/build/elements/color/index.d.cts +18 -0
  116. package/build/elements/color/index.d.ts +18 -0
  117. package/build/elements/color/index.js +150 -0
  118. package/build/elements/color/picker.cjs +544 -0
  119. package/build/elements/color/picker.d.cts +37 -0
  120. package/build/elements/color/picker.d.ts +37 -0
  121. package/build/elements/color/picker.js +540 -0
  122. package/build/elements/color/utils.cjs +235 -0
  123. package/build/elements/color/utils.d.cts +37 -0
  124. package/build/elements/color/utils.d.ts +37 -0
  125. package/build/elements/color/utils.js +218 -0
  126. package/build/elements/dropdown/index.cjs +875 -0
  127. package/build/elements/dropdown/index.d.cts +30 -0
  128. package/build/elements/dropdown/index.d.ts +30 -0
  129. package/build/elements/dropdown/index.js +871 -0
  130. package/build/elements/field/index.cjs +82 -0
  131. package/build/elements/field/index.d.cts +4 -0
  132. package/build/elements/field/index.d.ts +4 -0
  133. package/build/elements/field/index.js +78 -0
  134. package/build/elements/icons/animation/chevron.cjs +57 -0
  135. package/build/elements/icons/animation/chevron.d.cts +10 -0
  136. package/build/elements/icons/animation/chevron.d.ts +10 -0
  137. package/build/elements/icons/animation/chevron.js +53 -0
  138. package/build/elements/icons/animation/clear.cjs +74 -0
  139. package/build/elements/icons/animation/clear.d.cts +3 -0
  140. package/build/elements/icons/animation/clear.d.ts +3 -0
  141. package/build/elements/icons/animation/clear.js +70 -0
  142. package/build/elements/icons/animation/grid.cjs +77 -0
  143. package/build/elements/icons/animation/grid.d.cts +8 -0
  144. package/build/elements/icons/animation/grid.d.ts +8 -0
  145. package/build/elements/icons/animation/grid.js +73 -0
  146. package/build/elements/icons/animation/loading.cjs +68 -0
  147. package/build/elements/icons/animation/loading.d.cts +3 -0
  148. package/build/elements/icons/animation/loading.d.ts +3 -0
  149. package/build/elements/icons/animation/loading.js +64 -0
  150. package/build/elements/icons/animation/snap.cjs +133 -0
  151. package/build/elements/icons/animation/snap.d.cts +8 -0
  152. package/build/elements/icons/animation/snap.d.ts +8 -0
  153. package/build/elements/icons/animation/snap.js +129 -0
  154. package/build/elements/icons/index.cjs +40 -0
  155. package/build/elements/icons/index.d.cts +24 -0
  156. package/build/elements/icons/index.d.ts +24 -0
  157. package/build/elements/icons/index.js +24 -0
  158. package/build/elements/icons/interface/anchor-add.cjs +35 -0
  159. package/build/elements/icons/interface/anchor-add.d.cts +3 -0
  160. package/build/elements/icons/interface/anchor-add.d.ts +3 -0
  161. package/build/elements/icons/interface/anchor-add.js +31 -0
  162. package/build/elements/icons/interface/anchor-remove.cjs +34 -0
  163. package/build/elements/icons/interface/anchor-remove.d.cts +3 -0
  164. package/build/elements/icons/interface/anchor-remove.d.ts +3 -0
  165. package/build/elements/icons/interface/anchor-remove.js +30 -0
  166. package/build/elements/icons/interface/arrow-up.cjs +30 -0
  167. package/build/elements/icons/interface/arrow-up.d.cts +3 -0
  168. package/build/elements/icons/interface/arrow-up.d.ts +3 -0
  169. package/build/elements/icons/interface/arrow-up.js +26 -0
  170. package/build/elements/icons/interface/arrows-vertical.cjs +30 -0
  171. package/build/elements/icons/interface/arrows-vertical.d.cts +3 -0
  172. package/build/elements/icons/interface/arrows-vertical.d.ts +3 -0
  173. package/build/elements/icons/interface/arrows-vertical.js +26 -0
  174. package/build/elements/icons/interface/bezier-angle.cjs +33 -0
  175. package/build/elements/icons/interface/bezier-angle.d.cts +3 -0
  176. package/build/elements/icons/interface/bezier-angle.d.ts +3 -0
  177. package/build/elements/icons/interface/bezier-angle.js +29 -0
  178. package/build/elements/icons/interface/bezier-distribute.cjs +34 -0
  179. package/build/elements/icons/interface/bezier-distribute.d.cts +3 -0
  180. package/build/elements/icons/interface/bezier-distribute.d.ts +3 -0
  181. package/build/elements/icons/interface/bezier-distribute.js +30 -0
  182. package/build/elements/icons/interface/bezier-length.cjs +31 -0
  183. package/build/elements/icons/interface/bezier-length.d.cts +3 -0
  184. package/build/elements/icons/interface/bezier-length.d.ts +3 -0
  185. package/build/elements/icons/interface/bezier-length.js +27 -0
  186. package/build/elements/icons/interface/bezier-mirror.cjs +31 -0
  187. package/build/elements/icons/interface/bezier-mirror.d.cts +3 -0
  188. package/build/elements/icons/interface/bezier-mirror.d.ts +3 -0
  189. package/build/elements/icons/interface/bezier-mirror.js +27 -0
  190. package/build/elements/icons/interface/bezier.cjs +26 -0
  191. package/build/elements/icons/interface/bezier.d.cts +3 -0
  192. package/build/elements/icons/interface/bezier.d.ts +3 -0
  193. package/build/elements/icons/interface/bezier.js +22 -0
  194. package/build/elements/icons/interface/check.cjs +30 -0
  195. package/build/elements/icons/interface/check.d.cts +3 -0
  196. package/build/elements/icons/interface/check.d.ts +3 -0
  197. package/build/elements/icons/interface/check.js +26 -0
  198. package/build/elements/icons/interface/circle-arrow-left.cjs +30 -0
  199. package/build/elements/icons/interface/circle-arrow-left.d.cts +3 -0
  200. package/build/elements/icons/interface/circle-arrow-left.d.ts +3 -0
  201. package/build/elements/icons/interface/circle-arrow-left.js +26 -0
  202. package/build/elements/icons/interface/circle-arrow-right.cjs +30 -0
  203. package/build/elements/icons/interface/circle-arrow-right.d.cts +3 -0
  204. package/build/elements/icons/interface/circle-arrow-right.d.ts +3 -0
  205. package/build/elements/icons/interface/circle-arrow-right.js +26 -0
  206. package/build/elements/icons/interface/code.cjs +30 -0
  207. package/build/elements/icons/interface/code.d.cts +3 -0
  208. package/build/elements/icons/interface/code.d.ts +3 -0
  209. package/build/elements/icons/interface/code.js +26 -0
  210. package/build/elements/icons/interface/dots.cjs +32 -0
  211. package/build/elements/icons/interface/dots.d.cts +3 -0
  212. package/build/elements/icons/interface/dots.d.ts +3 -0
  213. package/build/elements/icons/interface/dots.js +28 -0
  214. package/build/elements/icons/interface/mention.cjs +30 -0
  215. package/build/elements/icons/interface/mention.d.cts +3 -0
  216. package/build/elements/icons/interface/mention.d.ts +3 -0
  217. package/build/elements/icons/interface/mention.js +26 -0
  218. package/build/elements/icons/interface/minus.cjs +30 -0
  219. package/build/elements/icons/interface/minus.d.cts +3 -0
  220. package/build/elements/icons/interface/minus.d.ts +3 -0
  221. package/build/elements/icons/interface/minus.js +26 -0
  222. package/build/elements/icons/interface/picker.cjs +34 -0
  223. package/build/elements/icons/interface/picker.d.cts +3 -0
  224. package/build/elements/icons/interface/picker.d.ts +3 -0
  225. package/build/elements/icons/interface/picker.js +30 -0
  226. package/build/elements/icons/interface/plus.cjs +30 -0
  227. package/build/elements/icons/interface/plus.d.cts +3 -0
  228. package/build/elements/icons/interface/plus.d.ts +3 -0
  229. package/build/elements/icons/interface/plus.js +26 -0
  230. package/build/elements/icons/interface/settings.cjs +30 -0
  231. package/build/elements/icons/interface/settings.d.cts +3 -0
  232. package/build/elements/icons/interface/settings.d.ts +3 -0
  233. package/build/elements/icons/interface/settings.js +26 -0
  234. package/build/elements/index.cjs +62 -0
  235. package/build/elements/index.d.cts +22 -0
  236. package/build/elements/index.d.ts +22 -0
  237. package/build/elements/index.js +22 -0
  238. package/build/elements/input/index.cjs +273 -0
  239. package/build/elements/input/index.d.cts +17 -0
  240. package/build/elements/input/index.d.ts +17 -0
  241. package/build/elements/input/index.js +269 -0
  242. package/build/elements/logo/index.cjs +732 -0
  243. package/build/elements/logo/index.d.cts +17 -0
  244. package/build/elements/logo/index.d.ts +17 -0
  245. package/build/elements/logo/index.js +728 -0
  246. package/build/elements/monitor/fps.cjs +432 -0
  247. package/build/elements/monitor/fps.d.cts +21 -0
  248. package/build/elements/monitor/fps.d.ts +21 -0
  249. package/build/elements/monitor/fps.js +428 -0
  250. package/build/elements/monitor/index.cjs +670 -0
  251. package/build/elements/monitor/index.d.cts +112 -0
  252. package/build/elements/monitor/index.d.ts +112 -0
  253. package/build/elements/monitor/index.js +666 -0
  254. package/build/elements/number/index.cjs +173 -0
  255. package/build/elements/number/index.d.cts +19 -0
  256. package/build/elements/number/index.d.ts +19 -0
  257. package/build/elements/number/index.js +169 -0
  258. package/build/elements/origin/index.cjs +169 -0
  259. package/build/elements/origin/index.d.cts +12 -0
  260. package/build/elements/origin/index.d.ts +12 -0
  261. package/build/elements/origin/index.js +165 -0
  262. package/build/elements/popover/index.cjs +209 -0
  263. package/build/elements/popover/index.d.cts +19 -0
  264. package/build/elements/popover/index.d.ts +19 -0
  265. package/build/elements/popover/index.js +205 -0
  266. package/build/elements/radio/index.cjs +301 -0
  267. package/build/elements/radio/index.d.cts +13 -0
  268. package/build/elements/radio/index.d.ts +13 -0
  269. package/build/elements/radio/index.js +283 -0
  270. package/build/elements/radio/input.cjs +329 -0
  271. package/build/elements/radio/input.d.cts +15 -0
  272. package/build/elements/radio/input.d.ts +15 -0
  273. package/build/elements/radio/input.js +325 -0
  274. package/build/elements/radio/option.cjs +15 -0
  275. package/build/elements/radio/option.d.cts +3 -0
  276. package/build/elements/radio/option.d.ts +3 -0
  277. package/build/elements/radio/option.js +11 -0
  278. package/build/elements/shared.cjs +66 -0
  279. package/build/elements/shared.d.cts +40 -0
  280. package/build/elements/shared.d.ts +40 -0
  281. package/build/elements/shared.js +59 -0
  282. package/build/elements/slider/index.cjs +232 -0
  283. package/build/elements/slider/index.d.cts +20 -0
  284. package/build/elements/slider/index.d.ts +20 -0
  285. package/build/elements/slider/index.js +228 -0
  286. package/build/elements/state/index.cjs +681 -0
  287. package/build/elements/state/index.d.cts +86 -0
  288. package/build/elements/state/index.d.ts +86 -0
  289. package/build/elements/state/index.js +677 -0
  290. package/build/elements/toggle/index.cjs +151 -0
  291. package/build/elements/toggle/index.d.cts +9 -0
  292. package/build/elements/toggle/index.d.ts +9 -0
  293. package/build/elements/toggle/index.js +147 -0
  294. package/build/elements/tooltip/index.cjs +187 -0
  295. package/build/elements/tooltip/index.d.cts +17 -0
  296. package/build/elements/tooltip/index.d.ts +17 -0
  297. package/build/elements/tooltip/index.js +183 -0
  298. package/build/index.cjs +40 -0
  299. package/build/index.d.cts +6 -0
  300. package/build/index.d.ts +6 -0
  301. package/build/index.js +12 -0
  302. package/build/init.cjs +325 -0
  303. package/build/init.d.cts +157 -0
  304. package/build/init.d.ts +157 -0
  305. package/build/init.js +289 -0
  306. package/build/internal/component-loaders.cjs +206 -0
  307. package/build/internal/component-loaders.d.cts +52 -0
  308. package/build/internal/component-loaders.d.ts +52 -0
  309. package/build/internal/component-loaders.js +167 -0
  310. package/build/internal/fonts.cjs +128 -0
  311. package/build/internal/fonts.d.cts +32 -0
  312. package/build/internal/fonts.d.ts +32 -0
  313. package/build/internal/fonts.js +123 -0
  314. package/build/internal/lazy-load.cjs +89 -0
  315. package/build/internal/lazy-load.d.cts +32 -0
  316. package/build/internal/lazy-load.d.ts +32 -0
  317. package/build/internal/lazy-load.js +86 -0
  318. package/build/internal/style-inject.cjs +236 -0
  319. package/build/internal/style-inject.d.cts +44 -0
  320. package/build/internal/style-inject.d.ts +44 -0
  321. package/build/internal/style-inject.js +226 -0
  322. package/build/register.cjs +36 -0
  323. package/build/register.d.cts +32 -0
  324. package/build/register.d.ts +32 -0
  325. package/build/register.js +34 -0
  326. package/build/theme/index.cjs +452 -0
  327. package/build/theme/index.d.cts +146 -0
  328. package/build/theme/index.d.ts +146 -0
  329. package/build/theme/index.js +423 -0
  330. package/build/theme/presets.cjs +54 -0
  331. package/build/theme/presets.d.cts +19 -0
  332. package/build/theme/presets.d.ts +19 -0
  333. package/build/theme/presets.js +51 -0
  334. package/build/theme/registry.cjs +204 -0
  335. package/build/theme/registry.d.cts +99 -0
  336. package/build/theme/registry.d.ts +99 -0
  337. package/build/theme/registry.js +194 -0
  338. package/build/theme/tokens.cjs +148 -0
  339. package/build/theme/tokens.d.cts +163 -0
  340. package/build/theme/tokens.d.ts +163 -0
  341. package/build/theme/tokens.js +145 -0
  342. package/build/utils/dismiss-controller.cjs +77 -0
  343. package/build/utils/dismiss-controller.d.cts +14 -0
  344. package/build/utils/dismiss-controller.d.ts +14 -0
  345. package/build/utils/dismiss-controller.js +73 -0
  346. package/build/utils/index.cjs +18 -0
  347. package/build/utils/index.d.cts +3 -0
  348. package/build/utils/index.d.ts +3 -0
  349. package/build/utils/index.js +3 -0
  350. package/build/utils/outside-click.cjs +82 -0
  351. package/build/utils/outside-click.d.cts +18 -0
  352. package/build/utils/outside-click.d.ts +18 -0
  353. package/build/utils/outside-click.js +74 -0
  354. package/build/utils/template-helpers.cjs +39 -0
  355. package/build/utils/template-helpers.d.cts +13 -0
  356. package/build/utils/template-helpers.d.ts +13 -0
  357. package/build/utils/template-helpers.js +28 -0
  358. package/package.json +96 -0
@@ -0,0 +1,871 @@
1
+ import "../popover/index.js";
2
+ import "../icons/animation/chevron.js";
3
+ import { html } from 'lit-html';
4
+ import { dispatchControlEvent, setBooleanAttribute } from "../shared.js";
5
+ import { Component } from '~/decorators/Component';
6
+ import { OutsideClick, requestOutsideClickUpdate } from '~/decorators/OutsideClick';
7
+ import { Prop } from '~/decorators/Prop';
8
+ import { Query } from '~/decorators/Query';
9
+ const nextOptionId = (() => {
10
+ let counter = 0;
11
+ return () => {
12
+ counter += 1;
13
+ return `ease-dropdown-option-${counter}`;
14
+ };
15
+ })();
16
+ const nextPanelId = (() => {
17
+ let counter = 0;
18
+ return () => {
19
+ counter += 1;
20
+ return `ease-dropdown-content-${counter}`;
21
+ };
22
+ })();
23
+ @Component({
24
+ tag: 'ease-dropdown',
25
+ styles: `
26
+ :host {
27
+ display: grid;
28
+ width: 100%;
29
+ }
30
+
31
+ ease-popover {
32
+ --ease-popover-content-min-width: var(--ease-dropdown-panel-min-width, anchor-size(width));
33
+ --ease-popover-content-width: var(--ease-dropdown-panel-width, max-content);
34
+ --ease-popover-content-max-width: var(--ease-dropdown-panel-max-width, none);
35
+ }
36
+
37
+ [part="trigger"] {
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: space-between;
41
+ width: 100%;
42
+ gap: 8px;
43
+ padding: var(--ease-dropdown-trigger-padding, 8px 8px 8px 12px);
44
+ border-radius: var(--ease-dropdown-radius, var(--radii-md));
45
+ background-color: var(--ease-dropdown-background, var(--color-gray-850));
46
+ cursor: pointer;
47
+ box-shadow: var(
48
+ --ease-dropdown-shadow,
49
+ inset 0 1px 0.25px 0 var(--color-white-4),
50
+ 0 1px 2.5px 0 var(--color-black-8)
51
+ );
52
+ font-family: var(--ease-font-family, inherit);
53
+ font-optical-sizing: auto;
54
+ box-sizing: border-box;
55
+ font-size: var(--ease-dropdown-font-size, var(--ease-font-size, 13px));
56
+ font-weight: var(--ease-dropdown-font-weight, 500);
57
+ color: var(--ease-dropdown-color, var(--color-foreground));
58
+ min-width: 0;
59
+ border: none;
60
+ outline: none;
61
+ margin: 0;
62
+ line-height: var(--ease-dropdown-line-height, 14px);
63
+ transition: color 0.2s, background-color 0.2s;
64
+ text-align: left;
65
+
66
+ &:hover,
67
+ &:focus-within {
68
+ background-color: var(--ease-dropdown-background-hover, var(--color-gray-825));
69
+ }
70
+
71
+ [part="trigger-icon"] {
72
+ display: block;
73
+ margin: -1px 0 -1px -2px;
74
+ }
75
+
76
+ &[data-pill="true"] {
77
+ border-radius: 999px;
78
+ }
79
+
80
+ &[data-block="small"] {
81
+ padding: 4px 8px 4px 4px;
82
+ }
83
+
84
+ &[data-headless="true"] {
85
+ background: transparent;
86
+ box-shadow: none;
87
+ padding: 0;
88
+ border-radius: 0;
89
+ gap: 4px;
90
+
91
+ &:hover,
92
+ &:focus-within {
93
+ background: transparent;
94
+ }
95
+ }
96
+ }
97
+
98
+ input[part="trigger-input"] {
99
+ background: transparent;
100
+ border: none;
101
+ outline: none;
102
+ color: inherit;
103
+ font: inherit;
104
+ width: fit-content;
105
+ min-width: 0;
106
+ padding: 0;
107
+ margin: 0;
108
+ cursor: pointer;
109
+ field-sizing: var(--ease-dropdown-field-sizing, fixed);
110
+ }
111
+
112
+ :host([open]) input[part="trigger-input"] {
113
+ cursor: text;
114
+ }
115
+
116
+ [part="trigger"][data-placeholder="true"],
117
+ [part="trigger"] [part="trigger-label"][data-placeholder="true"],
118
+ input[part="trigger-input"]::placeholder {
119
+ color: var(--ease-dropdown-placeholder-color, var(--color-gray-600));
120
+ }
121
+
122
+ [part="trigger"] [part="trigger-label"] {
123
+ flex-grow: 1;
124
+ text-overflow: ellipsis;
125
+ overflow: hidden;
126
+ white-space: nowrap;
127
+ }
128
+
129
+ [part="content"] {
130
+ border-radius: var(--ease-dropdown-panel-radius, var(--ease-dropdown-radius, var(--radii-md)));
131
+ background: var(--ease-dropdown-panel-background, var(--color-gray-875));
132
+ border: none;
133
+ outline: none;
134
+ box-shadow: var(
135
+ --ease-dropdown-panel-shadow,
136
+ 0 5px 20px 0 var(--color-black-15),
137
+ 0 1px 4px 0 var(--color-black-15),
138
+ 0 0 0 1px var(--color-white-4) inset,
139
+ 0 1px 0 0 var(--color-white-4) inset
140
+ );
141
+ background-clip: padding-box;
142
+ border: 1px solid var(--ease-dropdown-panel-border-color, var(--color-gray-825));
143
+ box-sizing: border-box;
144
+ padding: var(--ease-dropdown-panel-padding, 4px);
145
+ }
146
+
147
+ [part="content"] ::slotted(hr) {
148
+ margin: 4px 0;
149
+ height: 1px;
150
+ background-color: var(--color-white-4);
151
+ border: none;
152
+
153
+ &:first-child,
154
+ &:last-child {
155
+ display: none;
156
+ }
157
+ }
158
+
159
+ [part="content"] ::slotted(h4) {
160
+ margin: 4px 0 8px 0 !important;
161
+ font-size: var(--ease-dropdown-group-label-font-size, 11px);
162
+ line-height: 1;
163
+ padding: var(--ease-dropdown-group-label-padding, 0 8px);
164
+ font-family: var(--ease-font-family, inherit);
165
+ font-weight: 450;
166
+ color: var(--ease-dropdown-group-label-color, var(--color-gray-700));
167
+ display: block;
168
+
169
+ &:first-child,
170
+ &:last-child {
171
+ display: none;
172
+ }
173
+ }
174
+
175
+ [part="section"] {
176
+ flex-grow: 1;
177
+ min-width: max(100%, var(--ease-dropdown-min-width, 140px));
178
+ display: grid;
179
+ grid-gap: 3px;
180
+ max-height: var(--ease-dropdown-max-height, auto);
181
+ overflow-y: auto;
182
+ overscroll-behavior: contain;
183
+ container-type: inline-size;
184
+ container-name: scroll-area;
185
+ mask: linear-gradient(to bottom,
186
+ #0000,
187
+ #ffff var(--top-fade) calc(100% - var(--bottom-fade)),
188
+ #0000
189
+ );
190
+ animation: scroll;
191
+ animation-timeline: --scroll;
192
+ scroll-timeline: --scroll y;
193
+ scroll-snap-type: y mandatory;
194
+ scrollbar-width: none;
195
+ -ms-overflow-style: none;
196
+ box-sizing: border-box;
197
+ }
198
+
199
+ [part="section"]::-webkit-scrollbar {
200
+ width: 0;
201
+ height: 0;
202
+ display: none;
203
+ }
204
+
205
+ @property --top-fade {
206
+ syntax: '<length>';
207
+ inherits: false;
208
+ initial-value: 0;
209
+ }
210
+
211
+ @property --bottom-fade {
212
+ syntax: '<length>';
213
+ inherits: false;
214
+ initial-value: 0;
215
+ }
216
+
217
+ @keyframes scroll {
218
+ 0% {
219
+ --bottom-fade: 16px;
220
+ --top-fade: 0;
221
+ }
222
+ 10%,
223
+ 100% {
224
+ --top-fade: 16px;
225
+ }
226
+ 90% {
227
+ --bottom-fade: 16px;
228
+ }
229
+ 100% {
230
+ --bottom-fade: 0;
231
+ }
232
+ }
233
+
234
+ ::slotted(button[slot="content"]) {
235
+ appearance: none;
236
+ font-family: var(--ease-font-family, inherit);
237
+ font-optical-sizing: auto;
238
+ font-size: var(--ease-dropdown-option-font-size, var(--ease-font-size-sm, 12px));
239
+ font-weight: var(--ease-dropdown-option-font-weight, 400);
240
+ color: var(--ease-dropdown-option-color, var(--color-blue-100));
241
+ min-width: 0;
242
+ padding: var(--ease-dropdown-option-padding, 7px 8px);
243
+ display: block;
244
+ border-radius: var(--ease-dropdown-option-radius, 5px);
245
+ background-color: var(--ease-dropdown-option-background, var(--color-gray-875));
246
+ border: none;
247
+ outline: none;
248
+ margin: 0;
249
+ line-height: var(--ease-dropdown-option-line-height, 14px);
250
+ transition: color 0.2s, background-color 0.2s;
251
+ cursor: pointer;
252
+ width: 100%;
253
+ text-align: left;
254
+ }
255
+
256
+ ::slotted(button[slot="content"]:hover),
257
+ ::slotted(button[slot="content"]:focus-visible),
258
+ ::slotted(button[slot="content"][data-active="true"]),
259
+ ::slotted(button[slot="content"][aria-selected="true"]) {
260
+ background-color: var(--ease-dropdown-option-background-active, var(--color-gray-825));
261
+ color: var(--ease-dropdown-option-color-active, var(--color-white));
262
+ }
263
+ `
264
+ })
265
+ export class Dropdown extends HTMLElement {
266
+ @Prop({
267
+ type: Boolean,
268
+ reflect: true,
269
+ onChange(next, previous) {
270
+ this.handleOpenChange(next, previous);
271
+ }
272
+ })
273
+ accessor open = false;
274
+ @Prop({ type: Boolean, reflect: true })
275
+ accessor disabled = false;
276
+ @Prop({ type: Boolean, reflect: true })
277
+ accessor pill = false;
278
+ @Prop({ type: Boolean, reflect: true })
279
+ accessor headless = false;
280
+ @Prop({ type: Boolean, reflect: true })
281
+ accessor searchable = false;
282
+ @Prop({
283
+ type: String,
284
+ reflect: true,
285
+ defaultValue: 'medium',
286
+ onAttributeChange() {
287
+ this.requestRender?.();
288
+ }
289
+ })
290
+ accessor block = 'medium';
291
+ @Prop({
292
+ reflect: true,
293
+ defaultValue: 'auto',
294
+ onChange(next) {
295
+ this.style.setProperty('--ease-dropdown-max-height', next ?? 'auto');
296
+ }
297
+ })
298
+ accessor maxHeight = 'auto';
299
+ @Prop({ reflect: true, defaultValue: null })
300
+ accessor name = null;
301
+ @Prop({ reflect: true, defaultValue: null })
302
+ accessor value = null;
303
+ @Prop({
304
+ reflect: true,
305
+ attribute: 'placeholder',
306
+ defaultValue: 'Select an option'
307
+ })
308
+ accessor placeholder = 'Select an option';
309
+ @Prop({ reflect: true, defaultValue: 'bottom-start' })
310
+ accessor placement = 'bottom-start';
311
+ @Query('[part="trigger"]')
312
+ accessor trigger;
313
+ @Query('input[part="trigger-input"]')
314
+ accessor searchInput;
315
+ @Query('[part="content"]')
316
+ accessor panelContent;
317
+ @Query('slot[name="content"]')
318
+ accessor contentSlot;
319
+ #options = [];
320
+ #selectedLabel = null;
321
+ #currentSlot = null;
322
+ #pendingFocus = null;
323
+ #optionsInitialized = false;
324
+ #lastToggleOrigin = null;
325
+ #handleSlotChange = () => {
326
+ this.#syncOptions();
327
+ };
328
+ connectedCallback() {
329
+ this.#ensureSlotListener();
330
+ }
331
+ disconnectedCallback() {
332
+ this.#removeSlotListener();
333
+ this.#teardownOptions();
334
+ this.#lastToggleOrigin = null;
335
+ }
336
+ afterRender() {
337
+ const trigger = this.trigger;
338
+ const ariaLabel = this.getAttribute('aria-label');
339
+ if (trigger) {
340
+ if (trigger instanceof HTMLButtonElement) {
341
+ trigger.disabled = Boolean(this.disabled);
342
+ }
343
+ trigger.setAttribute('aria-expanded', this.open && !this.disabled ? 'true' : 'false');
344
+ trigger.setAttribute('aria-haspopup', 'listbox');
345
+ if (trigger instanceof HTMLButtonElement) {
346
+ if (ariaLabel && ariaLabel.trim().length > 0) {
347
+ trigger.setAttribute('aria-label', ariaLabel.trim());
348
+ }
349
+ else {
350
+ trigger.removeAttribute('aria-label');
351
+ }
352
+ }
353
+ }
354
+ if (this.searchInput) {
355
+ this.searchInput.disabled = Boolean(this.disabled);
356
+ if (ariaLabel && ariaLabel.trim().length > 0) {
357
+ this.searchInput.setAttribute('aria-label', ariaLabel.trim());
358
+ }
359
+ else {
360
+ this.searchInput.removeAttribute('aria-label');
361
+ }
362
+ }
363
+ const panel = this.panelContent;
364
+ const isInteractive = this.open && !this.disabled;
365
+ if (panel) {
366
+ if (!panel.id) {
367
+ panel.id = nextPanelId();
368
+ }
369
+ panel.tabIndex = -1;
370
+ panel.setAttribute('role', 'listbox');
371
+ panel.setAttribute('aria-hidden', isInteractive ? 'false' : 'true');
372
+ if (trigger) {
373
+ trigger.setAttribute('aria-controls', panel.id);
374
+ }
375
+ }
376
+ else if (trigger) {
377
+ trigger.removeAttribute('aria-controls');
378
+ }
379
+ setBooleanAttribute(this, 'disabled', Boolean(this.disabled));
380
+ this.#ensureSlotListener();
381
+ if (!this.#optionsInitialized) {
382
+ queueMicrotask(() => {
383
+ this.#syncOptions();
384
+ });
385
+ }
386
+ else {
387
+ this.#updateOptionSelectionState();
388
+ }
389
+ if (this.disabled && this.open) {
390
+ this.#lastToggleOrigin = null;
391
+ this.open = false;
392
+ }
393
+ }
394
+ render() {
395
+ const placeholderActive = this.#isPlaceholderActive();
396
+ return html `
397
+ <ease-popover
398
+ .placement=${this.placement}
399
+ .open=${this.open && !this.disabled}
400
+ >
401
+ ${this.searchable ? this.#renderSearchTrigger(placeholderActive) : this.#renderButtonTrigger(placeholderActive)}
402
+ <div
403
+ part="content"
404
+ role="listbox"
405
+ tabindex="-1"
406
+ data-open=${this.open && !this.disabled ? 'true' : 'false'}
407
+ ?hidden=${!this.open || this.disabled}
408
+ @keydown=${this.#handlePanelKeydown}
409
+ >
410
+ <div part="section">
411
+ <slot name="content"></slot>
412
+ </div>
413
+ </div>
414
+ </ease-popover>
415
+ `;
416
+ }
417
+ #renderButtonTrigger(placeholderActive) {
418
+ return html `
419
+ <button
420
+ part="trigger"
421
+ slot="trigger"
422
+ type="button"
423
+ data-placeholder=${placeholderActive ? 'true' : 'false'}
424
+ data-headless=${this.headless}
425
+ @keydown=${this.#handleTriggerKeydown}
426
+ @click=${this.#handleTriggerClick}
427
+ data-pill=${this.pill}
428
+ >
429
+ <slot name="trigger">
430
+ <span part="trigger-label" data-placeholder=${placeholderActive ? 'true' : 'false'}>
431
+ ${this.#getTriggerLabel()}
432
+ </span>
433
+
434
+ <ease-icon-chevron part="trigger-icon" state=${this.open ? 'up' : 'down'} />
435
+ </slot>
436
+ </button>
437
+ `;
438
+ }
439
+ #renderSearchTrigger(placeholderActive) {
440
+ return html `
441
+ <div
442
+ part="trigger"
443
+ slot="trigger"
444
+ data-placeholder=${placeholderActive ? 'true' : 'false'}
445
+ data-headless=${this.headless}
446
+ @click=${this.#handleSearchTriggerClick}
447
+ data-pill=${this.pill}
448
+ >
449
+ <slot name="trigger">
450
+ <input
451
+ part="trigger-input"
452
+ type="text"
453
+ .value=${this.open ? (this.searchInput?.value ?? '') : this.#getTriggerLabel()}
454
+ placeholder=${this.placeholder ?? ''}
455
+ ?readonly=${!this.open}
456
+ @input=${this.#handleSearchInput}
457
+ @keydown=${this.#handleTriggerKeydown}
458
+ />
459
+ <ease-icon-chevron part="trigger-icon" state=${this.open ? 'up' : 'down'} />
460
+ </slot>
461
+ </div>
462
+ `;
463
+ }
464
+ #handleSearchTriggerClick = (event) => {
465
+ if (this.disabled) {
466
+ return;
467
+ }
468
+ if (!this.open) {
469
+ this.toggle(true, event);
470
+ if (this.searchInput) {
471
+ this.searchInput.value = '';
472
+ this.#filterOptions('');
473
+ this.searchInput.focus();
474
+ }
475
+ }
476
+ else {
477
+ if (event.target !== this.searchInput) {
478
+ this.toggle(false, event);
479
+ }
480
+ }
481
+ };
482
+ #handleSearchInput = (event) => {
483
+ const input = event.target;
484
+ this.#filterOptions(input.value);
485
+ if (!this.open) {
486
+ this.toggle(true, event);
487
+ }
488
+ };
489
+ #filterOptions(query) {
490
+ const lowerQuery = query.toLowerCase();
491
+ this.#options.forEach((option) => {
492
+ const match = option.label.toLowerCase().includes(lowerQuery);
493
+ if (match) {
494
+ option.element.style.display = '';
495
+ }
496
+ else {
497
+ option.element.style.display = 'none';
498
+ }
499
+ });
500
+ }
501
+ toggle(force, originEvent) {
502
+ if (this.disabled) {
503
+ return;
504
+ }
505
+ const current = this.open;
506
+ const next = force ?? !current;
507
+ if (current === next) {
508
+ return;
509
+ }
510
+ this.#lastToggleOrigin = originEvent ?? null;
511
+ this.open = next;
512
+ if (!next) {
513
+ this.#pendingFocus = null;
514
+ // Reset filter when closing
515
+ this.#filterOptions('');
516
+ // Restore label in input if searchable
517
+ if (this.searchable && this.searchInput) {
518
+ this.searchInput.value = this.#getTriggerLabel();
519
+ this.searchInput.blur(); // Blur to look like static text
520
+ }
521
+ }
522
+ }
523
+ handleOpenChange(next, previous) {
524
+ if (next === previous) {
525
+ return;
526
+ }
527
+ if (!next) {
528
+ this.#pendingFocus = null;
529
+ }
530
+ const origin = this.#lastToggleOrigin ?? new Event(next ? 'open' : 'close');
531
+ this.#lastToggleOrigin = null;
532
+ if (next) {
533
+ if (this.searchable && this.searchInput) {
534
+ this.searchInput.focus();
535
+ }
536
+ else {
537
+ queueMicrotask(() => this.#focusActiveOption());
538
+ }
539
+ }
540
+ dispatchControlEvent(this, 'toggle', { value: next, event: origin });
541
+ requestOutsideClickUpdate(this);
542
+ }
543
+ @OutsideClick({
544
+ content: (host) => host.panelContent,
545
+ triggers: (host) => [host.trigger, host.panelContent],
546
+ disabled: (host) => host.disabled || !host.open
547
+ })
548
+ handleOutsideDismiss(event) {
549
+ if (!this.open) {
550
+ return;
551
+ }
552
+ this.toggle(false, event);
553
+ }
554
+ #handleTriggerKeydown = (event) => {
555
+ switch (event.key) {
556
+ case 'ArrowDown':
557
+ event.preventDefault();
558
+ this.#pendingFocus = 'first';
559
+ this.toggle(true, event);
560
+ break;
561
+ case 'ArrowUp':
562
+ event.preventDefault();
563
+ this.#pendingFocus = 'last';
564
+ this.toggle(true, event);
565
+ break;
566
+ case 'Enter':
567
+ if (!this.searchable) {
568
+ event.preventDefault();
569
+ this.toggle(true, event);
570
+ }
571
+ break;
572
+ case ' ': // Space
573
+ if (!this.searchable) {
574
+ event.preventDefault();
575
+ this.toggle(true, event);
576
+ }
577
+ break;
578
+ case 'Escape':
579
+ if (this.open) {
580
+ event.preventDefault();
581
+ this.toggle(false, event);
582
+ }
583
+ break;
584
+ default:
585
+ break;
586
+ }
587
+ };
588
+ #ensureSlotListener() {
589
+ const slot = this.contentSlot;
590
+ if (slot === this.#currentSlot) {
591
+ return;
592
+ }
593
+ if (this.#currentSlot) {
594
+ this.#currentSlot.removeEventListener('slotchange', this.#handleSlotChange);
595
+ }
596
+ if (slot) {
597
+ slot.addEventListener('slotchange', this.#handleSlotChange);
598
+ }
599
+ this.#currentSlot = slot ?? null;
600
+ }
601
+ #removeSlotListener() {
602
+ if (!this.#currentSlot) {
603
+ return;
604
+ }
605
+ this.#currentSlot.removeEventListener('slotchange', this.#handleSlotChange);
606
+ this.#currentSlot = null;
607
+ }
608
+ #handleTriggerClick = (event) => {
609
+ this.toggle(!this.open, event);
610
+ };
611
+ #syncOptions() {
612
+ const assigned = this.contentSlot?.assignedElements({ flatten: true }) ?? [];
613
+ const elements = assigned.filter((node) => node instanceof HTMLElement);
614
+ this.#removeOptionListeners(this.#options);
615
+ const options = [];
616
+ elements.forEach((element) => {
617
+ if (element.hasAttribute('data-select-ignore') || element.getAttribute('role') === 'separator') {
618
+ return;
619
+ }
620
+ const value = this.#resolveOptionValue(element);
621
+ const label = this.#resolveOptionLabel(element, value);
622
+ const id = element.id && element.id.trim().length > 0 ? element.id : nextOptionId();
623
+ if (!element.id) {
624
+ element.id = id;
625
+ }
626
+ element.setAttribute('role', 'option');
627
+ element.setAttribute('aria-selected', 'false');
628
+ element.dataset.active = 'false';
629
+ element.tabIndex = -1;
630
+ const handlers = {
631
+ click: (event) => {
632
+ event.preventDefault();
633
+ event.stopPropagation();
634
+ this.#selectOption(value, label, event);
635
+ },
636
+ keydown: (event) => {
637
+ if (event.key === 'Enter' || event.key === ' ') {
638
+ event.preventDefault();
639
+ this.#selectOption(value, label, event);
640
+ }
641
+ }
642
+ };
643
+ element.addEventListener('click', handlers.click, { passive: false });
644
+ element.addEventListener('keydown', handlers.keydown);
645
+ options.push({ element, value, label, id, handlers });
646
+ });
647
+ this.#options = options;
648
+ this.#optionsInitialized = options.length > 0;
649
+ if (this.value === null && options.length > 0) {
650
+ const preselected = options.find((option) => option.element.hasAttribute('selected') ||
651
+ option.element.dataset.selected === 'true' ||
652
+ option.element.getAttribute('aria-selected') === 'true');
653
+ if (preselected) {
654
+ this.value = preselected.value;
655
+ }
656
+ }
657
+ this.#updateOptionSelectionState();
658
+ }
659
+ #teardownOptions() {
660
+ this.#removeOptionListeners(this.#options);
661
+ this.#options = [];
662
+ this.#optionsInitialized = false;
663
+ this.#selectedLabel = null;
664
+ }
665
+ #removeOptionListeners(options) {
666
+ options.forEach((option) => {
667
+ option.element.removeEventListener('click', option.handlers.click);
668
+ option.element.removeEventListener('keydown', option.handlers.keydown);
669
+ option.element.dataset.active = 'false';
670
+ option.element.setAttribute('aria-selected', 'false');
671
+ option.element.tabIndex = -1;
672
+ });
673
+ }
674
+ #updateOptionSelectionState() {
675
+ if (!this.#optionsInitialized) {
676
+ this.toggleAttribute('data-has-value', false);
677
+ return;
678
+ }
679
+ let selectedLabel = null;
680
+ const previousLabel = this.#selectedLabel;
681
+ const options = this.#options;
682
+ options.forEach((option) => {
683
+ const isSelected = this.value !== null && option.value === this.value;
684
+ option.element.setAttribute('aria-selected', isSelected ? 'true' : 'false');
685
+ option.element.dataset.active = isSelected ? 'true' : 'false';
686
+ option.element.tabIndex = isSelected ? 0 : -1;
687
+ if (isSelected) {
688
+ selectedLabel = option.label;
689
+ }
690
+ });
691
+ if (!selectedLabel) {
692
+ const fallback = options[0];
693
+ if (fallback) {
694
+ fallback.element.tabIndex = 0;
695
+ }
696
+ }
697
+ this.#selectedLabel = selectedLabel;
698
+ const hasValue = Boolean(this.value && this.#selectedLabel);
699
+ this.toggleAttribute('data-has-value', hasValue);
700
+ if (previousLabel !== this.#selectedLabel && typeof this.requestRender === 'function') {
701
+ this.requestRender();
702
+ }
703
+ }
704
+ #isPlaceholderActive() {
705
+ return !this.value || !this.#selectedLabel;
706
+ }
707
+ #getTriggerLabel() {
708
+ if (this.#selectedLabel) {
709
+ return this.#selectedLabel;
710
+ }
711
+ if (typeof this.placeholder === 'string' && this.placeholder.trim().length > 0) {
712
+ return this.placeholder.trim();
713
+ }
714
+ return 'Select';
715
+ }
716
+ #focusActiveOption() {
717
+ if (!this.open) {
718
+ return;
719
+ }
720
+ const options = this.#options.filter((o) => o.element.style.display !== 'none');
721
+ if (options.length === 0) {
722
+ this.panelContent?.focus();
723
+ return;
724
+ }
725
+ let target = options[0];
726
+ if (this.#pendingFocus === 'first') {
727
+ target = options[0];
728
+ }
729
+ else if (this.#pendingFocus === 'last') {
730
+ target = options[options.length - 1];
731
+ }
732
+ else if (this.#pendingFocus === null && this.value !== null) {
733
+ const match = this.#findOptionByValue(this.value);
734
+ if (match && match.element.style.display !== 'none') {
735
+ target = match;
736
+ }
737
+ }
738
+ if (target) {
739
+ this.#focusOption(target);
740
+ }
741
+ this.#pendingFocus = null;
742
+ }
743
+ #focusOption(option) {
744
+ this.#options.forEach((entry) => {
745
+ entry.element.tabIndex = entry === option ? 0 : -1;
746
+ });
747
+ option.element.focus({ preventScroll: true });
748
+ }
749
+ #focusOptionByIndex(index) {
750
+ const options = this.#options.filter((o) => o.element.style.display !== 'none');
751
+ if (options.length === 0) {
752
+ this.panelContent?.focus();
753
+ return;
754
+ }
755
+ const normalized = Math.max(0, Math.min(index, options.length - 1));
756
+ const option = options[normalized];
757
+ if (option) {
758
+ this.#focusOption(option);
759
+ }
760
+ }
761
+ #findOptionByValue(value) {
762
+ if (value === null) {
763
+ return undefined;
764
+ }
765
+ return this.#options.find((option) => option.value === value);
766
+ }
767
+ #moveFocus(step) {
768
+ const options = this.#options.filter((o) => o.element.style.display !== 'none');
769
+ if (options.length === 0) {
770
+ this.panelContent?.focus();
771
+ return;
772
+ }
773
+ const activeElement = document.activeElement;
774
+ const currentIndex = options.findIndex((option) => option.element === activeElement);
775
+ if (currentIndex === -1) {
776
+ this.#focusOptionByIndex(step > 0 ? 0 : options.length - 1);
777
+ return;
778
+ }
779
+ const nextIndex = (currentIndex + step + options.length) % options.length;
780
+ this.#focusOptionByIndex(nextIndex);
781
+ }
782
+ #handlePanelKeydown = (event) => {
783
+ switch (event.key) {
784
+ case 'ArrowDown':
785
+ event.preventDefault();
786
+ this.#moveFocus(1);
787
+ break;
788
+ case 'ArrowUp':
789
+ event.preventDefault();
790
+ this.#moveFocus(-1);
791
+ break;
792
+ case 'Home':
793
+ event.preventDefault();
794
+ this.#focusOptionByIndex(0);
795
+ break;
796
+ case 'End':
797
+ event.preventDefault();
798
+ this.#focusOptionByIndex(this.#options.length - 1);
799
+ break;
800
+ case 'Enter':
801
+ case ' ': {
802
+ event.preventDefault();
803
+ this.#activateFocusedOption(event);
804
+ break;
805
+ }
806
+ case 'Escape':
807
+ event.preventDefault();
808
+ this.toggle(false, event);
809
+ this.trigger?.focus();
810
+ break;
811
+ default:
812
+ break;
813
+ }
814
+ };
815
+ #activateFocusedOption(event) {
816
+ const active = this.#options.find((option) => option.element === document.activeElement);
817
+ if (active) {
818
+ this.#selectOption(active.value, active.label, event);
819
+ }
820
+ }
821
+ #selectOption(value, label, originEvent) {
822
+ if (this.disabled) {
823
+ return;
824
+ }
825
+ originEvent.preventDefault();
826
+ originEvent.stopPropagation();
827
+ const previousValue = this.value;
828
+ this.value = value;
829
+ this.#selectedLabel = label;
830
+ this.#updateOptionSelectionState();
831
+ this.toggle(false, originEvent);
832
+ if (previousValue !== value) {
833
+ this.#dispatchValueChange(value, label, originEvent);
834
+ }
835
+ if (!this.searchable) {
836
+ queueMicrotask(() => this.trigger?.focus());
837
+ }
838
+ }
839
+ #resolveOptionValue(element) {
840
+ const explicitValue = element.getAttribute('value') ?? element.getAttribute('data-value') ?? element.dataset.value;
841
+ if (explicitValue && explicitValue.trim().length > 0) {
842
+ return explicitValue.trim();
843
+ }
844
+ const text = element.textContent?.trim();
845
+ if (text && text.length > 0) {
846
+ return text;
847
+ }
848
+ const fallback = nextOptionId();
849
+ element.dataset.value = fallback;
850
+ return fallback;
851
+ }
852
+ #resolveOptionLabel(element, fallback) {
853
+ const explicitLabel = element.getAttribute('data-label') ?? element.getAttribute('aria-label');
854
+ if (explicitLabel && explicitLabel.trim().length > 0) {
855
+ return explicitLabel.trim();
856
+ }
857
+ const text = element.textContent?.trim();
858
+ if (text && text.length > 0) {
859
+ return text;
860
+ }
861
+ return fallback;
862
+ }
863
+ #dispatchValueChange(value, label, event) {
864
+ dispatchControlEvent(this, 'change', { value, event });
865
+ this.dispatchEvent(new CustomEvent('value-change', {
866
+ detail: { value, label, event },
867
+ bubbles: true,
868
+ composed: true
869
+ }));
870
+ }
871
+ }