@vielzeug/refine 26.10.0 → 26.10.1

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 (463) hide show
  1. package/README.md +1 -1
  2. package/dist/accordion-item.cjs.map +1 -1
  3. package/dist/accordion-item.js.map +1 -1
  4. package/dist/accordion.cjs.map +1 -1
  5. package/dist/accordion.js.map +1 -1
  6. package/dist/async.cjs.map +1 -1
  7. package/dist/async.js.map +1 -1
  8. package/dist/badge.cjs.map +1 -1
  9. package/dist/badge.js.map +1 -1
  10. package/dist/box.cjs.map +1 -1
  11. package/dist/box.js.map +1 -1
  12. package/dist/button.cjs +3 -3
  13. package/dist/button.cjs.map +1 -1
  14. package/dist/button.js +3 -3
  15. package/dist/button.js.map +1 -1
  16. package/dist/calendar.cjs.map +1 -1
  17. package/dist/calendar.js.map +1 -1
  18. package/dist/carousel.cjs.map +1 -1
  19. package/dist/carousel.js.map +1 -1
  20. package/dist/chat-message.cjs.map +1 -1
  21. package/dist/chat-message.js.map +1 -1
  22. package/dist/chat-panel.cjs.map +1 -1
  23. package/dist/chat-panel.js.map +1 -1
  24. package/dist/checkbox-group.cjs.map +1 -1
  25. package/dist/checkbox-group.js.map +1 -1
  26. package/dist/chip.cjs.map +1 -1
  27. package/dist/chip.js.map +1 -1
  28. package/dist/code-window.cjs.map +1 -1
  29. package/dist/code-window.js.map +1 -1
  30. package/dist/combobox.cjs.map +1 -1
  31. package/dist/combobox.js.map +1 -1
  32. package/dist/command-palette.cjs.map +1 -1
  33. package/dist/command-palette.js.map +1 -1
  34. package/dist/content/card/card.css.cjs.map +1 -1
  35. package/dist/content/card/card.css.js.map +1 -1
  36. package/dist/content/carousel/carousel-slide.css.cjs.map +1 -1
  37. package/dist/content/carousel/carousel-slide.css.js.map +1 -1
  38. package/dist/content/carousel/carousel.css.cjs.map +1 -1
  39. package/dist/content/carousel/carousel.css.js.map +1 -1
  40. package/dist/content/carousel/carousel.d.ts +5 -5
  41. package/dist/content/chat-message/chat-message.css.cjs +1 -1
  42. package/dist/content/chat-message/chat-message.css.cjs.map +1 -1
  43. package/dist/content/chat-message/chat-message.css.js +1 -1
  44. package/dist/content/chat-message/chat-message.css.js.map +1 -1
  45. package/dist/content/chat-message/chat-message.d.ts +4 -4
  46. package/dist/content/chat-message/chat-message.d.ts.map +1 -1
  47. package/dist/content/chat-panel/chat-panel.d.ts +1 -1
  48. package/dist/content/code-window/code-window.css.cjs.map +1 -1
  49. package/dist/content/code-window/code-window.css.js.map +1 -1
  50. package/dist/content/code-window/code-window.d.ts +4 -4
  51. package/dist/content/index.d.ts +4 -0
  52. package/dist/content/index.d.ts.map +1 -1
  53. package/dist/content/list/_list-item-reveal.cjs.map +1 -1
  54. package/dist/content/list/_list-item-reveal.d.ts +4 -4
  55. package/dist/content/list/_list-item-reveal.d.ts.map +1 -1
  56. package/dist/content/list/_list-item-reveal.js.map +1 -1
  57. package/dist/content/list/list-item.css.cjs.map +1 -1
  58. package/dist/content/list/list-item.css.js.map +1 -1
  59. package/dist/content/list/list-item.d.ts +8 -8
  60. package/dist/content/list/list-item.d.ts.map +1 -1
  61. package/dist/content/list/list.d.ts +8 -8
  62. package/dist/content/list/list.d.ts.map +1 -1
  63. package/dist/content/qr-code/qr-code.d.ts +1 -1
  64. package/dist/content/rank-list/rank-item.css.cjs +2 -0
  65. package/dist/content/rank-list/rank-item.css.cjs.map +1 -0
  66. package/dist/content/rank-list/rank-item.css.js +2 -0
  67. package/dist/content/rank-list/rank-item.css.js.map +1 -0
  68. package/dist/content/rank-list/rank-item.d.ts +68 -0
  69. package/dist/content/rank-list/rank-item.d.ts.map +1 -0
  70. package/dist/content/rank-list/rank-list.css.cjs +2 -0
  71. package/dist/content/rank-list/rank-list.css.cjs.map +1 -0
  72. package/dist/content/rank-list/rank-list.css.js +2 -0
  73. package/dist/content/rank-list/rank-list.css.js.map +1 -0
  74. package/dist/content/rank-list/rank-list.d.ts +44 -0
  75. package/dist/content/rank-list/rank-list.d.ts.map +1 -0
  76. package/dist/content/separator/separator.d.ts +1 -1
  77. package/dist/content/separator/separator.d.ts.map +1 -1
  78. package/dist/content/speech-player/speech-player.css.cjs.map +1 -1
  79. package/dist/content/speech-player/speech-player.css.js.map +1 -1
  80. package/dist/content/speech-player/speech-player.d.ts +12 -12
  81. package/dist/content/speech-player/speech-player.d.ts.map +1 -1
  82. package/dist/content/stepper/step.css.cjs.map +1 -1
  83. package/dist/content/stepper/step.css.js.map +1 -1
  84. package/dist/content/stepper/step.d.ts +3 -3
  85. package/dist/content/stepper/step.d.ts.map +1 -1
  86. package/dist/content/stepper/stepper.css.cjs.map +1 -1
  87. package/dist/content/stepper/stepper.css.js.map +1 -1
  88. package/dist/content/stepper/stepper.d.ts +3 -3
  89. package/dist/content/stepper/stepper.d.ts.map +1 -1
  90. package/dist/content/table/table.css.cjs.map +1 -1
  91. package/dist/content/table/table.css.js.map +1 -1
  92. package/dist/content/table/table.d.ts +3 -3
  93. package/dist/content/table/table.d.ts.map +1 -1
  94. package/dist/content/text/text.css.cjs.map +1 -1
  95. package/dist/content/text/text.css.js.map +1 -1
  96. package/dist/content/text/text.d.ts +2 -2
  97. package/dist/content/text/text.d.ts.map +1 -1
  98. package/dist/cookie-banner.cjs.map +1 -1
  99. package/dist/cookie-banner.js.map +1 -1
  100. package/dist/copy-command.cjs +1 -1
  101. package/dist/copy-command.cjs.map +1 -1
  102. package/dist/copy-command.js +1 -1
  103. package/dist/copy-command.js.map +1 -1
  104. package/dist/core/announcer.cjs.map +1 -1
  105. package/dist/core/announcer.d.ts +2 -2
  106. package/dist/core/announcer.js.map +1 -1
  107. package/dist/core/aria-reflection.cjs.map +1 -1
  108. package/dist/core/aria-reflection.d.ts +3 -3
  109. package/dist/core/aria-reflection.js.map +1 -1
  110. package/dist/core/auto-resize.cjs.map +1 -1
  111. package/dist/core/auto-resize.d.ts +1 -1
  112. package/dist/core/auto-resize.js.map +1 -1
  113. package/dist/core/checkable.cjs.map +1 -1
  114. package/dist/core/checkable.d.ts +2 -2
  115. package/dist/core/checkable.d.ts.map +1 -1
  116. package/dist/core/checkable.js.map +1 -1
  117. package/dist/core/choice-field.cjs.map +1 -1
  118. package/dist/core/choice-field.d.ts +3 -3
  119. package/dist/core/choice-field.d.ts.map +1 -1
  120. package/dist/core/choice-field.js.map +1 -1
  121. package/dist/core/composer.cjs.map +1 -1
  122. package/dist/core/composer.d.ts +4 -4
  123. package/dist/core/composer.d.ts.map +1 -1
  124. package/dist/core/composer.js.map +1 -1
  125. package/dist/core/date-picker.cjs.map +1 -1
  126. package/dist/core/date-picker.d.ts +5 -5
  127. package/dist/core/date-picker.d.ts.map +1 -1
  128. package/dist/core/date-picker.js.map +1 -1
  129. package/dist/core/direction.cjs.map +1 -1
  130. package/dist/core/direction.d.ts +1 -1
  131. package/dist/core/direction.js.map +1 -1
  132. package/dist/core/field-base.cjs.map +1 -1
  133. package/dist/core/field-base.d.ts +9 -9
  134. package/dist/core/field-base.d.ts.map +1 -1
  135. package/dist/core/field-base.js.map +1 -1
  136. package/dist/core/focus.cjs.map +1 -1
  137. package/dist/core/focus.d.ts +1 -1
  138. package/dist/core/focus.js.map +1 -1
  139. package/dist/core/icon-registry.cjs.map +1 -1
  140. package/dist/core/icon-registry.d.ts +1 -1
  141. package/dist/core/icon-registry.js.map +1 -1
  142. package/dist/core/icons.cjs.map +1 -1
  143. package/dist/core/icons.js.map +1 -1
  144. package/dist/core/keyboard.cjs.map +1 -1
  145. package/dist/core/keyboard.d.ts +1 -1
  146. package/dist/core/keyboard.js.map +1 -1
  147. package/dist/core/numbers.cjs.map +1 -1
  148. package/dist/core/numbers.d.ts +1 -1
  149. package/dist/core/numbers.js.map +1 -1
  150. package/dist/core/overlay.cjs.map +1 -1
  151. package/dist/core/overlay.d.ts +6 -6
  152. package/dist/core/overlay.js.map +1 -1
  153. package/dist/core/parse.cjs.map +1 -1
  154. package/dist/core/parse.d.ts +1 -1
  155. package/dist/core/parse.js.map +1 -1
  156. package/dist/core/positioner.cjs.map +1 -1
  157. package/dist/core/positioner.d.ts +3 -3
  158. package/dist/core/positioner.js.map +1 -1
  159. package/dist/core/ref-callback.cjs.map +1 -1
  160. package/dist/core/ref-callback.d.ts +2 -2
  161. package/dist/core/ref-callback.d.ts.map +1 -1
  162. package/dist/core/ref-callback.js.map +1 -1
  163. package/dist/core/text-field.cjs.map +1 -1
  164. package/dist/core/text-field.d.ts +6 -6
  165. package/dist/core/text-field.d.ts.map +1 -1
  166. package/dist/core/text-field.js.map +1 -1
  167. package/dist/counter.cjs +1 -0
  168. package/dist/counter.cjs.map +1 -1
  169. package/dist/counter.js +1 -0
  170. package/dist/counter.js.map +1 -1
  171. package/dist/custom-elements.json +315 -87
  172. package/dist/datagrid.cjs.map +1 -1
  173. package/dist/datagrid.js.map +1 -1
  174. package/dist/date-picker.cjs +1 -1
  175. package/dist/date-picker.cjs.map +1 -1
  176. package/dist/date-picker.js +1 -1
  177. package/dist/date-picker.js.map +1 -1
  178. package/dist/dialog.cjs.map +1 -1
  179. package/dist/dialog.js.map +1 -1
  180. package/dist/disclosure/accordion-item/accordion-item.css.cjs +1 -1
  181. package/dist/disclosure/accordion-item/accordion-item.css.cjs.map +1 -1
  182. package/dist/disclosure/accordion-item/accordion-item.css.js +1 -1
  183. package/dist/disclosure/accordion-item/accordion-item.css.js.map +1 -1
  184. package/dist/disclosure/tab-item/tab-item.d.ts +1 -1
  185. package/dist/disclosure/tab-item/tab-item.d.ts.map +1 -1
  186. package/dist/drawer.cjs.map +1 -1
  187. package/dist/drawer.js.map +1 -1
  188. package/dist/feedback/alert/alert.css.cjs.map +1 -1
  189. package/dist/feedback/alert/alert.css.js.map +1 -1
  190. package/dist/feedback/badge/badge.css.cjs.map +1 -1
  191. package/dist/feedback/badge/badge.css.js.map +1 -1
  192. package/dist/feedback/badge/badge.d.ts +1 -1
  193. package/dist/feedback/badge/badge.d.ts.map +1 -1
  194. package/dist/feedback/chip/chip.css.cjs.map +1 -1
  195. package/dist/feedback/chip/chip.css.js.map +1 -1
  196. package/dist/feedback/chip/chip.d.ts +3 -3
  197. package/dist/feedback/chip/chip.d.ts.map +1 -1
  198. package/dist/feedback/cookie-banner/cookie-banner.css.cjs.map +1 -1
  199. package/dist/feedback/cookie-banner/cookie-banner.css.js.map +1 -1
  200. package/dist/feedback/cookie-banner/cookie-banner.d.ts +8 -8
  201. package/dist/feedback/cookie-banner/cookie-banner.d.ts.map +1 -1
  202. package/dist/feedback/progress/progress.css.cjs.map +1 -1
  203. package/dist/feedback/progress/progress.css.js.map +1 -1
  204. package/dist/feedback/progress/progress.d.ts +1 -1
  205. package/dist/feedback/progress/progress.d.ts.map +1 -1
  206. package/dist/feedback/skeleton/skeleton.css.cjs.map +1 -1
  207. package/dist/feedback/skeleton/skeleton.css.js.map +1 -1
  208. package/dist/feedback/skeleton/skeleton.d.ts +1 -1
  209. package/dist/feedback/skeleton/skeleton.d.ts.map +1 -1
  210. package/dist/feedback/toast/toast.css.cjs +1 -1
  211. package/dist/feedback/toast/toast.css.cjs.map +1 -1
  212. package/dist/feedback/toast/toast.css.js +1 -1
  213. package/dist/feedback/toast/toast.css.js.map +1 -1
  214. package/dist/feedback/toast/toast.d.ts +1 -1
  215. package/dist/feedback/toast/toast.d.ts.map +1 -1
  216. package/dist/feedback/typing-indicator/typing-indicator.d.ts +2 -2
  217. package/dist/file-input.cjs +1 -1
  218. package/dist/file-input.cjs.map +1 -1
  219. package/dist/file-input.js +1 -1
  220. package/dist/file-input.js.map +1 -1
  221. package/dist/frameworks/elements.d.ts +1 -1
  222. package/dist/grid-item.cjs.map +1 -1
  223. package/dist/grid-item.js.map +1 -1
  224. package/dist/grid.cjs.map +1 -1
  225. package/dist/grid.js.map +1 -1
  226. package/dist/icon.cjs.map +1 -1
  227. package/dist/icon.js.map +1 -1
  228. package/dist/input.cjs.map +1 -1
  229. package/dist/input.js.map +1 -1
  230. package/dist/inputs/button/button.css.cjs.map +1 -1
  231. package/dist/inputs/button/button.css.js.map +1 -1
  232. package/dist/inputs/button/button.d.ts +3 -3
  233. package/dist/inputs/button/button.d.ts.map +1 -1
  234. package/dist/inputs/calendar/calendar.css.cjs.map +1 -1
  235. package/dist/inputs/calendar/calendar.css.js.map +1 -1
  236. package/dist/inputs/calendar/calendar.d.ts +2 -2
  237. package/dist/inputs/checkbox-group/checkbox-group.d.ts +2 -2
  238. package/dist/inputs/checkbox-group/checkbox-group.d.ts.map +1 -1
  239. package/dist/inputs/combobox/combobox.css.cjs.map +1 -1
  240. package/dist/inputs/combobox/combobox.css.js.map +1 -1
  241. package/dist/inputs/counter/counter.css.cjs +1 -1
  242. package/dist/inputs/counter/counter.css.cjs.map +1 -1
  243. package/dist/inputs/counter/counter.css.js +1 -1
  244. package/dist/inputs/counter/counter.css.js.map +1 -1
  245. package/dist/inputs/counter/counter.d.ts +2 -1
  246. package/dist/inputs/counter/counter.d.ts.map +1 -1
  247. package/dist/inputs/datagrid/datagrid.css.cjs.map +1 -1
  248. package/dist/inputs/datagrid/datagrid.css.js.map +1 -1
  249. package/dist/inputs/datagrid/datagrid.d.ts +5 -5
  250. package/dist/inputs/date-picker/date-picker.css.cjs.map +1 -1
  251. package/dist/inputs/date-picker/date-picker.css.js.map +1 -1
  252. package/dist/inputs/date-picker/date-picker.d.ts +1 -1
  253. package/dist/inputs/file-input/file-input-upload.cjs.map +1 -1
  254. package/dist/inputs/file-input/file-input-upload.d.ts +6 -6
  255. package/dist/inputs/file-input/file-input-upload.js.map +1 -1
  256. package/dist/inputs/file-input/file-input.css.cjs.map +1 -1
  257. package/dist/inputs/file-input/file-input.css.js.map +1 -1
  258. package/dist/inputs/file-input/file-input.d.ts +6 -6
  259. package/dist/inputs/input/input.css.cjs +1 -1
  260. package/dist/inputs/input/input.css.cjs.map +1 -1
  261. package/dist/inputs/input/input.css.js +1 -1
  262. package/dist/inputs/input/input.css.js.map +1 -1
  263. package/dist/inputs/input/input.d.ts +6 -6
  264. package/dist/inputs/input/input.d.ts.map +1 -1
  265. package/dist/inputs/message-composer/message-composer.css.cjs.map +1 -1
  266. package/dist/inputs/message-composer/message-composer.css.js.map +1 -1
  267. package/dist/inputs/message-composer/message-composer.d.ts +7 -7
  268. package/dist/inputs/message-composer/message-composer.d.ts.map +1 -1
  269. package/dist/inputs/number-input/number-input.css.cjs.map +1 -1
  270. package/dist/inputs/number-input/number-input.css.js.map +1 -1
  271. package/dist/inputs/number-input/number-input.d.ts +2 -2
  272. package/dist/inputs/otp-input/otp-input.css.cjs.map +1 -1
  273. package/dist/inputs/otp-input/otp-input.css.js.map +1 -1
  274. package/dist/inputs/qr-scanner/qr-scanner.d.ts +1 -1
  275. package/dist/inputs/qr-scanner/qr-scanner.d.ts.map +1 -1
  276. package/dist/inputs/rating/rating.d.ts +1 -1
  277. package/dist/inputs/rating/rating.d.ts.map +1 -1
  278. package/dist/inputs/select/select.css.cjs +1 -1
  279. package/dist/inputs/select/select.css.cjs.map +1 -1
  280. package/dist/inputs/select/select.css.js +1 -1
  281. package/dist/inputs/select/select.css.js.map +1 -1
  282. package/dist/inputs/select/select.d.ts +1 -1
  283. package/dist/inputs/select/select.d.ts.map +1 -1
  284. package/dist/inputs/shared/field-binding.cjs.map +1 -1
  285. package/dist/inputs/shared/field-binding.d.ts +2 -2
  286. package/dist/inputs/shared/field-binding.d.ts.map +1 -1
  287. package/dist/inputs/shared/field-binding.js.map +1 -1
  288. package/dist/inputs/shared/native-field-event.cjs.map +1 -1
  289. package/dist/inputs/shared/native-field-event.d.ts +1 -1
  290. package/dist/inputs/shared/native-field-event.js.map +1 -1
  291. package/dist/inputs/shared/templates.cjs.map +1 -1
  292. package/dist/inputs/shared/templates.d.ts +3 -3
  293. package/dist/inputs/shared/templates.js.map +1 -1
  294. package/dist/inputs/slider/slider.css.cjs.map +1 -1
  295. package/dist/inputs/slider/slider.css.js.map +1 -1
  296. package/dist/inputs/slider/slider.d.ts +1 -1
  297. package/dist/inputs/slider/slider.d.ts.map +1 -1
  298. package/dist/inputs/textarea/textarea.css.cjs.map +1 -1
  299. package/dist/inputs/textarea/textarea.css.js.map +1 -1
  300. package/dist/inputs/textarea/textarea.d.ts +1 -1
  301. package/dist/inputs/time-picker/time-picker.css.cjs.map +1 -1
  302. package/dist/inputs/time-picker/time-picker.css.js.map +1 -1
  303. package/dist/inputs/time-picker/time-picker.d.ts +1 -1
  304. package/dist/layout/box/box.css.cjs.map +1 -1
  305. package/dist/layout/box/box.css.js.map +1 -1
  306. package/dist/layout/box/box.d.ts +1 -1
  307. package/dist/layout/grid/grid.d.ts +1 -1
  308. package/dist/layout/grid-item/grid-item.d.ts +3 -3
  309. package/dist/layout/grid-item/grid-item.d.ts.map +1 -1
  310. package/dist/layout/navbar/navbar.css.cjs +1 -1
  311. package/dist/layout/navbar/navbar.css.cjs.map +1 -1
  312. package/dist/layout/navbar/navbar.css.js +1 -1
  313. package/dist/layout/navbar/navbar.css.js.map +1 -1
  314. package/dist/layout/navbar/navbar.d.ts +3 -3
  315. package/dist/layout/sidebar/sidebar-item.css.cjs.map +1 -1
  316. package/dist/layout/sidebar/sidebar-item.css.js.map +1 -1
  317. package/dist/layout/sidebar/sidebar.d.ts +5 -5
  318. package/dist/layout/sidebar/sidebar.d.ts.map +1 -1
  319. package/dist/list-item.cjs.map +1 -1
  320. package/dist/list-item.js.map +1 -1
  321. package/dist/list.cjs.map +1 -1
  322. package/dist/list.js.map +1 -1
  323. package/dist/menu.cjs +6 -5
  324. package/dist/menu.cjs.map +1 -1
  325. package/dist/menu.js +6 -5
  326. package/dist/menu.js.map +1 -1
  327. package/dist/message-composer.cjs.map +1 -1
  328. package/dist/message-composer.js.map +1 -1
  329. package/dist/navbar.cjs.map +1 -1
  330. package/dist/navbar.js.map +1 -1
  331. package/dist/number-input.cjs.map +1 -1
  332. package/dist/number-input.js.map +1 -1
  333. package/dist/otp-input.cjs.map +1 -1
  334. package/dist/otp-input.js.map +1 -1
  335. package/dist/overlay/command-palette/command-palette-items.cjs.map +1 -1
  336. package/dist/overlay/command-palette/command-palette-items.d.ts +1 -1
  337. package/dist/overlay/command-palette/command-palette-items.js.map +1 -1
  338. package/dist/overlay/command-palette/command-palette.css.cjs.map +1 -1
  339. package/dist/overlay/command-palette/command-palette.css.js.map +1 -1
  340. package/dist/overlay/command-palette/command-palette.d.ts +3 -3
  341. package/dist/overlay/command-palette/command-palette.types.d.ts +6 -6
  342. package/dist/overlay/command-palette/command-palette.types.d.ts.map +1 -1
  343. package/dist/overlay/dialog/dialog.css.cjs +1 -1
  344. package/dist/overlay/dialog/dialog.css.cjs.map +1 -1
  345. package/dist/overlay/dialog/dialog.css.js +1 -1
  346. package/dist/overlay/dialog/dialog.css.js.map +1 -1
  347. package/dist/overlay/dialog/dialog.d.ts +2 -2
  348. package/dist/overlay/dialog/dialog.d.ts.map +1 -1
  349. package/dist/overlay/drawer/drawer.css.cjs.map +1 -1
  350. package/dist/overlay/drawer/drawer.css.js.map +1 -1
  351. package/dist/overlay/drawer/drawer.d.ts +1 -1
  352. package/dist/overlay/drawer/drawer.d.ts.map +1 -1
  353. package/dist/overlay/menu/menu-item.css.cjs.map +1 -1
  354. package/dist/overlay/menu/menu-item.css.js.map +1 -1
  355. package/dist/overlay/menu/menu.d.ts.map +1 -1
  356. package/dist/overlay/shared/use-dialog.cjs.map +1 -1
  357. package/dist/overlay/shared/use-dialog.d.ts +6 -6
  358. package/dist/overlay/shared/use-dialog.d.ts.map +1 -1
  359. package/dist/overlay/shared/use-dialog.js.map +1 -1
  360. package/dist/overlay/shared/use-floating-trigger.cjs.map +1 -1
  361. package/dist/overlay/shared/use-floating-trigger.d.ts +1 -1
  362. package/dist/overlay/shared/use-floating-trigger.d.ts.map +1 -1
  363. package/dist/overlay/shared/use-floating-trigger.js.map +1 -1
  364. package/dist/overlay/tooltip/tooltip.css.cjs.map +1 -1
  365. package/dist/overlay/tooltip/tooltip.css.js.map +1 -1
  366. package/dist/progress.cjs.map +1 -1
  367. package/dist/progress.js.map +1 -1
  368. package/dist/qr-code.cjs.map +1 -1
  369. package/dist/qr-code.js.map +1 -1
  370. package/dist/qr-scanner.cjs.map +1 -1
  371. package/dist/qr-scanner.js.map +1 -1
  372. package/dist/radio-group.cjs.map +1 -1
  373. package/dist/radio-group.js.map +1 -1
  374. package/dist/radio.cjs.map +1 -1
  375. package/dist/radio.js.map +1 -1
  376. package/dist/rank-item.cjs +21 -0
  377. package/dist/rank-item.cjs.map +1 -0
  378. package/dist/rank-item.js +21 -0
  379. package/dist/rank-item.js.map +1 -0
  380. package/dist/rank-list.cjs +4 -0
  381. package/dist/rank-list.cjs.map +1 -0
  382. package/dist/rank-list.js +4 -0
  383. package/dist/rank-list.js.map +1 -0
  384. package/dist/rating.cjs.map +1 -1
  385. package/dist/rating.js.map +1 -1
  386. package/dist/refine.cjs +103 -80
  387. package/dist/refine.cjs.map +1 -1
  388. package/dist/refine.iife.js +103 -80
  389. package/dist/refine.iife.js.map +1 -1
  390. package/dist/refine.js +104 -81
  391. package/dist/refine.js.map +1 -1
  392. package/dist/select.cjs.map +1 -1
  393. package/dist/select.js.map +1 -1
  394. package/dist/separator.cjs.map +1 -1
  395. package/dist/separator.js.map +1 -1
  396. package/dist/shared/index.d.ts +3 -3
  397. package/dist/shared/size-presets.cjs.map +1 -1
  398. package/dist/shared/size-presets.d.ts +4 -4
  399. package/dist/shared/size-presets.js.map +1 -1
  400. package/dist/shared/types.d.ts +1 -1
  401. package/dist/sidebar.cjs.map +1 -1
  402. package/dist/sidebar.js.map +1 -1
  403. package/dist/skeleton.cjs.map +1 -1
  404. package/dist/skeleton.js.map +1 -1
  405. package/dist/slider.cjs.map +1 -1
  406. package/dist/slider.js.map +1 -1
  407. package/dist/speech-player.cjs.map +1 -1
  408. package/dist/speech-player.js.map +1 -1
  409. package/dist/step.cjs.map +1 -1
  410. package/dist/step.js.map +1 -1
  411. package/dist/stepper.cjs.map +1 -1
  412. package/dist/stepper.js.map +1 -1
  413. package/dist/styles/fouc.css +2 -2
  414. package/dist/styles/mixins/accessibility.css.cjs.map +1 -1
  415. package/dist/styles/mixins/accessibility.css.d.ts +4 -4
  416. package/dist/styles/mixins/accessibility.css.js.map +1 -1
  417. package/dist/styles/mixins/animation.css.cjs +2 -2
  418. package/dist/styles/mixins/animation.css.cjs.map +1 -1
  419. package/dist/styles/mixins/animation.css.d.ts +4 -4
  420. package/dist/styles/mixins/animation.css.js +2 -2
  421. package/dist/styles/mixins/animation.css.js.map +1 -1
  422. package/dist/styles/mixins/field-variants.css.cjs +2 -2
  423. package/dist/styles/mixins/field-variants.css.cjs.map +1 -1
  424. package/dist/styles/mixins/field-variants.css.d.ts +6 -6
  425. package/dist/styles/mixins/field-variants.css.d.ts.map +1 -1
  426. package/dist/styles/mixins/field-variants.css.js +2 -2
  427. package/dist/styles/mixins/field-variants.css.js.map +1 -1
  428. package/dist/styles/mixins/shape.css.cjs.map +1 -1
  429. package/dist/styles/mixins/shape.css.d.ts +9 -9
  430. package/dist/styles/mixins/shape.css.d.ts.map +1 -1
  431. package/dist/styles/mixins/shape.css.js.map +1 -1
  432. package/dist/styles/mixins/variants.css.cjs.map +1 -1
  433. package/dist/styles/mixins/variants.css.d.ts +1 -1
  434. package/dist/styles/mixins/variants.css.js.map +1 -1
  435. package/dist/styles/theme.css +36 -28
  436. package/dist/tab-item.cjs.map +1 -1
  437. package/dist/tab-item.js.map +1 -1
  438. package/dist/tab-panel.cjs.map +1 -1
  439. package/dist/tab-panel.js.map +1 -1
  440. package/dist/table.cjs.map +1 -1
  441. package/dist/table.js.map +1 -1
  442. package/dist/text.cjs.map +1 -1
  443. package/dist/text.js.map +1 -1
  444. package/dist/textarea.cjs.map +1 -1
  445. package/dist/textarea.js.map +1 -1
  446. package/dist/theme.cjs.map +1 -1
  447. package/dist/theme.d.ts +2 -2
  448. package/dist/theme.js.map +1 -1
  449. package/dist/time-picker.cjs +1 -1
  450. package/dist/time-picker.cjs.map +1 -1
  451. package/dist/time-picker.js +1 -1
  452. package/dist/time-picker.js.map +1 -1
  453. package/dist/toast.cjs.map +1 -1
  454. package/dist/toast.js.map +1 -1
  455. package/dist/types/elements.d.ts +6 -2
  456. package/dist/typing-indicator.cjs.map +1 -1
  457. package/dist/typing-indicator.js.map +1 -1
  458. package/dist/utils/css.cjs.map +1 -1
  459. package/dist/utils/css.js.map +1 -1
  460. package/dist/utils/link.cjs.map +1 -1
  461. package/dist/utils/link.d.ts +2 -2
  462. package/dist/utils/link.js.map +1 -1
  463. package/package.json +31 -14
@@ -1 +1 @@
1
- {"version":3,"file":"qr-code.cjs","names":[],"sources":["../src/content/qr-code/qr-code.ts"],"sourcesContent":["import { define, html, prop, unsafeHtml, useEmit, useSlots, when } from '@vielzeug/ore';\nimport { computed, watch } from '@vielzeug/ripple';\nimport { encodeQr, type QrErrorCorrection, SigilError, toSvg } from '@vielzeug/sigil';\nimport { roundableBundle } from '../../shared';\nimport { roundedVariantMixin } from '../../styles';\nimport type { ComponentSize, RoundedSize } from '../../types';\nimport componentStyles from './qr-code.css?inline';\n\n/** QR code component properties */\nexport type OreQrCodeProps = {\n /** Error-correction level */\n 'error-correction'?: QrErrorCorrection;\n /** Accessible label for the rendered code */\n label?: string;\n /** Quiet-zone width in modules */\n margin?: number;\n /** Border radius size */\n rounded?: RoundedSize;\n /** Rendered size: named scale or pixel number */\n size?: ComponentSize | number;\n /** The payload to encode */\n value?: string;\n /** Surface variant: 'flat' (transparent) or 'card' (themed background) */\n variant?: 'flat' | 'card';\n};\n\n/** QR code component events */\nexport type OreQrCodeEvents = {\n /** Emitted whenever the code is (re)rendered. detail: `{ version, size }` */\n render: { version: number; size: number };\n /** Emitted when encoding fails (e.g. payload exceeds capacity). detail: `{ error }` */\n error: { error: SigilError };\n};\n\nconst SIZES: Record<ComponentSize, number> = { lg: 256, md: 192, sm: 128 };\n\nconst cssSize = (size: ComponentSize | number): number => (typeof size === 'number' ? size : (SIZES[size] ?? SIZES.md));\n\n/**\n * Renders a QR code for `value` as inline SVG — themeable via `currentColor`,\n * crisp at any scale, no canvas. On capacity errors the component shows an\n * error state and emits `error` rather than throwing.\n *\n * @element ore-qr-code\n *\n * @attr {string} value - The payload to encode\n * @attr {string} error-correction - 'L' | 'M' | 'Q' | 'H' (default 'M')\n * @attr {number} margin - Quiet-zone modules (default 4)\n * @attr {string} size - 'sm' | 'md' | 'lg', or a pixel value\n * @attr {string} label - Accessible label (default \"QR code\")\n * @attr {string} rounded - Border radius for the card variant\n *\n * @fires render - detail: `{ version, size }` after each successful render\n * @fires error - detail: `{ error }` when encoding fails\n *\n * @slot caption - Optional caption rendered below the code\n *\n * @cssprop --qr-code-dark - Module color (default `currentColor`)\n * @cssprop --qr-code-light - Background color (default `transparent`)\n * @cssprop --qr-code-size - Rendered size in px (overrides `size`)\n * @cssprop --qr-code-radius - Border radius\n * @cssprop --qr-code-padding - Inner padding\n * @cssprop --qr-code-bg - Card background (variant=\"card\")\n * @cssprop --qr-code-border - Card border width (variant=\"card\")\n * @cssprop --qr-code-border-color - Card border color (variant=\"card\")\n * @cssprop --qr-code-shadow - Card box shadow (variant=\"card\")\n * @cssprop --qr-code-error-color - Error-state text color\n *\n * @part wrapper - Outer container\n * @part svg - The rendered `<svg>` element\n * @part caption - Caption slot container\n * @part error - Error-state container\n *\n * @example\n * ```html\n * <ore-qr-code value=\"https://example.com\"></ore-qr-code>\n * <ore-qr-code value=\"otpauth://…\" error-correction=\"H\" size=\"256\"></ore-qr-code>\n * ```\n */\nexport const QR_CODE_TAG = 'ore-qr-code' as const;\n\ndefine<OreQrCodeProps>(QR_CODE_TAG, {\n props: {\n 'error-correction': prop.string<QrErrorCorrection>('M'),\n label: prop.string('QR code'),\n margin: prop.number(4),\n ...roundableBundle,\n size: {\n default: 'md' as ComponentSize | number,\n parse: (v) => (v !== null && /^\\d+$/.test(v) ? Number(v) : ((v ?? 'md') as ComponentSize)),\n },\n value: prop.string(''),\n variant: prop.string<'flat' | 'card'>('flat'),\n },\n\n setup(props) {\n const emit = useEmit<OreQrCodeEvents>();\n const slots = useSlots();\n\n // Encode → SVG string in one computed; SigilError flips the error state.\n const encoded = computed<{ svg: string; version: number } | { error: SigilError }>(() => {\n const value = props.value.value ?? '';\n if (!value) return { error: new SigilError('No value to encode') };\n try {\n const matrix = encodeQr(value, { errorCorrection: props['error-correction'].value ?? 'M' });\n const svg = toSvg(matrix, {\n dark: 'var(--_dark)',\n label: props.label.value ?? 'QR code',\n light: 'var(--_light)',\n margin: props.margin.value ?? 4,\n });\n return { svg, version: matrix.version };\n } catch (error) {\n return { error: error instanceof SigilError ? error : new SigilError('QR encoding failed', { cause: error }) };\n }\n });\n\n const failed = computed(() => 'error' in encoded.value);\n const hasCaption = computed(() => slots.has('caption').value);\n\n // Emit once per encode outcome, not per reactive read.\n watch(encoded, (result) => {\n if ('error' in result) emit('error', { error: result.error });\n else emit('render', { size: cssSize(props.size.value ?? 'md'), version: result.version });\n });\n\n return html`\n <div class=\"wrapper\" part=\"wrapper\" style=${() => `--_size-px: ${cssSize(props.size.value ?? 'md')}px`}>\n ${when(\n () => failed.value,\n () => html`\n <div class=\"error\" part=\"error\" role=\"alert\">\n <span class=\"error-text\">${() => ('error' in encoded.value ? encoded.value.error.message : '')}</span>\n </div>\n `,\n () => html`\n <span class=\"svg\" part=\"svg\">${unsafeHtml(() => ('error' in encoded.value ? '' : encoded.value.svg))}</span>\n `,\n )}\n <span class=\"caption\" part=\"caption\" ?hidden=${() => !hasCaption.value}>\n <slot name=\"caption\"></slot>\n </span>\n </div>\n `;\n },\n styles: [roundedVariantMixin, componentStyles],\n});\n"],"mappings":"oSAkCA,IAAM,EAAuC,CAAE,GAAI,IAAK,GAAI,IAAK,GAAI,GAAI,EAEnE,QAAW,GAA0C,OAAO,GAAS,SAAW,EAAQ,EAAM,IAAS,EAAM,GA2CtG,EAAc,eAE3B,EAAA,EAAA,OAAA,CAAuB,EAAa,CAClC,MAAO,CACL,mBAAoB,EAAA,KAAK,OAA0B,GAAG,EACtD,MAAO,EAAA,KAAK,OAAO,SAAS,EAC5B,OAAQ,EAAA,KAAK,OAAO,CAAC,EACrB,GAAG,EAAA,gBACH,KAAM,CACJ,QAAS,KACT,MAAQ,GAAO,IAAM,MAAQ,QAAQ,KAAK,CAAC,EAAI,OAAO,CAAC,EAAM,GAAK,IACpE,EACA,MAAO,EAAA,KAAK,OAAO,EAAE,EACrB,QAAS,EAAA,KAAK,OAAwB,MAAM,CAC9C,EAEA,MAAM,EAAO,CACX,IAAM,GAAA,EAAO,EAAA,QAAA,CAAyB,EAChC,GAAA,EAAQ,EAAA,SAAA,CAAS,EAGjB,GAAA,EAAU,EAAA,SAAA,KAAyE,CACvF,IAAM,EAAQ,EAAM,MAAM,OAAS,GACnC,GAAI,CAAC,EAAO,MAAO,CAAE,MAAO,IAAI,EAAA,WAAW,oBAAoB,CAAE,EACjE,GAAI,CACF,IAAM,GAAA,EAAS,EAAA,SAAA,CAAS,EAAO,CAAE,gBAAiB,EAAM,mBAAmB,CAAC,OAAS,GAAI,CAAC,EAO1F,MAAO,CAAE,KAAA,EANG,EAAA,MAAA,CAAM,EAAQ,CACxB,KAAM,eACN,MAAO,EAAM,MAAM,OAAS,UAC5B,MAAO,gBACP,OAAQ,EAAM,OAAO,OAAS,CAChC,CACS,EAAK,QAAS,EAAO,OAAQ,CACxC,OAAS,EAAO,CACd,MAAO,CAAE,MAAO,aAAiB,EAAA,WAAa,EAAQ,IAAI,EAAA,WAAW,qBAAsB,CAAE,MAAO,CAAM,CAAC,CAAE,CAC/G,CACF,CAAC,EAEK,GAAA,EAAS,EAAA,SAAA,KAAe,UAAW,EAAQ,KAAK,EAChD,GAAA,EAAa,EAAA,SAAA,KAAe,EAAM,IAAI,SAAS,CAAC,CAAC,KAAK,EAQ5D,OALA,EAAA,EAAA,MAAA,CAAM,EAAU,GAAW,CACrB,UAAW,EAAQ,EAAK,QAAS,CAAE,MAAO,EAAO,KAAM,CAAC,EACvD,EAAK,SAAU,CAAE,KAAM,QAAQ,EAAM,KAAK,OAAS,IAAI,EAAG,QAAS,EAAO,OAAQ,CAAC,CAC1F,CAAC,EAEM,EAAA,IAAI;sDACyC,eAAe,QAAQ,EAAM,KAAK,OAAS,IAAI,EAAE,IAAI;WACnG,EAAA,EAAA,KAAA,KACM,EAAO,UACP,EAAA,IAAI;;6CAE4B,UAAW,EAAQ,MAAQ,EAAQ,MAAM,MAAM,QAAU,GAAI;;gBAG7F,EAAA,IAAI;4CACuB,EAAA,EAAA,WAAA,KAAkB,UAAW,EAAQ,MAAQ,GAAK,EAAQ,MAAM,GAAI,EAAE;WAEzG,EAAE;2DACmD,CAAC,EAAW,MAAM;;;;KAK7E,EACA,OAAQ,CAAC,EAAA,oBAAqB,EAAA,OAAe,CAC/C,CAAC"}
1
+ {"version":3,"file":"qr-code.cjs","names":[],"sources":["../src/content/qr-code/qr-code.ts"],"sourcesContent":["import { define, html, prop, unsafeHtml, useEmit, useSlots, when } from '@vielzeug/ore';\nimport { computed, watch } from '@vielzeug/ripple';\nimport { encodeQr, type QrErrorCorrection, SigilError, toSvg } from '@vielzeug/sigil';\nimport { roundableBundle } from '../../shared';\nimport { roundedVariantMixin } from '../../styles';\nimport type { ComponentSize, RoundedSize } from '../../types';\nimport componentStyles from './qr-code.css?inline';\n\n/** QR code component properties */\nexport type OreQrCodeProps = {\n /** Error-correction level */\n 'error-correction'?: QrErrorCorrection;\n /** Accessible label for the rendered code */\n label?: string;\n /** Quiet-zone width in modules */\n margin?: number;\n /** Border radius size */\n rounded?: RoundedSize;\n /** Rendered size: named scale or pixel number */\n size?: ComponentSize | number;\n /** The payload to encode */\n value?: string;\n /** Surface variant: 'flat' (transparent) or 'card' (themed background) */\n variant?: 'flat' | 'card';\n};\n\n/** QR code component events */\nexport type OreQrCodeEvents = {\n /** Emitted whenever the code is (re)rendered. detail: `{ version, size }` */\n render: { version: number; size: number };\n /** Emitted when encoding fails (e.g. payload exceeds capacity). detail: `{ error }` */\n error: { error: SigilError };\n};\n\nconst SIZES: Record<ComponentSize, number> = { lg: 256, md: 192, sm: 128 };\n\nconst cssSize = (size: ComponentSize | number): number => (typeof size === 'number' ? size : (SIZES[size] ?? SIZES.md));\n\n/**\n * Renders a QR code for `value` as inline SVG: themeable via `currentColor`,\n * crisp at any scale, no canvas. On capacity errors the component shows an\n * error state and emits `error` rather than throwing.\n *\n * @element ore-qr-code\n *\n * @attr {string} value - The payload to encode\n * @attr {string} error-correction - 'L' | 'M' | 'Q' | 'H' (default 'M')\n * @attr {number} margin - Quiet-zone modules (default 4)\n * @attr {string} size - 'sm' | 'md' | 'lg', or a pixel value\n * @attr {string} label - Accessible label (default \"QR code\")\n * @attr {string} rounded - Border radius for the card variant\n *\n * @fires render - detail: `{ version, size }` after each successful render\n * @fires error - detail: `{ error }` when encoding fails\n *\n * @slot caption - Optional caption rendered below the code\n *\n * @cssprop --qr-code-dark - Module color (default `currentColor`)\n * @cssprop --qr-code-light - Background color (default `transparent`)\n * @cssprop --qr-code-size - Rendered size in px (overrides `size`)\n * @cssprop --qr-code-radius - Border radius\n * @cssprop --qr-code-padding - Inner padding\n * @cssprop --qr-code-bg - Card background (variant=\"card\")\n * @cssprop --qr-code-border - Card border width (variant=\"card\")\n * @cssprop --qr-code-border-color - Card border color (variant=\"card\")\n * @cssprop --qr-code-shadow - Card box shadow (variant=\"card\")\n * @cssprop --qr-code-error-color - Error-state text color\n *\n * @part wrapper - Outer container\n * @part svg - The rendered `<svg>` element\n * @part caption - Caption slot container\n * @part error - Error-state container\n *\n * @example\n * ```html\n * <ore-qr-code value=\"https://example.com\"></ore-qr-code>\n * <ore-qr-code value=\"otpauth://…\" error-correction=\"H\" size=\"256\"></ore-qr-code>\n * ```\n */\nexport const QR_CODE_TAG = 'ore-qr-code' as const;\n\ndefine<OreQrCodeProps>(QR_CODE_TAG, {\n props: {\n 'error-correction': prop.string<QrErrorCorrection>('M'),\n label: prop.string('QR code'),\n margin: prop.number(4),\n ...roundableBundle,\n size: {\n default: 'md' as ComponentSize | number,\n parse: (v) => (v !== null && /^\\d+$/.test(v) ? Number(v) : ((v ?? 'md') as ComponentSize)),\n },\n value: prop.string(''),\n variant: prop.string<'flat' | 'card'>('flat'),\n },\n\n setup(props) {\n const emit = useEmit<OreQrCodeEvents>();\n const slots = useSlots();\n\n // Encode → SVG string in one computed; SigilError flips the error state.\n const encoded = computed<{ svg: string; version: number } | { error: SigilError }>(() => {\n const value = props.value.value ?? '';\n if (!value) return { error: new SigilError('No value to encode') };\n try {\n const matrix = encodeQr(value, { errorCorrection: props['error-correction'].value ?? 'M' });\n const svg = toSvg(matrix, {\n dark: 'var(--_dark)',\n label: props.label.value ?? 'QR code',\n light: 'var(--_light)',\n margin: props.margin.value ?? 4,\n });\n return { svg, version: matrix.version };\n } catch (error) {\n return { error: error instanceof SigilError ? error : new SigilError('QR encoding failed', { cause: error }) };\n }\n });\n\n const failed = computed(() => 'error' in encoded.value);\n const hasCaption = computed(() => slots.has('caption').value);\n\n // Emit once per encode outcome, not per reactive read.\n watch(encoded, (result) => {\n if ('error' in result) emit('error', { error: result.error });\n else emit('render', { size: cssSize(props.size.value ?? 'md'), version: result.version });\n });\n\n return html`\n <div class=\"wrapper\" part=\"wrapper\" style=${() => `--_size-px: ${cssSize(props.size.value ?? 'md')}px`}>\n ${when(\n () => failed.value,\n () => html`\n <div class=\"error\" part=\"error\" role=\"alert\">\n <span class=\"error-text\">${() => ('error' in encoded.value ? encoded.value.error.message : '')}</span>\n </div>\n `,\n () => html`\n <span class=\"svg\" part=\"svg\">${unsafeHtml(() => ('error' in encoded.value ? '' : encoded.value.svg))}</span>\n `,\n )}\n <span class=\"caption\" part=\"caption\" ?hidden=${() => !hasCaption.value}>\n <slot name=\"caption\"></slot>\n </span>\n </div>\n `;\n },\n styles: [roundedVariantMixin, componentStyles],\n});\n"],"mappings":"oSAkCA,IAAM,EAAuC,CAAE,GAAI,IAAK,GAAI,IAAK,GAAI,GAAI,EAEnE,QAAW,GAA0C,OAAO,GAAS,SAAW,EAAQ,EAAM,IAAS,EAAM,GA2CtG,EAAc,eAE3B,EAAA,EAAA,OAAA,CAAuB,EAAa,CAClC,MAAO,CACL,mBAAoB,EAAA,KAAK,OAA0B,GAAG,EACtD,MAAO,EAAA,KAAK,OAAO,SAAS,EAC5B,OAAQ,EAAA,KAAK,OAAO,CAAC,EACrB,GAAG,EAAA,gBACH,KAAM,CACJ,QAAS,KACT,MAAQ,GAAO,IAAM,MAAQ,QAAQ,KAAK,CAAC,EAAI,OAAO,CAAC,EAAM,GAAK,IACpE,EACA,MAAO,EAAA,KAAK,OAAO,EAAE,EACrB,QAAS,EAAA,KAAK,OAAwB,MAAM,CAC9C,EAEA,MAAM,EAAO,CACX,IAAM,GAAA,EAAO,EAAA,QAAA,CAAyB,EAChC,GAAA,EAAQ,EAAA,SAAA,CAAS,EAGjB,GAAA,EAAU,EAAA,SAAA,KAAyE,CACvF,IAAM,EAAQ,EAAM,MAAM,OAAS,GACnC,GAAI,CAAC,EAAO,MAAO,CAAE,MAAO,IAAI,EAAA,WAAW,oBAAoB,CAAE,EACjE,GAAI,CACF,IAAM,GAAA,EAAS,EAAA,SAAA,CAAS,EAAO,CAAE,gBAAiB,EAAM,mBAAmB,CAAC,OAAS,GAAI,CAAC,EAO1F,MAAO,CAAE,KAAA,EANG,EAAA,MAAA,CAAM,EAAQ,CACxB,KAAM,eACN,MAAO,EAAM,MAAM,OAAS,UAC5B,MAAO,gBACP,OAAQ,EAAM,OAAO,OAAS,CAChC,CACS,EAAK,QAAS,EAAO,OAAQ,CACxC,OAAS,EAAO,CACd,MAAO,CAAE,MAAO,aAAiB,EAAA,WAAa,EAAQ,IAAI,EAAA,WAAW,qBAAsB,CAAE,MAAO,CAAM,CAAC,CAAE,CAC/G,CACF,CAAC,EAEK,GAAA,EAAS,EAAA,SAAA,KAAe,UAAW,EAAQ,KAAK,EAChD,GAAA,EAAa,EAAA,SAAA,KAAe,EAAM,IAAI,SAAS,CAAC,CAAC,KAAK,EAQ5D,OALA,EAAA,EAAA,MAAA,CAAM,EAAU,GAAW,CACrB,UAAW,EAAQ,EAAK,QAAS,CAAE,MAAO,EAAO,KAAM,CAAC,EACvD,EAAK,SAAU,CAAE,KAAM,QAAQ,EAAM,KAAK,OAAS,IAAI,EAAG,QAAS,EAAO,OAAQ,CAAC,CAC1F,CAAC,EAEM,EAAA,IAAI;sDACyC,eAAe,QAAQ,EAAM,KAAK,OAAS,IAAI,EAAE,IAAI;WACnG,EAAA,EAAA,KAAA,KACM,EAAO,UACP,EAAA,IAAI;;6CAE4B,UAAW,EAAQ,MAAQ,EAAQ,MAAM,MAAM,QAAU,GAAI;;gBAG7F,EAAA,IAAI;4CACuB,EAAA,EAAA,WAAA,KAAkB,UAAW,EAAQ,MAAQ,GAAK,EAAQ,MAAM,GAAI,EAAE;WAEzG,EAAE;2DACmD,CAAC,EAAW,MAAM;;;;KAK7E,EACA,OAAQ,CAAC,EAAA,oBAAqB,EAAA,OAAe,CAC/C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"qr-code.js","names":[],"sources":["../src/content/qr-code/qr-code.ts"],"sourcesContent":["import { define, html, prop, unsafeHtml, useEmit, useSlots, when } from '@vielzeug/ore';\nimport { computed, watch } from '@vielzeug/ripple';\nimport { encodeQr, type QrErrorCorrection, SigilError, toSvg } from '@vielzeug/sigil';\nimport { roundableBundle } from '../../shared';\nimport { roundedVariantMixin } from '../../styles';\nimport type { ComponentSize, RoundedSize } from '../../types';\nimport componentStyles from './qr-code.css?inline';\n\n/** QR code component properties */\nexport type OreQrCodeProps = {\n /** Error-correction level */\n 'error-correction'?: QrErrorCorrection;\n /** Accessible label for the rendered code */\n label?: string;\n /** Quiet-zone width in modules */\n margin?: number;\n /** Border radius size */\n rounded?: RoundedSize;\n /** Rendered size: named scale or pixel number */\n size?: ComponentSize | number;\n /** The payload to encode */\n value?: string;\n /** Surface variant: 'flat' (transparent) or 'card' (themed background) */\n variant?: 'flat' | 'card';\n};\n\n/** QR code component events */\nexport type OreQrCodeEvents = {\n /** Emitted whenever the code is (re)rendered. detail: `{ version, size }` */\n render: { version: number; size: number };\n /** Emitted when encoding fails (e.g. payload exceeds capacity). detail: `{ error }` */\n error: { error: SigilError };\n};\n\nconst SIZES: Record<ComponentSize, number> = { lg: 256, md: 192, sm: 128 };\n\nconst cssSize = (size: ComponentSize | number): number => (typeof size === 'number' ? size : (SIZES[size] ?? SIZES.md));\n\n/**\n * Renders a QR code for `value` as inline SVG — themeable via `currentColor`,\n * crisp at any scale, no canvas. On capacity errors the component shows an\n * error state and emits `error` rather than throwing.\n *\n * @element ore-qr-code\n *\n * @attr {string} value - The payload to encode\n * @attr {string} error-correction - 'L' | 'M' | 'Q' | 'H' (default 'M')\n * @attr {number} margin - Quiet-zone modules (default 4)\n * @attr {string} size - 'sm' | 'md' | 'lg', or a pixel value\n * @attr {string} label - Accessible label (default \"QR code\")\n * @attr {string} rounded - Border radius for the card variant\n *\n * @fires render - detail: `{ version, size }` after each successful render\n * @fires error - detail: `{ error }` when encoding fails\n *\n * @slot caption - Optional caption rendered below the code\n *\n * @cssprop --qr-code-dark - Module color (default `currentColor`)\n * @cssprop --qr-code-light - Background color (default `transparent`)\n * @cssprop --qr-code-size - Rendered size in px (overrides `size`)\n * @cssprop --qr-code-radius - Border radius\n * @cssprop --qr-code-padding - Inner padding\n * @cssprop --qr-code-bg - Card background (variant=\"card\")\n * @cssprop --qr-code-border - Card border width (variant=\"card\")\n * @cssprop --qr-code-border-color - Card border color (variant=\"card\")\n * @cssprop --qr-code-shadow - Card box shadow (variant=\"card\")\n * @cssprop --qr-code-error-color - Error-state text color\n *\n * @part wrapper - Outer container\n * @part svg - The rendered `<svg>` element\n * @part caption - Caption slot container\n * @part error - Error-state container\n *\n * @example\n * ```html\n * <ore-qr-code value=\"https://example.com\"></ore-qr-code>\n * <ore-qr-code value=\"otpauth://…\" error-correction=\"H\" size=\"256\"></ore-qr-code>\n * ```\n */\nexport const QR_CODE_TAG = 'ore-qr-code' as const;\n\ndefine<OreQrCodeProps>(QR_CODE_TAG, {\n props: {\n 'error-correction': prop.string<QrErrorCorrection>('M'),\n label: prop.string('QR code'),\n margin: prop.number(4),\n ...roundableBundle,\n size: {\n default: 'md' as ComponentSize | number,\n parse: (v) => (v !== null && /^\\d+$/.test(v) ? Number(v) : ((v ?? 'md') as ComponentSize)),\n },\n value: prop.string(''),\n variant: prop.string<'flat' | 'card'>('flat'),\n },\n\n setup(props) {\n const emit = useEmit<OreQrCodeEvents>();\n const slots = useSlots();\n\n // Encode → SVG string in one computed; SigilError flips the error state.\n const encoded = computed<{ svg: string; version: number } | { error: SigilError }>(() => {\n const value = props.value.value ?? '';\n if (!value) return { error: new SigilError('No value to encode') };\n try {\n const matrix = encodeQr(value, { errorCorrection: props['error-correction'].value ?? 'M' });\n const svg = toSvg(matrix, {\n dark: 'var(--_dark)',\n label: props.label.value ?? 'QR code',\n light: 'var(--_light)',\n margin: props.margin.value ?? 4,\n });\n return { svg, version: matrix.version };\n } catch (error) {\n return { error: error instanceof SigilError ? error : new SigilError('QR encoding failed', { cause: error }) };\n }\n });\n\n const failed = computed(() => 'error' in encoded.value);\n const hasCaption = computed(() => slots.has('caption').value);\n\n // Emit once per encode outcome, not per reactive read.\n watch(encoded, (result) => {\n if ('error' in result) emit('error', { error: result.error });\n else emit('render', { size: cssSize(props.size.value ?? 'md'), version: result.version });\n });\n\n return html`\n <div class=\"wrapper\" part=\"wrapper\" style=${() => `--_size-px: ${cssSize(props.size.value ?? 'md')}px`}>\n ${when(\n () => failed.value,\n () => html`\n <div class=\"error\" part=\"error\" role=\"alert\">\n <span class=\"error-text\">${() => ('error' in encoded.value ? encoded.value.error.message : '')}</span>\n </div>\n `,\n () => html`\n <span class=\"svg\" part=\"svg\">${unsafeHtml(() => ('error' in encoded.value ? '' : encoded.value.svg))}</span>\n `,\n )}\n <span class=\"caption\" part=\"caption\" ?hidden=${() => !hasCaption.value}>\n <slot name=\"caption\"></slot>\n </span>\n </div>\n `;\n },\n styles: [roundedVariantMixin, componentStyles],\n});\n"],"mappings":"2ZAkCA,IAAM,EAAuC,CAAE,GAAI,IAAK,GAAI,IAAK,GAAI,GAAI,EAEnE,QAAW,GAA0C,OAAO,GAAS,SAAW,EAAQ,EAAM,IAAS,EAAM,GA2CtG,EAAc,cAE3B,EAAuB,EAAa,CAClC,MAAO,CACL,mBAAoB,EAAK,OAA0B,GAAG,EACtD,MAAO,EAAK,OAAO,SAAS,EAC5B,OAAQ,EAAK,OAAO,CAAC,EACrB,GAAG,EACH,KAAM,CACJ,QAAS,KACT,MAAQ,GAAO,IAAM,MAAQ,QAAQ,KAAK,CAAC,EAAI,OAAO,CAAC,EAAM,GAAK,IACpE,EACA,MAAO,EAAK,OAAO,EAAE,EACrB,QAAS,EAAK,OAAwB,MAAM,CAC9C,EAEA,MAAM,EAAO,CACX,IAAM,EAAO,EAAyB,EAChC,EAAQ,EAAS,EAGjB,EAAU,MAAyE,CACvF,IAAM,EAAQ,EAAM,MAAM,OAAS,GACnC,GAAI,CAAC,EAAO,MAAO,CAAE,MAAO,IAAI,EAAW,oBAAoB,CAAE,EACjE,GAAI,CACF,IAAM,EAAS,EAAS,EAAO,CAAE,gBAAiB,EAAM,mBAAmB,CAAC,OAAS,GAAI,CAAC,EAO1F,MAAO,CAAE,IANG,EAAM,EAAQ,CACxB,KAAM,eACN,MAAO,EAAM,MAAM,OAAS,UAC5B,MAAO,gBACP,OAAQ,EAAM,OAAO,OAAS,CAChC,CACS,EAAK,QAAS,EAAO,OAAQ,CACxC,OAAS,EAAO,CACd,MAAO,CAAE,MAAO,aAAiB,EAAa,EAAQ,IAAI,EAAW,qBAAsB,CAAE,MAAO,CAAM,CAAC,CAAE,CAC/G,CACF,CAAC,EAEK,EAAS,MAAe,UAAW,EAAQ,KAAK,EAChD,EAAa,MAAe,EAAM,IAAI,SAAS,CAAC,CAAC,KAAK,EAQ5D,OALA,EAAM,EAAU,GAAW,CACrB,UAAW,EAAQ,EAAK,QAAS,CAAE,MAAO,EAAO,KAAM,CAAC,EACvD,EAAK,SAAU,CAAE,KAAM,QAAQ,EAAM,KAAK,OAAS,IAAI,EAAG,QAAS,EAAO,OAAQ,CAAC,CAC1F,CAAC,EAEM,CAAI;sDACyC,eAAe,QAAQ,EAAM,KAAK,OAAS,IAAI,EAAE,IAAI;UACnG,MACM,EAAO,UACP,CAAI;;6CAE4B,UAAW,EAAQ,MAAQ,EAAQ,MAAM,MAAM,QAAU,GAAI;;gBAG7F,CAAI;2CACuB,MAAkB,UAAW,EAAQ,MAAQ,GAAK,EAAQ,MAAM,GAAI,EAAE;WAEzG,EAAE;2DACmD,CAAC,EAAW,MAAM;;;;KAK7E,EACA,OAAQ,CAAC,EAAqB,CAAe,CAC/C,CAAC"}
1
+ {"version":3,"file":"qr-code.js","names":[],"sources":["../src/content/qr-code/qr-code.ts"],"sourcesContent":["import { define, html, prop, unsafeHtml, useEmit, useSlots, when } from '@vielzeug/ore';\nimport { computed, watch } from '@vielzeug/ripple';\nimport { encodeQr, type QrErrorCorrection, SigilError, toSvg } from '@vielzeug/sigil';\nimport { roundableBundle } from '../../shared';\nimport { roundedVariantMixin } from '../../styles';\nimport type { ComponentSize, RoundedSize } from '../../types';\nimport componentStyles from './qr-code.css?inline';\n\n/** QR code component properties */\nexport type OreQrCodeProps = {\n /** Error-correction level */\n 'error-correction'?: QrErrorCorrection;\n /** Accessible label for the rendered code */\n label?: string;\n /** Quiet-zone width in modules */\n margin?: number;\n /** Border radius size */\n rounded?: RoundedSize;\n /** Rendered size: named scale or pixel number */\n size?: ComponentSize | number;\n /** The payload to encode */\n value?: string;\n /** Surface variant: 'flat' (transparent) or 'card' (themed background) */\n variant?: 'flat' | 'card';\n};\n\n/** QR code component events */\nexport type OreQrCodeEvents = {\n /** Emitted whenever the code is (re)rendered. detail: `{ version, size }` */\n render: { version: number; size: number };\n /** Emitted when encoding fails (e.g. payload exceeds capacity). detail: `{ error }` */\n error: { error: SigilError };\n};\n\nconst SIZES: Record<ComponentSize, number> = { lg: 256, md: 192, sm: 128 };\n\nconst cssSize = (size: ComponentSize | number): number => (typeof size === 'number' ? size : (SIZES[size] ?? SIZES.md));\n\n/**\n * Renders a QR code for `value` as inline SVG: themeable via `currentColor`,\n * crisp at any scale, no canvas. On capacity errors the component shows an\n * error state and emits `error` rather than throwing.\n *\n * @element ore-qr-code\n *\n * @attr {string} value - The payload to encode\n * @attr {string} error-correction - 'L' | 'M' | 'Q' | 'H' (default 'M')\n * @attr {number} margin - Quiet-zone modules (default 4)\n * @attr {string} size - 'sm' | 'md' | 'lg', or a pixel value\n * @attr {string} label - Accessible label (default \"QR code\")\n * @attr {string} rounded - Border radius for the card variant\n *\n * @fires render - detail: `{ version, size }` after each successful render\n * @fires error - detail: `{ error }` when encoding fails\n *\n * @slot caption - Optional caption rendered below the code\n *\n * @cssprop --qr-code-dark - Module color (default `currentColor`)\n * @cssprop --qr-code-light - Background color (default `transparent`)\n * @cssprop --qr-code-size - Rendered size in px (overrides `size`)\n * @cssprop --qr-code-radius - Border radius\n * @cssprop --qr-code-padding - Inner padding\n * @cssprop --qr-code-bg - Card background (variant=\"card\")\n * @cssprop --qr-code-border - Card border width (variant=\"card\")\n * @cssprop --qr-code-border-color - Card border color (variant=\"card\")\n * @cssprop --qr-code-shadow - Card box shadow (variant=\"card\")\n * @cssprop --qr-code-error-color - Error-state text color\n *\n * @part wrapper - Outer container\n * @part svg - The rendered `<svg>` element\n * @part caption - Caption slot container\n * @part error - Error-state container\n *\n * @example\n * ```html\n * <ore-qr-code value=\"https://example.com\"></ore-qr-code>\n * <ore-qr-code value=\"otpauth://…\" error-correction=\"H\" size=\"256\"></ore-qr-code>\n * ```\n */\nexport const QR_CODE_TAG = 'ore-qr-code' as const;\n\ndefine<OreQrCodeProps>(QR_CODE_TAG, {\n props: {\n 'error-correction': prop.string<QrErrorCorrection>('M'),\n label: prop.string('QR code'),\n margin: prop.number(4),\n ...roundableBundle,\n size: {\n default: 'md' as ComponentSize | number,\n parse: (v) => (v !== null && /^\\d+$/.test(v) ? Number(v) : ((v ?? 'md') as ComponentSize)),\n },\n value: prop.string(''),\n variant: prop.string<'flat' | 'card'>('flat'),\n },\n\n setup(props) {\n const emit = useEmit<OreQrCodeEvents>();\n const slots = useSlots();\n\n // Encode → SVG string in one computed; SigilError flips the error state.\n const encoded = computed<{ svg: string; version: number } | { error: SigilError }>(() => {\n const value = props.value.value ?? '';\n if (!value) return { error: new SigilError('No value to encode') };\n try {\n const matrix = encodeQr(value, { errorCorrection: props['error-correction'].value ?? 'M' });\n const svg = toSvg(matrix, {\n dark: 'var(--_dark)',\n label: props.label.value ?? 'QR code',\n light: 'var(--_light)',\n margin: props.margin.value ?? 4,\n });\n return { svg, version: matrix.version };\n } catch (error) {\n return { error: error instanceof SigilError ? error : new SigilError('QR encoding failed', { cause: error }) };\n }\n });\n\n const failed = computed(() => 'error' in encoded.value);\n const hasCaption = computed(() => slots.has('caption').value);\n\n // Emit once per encode outcome, not per reactive read.\n watch(encoded, (result) => {\n if ('error' in result) emit('error', { error: result.error });\n else emit('render', { size: cssSize(props.size.value ?? 'md'), version: result.version });\n });\n\n return html`\n <div class=\"wrapper\" part=\"wrapper\" style=${() => `--_size-px: ${cssSize(props.size.value ?? 'md')}px`}>\n ${when(\n () => failed.value,\n () => html`\n <div class=\"error\" part=\"error\" role=\"alert\">\n <span class=\"error-text\">${() => ('error' in encoded.value ? encoded.value.error.message : '')}</span>\n </div>\n `,\n () => html`\n <span class=\"svg\" part=\"svg\">${unsafeHtml(() => ('error' in encoded.value ? '' : encoded.value.svg))}</span>\n `,\n )}\n <span class=\"caption\" part=\"caption\" ?hidden=${() => !hasCaption.value}>\n <slot name=\"caption\"></slot>\n </span>\n </div>\n `;\n },\n styles: [roundedVariantMixin, componentStyles],\n});\n"],"mappings":"2ZAkCA,IAAM,EAAuC,CAAE,GAAI,IAAK,GAAI,IAAK,GAAI,GAAI,EAEnE,QAAW,GAA0C,OAAO,GAAS,SAAW,EAAQ,EAAM,IAAS,EAAM,GA2CtG,EAAc,cAE3B,EAAuB,EAAa,CAClC,MAAO,CACL,mBAAoB,EAAK,OAA0B,GAAG,EACtD,MAAO,EAAK,OAAO,SAAS,EAC5B,OAAQ,EAAK,OAAO,CAAC,EACrB,GAAG,EACH,KAAM,CACJ,QAAS,KACT,MAAQ,GAAO,IAAM,MAAQ,QAAQ,KAAK,CAAC,EAAI,OAAO,CAAC,EAAM,GAAK,IACpE,EACA,MAAO,EAAK,OAAO,EAAE,EACrB,QAAS,EAAK,OAAwB,MAAM,CAC9C,EAEA,MAAM,EAAO,CACX,IAAM,EAAO,EAAyB,EAChC,EAAQ,EAAS,EAGjB,EAAU,MAAyE,CACvF,IAAM,EAAQ,EAAM,MAAM,OAAS,GACnC,GAAI,CAAC,EAAO,MAAO,CAAE,MAAO,IAAI,EAAW,oBAAoB,CAAE,EACjE,GAAI,CACF,IAAM,EAAS,EAAS,EAAO,CAAE,gBAAiB,EAAM,mBAAmB,CAAC,OAAS,GAAI,CAAC,EAO1F,MAAO,CAAE,IANG,EAAM,EAAQ,CACxB,KAAM,eACN,MAAO,EAAM,MAAM,OAAS,UAC5B,MAAO,gBACP,OAAQ,EAAM,OAAO,OAAS,CAChC,CACS,EAAK,QAAS,EAAO,OAAQ,CACxC,OAAS,EAAO,CACd,MAAO,CAAE,MAAO,aAAiB,EAAa,EAAQ,IAAI,EAAW,qBAAsB,CAAE,MAAO,CAAM,CAAC,CAAE,CAC/G,CACF,CAAC,EAEK,EAAS,MAAe,UAAW,EAAQ,KAAK,EAChD,EAAa,MAAe,EAAM,IAAI,SAAS,CAAC,CAAC,KAAK,EAQ5D,OALA,EAAM,EAAU,GAAW,CACrB,UAAW,EAAQ,EAAK,QAAS,CAAE,MAAO,EAAO,KAAM,CAAC,EACvD,EAAK,SAAU,CAAE,KAAM,QAAQ,EAAM,KAAK,OAAS,IAAI,EAAG,QAAS,EAAO,OAAQ,CAAC,CAC1F,CAAC,EAEM,CAAI;sDACyC,eAAe,QAAQ,EAAM,KAAK,OAAS,IAAI,EAAE,IAAI;UACnG,MACM,EAAO,UACP,CAAI;;6CAE4B,UAAW,EAAQ,MAAQ,EAAQ,MAAM,MAAM,QAAU,GAAI;;gBAG7F,CAAI;2CACuB,MAAkB,UAAW,EAAQ,MAAQ,GAAK,EAAQ,MAAM,GAAI,EAAE;WAEzG,EAAE;2DACmD,CAAC,EAAW,MAAM;;;;KAK7E,EACA,OAAQ,CAAC,EAAqB,CAAe,CAC/C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"qr-scanner.cjs","names":[],"sources":["../src/inputs/qr-scanner/qr-scanner.ts"],"sourcesContent":["import { define, html, onElement, prop, ref, useEmit, watchEffect, when } from '@vielzeug/ore';\nimport { signal, watch } from '@vielzeug/ripple';\nimport {\n createQrScanner,\n isQrScanSupported,\n type QrScanner,\n type QrScannerOptions,\n type QrScanResult,\n SigilError,\n SigilPermissionError,\n SigilUnsupportedError,\n} from '@vielzeug/sigil';\nimport { roundableBundle } from '../../shared';\nimport { roundedVariantMixin } from '../../styles';\nimport type { ComponentSize, RoundedSize } from '../../types';\n\nimport componentStyles from './qr-scanner.css?inline';\n\nexport { ICON_TAG } from '../../content/icon/icon';\nexport { BUTTON_TAG } from '../button/button';\n/** Testing hook — inject a fake `createQrScanner` without touching globals. */\nexport type QrScannerFactory = (options: QrScannerOptions) => QrScanner;\n\n/** Component-level view states; sigil's transport states are mapped onto these. */\nexport type OreQrScannerStatus = 'idle' | 'starting' | 'scanning' | 'result' | 'unsupported' | 'denied' | 'error';\n\n/** QR scanner component properties */\nexport type OreQrScannerProps = {\n /** Start/stop the camera externally; mirrors into internal intent on change */\n active?: boolean;\n /** Preferred camera: 'environment' (rear) or 'user' (front) */\n 'facing-mode'?: 'environment' | 'user';\n /** Minimum ms between detection passes */\n interval?: number;\n /** Accessible label for the scanner region */\n label?: string;\n /** Stop after the first successful scan (default true) */\n once?: boolean;\n /** Border radius size */\n rounded?: RoundedSize;\n /** JS-only testing hook: custom scanner factory (defaults to `createQrScanner`) */\n scannerFactory?: QrScannerFactory;\n /** Preview size: named scale or pixel number */\n size?: ComponentSize | number;\n};\n\n/** QR scanner component events */\nexport type OreQrScannerEvents = {\n /** Emitted on every successful decode. detail: `{ value }` */\n scan: { value: string };\n /** Emitted on permission, support, or detection failure. detail: `{ error }` */\n error: { error: SigilError };\n /** Emitted on every view-state transition. detail: `{ status }` */\n status: { status: OreQrScannerStatus };\n};\n\nconst SIZES: Record<ComponentSize, number> = { lg: 360, md: 280, sm: 200 };\n\nconst cssSize = (size: ComponentSize | number): number => (typeof size === 'number' ? size : (SIZES[size] ?? SIZES.md));\n\nconst STATUS_TEXT: Record<OreQrScannerStatus, string> = {\n denied: 'Camera access denied. Allow camera permission and retry.',\n error: 'Scanning failed.',\n idle: 'Camera is off.',\n result: 'Code scanned.',\n scanning: 'Point the camera at a QR code.',\n starting: 'Starting camera…',\n unsupported: 'QR scanning is not supported in this browser.',\n};\n\n/**\n * Camera QR scanner built on `createQrScanner` (native `BarcodeDetector`).\n * `active` starts/stops the camera externally; internal Start/Stop controls\n * drive the same intent. Unsupported browsers render the `unsupported` slot\n * so apps can substitute a paste fallback.\n *\n * @element ore-qr-scanner\n *\n * @attr {boolean} active - Start/stop the camera\n * @attr {string} facing-mode - 'environment' | 'user' (default 'environment')\n * @attr {number} interval - Ms between detection passes (default 200)\n * @attr {boolean} once - Stop after first scan (default true)\n * @attr {string} size - 'sm' | 'md' | 'lg', or a pixel value\n * @attr {string} label - Region label (default \"QR code scanner\")\n * @attr {string} rounded - Border radius\n *\n * @fires scan - detail: `{ value }` on each decode\n * @fires error - detail: `{ error }` on permission/support/detection failure\n * @fires status - detail: `{ status }` on view-state transitions\n *\n * @slot overlay - Content layered over the camera preview\n * @slot unsupported - Fallback UI when scanning is unsupported (e.g. paste input)\n * @slot footer - Extra content below the status row\n *\n * @cssprop --qr-scanner-size - Preview edge length in px (overrides `size`)\n * @cssprop --qr-scanner-radius - Border radius\n * @cssprop --qr-scanner-bg - Preview background while idle\n * @cssprop --qr-scanner-viewfinder-color - Viewfinder frame color\n * @cssprop --qr-scanner-status-color - Status text color\n *\n * @part wrapper - Outer container\n * @part stage - Square preview area (video + overlays)\n * @part video - The `<video>` preview element\n * @part viewfinder - Decorative scan frame over the preview\n * @part state - Idle/error/denied/unsupported/result panel\n * @part status - Live status line\n * @part controls - Button row\n *\n * @example\n * ```html\n * <ore-qr-scanner active @scan=${(e) => pair(e.detail.value)}></ore-qr-scanner>\n * ```\n */\nexport const QR_SCANNER_TAG = 'ore-qr-scanner' as const;\n\ndefine<OreQrScannerProps>(QR_SCANNER_TAG, {\n props: {\n active: prop.bool(false),\n 'facing-mode': prop.oneOf(['environment', 'user'] as const, 'environment'),\n interval: prop.number(200),\n label: prop.string('QR code scanner'),\n once: prop.bool(true),\n ...roundableBundle,\n scannerFactory: prop.data<QrScannerFactory>(),\n size: {\n default: 'md' as ComponentSize | number,\n parse: (v) => (v !== null && /^\\d+$/.test(v) ? Number(v) : ((v ?? 'md') as ComponentSize)),\n },\n },\n\n setup(props) {\n const emit = useEmit<OreQrScannerEvents>();\n const videoRef = ref<HTMLVideoElement>();\n\n const status = signal<OreQrScannerStatus>('idle');\n const failure = signal('');\n const lastValue = signal('');\n\n // `active` is an imperative hint mirrored into internal intent; the\n // internal Start/Stop buttons drive the same signal.\n const requested = signal(false);\n watch(\n props.active,\n (active) => {\n requested.value = active === true;\n },\n { immediate: true },\n );\n\n const setStatus = (next: OreQrScannerStatus, message = ''): void => {\n failure.value = message;\n if (status.value === next) return;\n status.value = next;\n emit('status', { status: next });\n };\n\n const statusText = (): string =>\n status.value === 'result' && lastValue.value ? `Code scanned: ${lastValue.value}` : STATUS_TEXT[status.value];\n\n // Eager support check so VitePress/jsdom (no BarcodeDetector) land on the\n // unsupported state immediately; an injected factory skips the gate.\n if (!props.scannerFactory.value && !isQrScanSupported()) {\n setStatus('unsupported', STATUS_TEXT.unsupported);\n }\n\n const startScanner = async (scanner: QrScanner): Promise<void> => {\n try {\n await scanner.start();\n // Intent may have been withdrawn while getUserMedia was pending.\n if (!requested.value) scanner.stop();\n } catch (error) {\n requested.value = false;\n const sigilError =\n error instanceof SigilError ? error : new SigilError('QR scanner failed to start', { cause: error });\n if (sigilError instanceof SigilUnsupportedError) setStatus('unsupported', sigilError.message);\n else if (sigilError instanceof SigilPermissionError) setStatus('denied', sigilError.message);\n else setStatus('error', sigilError.message);\n emit('error', { error: sigilError });\n }\n };\n\n const wire = (scanner: QrScanner): void => {\n scanner.onResult((result: QrScanResult) => {\n lastValue.value = result.value;\n emit('scan', { value: result.value });\n if (props.once.value) setStatus('result');\n });\n scanner.tap((event) => {\n if (event.type === 'status-change') {\n if (event.status === 'starting') setStatus('starting');\n else if (event.status === 'scanning') setStatus('scanning');\n // 'idle' after a once-result must not clobber the result view.\n else if (event.status === 'idle' && status.value !== 'result') setStatus('idle');\n } else if (event.type === 'error') {\n emit('error', { error: event.error });\n if (status.value === 'scanning' || status.value === 'starting') setStatus('error', event.error.message);\n }\n });\n };\n\n onElement(videoRef, (video) => {\n const current = signal<QrScanner | null>(null);\n\n // Rebuild when scan-affecting props change; cleanup disposes the previous scanner.\n watchEffect(() => {\n const factory = props.scannerFactory.value ?? createQrScanner;\n const scanner = factory({\n constraints: { facingMode: props['facing-mode'].value ?? 'environment' },\n intervalMs: props.interval.value ?? 200,\n once: props.once.value ?? true,\n video,\n });\n current.value = scanner;\n wire(scanner);\n return () => scanner.dispose();\n });\n\n // Drive start/stop from intent + the current scanner instance. `watch`\n // (untracked callback) is required over `watchEffect`: `scanner.stop()`\n // synchronously re-enters the tap handler, and tracking its `status`\n // reads would retrigger this effect on every transition — an infinite loop.\n watch(\n () => [requested.value, current.value] as const,\n ([want, scanner]) => {\n if (!scanner || scanner.disposed) return;\n if (want) void startScanner(scanner);\n else scanner.stop();\n },\n { immediate: true },\n );\n });\n\n const resume = (): void => {\n requested.value = true;\n };\n const pause = (): void => {\n requested.value = false;\n };\n\n const showVideo = (): boolean => status.value === 'starting' || status.value === 'scanning';\n const showControls = (): boolean => status.value !== 'unsupported';\n\n return html`\n <div\n class=\"wrapper\"\n part=\"wrapper\"\n role=\"region\"\n aria-label=${() => props.label.value}\n style=${() => `--_size-px: ${cssSize(props.size.value ?? 'md')}px`}>\n <div class=\"stage\" part=\"stage\">\n <video\n ref=${videoRef}\n part=\"video\"\n autoplay\n playsinline\n muted\n aria-hidden=\"true\"\n ?hidden=${() => !showVideo()}></video>\n <div class=\"viewfinder\" part=\"viewfinder\" aria-hidden=\"true\" ?hidden=${() => status.value !== 'scanning'}></div>\n <div class=\"overlay\"><slot name=\"overlay\"></slot></div>\n ${when(\n () => !showVideo(),\n () => html`\n <div class=\"state\" part=\"state\" data-state=${() => status.value}>\n ${when(\n () => status.value === 'unsupported',\n () => html`<slot name=\"unsupported\"></slot>`,\n () => html`\n ${when(\n () => status.value === 'result',\n () => html`<ore-icon name=\"check\" size=\"24\" aria-hidden=\"true\"></ore-icon>`,\n () => html`<ore-icon name=\"camera\" size=\"24\" aria-hidden=\"true\"></ore-icon>`,\n )}\n <span class=\"state-text\">${() => failure.value || STATUS_TEXT[status.value]}</span>\n `,\n )}\n </div>\n `,\n )}\n </div>\n <div class=\"status\" part=\"status\" role=\"status\">${statusText}</div>\n ${when(\n showControls,\n () => html`\n <div class=\"controls\" part=\"controls\">\n ${when(\n () => status.value === 'starting' || status.value === 'scanning',\n () => html`\n <ore-button variant=\"ghost\" size=\"sm\" @click=${pause}>Stop</ore-button>\n `,\n () => html`\n <ore-button\n variant=\"ghost\"\n size=\"sm\"\n @click=${resume}>\n ${() =>\n status.value === 'result'\n ? 'Scan again'\n : status.value === 'denied' || status.value === 'error'\n ? 'Retry'\n : 'Start camera'}\n </ore-button>\n `,\n )}\n </div>\n `,\n )}\n <slot name=\"footer\"></slot>\n </div>\n `;\n },\n styles: [roundedVariantMixin, componentStyles],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"qr-scanner.cjs","names":[],"sources":["../src/inputs/qr-scanner/qr-scanner.ts"],"sourcesContent":["import { define, html, onElement, prop, ref, useEmit, watchEffect, when } from '@vielzeug/ore';\nimport { signal, watch } from '@vielzeug/ripple';\nimport {\n createQrScanner,\n isQrScanSupported,\n type QrScanner,\n type QrScannerOptions,\n type QrScanResult,\n SigilError,\n SigilPermissionError,\n SigilUnsupportedError,\n} from '@vielzeug/sigil';\nimport { roundableBundle } from '../../shared';\nimport { roundedVariantMixin } from '../../styles';\nimport type { ComponentSize, RoundedSize } from '../../types';\n\nimport componentStyles from './qr-scanner.css?inline';\n\nexport { ICON_TAG } from '../../content/icon/icon';\nexport { BUTTON_TAG } from '../button/button';\n/** Testing hook: inject a fake `createQrScanner` without touching globals. */\nexport type QrScannerFactory = (options: QrScannerOptions) => QrScanner;\n\n/** Component-level view states; sigil's transport states are mapped onto these. */\nexport type OreQrScannerStatus = 'idle' | 'starting' | 'scanning' | 'result' | 'unsupported' | 'denied' | 'error';\n\n/** QR scanner component properties */\nexport type OreQrScannerProps = {\n /** Start/stop the camera externally; mirrors into internal intent on change */\n active?: boolean;\n /** Preferred camera: 'environment' (rear) or 'user' (front) */\n 'facing-mode'?: 'environment' | 'user';\n /** Minimum ms between detection passes */\n interval?: number;\n /** Accessible label for the scanner region */\n label?: string;\n /** Stop after the first successful scan (default true) */\n once?: boolean;\n /** Border radius size */\n rounded?: RoundedSize;\n /** JS-only testing hook: custom scanner factory (defaults to `createQrScanner`) */\n scannerFactory?: QrScannerFactory;\n /** Preview size: named scale or pixel number */\n size?: ComponentSize | number;\n};\n\n/** QR scanner component events */\nexport type OreQrScannerEvents = {\n /** Emitted on every successful decode. detail: `{ value }` */\n scan: { value: string };\n /** Emitted on permission, support, or detection failure. detail: `{ error }` */\n error: { error: SigilError };\n /** Emitted on every view-state transition. detail: `{ status }` */\n status: { status: OreQrScannerStatus };\n};\n\nconst SIZES: Record<ComponentSize, number> = { lg: 360, md: 280, sm: 200 };\n\nconst cssSize = (size: ComponentSize | number): number => (typeof size === 'number' ? size : (SIZES[size] ?? SIZES.md));\n\nconst STATUS_TEXT: Record<OreQrScannerStatus, string> = {\n denied: 'Camera access denied. Allow camera permission and retry.',\n error: 'Scanning failed.',\n idle: 'Camera is off.',\n result: 'Code scanned.',\n scanning: 'Point the camera at a QR code.',\n starting: 'Starting camera…',\n unsupported: 'QR scanning is not supported in this browser.',\n};\n\n/**\n * Camera QR scanner built on `createQrScanner` (native `BarcodeDetector`).\n * `active` starts/stops the camera externally; internal Start/Stop controls\n * drive the same intent. Unsupported browsers render the `unsupported` slot\n * so apps can substitute a paste fallback.\n *\n * @element ore-qr-scanner\n *\n * @attr {boolean} active - Start/stop the camera\n * @attr {string} facing-mode - 'environment' | 'user' (default 'environment')\n * @attr {number} interval - Ms between detection passes (default 200)\n * @attr {boolean} once - Stop after first scan (default true)\n * @attr {string} size - 'sm' | 'md' | 'lg', or a pixel value\n * @attr {string} label - Region label (default \"QR code scanner\")\n * @attr {string} rounded - Border radius\n *\n * @fires scan - detail: `{ value }` on each decode\n * @fires error - detail: `{ error }` on permission/support/detection failure\n * @fires status - detail: `{ status }` on view-state transitions\n *\n * @slot overlay - Content layered over the camera preview\n * @slot unsupported - Fallback UI when scanning is unsupported (e.g. paste input)\n * @slot footer - Extra content below the status row\n *\n * @cssprop --qr-scanner-size - Preview edge length in px (overrides `size`)\n * @cssprop --qr-scanner-radius - Border radius\n * @cssprop --qr-scanner-bg - Preview background while idle\n * @cssprop --qr-scanner-viewfinder-color - Viewfinder frame color\n * @cssprop --qr-scanner-status-color - Status text color\n *\n * @part wrapper - Outer container\n * @part stage - Square preview area (video + overlays)\n * @part video - The `<video>` preview element\n * @part viewfinder - Decorative scan frame over the preview\n * @part state - Idle/error/denied/unsupported/result panel\n * @part status - Live status line\n * @part controls - Button row\n *\n * @example\n * ```html\n * <ore-qr-scanner active @scan=${(e) => pair(e.detail.value)}></ore-qr-scanner>\n * ```\n */\nexport const QR_SCANNER_TAG = 'ore-qr-scanner' as const;\n\ndefine<OreQrScannerProps>(QR_SCANNER_TAG, {\n props: {\n active: prop.bool(false),\n 'facing-mode': prop.oneOf(['environment', 'user'] as const, 'environment'),\n interval: prop.number(200),\n label: prop.string('QR code scanner'),\n once: prop.bool(true),\n ...roundableBundle,\n scannerFactory: prop.data<QrScannerFactory>(),\n size: {\n default: 'md' as ComponentSize | number,\n parse: (v) => (v !== null && /^\\d+$/.test(v) ? Number(v) : ((v ?? 'md') as ComponentSize)),\n },\n },\n\n setup(props) {\n const emit = useEmit<OreQrScannerEvents>();\n const videoRef = ref<HTMLVideoElement>();\n\n const status = signal<OreQrScannerStatus>('idle');\n const failure = signal('');\n const lastValue = signal('');\n\n // `active` is an imperative hint mirrored into internal intent; the\n // internal Start/Stop buttons drive the same signal.\n const requested = signal(false);\n watch(\n props.active,\n (active) => {\n requested.value = active === true;\n },\n { immediate: true },\n );\n\n const setStatus = (next: OreQrScannerStatus, message = ''): void => {\n failure.value = message;\n if (status.value === next) return;\n status.value = next;\n emit('status', { status: next });\n };\n\n const statusText = (): string =>\n status.value === 'result' && lastValue.value ? `Code scanned: ${lastValue.value}` : STATUS_TEXT[status.value];\n\n // Eager support check so VitePress/jsdom (no BarcodeDetector) land on the\n // unsupported state immediately; an injected factory skips the gate.\n if (!props.scannerFactory.value && !isQrScanSupported()) {\n setStatus('unsupported', STATUS_TEXT.unsupported);\n }\n\n const startScanner = async (scanner: QrScanner): Promise<void> => {\n try {\n await scanner.start();\n // Intent may have been withdrawn while getUserMedia was pending.\n if (!requested.value) scanner.stop();\n } catch (error) {\n requested.value = false;\n const sigilError =\n error instanceof SigilError ? error : new SigilError('QR scanner failed to start', { cause: error });\n if (sigilError instanceof SigilUnsupportedError) setStatus('unsupported', sigilError.message);\n else if (sigilError instanceof SigilPermissionError) setStatus('denied', sigilError.message);\n else setStatus('error', sigilError.message);\n emit('error', { error: sigilError });\n }\n };\n\n const wire = (scanner: QrScanner): void => {\n scanner.onResult((result: QrScanResult) => {\n lastValue.value = result.value;\n emit('scan', { value: result.value });\n if (props.once.value) setStatus('result');\n });\n scanner.tap((event) => {\n if (event.type === 'status-change') {\n if (event.status === 'starting') setStatus('starting');\n else if (event.status === 'scanning') setStatus('scanning');\n // 'idle' after a once-result must not clobber the result view.\n else if (event.status === 'idle' && status.value !== 'result') setStatus('idle');\n } else if (event.type === 'error') {\n emit('error', { error: event.error });\n if (status.value === 'scanning' || status.value === 'starting') setStatus('error', event.error.message);\n }\n });\n };\n\n onElement(videoRef, (video) => {\n const current = signal<QrScanner | null>(null);\n\n // Rebuild when scan-affecting props change; cleanup disposes the previous scanner.\n watchEffect(() => {\n const factory = props.scannerFactory.value ?? createQrScanner;\n const scanner = factory({\n constraints: { facingMode: props['facing-mode'].value ?? 'environment' },\n intervalMs: props.interval.value ?? 200,\n once: props.once.value ?? true,\n video,\n });\n current.value = scanner;\n wire(scanner);\n return () => scanner.dispose();\n });\n\n // Drive start/stop from intent + the current scanner instance. `watch`\n // (untracked callback) is required over `watchEffect`: `scanner.stop()`\n // synchronously re-enters the tap handler, and tracking its `status`\n // reads would retrigger this effect on every transition: an infinite loop.\n watch(\n () => [requested.value, current.value] as const,\n ([want, scanner]) => {\n if (!scanner || scanner.disposed) return;\n if (want) void startScanner(scanner);\n else scanner.stop();\n },\n { immediate: true },\n );\n });\n\n const resume = (): void => {\n requested.value = true;\n };\n const pause = (): void => {\n requested.value = false;\n };\n\n const showVideo = (): boolean => status.value === 'starting' || status.value === 'scanning';\n const showControls = (): boolean => status.value !== 'unsupported';\n\n return html`\n <div\n class=\"wrapper\"\n part=\"wrapper\"\n role=\"region\"\n aria-label=${() => props.label.value}\n style=${() => `--_size-px: ${cssSize(props.size.value ?? 'md')}px`}>\n <div class=\"stage\" part=\"stage\">\n <video\n ref=${videoRef}\n part=\"video\"\n autoplay\n playsinline\n muted\n aria-hidden=\"true\"\n ?hidden=${() => !showVideo()}></video>\n <div class=\"viewfinder\" part=\"viewfinder\" aria-hidden=\"true\" ?hidden=${() => status.value !== 'scanning'}></div>\n <div class=\"overlay\"><slot name=\"overlay\"></slot></div>\n ${when(\n () => !showVideo(),\n () => html`\n <div class=\"state\" part=\"state\" data-state=${() => status.value}>\n ${when(\n () => status.value === 'unsupported',\n () => html`<slot name=\"unsupported\"></slot>`,\n () => html`\n ${when(\n () => status.value === 'result',\n () => html`<ore-icon name=\"check\" size=\"24\" aria-hidden=\"true\"></ore-icon>`,\n () => html`<ore-icon name=\"camera\" size=\"24\" aria-hidden=\"true\"></ore-icon>`,\n )}\n <span class=\"state-text\">${() => failure.value || STATUS_TEXT[status.value]}</span>\n `,\n )}\n </div>\n `,\n )}\n </div>\n <div class=\"status\" part=\"status\" role=\"status\">${statusText}</div>\n ${when(\n showControls,\n () => html`\n <div class=\"controls\" part=\"controls\">\n ${when(\n () => status.value === 'starting' || status.value === 'scanning',\n () => html`\n <ore-button variant=\"ghost\" size=\"sm\" @click=${pause}>Stop</ore-button>\n `,\n () => html`\n <ore-button\n variant=\"ghost\"\n size=\"sm\"\n @click=${resume}>\n ${() =>\n status.value === 'result'\n ? 'Scan again'\n : status.value === 'denied' || status.value === 'error'\n ? 'Retry'\n : 'Start camera'}\n </ore-button>\n `,\n )}\n </div>\n `,\n )}\n <slot name=\"footer\"></slot>\n </div>\n `;\n },\n styles: [roundedVariantMixin, componentStyles],\n});\n"],"mappings":"2VAwDA,IAAM,EAAuC,CAAE,GAAI,IAAK,GAAI,IAAK,GAAI,GAAI,EAEnE,QAAW,GAA0C,OAAO,GAAS,SAAW,EAAQ,EAAM,IAAS,EAAM,GAE7G,EAAkD,CACtD,OAAQ,2DACR,MAAO,mBACP,KAAM,iBACN,OAAQ,gBACR,SAAU,iCACV,SAAU,mBACV,YAAa,+CACf,EA6Ca,EAAiB,kBAE9B,EAAA,EAAA,OAAA,CAA0B,EAAgB,CACxC,MAAO,CACL,OAAQ,EAAA,KAAK,KAAK,EAAK,EACvB,cAAe,EAAA,KAAK,MAAM,CAAC,cAAe,MAAM,EAAY,aAAa,EACzE,SAAU,EAAA,KAAK,OAAO,GAAG,EACzB,MAAO,EAAA,KAAK,OAAO,iBAAiB,EACpC,KAAM,EAAA,KAAK,KAAK,EAAI,EACpB,GAAG,EAAA,gBACH,eAAgB,EAAA,KAAK,KAAuB,EAC5C,KAAM,CACJ,QAAS,KACT,MAAQ,GAAO,IAAM,MAAQ,QAAQ,KAAK,CAAC,EAAI,OAAO,CAAC,EAAM,GAAK,IACpE,CACF,EAEA,MAAM,EAAO,CACX,IAAM,GAAA,EAAO,EAAA,QAAA,CAA4B,EACnC,GAAA,EAAW,EAAA,IAAA,CAAsB,EAEjC,GAAA,EAAS,EAAA,OAAA,CAA2B,MAAM,EAC1C,GAAA,EAAU,EAAA,OAAA,CAAO,EAAE,EACnB,GAAA,EAAY,EAAA,OAAA,CAAO,EAAE,EAIrB,GAAA,EAAY,EAAA,OAAA,CAAO,EAAK,GAC9B,EAAA,EAAA,MAAA,CACE,EAAM,OACL,GAAW,CACV,EAAU,MAAQ,IAAW,EAC/B,EACA,CAAE,UAAW,EAAK,CACpB,EAEA,IAAM,WAAa,EAA0B,EAAU,KAAa,CAClE,EAAQ,MAAQ,EACZ,EAAO,QAAU,IACrB,EAAO,MAAQ,EACf,EAAK,SAAU,CAAE,OAAQ,CAAK,CAAC,EACjC,EAEM,eACJ,EAAO,QAAU,UAAY,EAAU,MAAQ,iBAAiB,EAAU,QAAU,EAAY,EAAO,OAIrG,CAAC,EAAM,eAAe,OAAS,EAAA,EAAC,EAAA,kBAAA,CAAkB,GACpD,UAAU,cAAe,EAAY,WAAW,EAGlD,IAAM,aAAe,KAAO,IAAsC,CAChE,GAAI,CACF,MAAM,EAAQ,MAAM,EAEf,EAAU,OAAO,EAAQ,KAAK,CACrC,OAAS,EAAO,CACd,EAAU,MAAQ,GAClB,IAAM,EACJ,aAAiB,EAAA,WAAa,EAAQ,IAAI,EAAA,WAAW,6BAA8B,CAAE,MAAO,CAAM,CAAC,EACjG,aAAsB,EAAA,sBAAuB,UAAU,cAAe,EAAW,OAAO,EACnF,aAAsB,EAAA,qBAAsB,UAAU,SAAU,EAAW,OAAO,EACtF,UAAU,QAAS,EAAW,OAAO,EAC1C,EAAK,QAAS,CAAE,MAAO,CAAW,CAAC,CACrC,CACF,EAEM,KAAQ,GAA6B,CACzC,EAAQ,SAAU,GAAyB,CACzC,EAAU,MAAQ,EAAO,MACzB,EAAK,OAAQ,CAAE,MAAO,EAAO,KAAM,CAAC,EAChC,EAAM,KAAK,OAAO,UAAU,QAAQ,CAC1C,CAAC,EACD,EAAQ,IAAK,GAAU,CACjB,EAAM,OAAS,gBACb,EAAM,SAAW,WAAY,UAAU,UAAU,EAC5C,EAAM,SAAW,WAAY,UAAU,UAAU,EAEjD,EAAM,SAAW,QAAU,EAAO,QAAU,UAAU,UAAU,MAAM,EACtE,EAAM,OAAS,UACxB,EAAK,QAAS,CAAE,MAAO,EAAM,KAAM,CAAC,GAChC,EAAO,QAAU,YAAc,EAAO,QAAU,aAAY,UAAU,QAAS,EAAM,MAAM,OAAO,EAE1G,CAAC,CACH,GAEA,EAAA,EAAA,UAAA,CAAU,EAAW,GAAU,CAC7B,IAAM,GAAA,EAAU,EAAA,OAAA,CAAyB,IAAI,GAG7C,EAAA,EAAA,YAAA,KAAkB,CAEhB,IAAM,GADU,EAAM,eAAe,OAAS,EAAA,gBAAA,CACtB,CACtB,YAAa,CAAE,WAAY,EAAM,cAAc,CAAC,OAAS,aAAc,EACvE,WAAY,EAAM,SAAS,OAAS,IACpC,KAAM,EAAM,KAAK,OAAS,GAC1B,OACF,CAAC,EAGD,MAFA,GAAQ,MAAQ,EAChB,KAAK,CAAO,MACC,EAAQ,QAAQ,CAC/B,CAAC,GAMD,EAAA,EAAA,MAAA,KACQ,CAAC,EAAU,MAAO,EAAQ,KAAK,GACpC,CAAC,EAAM,KAAa,CACd,GAAW,GAAQ,WACpB,EAAM,aAAkB,CAAO,EAC9B,EAAQ,KAAK,EACpB,EACA,CAAE,UAAW,EAAK,CACpB,CACF,CAAC,EAED,IAAM,WAAqB,CACzB,EAAU,MAAQ,EACpB,EACM,UAAoB,CACxB,EAAU,MAAQ,EACpB,EAEM,cAA2B,EAAO,QAAU,YAAc,EAAO,QAAU,WAC3E,iBAA8B,EAAO,QAAU,cAErD,MAAO,GAAA,IAAI;;;;;yBAKY,EAAM,MAAM,MAAM;oBACvB,eAAe,QAAQ,EAAM,KAAK,OAAS,IAAI,EAAE,IAAI;;;kBAGzD,EAAS;;;;;;0BAMC,CAAC,UAAU,EAAE;qFAC8C,EAAO,QAAU,WAAW;;aAEvG,EAAA,EAAA,KAAA,KACM,CAAC,UAAU,MACX,EAAA,IAAI;+DAC2C,EAAO,MAAM;mBAC5D,EAAA,EAAA,KAAA,KACM,EAAO,QAAU,kBACjB,EAAA,IAAI,uCACJ,EAAA,IAAI;uBACN,EAAA,EAAA,KAAA,KACM,EAAO,QAAU,aACjB,EAAA,IAAI,sEACJ,EAAA,IAAI,kEACZ,EAAE;mDAC+B,EAAQ,OAAS,EAAY,EAAO,OAAO;mBAEhF,EAAE;;aAGR,EAAE;;0DAE8C,WAAW;WAC3D,EAAA,EAAA,KAAA,CACA,iBACM,EAAA,IAAI;;iBAEJ,EAAA,EAAA,KAAA,KACM,EAAO,QAAU,YAAc,EAAO,QAAU,eAChD,EAAA,IAAI;iEACuC,MAAM;sBAEjD,EAAA,IAAI;;;;6BAIG,OAAO;0BAEd,EAAO,QAAU,SACb,aACA,EAAO,QAAU,UAAY,EAAO,QAAU,QAC5C,QACA,eAAe;;iBAG7B,EAAE;;WAGR,EAAE;;;KAIR,EACA,OAAQ,CAAC,EAAA,oBAAqB,EAAA,OAAe,CAC/C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"qr-scanner.js","names":[],"sources":["../src/inputs/qr-scanner/qr-scanner.ts"],"sourcesContent":["import { define, html, onElement, prop, ref, useEmit, watchEffect, when } from '@vielzeug/ore';\nimport { signal, watch } from '@vielzeug/ripple';\nimport {\n createQrScanner,\n isQrScanSupported,\n type QrScanner,\n type QrScannerOptions,\n type QrScanResult,\n SigilError,\n SigilPermissionError,\n SigilUnsupportedError,\n} from '@vielzeug/sigil';\nimport { roundableBundle } from '../../shared';\nimport { roundedVariantMixin } from '../../styles';\nimport type { ComponentSize, RoundedSize } from '../../types';\n\nimport componentStyles from './qr-scanner.css?inline';\n\nexport { ICON_TAG } from '../../content/icon/icon';\nexport { BUTTON_TAG } from '../button/button';\n/** Testing hook — inject a fake `createQrScanner` without touching globals. */\nexport type QrScannerFactory = (options: QrScannerOptions) => QrScanner;\n\n/** Component-level view states; sigil's transport states are mapped onto these. */\nexport type OreQrScannerStatus = 'idle' | 'starting' | 'scanning' | 'result' | 'unsupported' | 'denied' | 'error';\n\n/** QR scanner component properties */\nexport type OreQrScannerProps = {\n /** Start/stop the camera externally; mirrors into internal intent on change */\n active?: boolean;\n /** Preferred camera: 'environment' (rear) or 'user' (front) */\n 'facing-mode'?: 'environment' | 'user';\n /** Minimum ms between detection passes */\n interval?: number;\n /** Accessible label for the scanner region */\n label?: string;\n /** Stop after the first successful scan (default true) */\n once?: boolean;\n /** Border radius size */\n rounded?: RoundedSize;\n /** JS-only testing hook: custom scanner factory (defaults to `createQrScanner`) */\n scannerFactory?: QrScannerFactory;\n /** Preview size: named scale or pixel number */\n size?: ComponentSize | number;\n};\n\n/** QR scanner component events */\nexport type OreQrScannerEvents = {\n /** Emitted on every successful decode. detail: `{ value }` */\n scan: { value: string };\n /** Emitted on permission, support, or detection failure. detail: `{ error }` */\n error: { error: SigilError };\n /** Emitted on every view-state transition. detail: `{ status }` */\n status: { status: OreQrScannerStatus };\n};\n\nconst SIZES: Record<ComponentSize, number> = { lg: 360, md: 280, sm: 200 };\n\nconst cssSize = (size: ComponentSize | number): number => (typeof size === 'number' ? size : (SIZES[size] ?? SIZES.md));\n\nconst STATUS_TEXT: Record<OreQrScannerStatus, string> = {\n denied: 'Camera access denied. Allow camera permission and retry.',\n error: 'Scanning failed.',\n idle: 'Camera is off.',\n result: 'Code scanned.',\n scanning: 'Point the camera at a QR code.',\n starting: 'Starting camera…',\n unsupported: 'QR scanning is not supported in this browser.',\n};\n\n/**\n * Camera QR scanner built on `createQrScanner` (native `BarcodeDetector`).\n * `active` starts/stops the camera externally; internal Start/Stop controls\n * drive the same intent. Unsupported browsers render the `unsupported` slot\n * so apps can substitute a paste fallback.\n *\n * @element ore-qr-scanner\n *\n * @attr {boolean} active - Start/stop the camera\n * @attr {string} facing-mode - 'environment' | 'user' (default 'environment')\n * @attr {number} interval - Ms between detection passes (default 200)\n * @attr {boolean} once - Stop after first scan (default true)\n * @attr {string} size - 'sm' | 'md' | 'lg', or a pixel value\n * @attr {string} label - Region label (default \"QR code scanner\")\n * @attr {string} rounded - Border radius\n *\n * @fires scan - detail: `{ value }` on each decode\n * @fires error - detail: `{ error }` on permission/support/detection failure\n * @fires status - detail: `{ status }` on view-state transitions\n *\n * @slot overlay - Content layered over the camera preview\n * @slot unsupported - Fallback UI when scanning is unsupported (e.g. paste input)\n * @slot footer - Extra content below the status row\n *\n * @cssprop --qr-scanner-size - Preview edge length in px (overrides `size`)\n * @cssprop --qr-scanner-radius - Border radius\n * @cssprop --qr-scanner-bg - Preview background while idle\n * @cssprop --qr-scanner-viewfinder-color - Viewfinder frame color\n * @cssprop --qr-scanner-status-color - Status text color\n *\n * @part wrapper - Outer container\n * @part stage - Square preview area (video + overlays)\n * @part video - The `<video>` preview element\n * @part viewfinder - Decorative scan frame over the preview\n * @part state - Idle/error/denied/unsupported/result panel\n * @part status - Live status line\n * @part controls - Button row\n *\n * @example\n * ```html\n * <ore-qr-scanner active @scan=${(e) => pair(e.detail.value)}></ore-qr-scanner>\n * ```\n */\nexport const QR_SCANNER_TAG = 'ore-qr-scanner' as const;\n\ndefine<OreQrScannerProps>(QR_SCANNER_TAG, {\n props: {\n active: prop.bool(false),\n 'facing-mode': prop.oneOf(['environment', 'user'] as const, 'environment'),\n interval: prop.number(200),\n label: prop.string('QR code scanner'),\n once: prop.bool(true),\n ...roundableBundle,\n scannerFactory: prop.data<QrScannerFactory>(),\n size: {\n default: 'md' as ComponentSize | number,\n parse: (v) => (v !== null && /^\\d+$/.test(v) ? Number(v) : ((v ?? 'md') as ComponentSize)),\n },\n },\n\n setup(props) {\n const emit = useEmit<OreQrScannerEvents>();\n const videoRef = ref<HTMLVideoElement>();\n\n const status = signal<OreQrScannerStatus>('idle');\n const failure = signal('');\n const lastValue = signal('');\n\n // `active` is an imperative hint mirrored into internal intent; the\n // internal Start/Stop buttons drive the same signal.\n const requested = signal(false);\n watch(\n props.active,\n (active) => {\n requested.value = active === true;\n },\n { immediate: true },\n );\n\n const setStatus = (next: OreQrScannerStatus, message = ''): void => {\n failure.value = message;\n if (status.value === next) return;\n status.value = next;\n emit('status', { status: next });\n };\n\n const statusText = (): string =>\n status.value === 'result' && lastValue.value ? `Code scanned: ${lastValue.value}` : STATUS_TEXT[status.value];\n\n // Eager support check so VitePress/jsdom (no BarcodeDetector) land on the\n // unsupported state immediately; an injected factory skips the gate.\n if (!props.scannerFactory.value && !isQrScanSupported()) {\n setStatus('unsupported', STATUS_TEXT.unsupported);\n }\n\n const startScanner = async (scanner: QrScanner): Promise<void> => {\n try {\n await scanner.start();\n // Intent may have been withdrawn while getUserMedia was pending.\n if (!requested.value) scanner.stop();\n } catch (error) {\n requested.value = false;\n const sigilError =\n error instanceof SigilError ? error : new SigilError('QR scanner failed to start', { cause: error });\n if (sigilError instanceof SigilUnsupportedError) setStatus('unsupported', sigilError.message);\n else if (sigilError instanceof SigilPermissionError) setStatus('denied', sigilError.message);\n else setStatus('error', sigilError.message);\n emit('error', { error: sigilError });\n }\n };\n\n const wire = (scanner: QrScanner): void => {\n scanner.onResult((result: QrScanResult) => {\n lastValue.value = result.value;\n emit('scan', { value: result.value });\n if (props.once.value) setStatus('result');\n });\n scanner.tap((event) => {\n if (event.type === 'status-change') {\n if (event.status === 'starting') setStatus('starting');\n else if (event.status === 'scanning') setStatus('scanning');\n // 'idle' after a once-result must not clobber the result view.\n else if (event.status === 'idle' && status.value !== 'result') setStatus('idle');\n } else if (event.type === 'error') {\n emit('error', { error: event.error });\n if (status.value === 'scanning' || status.value === 'starting') setStatus('error', event.error.message);\n }\n });\n };\n\n onElement(videoRef, (video) => {\n const current = signal<QrScanner | null>(null);\n\n // Rebuild when scan-affecting props change; cleanup disposes the previous scanner.\n watchEffect(() => {\n const factory = props.scannerFactory.value ?? createQrScanner;\n const scanner = factory({\n constraints: { facingMode: props['facing-mode'].value ?? 'environment' },\n intervalMs: props.interval.value ?? 200,\n once: props.once.value ?? true,\n video,\n });\n current.value = scanner;\n wire(scanner);\n return () => scanner.dispose();\n });\n\n // Drive start/stop from intent + the current scanner instance. `watch`\n // (untracked callback) is required over `watchEffect`: `scanner.stop()`\n // synchronously re-enters the tap handler, and tracking its `status`\n // reads would retrigger this effect on every transition — an infinite loop.\n watch(\n () => [requested.value, current.value] as const,\n ([want, scanner]) => {\n if (!scanner || scanner.disposed) return;\n if (want) void startScanner(scanner);\n else scanner.stop();\n },\n { immediate: true },\n );\n });\n\n const resume = (): void => {\n requested.value = true;\n };\n const pause = (): void => {\n requested.value = false;\n };\n\n const showVideo = (): boolean => status.value === 'starting' || status.value === 'scanning';\n const showControls = (): boolean => status.value !== 'unsupported';\n\n return html`\n <div\n class=\"wrapper\"\n part=\"wrapper\"\n role=\"region\"\n aria-label=${() => props.label.value}\n style=${() => `--_size-px: ${cssSize(props.size.value ?? 'md')}px`}>\n <div class=\"stage\" part=\"stage\">\n <video\n ref=${videoRef}\n part=\"video\"\n autoplay\n playsinline\n muted\n aria-hidden=\"true\"\n ?hidden=${() => !showVideo()}></video>\n <div class=\"viewfinder\" part=\"viewfinder\" aria-hidden=\"true\" ?hidden=${() => status.value !== 'scanning'}></div>\n <div class=\"overlay\"><slot name=\"overlay\"></slot></div>\n ${when(\n () => !showVideo(),\n () => html`\n <div class=\"state\" part=\"state\" data-state=${() => status.value}>\n ${when(\n () => status.value === 'unsupported',\n () => html`<slot name=\"unsupported\"></slot>`,\n () => html`\n ${when(\n () => status.value === 'result',\n () => html`<ore-icon name=\"check\" size=\"24\" aria-hidden=\"true\"></ore-icon>`,\n () => html`<ore-icon name=\"camera\" size=\"24\" aria-hidden=\"true\"></ore-icon>`,\n )}\n <span class=\"state-text\">${() => failure.value || STATUS_TEXT[status.value]}</span>\n `,\n )}\n </div>\n `,\n )}\n </div>\n <div class=\"status\" part=\"status\" role=\"status\">${statusText}</div>\n ${when(\n showControls,\n () => html`\n <div class=\"controls\" part=\"controls\">\n ${when(\n () => status.value === 'starting' || status.value === 'scanning',\n () => html`\n <ore-button variant=\"ghost\" size=\"sm\" @click=${pause}>Stop</ore-button>\n `,\n () => html`\n <ore-button\n variant=\"ghost\"\n size=\"sm\"\n @click=${resume}>\n ${() =>\n status.value === 'result'\n ? 'Scan again'\n : status.value === 'denied' || status.value === 'error'\n ? 'Retry'\n : 'Start camera'}\n </ore-button>\n `,\n )}\n </div>\n `,\n )}\n <slot name=\"footer\"></slot>\n </div>\n `;\n },\n styles: [roundedVariantMixin, componentStyles],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"qr-scanner.js","names":[],"sources":["../src/inputs/qr-scanner/qr-scanner.ts"],"sourcesContent":["import { define, html, onElement, prop, ref, useEmit, watchEffect, when } from '@vielzeug/ore';\nimport { signal, watch } from '@vielzeug/ripple';\nimport {\n createQrScanner,\n isQrScanSupported,\n type QrScanner,\n type QrScannerOptions,\n type QrScanResult,\n SigilError,\n SigilPermissionError,\n SigilUnsupportedError,\n} from '@vielzeug/sigil';\nimport { roundableBundle } from '../../shared';\nimport { roundedVariantMixin } from '../../styles';\nimport type { ComponentSize, RoundedSize } from '../../types';\n\nimport componentStyles from './qr-scanner.css?inline';\n\nexport { ICON_TAG } from '../../content/icon/icon';\nexport { BUTTON_TAG } from '../button/button';\n/** Testing hook: inject a fake `createQrScanner` without touching globals. */\nexport type QrScannerFactory = (options: QrScannerOptions) => QrScanner;\n\n/** Component-level view states; sigil's transport states are mapped onto these. */\nexport type OreQrScannerStatus = 'idle' | 'starting' | 'scanning' | 'result' | 'unsupported' | 'denied' | 'error';\n\n/** QR scanner component properties */\nexport type OreQrScannerProps = {\n /** Start/stop the camera externally; mirrors into internal intent on change */\n active?: boolean;\n /** Preferred camera: 'environment' (rear) or 'user' (front) */\n 'facing-mode'?: 'environment' | 'user';\n /** Minimum ms between detection passes */\n interval?: number;\n /** Accessible label for the scanner region */\n label?: string;\n /** Stop after the first successful scan (default true) */\n once?: boolean;\n /** Border radius size */\n rounded?: RoundedSize;\n /** JS-only testing hook: custom scanner factory (defaults to `createQrScanner`) */\n scannerFactory?: QrScannerFactory;\n /** Preview size: named scale or pixel number */\n size?: ComponentSize | number;\n};\n\n/** QR scanner component events */\nexport type OreQrScannerEvents = {\n /** Emitted on every successful decode. detail: `{ value }` */\n scan: { value: string };\n /** Emitted on permission, support, or detection failure. detail: `{ error }` */\n error: { error: SigilError };\n /** Emitted on every view-state transition. detail: `{ status }` */\n status: { status: OreQrScannerStatus };\n};\n\nconst SIZES: Record<ComponentSize, number> = { lg: 360, md: 280, sm: 200 };\n\nconst cssSize = (size: ComponentSize | number): number => (typeof size === 'number' ? size : (SIZES[size] ?? SIZES.md));\n\nconst STATUS_TEXT: Record<OreQrScannerStatus, string> = {\n denied: 'Camera access denied. Allow camera permission and retry.',\n error: 'Scanning failed.',\n idle: 'Camera is off.',\n result: 'Code scanned.',\n scanning: 'Point the camera at a QR code.',\n starting: 'Starting camera…',\n unsupported: 'QR scanning is not supported in this browser.',\n};\n\n/**\n * Camera QR scanner built on `createQrScanner` (native `BarcodeDetector`).\n * `active` starts/stops the camera externally; internal Start/Stop controls\n * drive the same intent. Unsupported browsers render the `unsupported` slot\n * so apps can substitute a paste fallback.\n *\n * @element ore-qr-scanner\n *\n * @attr {boolean} active - Start/stop the camera\n * @attr {string} facing-mode - 'environment' | 'user' (default 'environment')\n * @attr {number} interval - Ms between detection passes (default 200)\n * @attr {boolean} once - Stop after first scan (default true)\n * @attr {string} size - 'sm' | 'md' | 'lg', or a pixel value\n * @attr {string} label - Region label (default \"QR code scanner\")\n * @attr {string} rounded - Border radius\n *\n * @fires scan - detail: `{ value }` on each decode\n * @fires error - detail: `{ error }` on permission/support/detection failure\n * @fires status - detail: `{ status }` on view-state transitions\n *\n * @slot overlay - Content layered over the camera preview\n * @slot unsupported - Fallback UI when scanning is unsupported (e.g. paste input)\n * @slot footer - Extra content below the status row\n *\n * @cssprop --qr-scanner-size - Preview edge length in px (overrides `size`)\n * @cssprop --qr-scanner-radius - Border radius\n * @cssprop --qr-scanner-bg - Preview background while idle\n * @cssprop --qr-scanner-viewfinder-color - Viewfinder frame color\n * @cssprop --qr-scanner-status-color - Status text color\n *\n * @part wrapper - Outer container\n * @part stage - Square preview area (video + overlays)\n * @part video - The `<video>` preview element\n * @part viewfinder - Decorative scan frame over the preview\n * @part state - Idle/error/denied/unsupported/result panel\n * @part status - Live status line\n * @part controls - Button row\n *\n * @example\n * ```html\n * <ore-qr-scanner active @scan=${(e) => pair(e.detail.value)}></ore-qr-scanner>\n * ```\n */\nexport const QR_SCANNER_TAG = 'ore-qr-scanner' as const;\n\ndefine<OreQrScannerProps>(QR_SCANNER_TAG, {\n props: {\n active: prop.bool(false),\n 'facing-mode': prop.oneOf(['environment', 'user'] as const, 'environment'),\n interval: prop.number(200),\n label: prop.string('QR code scanner'),\n once: prop.bool(true),\n ...roundableBundle,\n scannerFactory: prop.data<QrScannerFactory>(),\n size: {\n default: 'md' as ComponentSize | number,\n parse: (v) => (v !== null && /^\\d+$/.test(v) ? Number(v) : ((v ?? 'md') as ComponentSize)),\n },\n },\n\n setup(props) {\n const emit = useEmit<OreQrScannerEvents>();\n const videoRef = ref<HTMLVideoElement>();\n\n const status = signal<OreQrScannerStatus>('idle');\n const failure = signal('');\n const lastValue = signal('');\n\n // `active` is an imperative hint mirrored into internal intent; the\n // internal Start/Stop buttons drive the same signal.\n const requested = signal(false);\n watch(\n props.active,\n (active) => {\n requested.value = active === true;\n },\n { immediate: true },\n );\n\n const setStatus = (next: OreQrScannerStatus, message = ''): void => {\n failure.value = message;\n if (status.value === next) return;\n status.value = next;\n emit('status', { status: next });\n };\n\n const statusText = (): string =>\n status.value === 'result' && lastValue.value ? `Code scanned: ${lastValue.value}` : STATUS_TEXT[status.value];\n\n // Eager support check so VitePress/jsdom (no BarcodeDetector) land on the\n // unsupported state immediately; an injected factory skips the gate.\n if (!props.scannerFactory.value && !isQrScanSupported()) {\n setStatus('unsupported', STATUS_TEXT.unsupported);\n }\n\n const startScanner = async (scanner: QrScanner): Promise<void> => {\n try {\n await scanner.start();\n // Intent may have been withdrawn while getUserMedia was pending.\n if (!requested.value) scanner.stop();\n } catch (error) {\n requested.value = false;\n const sigilError =\n error instanceof SigilError ? error : new SigilError('QR scanner failed to start', { cause: error });\n if (sigilError instanceof SigilUnsupportedError) setStatus('unsupported', sigilError.message);\n else if (sigilError instanceof SigilPermissionError) setStatus('denied', sigilError.message);\n else setStatus('error', sigilError.message);\n emit('error', { error: sigilError });\n }\n };\n\n const wire = (scanner: QrScanner): void => {\n scanner.onResult((result: QrScanResult) => {\n lastValue.value = result.value;\n emit('scan', { value: result.value });\n if (props.once.value) setStatus('result');\n });\n scanner.tap((event) => {\n if (event.type === 'status-change') {\n if (event.status === 'starting') setStatus('starting');\n else if (event.status === 'scanning') setStatus('scanning');\n // 'idle' after a once-result must not clobber the result view.\n else if (event.status === 'idle' && status.value !== 'result') setStatus('idle');\n } else if (event.type === 'error') {\n emit('error', { error: event.error });\n if (status.value === 'scanning' || status.value === 'starting') setStatus('error', event.error.message);\n }\n });\n };\n\n onElement(videoRef, (video) => {\n const current = signal<QrScanner | null>(null);\n\n // Rebuild when scan-affecting props change; cleanup disposes the previous scanner.\n watchEffect(() => {\n const factory = props.scannerFactory.value ?? createQrScanner;\n const scanner = factory({\n constraints: { facingMode: props['facing-mode'].value ?? 'environment' },\n intervalMs: props.interval.value ?? 200,\n once: props.once.value ?? true,\n video,\n });\n current.value = scanner;\n wire(scanner);\n return () => scanner.dispose();\n });\n\n // Drive start/stop from intent + the current scanner instance. `watch`\n // (untracked callback) is required over `watchEffect`: `scanner.stop()`\n // synchronously re-enters the tap handler, and tracking its `status`\n // reads would retrigger this effect on every transition: an infinite loop.\n watch(\n () => [requested.value, current.value] as const,\n ([want, scanner]) => {\n if (!scanner || scanner.disposed) return;\n if (want) void startScanner(scanner);\n else scanner.stop();\n },\n { immediate: true },\n );\n });\n\n const resume = (): void => {\n requested.value = true;\n };\n const pause = (): void => {\n requested.value = false;\n };\n\n const showVideo = (): boolean => status.value === 'starting' || status.value === 'scanning';\n const showControls = (): boolean => status.value !== 'unsupported';\n\n return html`\n <div\n class=\"wrapper\"\n part=\"wrapper\"\n role=\"region\"\n aria-label=${() => props.label.value}\n style=${() => `--_size-px: ${cssSize(props.size.value ?? 'md')}px`}>\n <div class=\"stage\" part=\"stage\">\n <video\n ref=${videoRef}\n part=\"video\"\n autoplay\n playsinline\n muted\n aria-hidden=\"true\"\n ?hidden=${() => !showVideo()}></video>\n <div class=\"viewfinder\" part=\"viewfinder\" aria-hidden=\"true\" ?hidden=${() => status.value !== 'scanning'}></div>\n <div class=\"overlay\"><slot name=\"overlay\"></slot></div>\n ${when(\n () => !showVideo(),\n () => html`\n <div class=\"state\" part=\"state\" data-state=${() => status.value}>\n ${when(\n () => status.value === 'unsupported',\n () => html`<slot name=\"unsupported\"></slot>`,\n () => html`\n ${when(\n () => status.value === 'result',\n () => html`<ore-icon name=\"check\" size=\"24\" aria-hidden=\"true\"></ore-icon>`,\n () => html`<ore-icon name=\"camera\" size=\"24\" aria-hidden=\"true\"></ore-icon>`,\n )}\n <span class=\"state-text\">${() => failure.value || STATUS_TEXT[status.value]}</span>\n `,\n )}\n </div>\n `,\n )}\n </div>\n <div class=\"status\" part=\"status\" role=\"status\">${statusText}</div>\n ${when(\n showControls,\n () => html`\n <div class=\"controls\" part=\"controls\">\n ${when(\n () => status.value === 'starting' || status.value === 'scanning',\n () => html`\n <ore-button variant=\"ghost\" size=\"sm\" @click=${pause}>Stop</ore-button>\n `,\n () => html`\n <ore-button\n variant=\"ghost\"\n size=\"sm\"\n @click=${resume}>\n ${() =>\n status.value === 'result'\n ? 'Scan again'\n : status.value === 'denied' || status.value === 'error'\n ? 'Retry'\n : 'Start camera'}\n </ore-button>\n `,\n )}\n </div>\n `,\n )}\n <slot name=\"footer\"></slot>\n </div>\n `;\n },\n styles: [roundedVariantMixin, componentStyles],\n});\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"radio-group.cjs","names":[],"sources":["../src/inputs/radio-group/radio-group.ts"],"sourcesContent":["import {\n bind,\n createContext,\n createId,\n define,\n getHost,\n html,\n onCleanup,\n prop,\n provide,\n useEmit,\n useField,\n useSlots,\n watchEffect,\n when,\n} from '@vielzeug/ore';\nimport type { Readable } from '@vielzeug/ripple';\nimport {\n type ChoiceChangeDetail,\n createChoiceField,\n createListControl,\n getChoiceLabel,\n getLightChildrenByTag,\n lifecycleSignal,\n} from '../../core';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { disabledStateMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor } from '../../types';\nimport componentStyles from './radio-group.css?inline';\n\n/** Radio group component properties */\nexport type OreRadioGroupProps = {\n /** Theme color tint */\n color?: ThemeColor;\n /** Disabled state */\n disabled?: boolean;\n /** Error message text */\n error?: string;\n /** Helper text displayed below the items */\n helper?: string;\n /** Group label text */\n label?: string;\n /** Form field name */\n name?: string;\n /** Layout orientation */\n orientation?: 'horizontal' | 'vertical';\n /** Required field */\n required?: boolean;\n /** Items size preset */\n size?: ComponentSize;\n /** Initial selected value */\n value?: string;\n};\n\nexport type RadioGroupContext = {\n color: Readable<ThemeColor | undefined>;\n disabled: Readable<boolean>;\n name: Readable<string | undefined>;\n select: (value: string, originalEvent?: Event) => void;\n size: Readable<ComponentSize | undefined>;\n value: Readable<string | undefined>;\n};\n\nexport const RADIO_GROUP_CTX = createContext<RadioGroupContext | undefined>('OreRadioGroup');\n\n/** Events emitted by the radio-group component */\nexport type OreRadioGroupEvents = {\n /** Emitted when the selection changes */\n change: ChoiceChangeDetail;\n};\n\n/**\n * A group of radio buttons that allows users to select a single option from a set.\n * Supports keyboard navigation (arrows) and automatic value management.\n *\n * @element ore-radio-group\n *\n * @attr {string} value - Selected value\n * @attr {string} name - Form field name\n * @attr {string} label - Group label\n * @attr {string} orientation - Layout: 'vertical' | 'horizontal'\n * @attr {boolean} required - Required field\n *\n * @fires change - Emitted when a radio is selected. detail: { values: string[], labels: string[], originalEvent?: Event }\n *\n * @slot - Place `ore-radio` elements here\n *\n * @cssprop --radio-group-direction - Flex direction of the items list ('row' | 'column')\n * @cssprop --radio-group-gap - Gap between radio items\n * @part items - Items container.\n * @example\n * ```html\n * <ore-radio-group name=\"plan\" label=\"Choose a plan\" value=\"free\" required>\n * <ore-radio value=\"free\">Free</ore-radio>\n * <ore-radio value=\"pro\">Pro</ore-radio>\n * <ore-radio value=\"enterprise\">Enterprise</ore-radio>\n * </ore-radio-group>\n * <ore-radio-group name=\"direction\" orientation=\"horizontal\" color=\"primary\">\n * <ore-radio value=\"left\">Left</ore-radio>\n * <ore-radio value=\"right\">Right</ore-radio>\n * </ore-radio-group>\n * ```\n */\nexport const RADIO_GROUP_TAG = 'ore-radio-group' as const;\ndefine<OreRadioGroupProps>(RADIO_GROUP_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n error: prop.string(),\n helper: prop.string(),\n label: prop.string(),\n name: prop.string(),\n orientation: prop.oneOf(['horizontal', 'vertical'] as const, 'vertical'),\n required: prop.bool(false),\n // Not auto-reflected (`reflect: false`) — the derived, interaction-updated selection\n // (`selectedValue`) is the single writer for this attribute, via `bind()` below; letting\n // `prop.string()`'s own default reflection also write the raw incoming value would leave\n // two effects racing to set the same attribute from different sources.\n value: { ...prop.string(), reflect: false },\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreRadioGroupEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const choice = createChoiceField({\n disabled: props.disabled,\n error: props.error,\n helper: props.helper,\n label: props.label,\n prefix: 'radio-group',\n required: props.required,\n signal: lifecycleSignal(onCleanup),\n value: props.value,\n });\n\n choice.attachFormField(\n useField<string>({\n disabled: choice.disabled,\n onReset: choice.reset,\n toFormValue: (v) => v,\n validationMessage: choice.validationMessage,\n validity: choice.validity,\n value: choice.formValue,\n }),\n );\n\n const selectedValue = choice.selectedValue;\n const isDisabled = props.disabled;\n\n bind({\n attr: {\n size: props.size,\n value: () => selectedValue.value || null,\n },\n });\n\n const getSlottedRadios = (): HTMLElement[] => getLightChildrenByTag(el, 'ore-radio');\n\n const getEnabledRadios = (): HTMLElement[] =>\n isDisabled.value ? [] : getSlottedRadios().filter((radio) => !radio.hasAttribute('disabled'));\n\n const getLabelForValue = (value: string): string => getChoiceLabel(getSlottedRadios(), value);\n\n const selectRadio = (val: string, originalEvent?: Event): void => {\n choice.setValues(val ? [val] : []);\n\n const labels = val ? [getLabelForValue(val)] : [];\n const values = val ? [val] : [];\n\n emit('change', { labels, originalEvent, values });\n choice.triggerValidation('blur');\n };\n\n provide(RADIO_GROUP_CTX, {\n color: props.color as Readable<ThemeColor | undefined>,\n disabled: isDisabled,\n name: props.name,\n select: selectRadio,\n size: props.size as Readable<ComponentSize | undefined>,\n value: selectedValue,\n });\n\n // Sync name/color/size/disabled/checked onto slotted ore-radio children.\n watch(() => {\n void slots.elements().value;\n void selectedValue.value;\n\n const radios = getSlottedRadios();\n\n for (const radio of radios) {\n const val = radio.getAttribute('value') ?? '';\n\n radio.toggleAttribute('checked', val === selectedValue.value);\n\n if (props.name.value) radio.setAttribute('name', props.name.value);\n else radio.removeAttribute('name');\n\n if (props.color.value) radio.setAttribute('color', props.color.value);\n else radio.removeAttribute('color');\n\n if (props.size.value) radio.setAttribute('size', props.size.value);\n else radio.removeAttribute('size');\n\n radio.toggleAttribute('disabled', isDisabled.value);\n }\n });\n\n // Roving tabindex: only the selected (or first) radio is tabbable.\n watch(() => {\n void slots.elements().value;\n\n const radios = getSlottedRadios();\n let hasFocusable = false;\n\n for (const radio of radios) {\n const isSelected = radio.getAttribute('value') === selectedValue.value;\n\n radio.setAttribute('tabindex', isSelected && !isDisabled.value ? '0' : '-1');\n\n if (isSelected && !isDisabled.value) hasFocusable = true;\n }\n\n if (!hasFocusable && radios.length > 0) {\n const first = radios.find((r) => !r.hasAttribute('disabled'));\n\n if (first) first.setAttribute('tabindex', '0');\n }\n });\n\n const listControl = createListControl<HTMLElement>({\n getItems: getEnabledRadios,\n keys: { next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n loop: true,\n onNavigate: ({ event, item }) => {\n item.focus();\n\n if (item.tagName === 'ORE-RADIO') {\n selectRadio(item.getAttribute('value') ?? '', event);\n }\n },\n signal: lifecycleSignal(onCleanup),\n });\n\n bind({\n on: {\n change: (e: Event) => {\n if (e.target === el) return;\n\n e.stopPropagation();\n selectRadio((e.target as HTMLElement).getAttribute('value') ?? '', e);\n },\n keydown: (e: KeyboardEvent) => {\n const radios = getEnabledRadios();\n\n if (!radios.length) return;\n\n const focused = radios.indexOf(document.activeElement as HTMLElement);\n\n if (focused === -1) return;\n\n listControl.set(focused);\n listControl.handleKeydown(e);\n },\n },\n });\n\n const legendId = createId('radio-group-legend');\n\n return html`\n <fieldset\n role=\"radiogroup\"\n aria-required=\"${() => String(Boolean(props.required.value))}\"\n aria-invalid=\"${choice.ariaInvalid}\"\n aria-errormessage=\"${choice.ariaErrorMessage}\"\n aria-describedby=\"${choice.ariaDescribedBy}\">\n <legend id=\"${legendId}\" ?hidden=${() => !props.label.value}>\n ${props.label}${when(\n () => Boolean(props.required.value),\n () => html`\n <span aria-hidden=\"true\">*</span>\n `,\n )}\n </legend>\n <div class=\"radio-group-items\" part=\"items\">\n <slot></slot>\n </div>\n <div\n class=\"helper-text\"\n part=\"helper\"\n id=\"${choice.assistiveId}\"\n role=\"${() => (choice.errorText.value ? 'alert' : null)}\"\n aria-live=\"polite\"\n ?hidden=\"${() => !choice.errorText.value && !choice.helperText.value}\">\n ${() => choice.errorText.value || choice.helperText.value}\n </div>\n </fieldset>\n `;\n },\n styles: [disabledStateMixin, componentStyles],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"radio-group.cjs","names":[],"sources":["../src/inputs/radio-group/radio-group.ts"],"sourcesContent":["import {\n bind,\n createContext,\n createId,\n define,\n getHost,\n html,\n onCleanup,\n prop,\n provide,\n useEmit,\n useField,\n useSlots,\n watchEffect,\n when,\n} from '@vielzeug/ore';\nimport type { Readable } from '@vielzeug/ripple';\nimport {\n type ChoiceChangeDetail,\n createChoiceField,\n createListControl,\n getChoiceLabel,\n getLightChildrenByTag,\n lifecycleSignal,\n} from '../../core';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { disabledStateMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor } from '../../types';\nimport componentStyles from './radio-group.css?inline';\n\n/** Radio group component properties */\nexport type OreRadioGroupProps = {\n /** Theme color tint */\n color?: ThemeColor;\n /** Disabled state */\n disabled?: boolean;\n /** Error message text */\n error?: string;\n /** Helper text displayed below the items */\n helper?: string;\n /** Group label text */\n label?: string;\n /** Form field name */\n name?: string;\n /** Layout orientation */\n orientation?: 'horizontal' | 'vertical';\n /** Required field */\n required?: boolean;\n /** Items size preset */\n size?: ComponentSize;\n /** Initial selected value */\n value?: string;\n};\n\nexport type RadioGroupContext = {\n color: Readable<ThemeColor | undefined>;\n disabled: Readable<boolean>;\n name: Readable<string | undefined>;\n select: (value: string, originalEvent?: Event) => void;\n size: Readable<ComponentSize | undefined>;\n value: Readable<string | undefined>;\n};\n\nexport const RADIO_GROUP_CTX = createContext<RadioGroupContext | undefined>('OreRadioGroup');\n\n/** Events emitted by the radio-group component */\nexport type OreRadioGroupEvents = {\n /** Emitted when the selection changes */\n change: ChoiceChangeDetail;\n};\n\n/**\n * A group of radio buttons that allows users to select a single option from a set.\n * Supports keyboard navigation (arrows) and automatic value management.\n *\n * @element ore-radio-group\n *\n * @attr {string} value - Selected value\n * @attr {string} name - Form field name\n * @attr {string} label - Group label\n * @attr {string} orientation - Layout: 'vertical' | 'horizontal'\n * @attr {boolean} required - Required field\n *\n * @fires change - Emitted when a radio is selected. detail: { values: string[], labels: string[], originalEvent?: Event }\n *\n * @slot - Place `ore-radio` elements here\n *\n * @cssprop --radio-group-direction - Flex direction of the items list ('row' | 'column')\n * @cssprop --radio-group-gap - Gap between radio items\n * @part items - Items container.\n * @example\n * ```html\n * <ore-radio-group name=\"plan\" label=\"Choose a plan\" value=\"free\" required>\n * <ore-radio value=\"free\">Free</ore-radio>\n * <ore-radio value=\"pro\">Pro</ore-radio>\n * <ore-radio value=\"enterprise\">Enterprise</ore-radio>\n * </ore-radio-group>\n * <ore-radio-group name=\"direction\" orientation=\"horizontal\" color=\"primary\">\n * <ore-radio value=\"left\">Left</ore-radio>\n * <ore-radio value=\"right\">Right</ore-radio>\n * </ore-radio-group>\n * ```\n */\nexport const RADIO_GROUP_TAG = 'ore-radio-group' as const;\ndefine<OreRadioGroupProps>(RADIO_GROUP_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n error: prop.string(),\n helper: prop.string(),\n label: prop.string(),\n name: prop.string(),\n orientation: prop.oneOf(['horizontal', 'vertical'] as const, 'vertical'),\n required: prop.bool(false),\n // Not auto-reflected (`reflect: false`): the derived, interaction-updated selection\n // (`selectedValue`) is the single writer for this attribute, via `bind()` below; letting\n // `prop.string()`'s own default reflection also write the raw incoming value would leave\n // two effects racing to set the same attribute from different sources.\n value: { ...prop.string(), reflect: false },\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreRadioGroupEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const choice = createChoiceField({\n disabled: props.disabled,\n error: props.error,\n helper: props.helper,\n label: props.label,\n prefix: 'radio-group',\n required: props.required,\n signal: lifecycleSignal(onCleanup),\n value: props.value,\n });\n\n choice.attachFormField(\n useField<string>({\n disabled: choice.disabled,\n onReset: choice.reset,\n toFormValue: (v) => v,\n validationMessage: choice.validationMessage,\n validity: choice.validity,\n value: choice.formValue,\n }),\n );\n\n const selectedValue = choice.selectedValue;\n const isDisabled = props.disabled;\n\n bind({\n attr: {\n size: props.size,\n value: () => selectedValue.value || null,\n },\n });\n\n const getSlottedRadios = (): HTMLElement[] => getLightChildrenByTag(el, 'ore-radio');\n\n const getEnabledRadios = (): HTMLElement[] =>\n isDisabled.value ? [] : getSlottedRadios().filter((radio) => !radio.hasAttribute('disabled'));\n\n const getLabelForValue = (value: string): string => getChoiceLabel(getSlottedRadios(), value);\n\n const selectRadio = (val: string, originalEvent?: Event): void => {\n choice.setValues(val ? [val] : []);\n\n const labels = val ? [getLabelForValue(val)] : [];\n const values = val ? [val] : [];\n\n emit('change', { labels, originalEvent, values });\n choice.triggerValidation('blur');\n };\n\n provide(RADIO_GROUP_CTX, {\n color: props.color as Readable<ThemeColor | undefined>,\n disabled: isDisabled,\n name: props.name,\n select: selectRadio,\n size: props.size as Readable<ComponentSize | undefined>,\n value: selectedValue,\n });\n\n // Sync name/color/size/disabled/checked onto slotted ore-radio children.\n watch(() => {\n void slots.elements().value;\n void selectedValue.value;\n\n const radios = getSlottedRadios();\n\n for (const radio of radios) {\n const val = radio.getAttribute('value') ?? '';\n\n radio.toggleAttribute('checked', val === selectedValue.value);\n\n if (props.name.value) radio.setAttribute('name', props.name.value);\n else radio.removeAttribute('name');\n\n if (props.color.value) radio.setAttribute('color', props.color.value);\n else radio.removeAttribute('color');\n\n if (props.size.value) radio.setAttribute('size', props.size.value);\n else radio.removeAttribute('size');\n\n radio.toggleAttribute('disabled', isDisabled.value);\n }\n });\n\n // Roving tabindex: only the selected (or first) radio is tabbable.\n watch(() => {\n void slots.elements().value;\n\n const radios = getSlottedRadios();\n let hasFocusable = false;\n\n for (const radio of radios) {\n const isSelected = radio.getAttribute('value') === selectedValue.value;\n\n radio.setAttribute('tabindex', isSelected && !isDisabled.value ? '0' : '-1');\n\n if (isSelected && !isDisabled.value) hasFocusable = true;\n }\n\n if (!hasFocusable && radios.length > 0) {\n const first = radios.find((r) => !r.hasAttribute('disabled'));\n\n if (first) first.setAttribute('tabindex', '0');\n }\n });\n\n const listControl = createListControl<HTMLElement>({\n getItems: getEnabledRadios,\n keys: { next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n loop: true,\n onNavigate: ({ event, item }) => {\n item.focus();\n\n if (item.tagName === 'ORE-RADIO') {\n selectRadio(item.getAttribute('value') ?? '', event);\n }\n },\n signal: lifecycleSignal(onCleanup),\n });\n\n bind({\n on: {\n change: (e: Event) => {\n if (e.target === el) return;\n\n e.stopPropagation();\n selectRadio((e.target as HTMLElement).getAttribute('value') ?? '', e);\n },\n keydown: (e: KeyboardEvent) => {\n const radios = getEnabledRadios();\n\n if (!radios.length) return;\n\n const focused = radios.indexOf(document.activeElement as HTMLElement);\n\n if (focused === -1) return;\n\n listControl.set(focused);\n listControl.handleKeydown(e);\n },\n },\n });\n\n const legendId = createId('radio-group-legend');\n\n return html`\n <fieldset\n role=\"radiogroup\"\n aria-required=\"${() => String(Boolean(props.required.value))}\"\n aria-invalid=\"${choice.ariaInvalid}\"\n aria-errormessage=\"${choice.ariaErrorMessage}\"\n aria-describedby=\"${choice.ariaDescribedBy}\">\n <legend id=\"${legendId}\" ?hidden=${() => !props.label.value}>\n ${props.label}${when(\n () => Boolean(props.required.value),\n () => html`\n <span aria-hidden=\"true\">*</span>\n `,\n )}\n </legend>\n <div class=\"radio-group-items\" part=\"items\">\n <slot></slot>\n </div>\n <div\n class=\"helper-text\"\n part=\"helper\"\n id=\"${choice.assistiveId}\"\n role=\"${() => (choice.errorText.value ? 'alert' : null)}\"\n aria-live=\"polite\"\n ?hidden=\"${() => !choice.errorText.value && !choice.helperText.value}\">\n ${() => choice.errorText.value || choice.helperText.value}\n </div>\n </fieldset>\n `;\n },\n styles: [disabledStateMixin, componentStyles],\n});\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"radio-group.js","names":[],"sources":["../src/inputs/radio-group/radio-group.ts"],"sourcesContent":["import {\n bind,\n createContext,\n createId,\n define,\n getHost,\n html,\n onCleanup,\n prop,\n provide,\n useEmit,\n useField,\n useSlots,\n watchEffect,\n when,\n} from '@vielzeug/ore';\nimport type { Readable } from '@vielzeug/ripple';\nimport {\n type ChoiceChangeDetail,\n createChoiceField,\n createListControl,\n getChoiceLabel,\n getLightChildrenByTag,\n lifecycleSignal,\n} from '../../core';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { disabledStateMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor } from '../../types';\nimport componentStyles from './radio-group.css?inline';\n\n/** Radio group component properties */\nexport type OreRadioGroupProps = {\n /** Theme color tint */\n color?: ThemeColor;\n /** Disabled state */\n disabled?: boolean;\n /** Error message text */\n error?: string;\n /** Helper text displayed below the items */\n helper?: string;\n /** Group label text */\n label?: string;\n /** Form field name */\n name?: string;\n /** Layout orientation */\n orientation?: 'horizontal' | 'vertical';\n /** Required field */\n required?: boolean;\n /** Items size preset */\n size?: ComponentSize;\n /** Initial selected value */\n value?: string;\n};\n\nexport type RadioGroupContext = {\n color: Readable<ThemeColor | undefined>;\n disabled: Readable<boolean>;\n name: Readable<string | undefined>;\n select: (value: string, originalEvent?: Event) => void;\n size: Readable<ComponentSize | undefined>;\n value: Readable<string | undefined>;\n};\n\nexport const RADIO_GROUP_CTX = createContext<RadioGroupContext | undefined>('OreRadioGroup');\n\n/** Events emitted by the radio-group component */\nexport type OreRadioGroupEvents = {\n /** Emitted when the selection changes */\n change: ChoiceChangeDetail;\n};\n\n/**\n * A group of radio buttons that allows users to select a single option from a set.\n * Supports keyboard navigation (arrows) and automatic value management.\n *\n * @element ore-radio-group\n *\n * @attr {string} value - Selected value\n * @attr {string} name - Form field name\n * @attr {string} label - Group label\n * @attr {string} orientation - Layout: 'vertical' | 'horizontal'\n * @attr {boolean} required - Required field\n *\n * @fires change - Emitted when a radio is selected. detail: { values: string[], labels: string[], originalEvent?: Event }\n *\n * @slot - Place `ore-radio` elements here\n *\n * @cssprop --radio-group-direction - Flex direction of the items list ('row' | 'column')\n * @cssprop --radio-group-gap - Gap between radio items\n * @part items - Items container.\n * @example\n * ```html\n * <ore-radio-group name=\"plan\" label=\"Choose a plan\" value=\"free\" required>\n * <ore-radio value=\"free\">Free</ore-radio>\n * <ore-radio value=\"pro\">Pro</ore-radio>\n * <ore-radio value=\"enterprise\">Enterprise</ore-radio>\n * </ore-radio-group>\n * <ore-radio-group name=\"direction\" orientation=\"horizontal\" color=\"primary\">\n * <ore-radio value=\"left\">Left</ore-radio>\n * <ore-radio value=\"right\">Right</ore-radio>\n * </ore-radio-group>\n * ```\n */\nexport const RADIO_GROUP_TAG = 'ore-radio-group' as const;\ndefine<OreRadioGroupProps>(RADIO_GROUP_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n error: prop.string(),\n helper: prop.string(),\n label: prop.string(),\n name: prop.string(),\n orientation: prop.oneOf(['horizontal', 'vertical'] as const, 'vertical'),\n required: prop.bool(false),\n // Not auto-reflected (`reflect: false`) — the derived, interaction-updated selection\n // (`selectedValue`) is the single writer for this attribute, via `bind()` below; letting\n // `prop.string()`'s own default reflection also write the raw incoming value would leave\n // two effects racing to set the same attribute from different sources.\n value: { ...prop.string(), reflect: false },\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreRadioGroupEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const choice = createChoiceField({\n disabled: props.disabled,\n error: props.error,\n helper: props.helper,\n label: props.label,\n prefix: 'radio-group',\n required: props.required,\n signal: lifecycleSignal(onCleanup),\n value: props.value,\n });\n\n choice.attachFormField(\n useField<string>({\n disabled: choice.disabled,\n onReset: choice.reset,\n toFormValue: (v) => v,\n validationMessage: choice.validationMessage,\n validity: choice.validity,\n value: choice.formValue,\n }),\n );\n\n const selectedValue = choice.selectedValue;\n const isDisabled = props.disabled;\n\n bind({\n attr: {\n size: props.size,\n value: () => selectedValue.value || null,\n },\n });\n\n const getSlottedRadios = (): HTMLElement[] => getLightChildrenByTag(el, 'ore-radio');\n\n const getEnabledRadios = (): HTMLElement[] =>\n isDisabled.value ? [] : getSlottedRadios().filter((radio) => !radio.hasAttribute('disabled'));\n\n const getLabelForValue = (value: string): string => getChoiceLabel(getSlottedRadios(), value);\n\n const selectRadio = (val: string, originalEvent?: Event): void => {\n choice.setValues(val ? [val] : []);\n\n const labels = val ? [getLabelForValue(val)] : [];\n const values = val ? [val] : [];\n\n emit('change', { labels, originalEvent, values });\n choice.triggerValidation('blur');\n };\n\n provide(RADIO_GROUP_CTX, {\n color: props.color as Readable<ThemeColor | undefined>,\n disabled: isDisabled,\n name: props.name,\n select: selectRadio,\n size: props.size as Readable<ComponentSize | undefined>,\n value: selectedValue,\n });\n\n // Sync name/color/size/disabled/checked onto slotted ore-radio children.\n watch(() => {\n void slots.elements().value;\n void selectedValue.value;\n\n const radios = getSlottedRadios();\n\n for (const radio of radios) {\n const val = radio.getAttribute('value') ?? '';\n\n radio.toggleAttribute('checked', val === selectedValue.value);\n\n if (props.name.value) radio.setAttribute('name', props.name.value);\n else radio.removeAttribute('name');\n\n if (props.color.value) radio.setAttribute('color', props.color.value);\n else radio.removeAttribute('color');\n\n if (props.size.value) radio.setAttribute('size', props.size.value);\n else radio.removeAttribute('size');\n\n radio.toggleAttribute('disabled', isDisabled.value);\n }\n });\n\n // Roving tabindex: only the selected (or first) radio is tabbable.\n watch(() => {\n void slots.elements().value;\n\n const radios = getSlottedRadios();\n let hasFocusable = false;\n\n for (const radio of radios) {\n const isSelected = radio.getAttribute('value') === selectedValue.value;\n\n radio.setAttribute('tabindex', isSelected && !isDisabled.value ? '0' : '-1');\n\n if (isSelected && !isDisabled.value) hasFocusable = true;\n }\n\n if (!hasFocusable && radios.length > 0) {\n const first = radios.find((r) => !r.hasAttribute('disabled'));\n\n if (first) first.setAttribute('tabindex', '0');\n }\n });\n\n const listControl = createListControl<HTMLElement>({\n getItems: getEnabledRadios,\n keys: { next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n loop: true,\n onNavigate: ({ event, item }) => {\n item.focus();\n\n if (item.tagName === 'ORE-RADIO') {\n selectRadio(item.getAttribute('value') ?? '', event);\n }\n },\n signal: lifecycleSignal(onCleanup),\n });\n\n bind({\n on: {\n change: (e: Event) => {\n if (e.target === el) return;\n\n e.stopPropagation();\n selectRadio((e.target as HTMLElement).getAttribute('value') ?? '', e);\n },\n keydown: (e: KeyboardEvent) => {\n const radios = getEnabledRadios();\n\n if (!radios.length) return;\n\n const focused = radios.indexOf(document.activeElement as HTMLElement);\n\n if (focused === -1) return;\n\n listControl.set(focused);\n listControl.handleKeydown(e);\n },\n },\n });\n\n const legendId = createId('radio-group-legend');\n\n return html`\n <fieldset\n role=\"radiogroup\"\n aria-required=\"${() => String(Boolean(props.required.value))}\"\n aria-invalid=\"${choice.ariaInvalid}\"\n aria-errormessage=\"${choice.ariaErrorMessage}\"\n aria-describedby=\"${choice.ariaDescribedBy}\">\n <legend id=\"${legendId}\" ?hidden=${() => !props.label.value}>\n ${props.label}${when(\n () => Boolean(props.required.value),\n () => html`\n <span aria-hidden=\"true\">*</span>\n `,\n )}\n </legend>\n <div class=\"radio-group-items\" part=\"items\">\n <slot></slot>\n </div>\n <div\n class=\"helper-text\"\n part=\"helper\"\n id=\"${choice.assistiveId}\"\n role=\"${() => (choice.errorText.value ? 'alert' : null)}\"\n aria-live=\"polite\"\n ?hidden=\"${() => !choice.errorText.value && !choice.helperText.value}\">\n ${() => choice.errorText.value || choice.helperText.value}\n </div>\n </fieldset>\n `;\n },\n styles: [disabledStateMixin, componentStyles],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"radio-group.js","names":[],"sources":["../src/inputs/radio-group/radio-group.ts"],"sourcesContent":["import {\n bind,\n createContext,\n createId,\n define,\n getHost,\n html,\n onCleanup,\n prop,\n provide,\n useEmit,\n useField,\n useSlots,\n watchEffect,\n when,\n} from '@vielzeug/ore';\nimport type { Readable } from '@vielzeug/ripple';\nimport {\n type ChoiceChangeDetail,\n createChoiceField,\n createListControl,\n getChoiceLabel,\n getLightChildrenByTag,\n lifecycleSignal,\n} from '../../core';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { disabledStateMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor } from '../../types';\nimport componentStyles from './radio-group.css?inline';\n\n/** Radio group component properties */\nexport type OreRadioGroupProps = {\n /** Theme color tint */\n color?: ThemeColor;\n /** Disabled state */\n disabled?: boolean;\n /** Error message text */\n error?: string;\n /** Helper text displayed below the items */\n helper?: string;\n /** Group label text */\n label?: string;\n /** Form field name */\n name?: string;\n /** Layout orientation */\n orientation?: 'horizontal' | 'vertical';\n /** Required field */\n required?: boolean;\n /** Items size preset */\n size?: ComponentSize;\n /** Initial selected value */\n value?: string;\n};\n\nexport type RadioGroupContext = {\n color: Readable<ThemeColor | undefined>;\n disabled: Readable<boolean>;\n name: Readable<string | undefined>;\n select: (value: string, originalEvent?: Event) => void;\n size: Readable<ComponentSize | undefined>;\n value: Readable<string | undefined>;\n};\n\nexport const RADIO_GROUP_CTX = createContext<RadioGroupContext | undefined>('OreRadioGroup');\n\n/** Events emitted by the radio-group component */\nexport type OreRadioGroupEvents = {\n /** Emitted when the selection changes */\n change: ChoiceChangeDetail;\n};\n\n/**\n * A group of radio buttons that allows users to select a single option from a set.\n * Supports keyboard navigation (arrows) and automatic value management.\n *\n * @element ore-radio-group\n *\n * @attr {string} value - Selected value\n * @attr {string} name - Form field name\n * @attr {string} label - Group label\n * @attr {string} orientation - Layout: 'vertical' | 'horizontal'\n * @attr {boolean} required - Required field\n *\n * @fires change - Emitted when a radio is selected. detail: { values: string[], labels: string[], originalEvent?: Event }\n *\n * @slot - Place `ore-radio` elements here\n *\n * @cssprop --radio-group-direction - Flex direction of the items list ('row' | 'column')\n * @cssprop --radio-group-gap - Gap between radio items\n * @part items - Items container.\n * @example\n * ```html\n * <ore-radio-group name=\"plan\" label=\"Choose a plan\" value=\"free\" required>\n * <ore-radio value=\"free\">Free</ore-radio>\n * <ore-radio value=\"pro\">Pro</ore-radio>\n * <ore-radio value=\"enterprise\">Enterprise</ore-radio>\n * </ore-radio-group>\n * <ore-radio-group name=\"direction\" orientation=\"horizontal\" color=\"primary\">\n * <ore-radio value=\"left\">Left</ore-radio>\n * <ore-radio value=\"right\">Right</ore-radio>\n * </ore-radio-group>\n * ```\n */\nexport const RADIO_GROUP_TAG = 'ore-radio-group' as const;\ndefine<OreRadioGroupProps>(RADIO_GROUP_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n error: prop.string(),\n helper: prop.string(),\n label: prop.string(),\n name: prop.string(),\n orientation: prop.oneOf(['horizontal', 'vertical'] as const, 'vertical'),\n required: prop.bool(false),\n // Not auto-reflected (`reflect: false`): the derived, interaction-updated selection\n // (`selectedValue`) is the single writer for this attribute, via `bind()` below; letting\n // `prop.string()`'s own default reflection also write the raw incoming value would leave\n // two effects racing to set the same attribute from different sources.\n value: { ...prop.string(), reflect: false },\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreRadioGroupEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const choice = createChoiceField({\n disabled: props.disabled,\n error: props.error,\n helper: props.helper,\n label: props.label,\n prefix: 'radio-group',\n required: props.required,\n signal: lifecycleSignal(onCleanup),\n value: props.value,\n });\n\n choice.attachFormField(\n useField<string>({\n disabled: choice.disabled,\n onReset: choice.reset,\n toFormValue: (v) => v,\n validationMessage: choice.validationMessage,\n validity: choice.validity,\n value: choice.formValue,\n }),\n );\n\n const selectedValue = choice.selectedValue;\n const isDisabled = props.disabled;\n\n bind({\n attr: {\n size: props.size,\n value: () => selectedValue.value || null,\n },\n });\n\n const getSlottedRadios = (): HTMLElement[] => getLightChildrenByTag(el, 'ore-radio');\n\n const getEnabledRadios = (): HTMLElement[] =>\n isDisabled.value ? [] : getSlottedRadios().filter((radio) => !radio.hasAttribute('disabled'));\n\n const getLabelForValue = (value: string): string => getChoiceLabel(getSlottedRadios(), value);\n\n const selectRadio = (val: string, originalEvent?: Event): void => {\n choice.setValues(val ? [val] : []);\n\n const labels = val ? [getLabelForValue(val)] : [];\n const values = val ? [val] : [];\n\n emit('change', { labels, originalEvent, values });\n choice.triggerValidation('blur');\n };\n\n provide(RADIO_GROUP_CTX, {\n color: props.color as Readable<ThemeColor | undefined>,\n disabled: isDisabled,\n name: props.name,\n select: selectRadio,\n size: props.size as Readable<ComponentSize | undefined>,\n value: selectedValue,\n });\n\n // Sync name/color/size/disabled/checked onto slotted ore-radio children.\n watch(() => {\n void slots.elements().value;\n void selectedValue.value;\n\n const radios = getSlottedRadios();\n\n for (const radio of radios) {\n const val = radio.getAttribute('value') ?? '';\n\n radio.toggleAttribute('checked', val === selectedValue.value);\n\n if (props.name.value) radio.setAttribute('name', props.name.value);\n else radio.removeAttribute('name');\n\n if (props.color.value) radio.setAttribute('color', props.color.value);\n else radio.removeAttribute('color');\n\n if (props.size.value) radio.setAttribute('size', props.size.value);\n else radio.removeAttribute('size');\n\n radio.toggleAttribute('disabled', isDisabled.value);\n }\n });\n\n // Roving tabindex: only the selected (or first) radio is tabbable.\n watch(() => {\n void slots.elements().value;\n\n const radios = getSlottedRadios();\n let hasFocusable = false;\n\n for (const radio of radios) {\n const isSelected = radio.getAttribute('value') === selectedValue.value;\n\n radio.setAttribute('tabindex', isSelected && !isDisabled.value ? '0' : '-1');\n\n if (isSelected && !isDisabled.value) hasFocusable = true;\n }\n\n if (!hasFocusable && radios.length > 0) {\n const first = radios.find((r) => !r.hasAttribute('disabled'));\n\n if (first) first.setAttribute('tabindex', '0');\n }\n });\n\n const listControl = createListControl<HTMLElement>({\n getItems: getEnabledRadios,\n keys: { next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n loop: true,\n onNavigate: ({ event, item }) => {\n item.focus();\n\n if (item.tagName === 'ORE-RADIO') {\n selectRadio(item.getAttribute('value') ?? '', event);\n }\n },\n signal: lifecycleSignal(onCleanup),\n });\n\n bind({\n on: {\n change: (e: Event) => {\n if (e.target === el) return;\n\n e.stopPropagation();\n selectRadio((e.target as HTMLElement).getAttribute('value') ?? '', e);\n },\n keydown: (e: KeyboardEvent) => {\n const radios = getEnabledRadios();\n\n if (!radios.length) return;\n\n const focused = radios.indexOf(document.activeElement as HTMLElement);\n\n if (focused === -1) return;\n\n listControl.set(focused);\n listControl.handleKeydown(e);\n },\n },\n });\n\n const legendId = createId('radio-group-legend');\n\n return html`\n <fieldset\n role=\"radiogroup\"\n aria-required=\"${() => String(Boolean(props.required.value))}\"\n aria-invalid=\"${choice.ariaInvalid}\"\n aria-errormessage=\"${choice.ariaErrorMessage}\"\n aria-describedby=\"${choice.ariaDescribedBy}\">\n <legend id=\"${legendId}\" ?hidden=${() => !props.label.value}>\n ${props.label}${when(\n () => Boolean(props.required.value),\n () => html`\n <span aria-hidden=\"true\">*</span>\n `,\n )}\n </legend>\n <div class=\"radio-group-items\" part=\"items\">\n <slot></slot>\n </div>\n <div\n class=\"helper-text\"\n part=\"helper\"\n id=\"${choice.assistiveId}\"\n role=\"${() => (choice.errorText.value ? 'alert' : null)}\"\n aria-live=\"polite\"\n ?hidden=\"${() => !choice.errorText.value && !choice.helperText.value}\">\n ${() => choice.errorText.value || choice.helperText.value}\n </div>\n </fieldset>\n `;\n },\n styles: [disabledStateMixin, componentStyles],\n});\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"radio.cjs","names":[],"sources":["../src/inputs/radio/radio.ts"],"sourcesContent":["import { bind, define, getHost, html, inject, onCleanup, prop, useField } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { createCheckable, createListControl, elementDirection, lifecycleSignal } from '../../core';\nimport { CONTROL_SIZE_PRESET, disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport {\n coarsePointerMixin,\n colorThemeMixin,\n disabledStateMixin,\n forcedColorsFormControlMixin,\n sizeVariantMixin,\n} from '../../styles';\nimport type { CheckableProps, ComponentSize, ThemeColor } from '../../types';\nimport { RADIO_GROUP_CTX } from '../radio-group/radio-group';\nimport { defineFieldChecked, dispatchNativeFieldEvent, setFieldChecked } from '../shared/native-field-event';\nimport { renderHelperRegion } from '../shared/templates';\nimport componentStyles from './radio.css?inline';\n\n/** Radio component properties */\n\nexport type OreRadioEvents = {\n change: Event;\n input: Event;\n};\n\nexport type OreRadioProps = CheckableProps & {\n /** Theme color */\n color?: ThemeColor;\n /** Disable interaction */\n disabled?: boolean;\n /** Error message (marks field as invalid) */\n error?: string;\n /** Helper text displayed below the radio */\n helper?: string;\n /** Component size */\n size?: ComponentSize;\n};\n\n/**\n * A customizable radio button component for mutually exclusive selections.\n *\n * @element ore-radio\n *\n * @attr {boolean} checked - Checked state\n * @attr {boolean} disabled - Disable radio interaction\n * @attr {string} value - Field value (required for radio groups)\n * @attr {string} name - Form field name (required for radio groups)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - Radio size: 'sm' | 'md' | 'lg'\n * @attr {string} error - Error message (marks field as invalid)\n * @attr {string} helper - Helper text displayed below the radio\n *\n * @fires input - Emitted when radio is selected.\n * @fires change - Emitted when radio is selected.\n *\n * @slot - Radio button label text\n *\n * @cssprop --radio-size - Control size (width and height)\n * @cssprop --radio-bg - Unchecked background color\n * @cssprop --radio-border-color - Unchecked border color\n * @cssprop --radio-checked-bg - Selected indicator background color\n * @cssprop --radio-color - Selected indicator dot color\n * @cssprop --radio-font-size - Label font size\n * @part radio - The radio wrapper element\n * @part circle - The visual radio circle\n * @part label - The label element\n * @part helper-text - The helper/error text element\n *\n * @example\n * ```html\n * <ore-radio name=\"plan\" value=\"free\">Free</ore-radio>\n * <ore-radio name=\"plan\" value=\"pro\" checked color=\"primary\">Pro</ore-radio>\n * <ore-radio name=\"plan\" value=\"enterprise\" disabled>Enterprise</ore-radio>\n * ```\n */\nexport const RADIO_TAG = 'ore-radio' as const;\ndefine<OreRadioProps>(RADIO_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n checked: prop.bool(false),\n error: prop.string(),\n helper: prop.string(),\n name: prop.string(),\n value: prop.string(),\n },\n setup(props) {\n const el = getHost();\n\n const groupCtx = inject(RADIO_GROUP_CTX);\n const effectiveName = computed(() => groupCtx?.name.value || props.name.value || '');\n const effectiveSize = computed(() => groupCtx?.size.value ?? props.size.value);\n const effectiveColor = computed(() => groupCtx?.color.value ?? props.color.value);\n const checkedFromState = computed(() => {\n if (groupCtx) return groupCtx.value.value === props.value.value;\n\n return Boolean(props.checked.value);\n });\n\n const getRadioGroup = (): HTMLElement[] => {\n const radioName = effectiveName.value;\n\n if (!radioName) return [];\n\n return Array.from(document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`)).filter(\n (r) => !r.hasAttribute('disabled'),\n );\n };\n\n const selectRadio = (radio: HTMLElement, originalEvent?: Event): void => {\n if (groupCtx) {\n groupCtx.select(radio.getAttribute('value') ?? '', originalEvent);\n\n return;\n }\n\n radio.click();\n };\n\n const listControl = createListControl({\n direction: () => elementDirection(el),\n getItems: () => getRadioGroup(),\n // 'both' accepts every arrow key regardless of the group's visual layout (radio groups\n // may render horizontally or vertically via CSS) — RTL mirroring of Left/Right still\n // applies via `direction` since nav.ts's default keymap covers 'both' too.\n loop: true,\n onNavigate: ({ event, item }) => {\n item.focus();\n selectRadio(item, event);\n },\n orientation: 'both',\n signal: lifecycleSignal(onCleanup),\n });\n\n const activateSelf = (originalEvent?: Event): void => {\n if (checkable.checked.value) return;\n\n if (groupCtx) {\n groupCtx.select(props.value.value ?? '', originalEvent);\n\n return;\n }\n\n checkable.toggle(originalEvent ?? new Event('change'));\n };\n\n const checkable = createCheckable({\n checked: checkedFromState,\n disabled: computed(() => props.disabled.value || Boolean(groupCtx?.disabled.value)),\n error: props.error,\n helper: props.helper,\n onToggle: (payload) => {\n setFieldChecked(el, payload.checked);\n dispatchNativeFieldEvent(el, 'input');\n dispatchNativeFieldEvent(el, 'change');\n },\n prefix: 'radio',\n signal: lifecycleSignal(onCleanup),\n value: props.value,\n });\n\n checkable.attachFormField(\n useField<string | null>({\n disabled: checkable.disabled,\n toFormValue: (v) => v,\n value: checkable.checkableFormValue,\n }),\n );\n\n const { assistiveId, checked, disabled, errorText, helperText, labelId, toggle } = checkable;\n\n defineFieldChecked(\n el,\n () => checked.value,\n (value) => {\n checked.value = value;\n },\n );\n\n bind({\n attr: {\n 'aria-checked': () => (checked.value ? 'true' : 'false'),\n 'aria-describedby': () => (errorText.value || helperText.value ? assistiveId : null),\n 'aria-disabled': () => (disabled.value ? 'true' : null),\n 'aria-invalid': () => (errorText.value ? 'true' : null),\n 'aria-labelledby': labelId,\n checked,\n color: effectiveColor,\n disabled: () => (disabled.value ? true : undefined),\n name: () => effectiveName.value || undefined,\n role: 'radio',\n size: effectiveSize,\n tabindex: () => {\n if (disabled.value) return undefined;\n\n return checked.value ? 0 : -1;\n },\n },\n class: () => ({\n 'is-checked': checked.value,\n 'is-disabled': disabled.value,\n }),\n on: {\n click: (e: MouseEvent) => {\n if (disabled.value) return;\n\n if (groupCtx) {\n groupCtx.select(props.value.value ?? '', e);\n } else {\n if (!effectiveName.value) return;\n\n if (!checked.value) {\n const radioName = props.name.value;\n const allRadios = document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`);\n\n allRadios.forEach((radio) => {\n if (radio !== el) radio.removeAttribute('checked');\n });\n\n toggle(e);\n }\n }\n },\n keydown: (e: KeyboardEvent) => {\n if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n activateSelf(e);\n\n return;\n }\n\n const radios = getRadioGroup();\n\n if (radios.length === 0) return;\n\n const selfIndex = radios.indexOf(el);\n\n if (selfIndex === -1) return;\n\n listControl.set(selfIndex);\n listControl.handleKeydown(e);\n },\n },\n });\n\n return html`\n <div class=\"radio-wrapper\" part=\"radio\">\n <div class=\"circle\" part=\"circle\">\n <div class=\"dot\" part=\"dot\"></div>\n </div>\n </div>\n <span class=\"label\" part=\"label\" id=\"${labelId}\"><slot></slot></span>\n ${renderHelperRegion(assistiveId, errorText, helperText)}\n `;\n },\n styles: [\n colorThemeMixin,\n forcedColorsFormControlMixin,\n disabledStateMixin,\n coarsePointerMixin,\n sizeVariantMixin(CONTROL_SIZE_PRESET),\n componentStyles,\n ],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"radio.cjs","names":[],"sources":["../src/inputs/radio/radio.ts"],"sourcesContent":["import { bind, define, getHost, html, inject, onCleanup, prop, useField } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { createCheckable, createListControl, elementDirection, lifecycleSignal } from '../../core';\nimport { CONTROL_SIZE_PRESET, disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport {\n coarsePointerMixin,\n colorThemeMixin,\n disabledStateMixin,\n forcedColorsFormControlMixin,\n sizeVariantMixin,\n} from '../../styles';\nimport type { CheckableProps, ComponentSize, ThemeColor } from '../../types';\nimport { RADIO_GROUP_CTX } from '../radio-group/radio-group';\nimport { defineFieldChecked, dispatchNativeFieldEvent, setFieldChecked } from '../shared/native-field-event';\nimport { renderHelperRegion } from '../shared/templates';\nimport componentStyles from './radio.css?inline';\n\n/** Radio component properties */\n\nexport type OreRadioEvents = {\n change: Event;\n input: Event;\n};\n\nexport type OreRadioProps = CheckableProps & {\n /** Theme color */\n color?: ThemeColor;\n /** Disable interaction */\n disabled?: boolean;\n /** Error message (marks field as invalid) */\n error?: string;\n /** Helper text displayed below the radio */\n helper?: string;\n /** Component size */\n size?: ComponentSize;\n};\n\n/**\n * A customizable radio button component for mutually exclusive selections.\n *\n * @element ore-radio\n *\n * @attr {boolean} checked - Checked state\n * @attr {boolean} disabled - Disable radio interaction\n * @attr {string} value - Field value (required for radio groups)\n * @attr {string} name - Form field name (required for radio groups)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - Radio size: 'sm' | 'md' | 'lg'\n * @attr {string} error - Error message (marks field as invalid)\n * @attr {string} helper - Helper text displayed below the radio\n *\n * @fires input - Emitted when radio is selected.\n * @fires change - Emitted when radio is selected.\n *\n * @slot - Radio button label text\n *\n * @cssprop --radio-size - Control size (width and height)\n * @cssprop --radio-bg - Unchecked background color\n * @cssprop --radio-border-color - Unchecked border color\n * @cssprop --radio-checked-bg - Selected indicator background color\n * @cssprop --radio-color - Selected indicator dot color\n * @cssprop --radio-font-size - Label font size\n * @part radio - The radio wrapper element\n * @part circle - The visual radio circle\n * @part label - The label element\n * @part helper-text - The helper/error text element\n *\n * @example\n * ```html\n * <ore-radio name=\"plan\" value=\"free\">Free</ore-radio>\n * <ore-radio name=\"plan\" value=\"pro\" checked color=\"primary\">Pro</ore-radio>\n * <ore-radio name=\"plan\" value=\"enterprise\" disabled>Enterprise</ore-radio>\n * ```\n */\nexport const RADIO_TAG = 'ore-radio' as const;\ndefine<OreRadioProps>(RADIO_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n checked: prop.bool(false),\n error: prop.string(),\n helper: prop.string(),\n name: prop.string(),\n value: prop.string(),\n },\n setup(props) {\n const el = getHost();\n\n const groupCtx = inject(RADIO_GROUP_CTX);\n const effectiveName = computed(() => groupCtx?.name.value || props.name.value || '');\n const effectiveSize = computed(() => groupCtx?.size.value ?? props.size.value);\n const effectiveColor = computed(() => groupCtx?.color.value ?? props.color.value);\n const checkedFromState = computed(() => {\n if (groupCtx) return groupCtx.value.value === props.value.value;\n\n return Boolean(props.checked.value);\n });\n\n const getRadioGroup = (): HTMLElement[] => {\n const radioName = effectiveName.value;\n\n if (!radioName) return [];\n\n return Array.from(document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`)).filter(\n (r) => !r.hasAttribute('disabled'),\n );\n };\n\n const selectRadio = (radio: HTMLElement, originalEvent?: Event): void => {\n if (groupCtx) {\n groupCtx.select(radio.getAttribute('value') ?? '', originalEvent);\n\n return;\n }\n\n radio.click();\n };\n\n const listControl = createListControl({\n direction: () => elementDirection(el),\n getItems: () => getRadioGroup(),\n // 'both' accepts every arrow key regardless of the group's visual layout (radio groups\n // may render horizontally or vertically via CSS): RTL mirroring of Left/Right still\n // applies via `direction` since nav.ts's default keymap covers 'both' too.\n loop: true,\n onNavigate: ({ event, item }) => {\n item.focus();\n selectRadio(item, event);\n },\n orientation: 'both',\n signal: lifecycleSignal(onCleanup),\n });\n\n const activateSelf = (originalEvent?: Event): void => {\n if (checkable.checked.value) return;\n\n if (groupCtx) {\n groupCtx.select(props.value.value ?? '', originalEvent);\n\n return;\n }\n\n checkable.toggle(originalEvent ?? new Event('change'));\n };\n\n const checkable = createCheckable({\n checked: checkedFromState,\n disabled: computed(() => props.disabled.value || Boolean(groupCtx?.disabled.value)),\n error: props.error,\n helper: props.helper,\n onToggle: (payload) => {\n setFieldChecked(el, payload.checked);\n dispatchNativeFieldEvent(el, 'input');\n dispatchNativeFieldEvent(el, 'change');\n },\n prefix: 'radio',\n signal: lifecycleSignal(onCleanup),\n value: props.value,\n });\n\n checkable.attachFormField(\n useField<string | null>({\n disabled: checkable.disabled,\n toFormValue: (v) => v,\n value: checkable.checkableFormValue,\n }),\n );\n\n const { assistiveId, checked, disabled, errorText, helperText, labelId, toggle } = checkable;\n\n defineFieldChecked(\n el,\n () => checked.value,\n (value) => {\n checked.value = value;\n },\n );\n\n bind({\n attr: {\n 'aria-checked': () => (checked.value ? 'true' : 'false'),\n 'aria-describedby': () => (errorText.value || helperText.value ? assistiveId : null),\n 'aria-disabled': () => (disabled.value ? 'true' : null),\n 'aria-invalid': () => (errorText.value ? 'true' : null),\n 'aria-labelledby': labelId,\n checked,\n color: effectiveColor,\n disabled: () => (disabled.value ? true : undefined),\n name: () => effectiveName.value || undefined,\n role: 'radio',\n size: effectiveSize,\n tabindex: () => {\n if (disabled.value) return undefined;\n\n return checked.value ? 0 : -1;\n },\n },\n class: () => ({\n 'is-checked': checked.value,\n 'is-disabled': disabled.value,\n }),\n on: {\n click: (e: MouseEvent) => {\n if (disabled.value) return;\n\n if (groupCtx) {\n groupCtx.select(props.value.value ?? '', e);\n } else {\n if (!effectiveName.value) return;\n\n if (!checked.value) {\n const radioName = props.name.value;\n const allRadios = document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`);\n\n allRadios.forEach((radio) => {\n if (radio !== el) radio.removeAttribute('checked');\n });\n\n toggle(e);\n }\n }\n },\n keydown: (e: KeyboardEvent) => {\n if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n activateSelf(e);\n\n return;\n }\n\n const radios = getRadioGroup();\n\n if (radios.length === 0) return;\n\n const selfIndex = radios.indexOf(el);\n\n if (selfIndex === -1) return;\n\n listControl.set(selfIndex);\n listControl.handleKeydown(e);\n },\n },\n });\n\n return html`\n <div class=\"radio-wrapper\" part=\"radio\">\n <div class=\"circle\" part=\"circle\">\n <div class=\"dot\" part=\"dot\"></div>\n </div>\n </div>\n <span class=\"label\" part=\"label\" id=\"${labelId}\"><slot></slot></span>\n ${renderHelperRegion(assistiveId, errorText, helperText)}\n `;\n },\n styles: [\n colorThemeMixin,\n forcedColorsFormControlMixin,\n disabledStateMixin,\n coarsePointerMixin,\n sizeVariantMixin(CONTROL_SIZE_PRESET),\n componentStyles,\n ],\n});\n"],"mappings":"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"}
package/dist/radio.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"radio.js","names":[],"sources":["../src/inputs/radio/radio.ts"],"sourcesContent":["import { bind, define, getHost, html, inject, onCleanup, prop, useField } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { createCheckable, createListControl, elementDirection, lifecycleSignal } from '../../core';\nimport { CONTROL_SIZE_PRESET, disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport {\n coarsePointerMixin,\n colorThemeMixin,\n disabledStateMixin,\n forcedColorsFormControlMixin,\n sizeVariantMixin,\n} from '../../styles';\nimport type { CheckableProps, ComponentSize, ThemeColor } from '../../types';\nimport { RADIO_GROUP_CTX } from '../radio-group/radio-group';\nimport { defineFieldChecked, dispatchNativeFieldEvent, setFieldChecked } from '../shared/native-field-event';\nimport { renderHelperRegion } from '../shared/templates';\nimport componentStyles from './radio.css?inline';\n\n/** Radio component properties */\n\nexport type OreRadioEvents = {\n change: Event;\n input: Event;\n};\n\nexport type OreRadioProps = CheckableProps & {\n /** Theme color */\n color?: ThemeColor;\n /** Disable interaction */\n disabled?: boolean;\n /** Error message (marks field as invalid) */\n error?: string;\n /** Helper text displayed below the radio */\n helper?: string;\n /** Component size */\n size?: ComponentSize;\n};\n\n/**\n * A customizable radio button component for mutually exclusive selections.\n *\n * @element ore-radio\n *\n * @attr {boolean} checked - Checked state\n * @attr {boolean} disabled - Disable radio interaction\n * @attr {string} value - Field value (required for radio groups)\n * @attr {string} name - Form field name (required for radio groups)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - Radio size: 'sm' | 'md' | 'lg'\n * @attr {string} error - Error message (marks field as invalid)\n * @attr {string} helper - Helper text displayed below the radio\n *\n * @fires input - Emitted when radio is selected.\n * @fires change - Emitted when radio is selected.\n *\n * @slot - Radio button label text\n *\n * @cssprop --radio-size - Control size (width and height)\n * @cssprop --radio-bg - Unchecked background color\n * @cssprop --radio-border-color - Unchecked border color\n * @cssprop --radio-checked-bg - Selected indicator background color\n * @cssprop --radio-color - Selected indicator dot color\n * @cssprop --radio-font-size - Label font size\n * @part radio - The radio wrapper element\n * @part circle - The visual radio circle\n * @part label - The label element\n * @part helper-text - The helper/error text element\n *\n * @example\n * ```html\n * <ore-radio name=\"plan\" value=\"free\">Free</ore-radio>\n * <ore-radio name=\"plan\" value=\"pro\" checked color=\"primary\">Pro</ore-radio>\n * <ore-radio name=\"plan\" value=\"enterprise\" disabled>Enterprise</ore-radio>\n * ```\n */\nexport const RADIO_TAG = 'ore-radio' as const;\ndefine<OreRadioProps>(RADIO_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n checked: prop.bool(false),\n error: prop.string(),\n helper: prop.string(),\n name: prop.string(),\n value: prop.string(),\n },\n setup(props) {\n const el = getHost();\n\n const groupCtx = inject(RADIO_GROUP_CTX);\n const effectiveName = computed(() => groupCtx?.name.value || props.name.value || '');\n const effectiveSize = computed(() => groupCtx?.size.value ?? props.size.value);\n const effectiveColor = computed(() => groupCtx?.color.value ?? props.color.value);\n const checkedFromState = computed(() => {\n if (groupCtx) return groupCtx.value.value === props.value.value;\n\n return Boolean(props.checked.value);\n });\n\n const getRadioGroup = (): HTMLElement[] => {\n const radioName = effectiveName.value;\n\n if (!radioName) return [];\n\n return Array.from(document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`)).filter(\n (r) => !r.hasAttribute('disabled'),\n );\n };\n\n const selectRadio = (radio: HTMLElement, originalEvent?: Event): void => {\n if (groupCtx) {\n groupCtx.select(radio.getAttribute('value') ?? '', originalEvent);\n\n return;\n }\n\n radio.click();\n };\n\n const listControl = createListControl({\n direction: () => elementDirection(el),\n getItems: () => getRadioGroup(),\n // 'both' accepts every arrow key regardless of the group's visual layout (radio groups\n // may render horizontally or vertically via CSS) — RTL mirroring of Left/Right still\n // applies via `direction` since nav.ts's default keymap covers 'both' too.\n loop: true,\n onNavigate: ({ event, item }) => {\n item.focus();\n selectRadio(item, event);\n },\n orientation: 'both',\n signal: lifecycleSignal(onCleanup),\n });\n\n const activateSelf = (originalEvent?: Event): void => {\n if (checkable.checked.value) return;\n\n if (groupCtx) {\n groupCtx.select(props.value.value ?? '', originalEvent);\n\n return;\n }\n\n checkable.toggle(originalEvent ?? new Event('change'));\n };\n\n const checkable = createCheckable({\n checked: checkedFromState,\n disabled: computed(() => props.disabled.value || Boolean(groupCtx?.disabled.value)),\n error: props.error,\n helper: props.helper,\n onToggle: (payload) => {\n setFieldChecked(el, payload.checked);\n dispatchNativeFieldEvent(el, 'input');\n dispatchNativeFieldEvent(el, 'change');\n },\n prefix: 'radio',\n signal: lifecycleSignal(onCleanup),\n value: props.value,\n });\n\n checkable.attachFormField(\n useField<string | null>({\n disabled: checkable.disabled,\n toFormValue: (v) => v,\n value: checkable.checkableFormValue,\n }),\n );\n\n const { assistiveId, checked, disabled, errorText, helperText, labelId, toggle } = checkable;\n\n defineFieldChecked(\n el,\n () => checked.value,\n (value) => {\n checked.value = value;\n },\n );\n\n bind({\n attr: {\n 'aria-checked': () => (checked.value ? 'true' : 'false'),\n 'aria-describedby': () => (errorText.value || helperText.value ? assistiveId : null),\n 'aria-disabled': () => (disabled.value ? 'true' : null),\n 'aria-invalid': () => (errorText.value ? 'true' : null),\n 'aria-labelledby': labelId,\n checked,\n color: effectiveColor,\n disabled: () => (disabled.value ? true : undefined),\n name: () => effectiveName.value || undefined,\n role: 'radio',\n size: effectiveSize,\n tabindex: () => {\n if (disabled.value) return undefined;\n\n return checked.value ? 0 : -1;\n },\n },\n class: () => ({\n 'is-checked': checked.value,\n 'is-disabled': disabled.value,\n }),\n on: {\n click: (e: MouseEvent) => {\n if (disabled.value) return;\n\n if (groupCtx) {\n groupCtx.select(props.value.value ?? '', e);\n } else {\n if (!effectiveName.value) return;\n\n if (!checked.value) {\n const radioName = props.name.value;\n const allRadios = document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`);\n\n allRadios.forEach((radio) => {\n if (radio !== el) radio.removeAttribute('checked');\n });\n\n toggle(e);\n }\n }\n },\n keydown: (e: KeyboardEvent) => {\n if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n activateSelf(e);\n\n return;\n }\n\n const radios = getRadioGroup();\n\n if (radios.length === 0) return;\n\n const selfIndex = radios.indexOf(el);\n\n if (selfIndex === -1) return;\n\n listControl.set(selfIndex);\n listControl.handleKeydown(e);\n },\n },\n });\n\n return html`\n <div class=\"radio-wrapper\" part=\"radio\">\n <div class=\"circle\" part=\"circle\">\n <div class=\"dot\" part=\"dot\"></div>\n </div>\n </div>\n <span class=\"label\" part=\"label\" id=\"${labelId}\"><slot></slot></span>\n ${renderHelperRegion(assistiveId, errorText, helperText)}\n `;\n },\n styles: [\n colorThemeMixin,\n forcedColorsFormControlMixin,\n disabledStateMixin,\n coarsePointerMixin,\n sizeVariantMixin(CONTROL_SIZE_PRESET),\n componentStyles,\n ],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"radio.js","names":[],"sources":["../src/inputs/radio/radio.ts"],"sourcesContent":["import { bind, define, getHost, html, inject, onCleanup, prop, useField } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { createCheckable, createListControl, elementDirection, lifecycleSignal } from '../../core';\nimport { CONTROL_SIZE_PRESET, disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport {\n coarsePointerMixin,\n colorThemeMixin,\n disabledStateMixin,\n forcedColorsFormControlMixin,\n sizeVariantMixin,\n} from '../../styles';\nimport type { CheckableProps, ComponentSize, ThemeColor } from '../../types';\nimport { RADIO_GROUP_CTX } from '../radio-group/radio-group';\nimport { defineFieldChecked, dispatchNativeFieldEvent, setFieldChecked } from '../shared/native-field-event';\nimport { renderHelperRegion } from '../shared/templates';\nimport componentStyles from './radio.css?inline';\n\n/** Radio component properties */\n\nexport type OreRadioEvents = {\n change: Event;\n input: Event;\n};\n\nexport type OreRadioProps = CheckableProps & {\n /** Theme color */\n color?: ThemeColor;\n /** Disable interaction */\n disabled?: boolean;\n /** Error message (marks field as invalid) */\n error?: string;\n /** Helper text displayed below the radio */\n helper?: string;\n /** Component size */\n size?: ComponentSize;\n};\n\n/**\n * A customizable radio button component for mutually exclusive selections.\n *\n * @element ore-radio\n *\n * @attr {boolean} checked - Checked state\n * @attr {boolean} disabled - Disable radio interaction\n * @attr {string} value - Field value (required for radio groups)\n * @attr {string} name - Form field name (required for radio groups)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - Radio size: 'sm' | 'md' | 'lg'\n * @attr {string} error - Error message (marks field as invalid)\n * @attr {string} helper - Helper text displayed below the radio\n *\n * @fires input - Emitted when radio is selected.\n * @fires change - Emitted when radio is selected.\n *\n * @slot - Radio button label text\n *\n * @cssprop --radio-size - Control size (width and height)\n * @cssprop --radio-bg - Unchecked background color\n * @cssprop --radio-border-color - Unchecked border color\n * @cssprop --radio-checked-bg - Selected indicator background color\n * @cssprop --radio-color - Selected indicator dot color\n * @cssprop --radio-font-size - Label font size\n * @part radio - The radio wrapper element\n * @part circle - The visual radio circle\n * @part label - The label element\n * @part helper-text - The helper/error text element\n *\n * @example\n * ```html\n * <ore-radio name=\"plan\" value=\"free\">Free</ore-radio>\n * <ore-radio name=\"plan\" value=\"pro\" checked color=\"primary\">Pro</ore-radio>\n * <ore-radio name=\"plan\" value=\"enterprise\" disabled>Enterprise</ore-radio>\n * ```\n */\nexport const RADIO_TAG = 'ore-radio' as const;\ndefine<OreRadioProps>(RADIO_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n checked: prop.bool(false),\n error: prop.string(),\n helper: prop.string(),\n name: prop.string(),\n value: prop.string(),\n },\n setup(props) {\n const el = getHost();\n\n const groupCtx = inject(RADIO_GROUP_CTX);\n const effectiveName = computed(() => groupCtx?.name.value || props.name.value || '');\n const effectiveSize = computed(() => groupCtx?.size.value ?? props.size.value);\n const effectiveColor = computed(() => groupCtx?.color.value ?? props.color.value);\n const checkedFromState = computed(() => {\n if (groupCtx) return groupCtx.value.value === props.value.value;\n\n return Boolean(props.checked.value);\n });\n\n const getRadioGroup = (): HTMLElement[] => {\n const radioName = effectiveName.value;\n\n if (!radioName) return [];\n\n return Array.from(document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`)).filter(\n (r) => !r.hasAttribute('disabled'),\n );\n };\n\n const selectRadio = (radio: HTMLElement, originalEvent?: Event): void => {\n if (groupCtx) {\n groupCtx.select(radio.getAttribute('value') ?? '', originalEvent);\n\n return;\n }\n\n radio.click();\n };\n\n const listControl = createListControl({\n direction: () => elementDirection(el),\n getItems: () => getRadioGroup(),\n // 'both' accepts every arrow key regardless of the group's visual layout (radio groups\n // may render horizontally or vertically via CSS): RTL mirroring of Left/Right still\n // applies via `direction` since nav.ts's default keymap covers 'both' too.\n loop: true,\n onNavigate: ({ event, item }) => {\n item.focus();\n selectRadio(item, event);\n },\n orientation: 'both',\n signal: lifecycleSignal(onCleanup),\n });\n\n const activateSelf = (originalEvent?: Event): void => {\n if (checkable.checked.value) return;\n\n if (groupCtx) {\n groupCtx.select(props.value.value ?? '', originalEvent);\n\n return;\n }\n\n checkable.toggle(originalEvent ?? new Event('change'));\n };\n\n const checkable = createCheckable({\n checked: checkedFromState,\n disabled: computed(() => props.disabled.value || Boolean(groupCtx?.disabled.value)),\n error: props.error,\n helper: props.helper,\n onToggle: (payload) => {\n setFieldChecked(el, payload.checked);\n dispatchNativeFieldEvent(el, 'input');\n dispatchNativeFieldEvent(el, 'change');\n },\n prefix: 'radio',\n signal: lifecycleSignal(onCleanup),\n value: props.value,\n });\n\n checkable.attachFormField(\n useField<string | null>({\n disabled: checkable.disabled,\n toFormValue: (v) => v,\n value: checkable.checkableFormValue,\n }),\n );\n\n const { assistiveId, checked, disabled, errorText, helperText, labelId, toggle } = checkable;\n\n defineFieldChecked(\n el,\n () => checked.value,\n (value) => {\n checked.value = value;\n },\n );\n\n bind({\n attr: {\n 'aria-checked': () => (checked.value ? 'true' : 'false'),\n 'aria-describedby': () => (errorText.value || helperText.value ? assistiveId : null),\n 'aria-disabled': () => (disabled.value ? 'true' : null),\n 'aria-invalid': () => (errorText.value ? 'true' : null),\n 'aria-labelledby': labelId,\n checked,\n color: effectiveColor,\n disabled: () => (disabled.value ? true : undefined),\n name: () => effectiveName.value || undefined,\n role: 'radio',\n size: effectiveSize,\n tabindex: () => {\n if (disabled.value) return undefined;\n\n return checked.value ? 0 : -1;\n },\n },\n class: () => ({\n 'is-checked': checked.value,\n 'is-disabled': disabled.value,\n }),\n on: {\n click: (e: MouseEvent) => {\n if (disabled.value) return;\n\n if (groupCtx) {\n groupCtx.select(props.value.value ?? '', e);\n } else {\n if (!effectiveName.value) return;\n\n if (!checked.value) {\n const radioName = props.name.value;\n const allRadios = document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`);\n\n allRadios.forEach((radio) => {\n if (radio !== el) radio.removeAttribute('checked');\n });\n\n toggle(e);\n }\n }\n },\n keydown: (e: KeyboardEvent) => {\n if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n activateSelf(e);\n\n return;\n }\n\n const radios = getRadioGroup();\n\n if (radios.length === 0) return;\n\n const selfIndex = radios.indexOf(el);\n\n if (selfIndex === -1) return;\n\n listControl.set(selfIndex);\n listControl.handleKeydown(e);\n },\n },\n });\n\n return html`\n <div class=\"radio-wrapper\" part=\"radio\">\n <div class=\"circle\" part=\"circle\">\n <div class=\"dot\" part=\"dot\"></div>\n </div>\n </div>\n <span class=\"label\" part=\"label\" id=\"${labelId}\"><slot></slot></span>\n ${renderHelperRegion(assistiveId, errorText, helperText)}\n `;\n },\n styles: [\n colorThemeMixin,\n forcedColorsFormControlMixin,\n disabledStateMixin,\n coarsePointerMixin,\n sizeVariantMixin(CONTROL_SIZE_PRESET),\n componentStyles,\n ],\n});\n"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./content/rank-list/rank-item.css.cjs"),n=require("./rank-list.cjs");let r=require("@vielzeug/ripple"),i=require("@vielzeug/ore");var a=`ore-rank-item`;(0,i.define)(a,{props:{bar:i.prop.bool(!0),rank:i.prop.number(),value:i.prop.number()},setup(e){let t=(0,i.useSlots)(),a=t.has(`description`),o=t.has(`leading`),s=(0,i.inject)(n.RANK_CTX),c=(0,r.computed)(()=>e.bar.value&&e.value.value!=null&&s!=null),l=(0,r.computed)(()=>{let t=s?.scale.value??1,n=e.value.value;return n==null||t<=0?0:Math.max(0,Math.min(1,n/t))}),u=new Intl.NumberFormat,d=(0,r.computed)(()=>{let t=e.value.value;return t==null?``:u.format(t)}),f=(0,r.computed)(()=>t.has(`trailing`).value||e.value.value!=null);return(0,i.bind)({attr:{role:()=>`listitem`}}),i.html`
2
+ <div class="row" part="row">
3
+ <span class="rank" part="rank" aria-hidden="true" ?hidden="${()=>e.rank.value==null}"
4
+ >${()=>e.rank.value??``}</span
5
+ >
6
+ <span class="leading" part="leading" ?hidden="${()=>!o.value}"><slot name="leading"></slot></span>
7
+ <span class="content" part="content">
8
+ <span class="label" part="label"><slot></slot></span>
9
+ <span class="description" part="description" ?hidden="${()=>!a.value}"
10
+ ><slot name="description"></slot
11
+ ></span>
12
+ </span>
13
+ <span class="trailing" part="trailing" ?hidden="${()=>!f.value}"
14
+ ><slot name="trailing">${d}</slot></span
15
+ >
16
+ </div>
17
+ <div class="bar" part="bar" aria-hidden="true" ?hidden="${()=>!c.value}">
18
+ <span class="bar-fill" part="bar-fill" style="${()=>`--_fill: ${l.value}`}"></span>
19
+ </div>
20
+ `},styles:[e.reducedMotionMixin,t.default]}),exports.RANK_ITEM_TAG=a;
21
+ //# sourceMappingURL=rank-item.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rank-item.cjs","names":[],"sources":["../src/content/rank-list/rank-item.ts"],"sourcesContent":["import { bind, define, html, inject, prop, useSlots } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\n\nimport { reducedMotionMixin } from '../../styles';\nimport componentStyles from './rank-item.css?inline';\nimport { RANK_CTX } from './rank-list';\n\n/** Rank-item component properties */\nexport type OreRankItemProps = {\n /**\n * Show the proportional bar (default). Disable with `bar=\"false\"` for rows whose `value` is\n * not a comparable count (a median duration, a rating), where a scaled bar would imply a\n * ranking the data does not support.\n */\n bar?: boolean;\n /**\n * Rank position within the parent `ore-rank-list`, starting at 1. Assigned automatically in\n * DOM order on every rescan; a hand-written value is overwritten the moment the list syncs.\n */\n rank?: number;\n /**\n * The row's numeric value. Against the largest sibling `value` it sizes the proportional\n * bar, and without a `trailing` slot it is also the row's own formatted display. Omit it on\n * rows whose display is not a count (pass the formatted text in the `trailing` slot instead).\n */\n value?: number;\n};\n\n/**\n * A single ranked row inside `ore-rank-list`: a rank numeral, an optional `leading` slot\n * (avatar, icon), the label, a secondary `description` line, and a right-aligned `trailing`\n * value, with a proportional bar under the row sized against the largest sibling `value`.\n * The row is the whole visualization: the label and value stay text, so the ranking is\n * readable and accessible with the bar treated as reinforcement, never as the only carrier.\n *\n * @element ore-rank-item\n *\n * @attr {boolean} bar - Show the proportional bar (default). Disable with `bar=\"false\"` for non-count rows.\n * @attr {number} rank - Rank position: assigned automatically by the parent list in DOM order; manual values are overwritten.\n * @attr {number} value - The row's numeric value: sizes the bar against the largest sibling, and is the formatted display when no `trailing` slot is present.\n *\n * @slot leading - Content before the label (e.g. avatar, icon)\n * @slot - Item label\n * @slot description - Secondary line below the label (e.g. sample count)\n * @slot trailing - Right-aligned value display; replaces the formatted `value`\n *\n * @cssprop --rank-number-color - Rank numeral color (default --color-contrast-500)\n * @cssprop --rank-number-width - Rank numeral column width (default --size-6)\n * @cssprop --rank-bar-color - Bar fill color (default --color-primary)\n * @cssprop --rank-track-color - Bar track color (default --color-contrast-100)\n * @cssprop --rank-bar-height - Bar height (default --size-1)\n *\n * @part row - The text row (rank, leading, label, trailing)\n * @part rank - The rank numeral (aria-hidden: the list order carries it)\n * @part leading - The leading slot container\n * @part content - The label + description container\n * @part label - The label slot container\n * @part description - The description slot container\n * @part trailing - The trailing value container\n * @part bar - The bar track\n * @part bar-fill - The bar fill\n * @example\n * ```html\n * <ore-rank-item value=\"8\">\n * <ore-avatar slot=\"leading\" src=\"alex.png\" alt=\"\" rounded=\"full\" size=\"sm\"></ore-avatar>\n * Alex\n * </ore-rank-item>\n * <ore-rank-item value=\"6\" bar=\"false\">\n * Overview\n * <span slot=\"description\">4 timed sessions</span>\n * <span slot=\"trailing\">24m 11s</span>\n * </ore-rank-item>\n * ```\n */\nexport const RANK_ITEM_TAG = 'ore-rank-item' as const;\ndefine<OreRankItemProps>(RANK_ITEM_TAG, {\n props: {\n bar: prop.bool(true),\n rank: prop.number(),\n value: prop.number(),\n },\n\n setup(props) {\n const slots = useSlots<'description' | 'leading' | 'trailing'>();\n const hasDescription = slots.has('description');\n const hasLeading = slots.has('leading');\n\n // Absent outside an `ore-rank-list`: the row degrades to a plain labeled line (no rank,\n // no bar), the same standalone shape `ore-list-item` keeps outside its list.\n const rankCtx = inject(RANK_CTX);\n\n const showBar = computed(() => props.bar.value && props.value.value != null && rankCtx != null);\n\n // The largest sibling value is the full track; anything above it is clamped so a stale\n // `value` written between rescans can never push the bar past the row. The share lands on\n // the fill's `--_fill` custom property, which its transform consumes: see rank-item.css.\n const barFill = computed(() => {\n const scale = rankCtx?.scale.value ?? 1;\n const value = props.value.value;\n\n if (value == null || scale <= 0) return 0;\n\n return Math.max(0, Math.min(1, value / scale));\n });\n\n const formatCount = new Intl.NumberFormat();\n const formattedValue = computed(() => {\n const value = props.value.value;\n\n return value == null ? '' : formatCount.format(value);\n });\n\n const hasTrailing = computed(() => slots.has('trailing').value || props.value.value != null);\n\n bind({\n attr: {\n role: () => 'listitem',\n },\n });\n\n return html`\n <div class=\"row\" part=\"row\">\n <span class=\"rank\" part=\"rank\" aria-hidden=\"true\" ?hidden=\"${() => props.rank.value == null}\"\n >${() => props.rank.value ?? ''}</span\n >\n <span class=\"leading\" part=\"leading\" ?hidden=\"${() => !hasLeading.value}\"><slot name=\"leading\"></slot></span>\n <span class=\"content\" part=\"content\">\n <span class=\"label\" part=\"label\"><slot></slot></span>\n <span class=\"description\" part=\"description\" ?hidden=\"${() => !hasDescription.value}\"\n ><slot name=\"description\"></slot\n ></span>\n </span>\n <span class=\"trailing\" part=\"trailing\" ?hidden=\"${() => !hasTrailing.value}\"\n ><slot name=\"trailing\">${formattedValue}</slot></span\n >\n </div>\n <div class=\"bar\" part=\"bar\" aria-hidden=\"true\" ?hidden=\"${() => !showBar.value}\">\n <span class=\"bar-fill\" part=\"bar-fill\" style=\"${() => `--_fill: ${barFill.value}`}\"></span>\n </div>\n `;\n },\n\n styles: [reducedMotionMixin, componentStyles],\n});\n"],"mappings":"yQA0EA,IAAa,EAAgB,iBAC7B,EAAA,EAAA,OAAA,CAAyB,EAAe,CACtC,MAAO,CACL,IAAK,EAAA,KAAK,KAAK,EAAI,EACnB,KAAM,EAAA,KAAK,OAAO,EAClB,MAAO,EAAA,KAAK,OAAO,CACrB,EAEA,MAAM,EAAO,CACX,IAAM,GAAA,EAAQ,EAAA,SAAA,CAAiD,EACzD,EAAiB,EAAM,IAAI,aAAa,EACxC,EAAa,EAAM,IAAI,SAAS,EAIhC,GAAA,EAAU,EAAA,OAAA,CAAO,EAAA,QAAQ,EAEzB,GAAA,EAAU,EAAA,SAAA,KAAe,EAAM,IAAI,OAAS,EAAM,MAAM,OAAS,MAAQ,GAAW,IAAI,EAKxF,GAAA,EAAU,EAAA,SAAA,KAAe,CAC7B,IAAM,EAAQ,GAAS,MAAM,OAAS,EAChC,EAAQ,EAAM,MAAM,MAI1B,OAFI,GAAS,MAAQ,GAAS,EAAU,EAEjC,KAAK,IAAI,EAAG,KAAK,IAAI,EAAG,EAAQ,CAAK,CAAC,CAC/C,CAAC,EAEK,EAAc,IAAI,KAAK,aACvB,GAAA,EAAiB,EAAA,SAAA,KAAe,CACpC,IAAM,EAAQ,EAAM,MAAM,MAE1B,OAAO,GAAS,KAAO,GAAK,EAAY,OAAO,CAAK,CACtD,CAAC,EAEK,GAAA,EAAc,EAAA,SAAA,KAAe,EAAM,IAAI,UAAU,CAAC,CAAC,OAAS,EAAM,MAAM,OAAS,IAAI,EAQ3F,OANA,EAAA,EAAA,KAAA,CAAK,CACH,KAAM,CACJ,SAAY,UACd,CACF,CAAC,EAEM,EAAA,IAAI;;yEAE4D,EAAM,KAAK,OAAS,KAAK;iBACjF,EAAM,KAAK,OAAS,GAAG;;4DAEoB,CAAC,EAAW,MAAM;;;sEAGR,CAAC,EAAe,MAAM;;;;8DAI9B,CAAC,EAAY,MAAM;mCAChD,EAAe;;;oEAGoB,CAAC,EAAQ,MAAM;4DACvB,YAAY,EAAQ,QAAQ;;KAGxF,EAEA,OAAQ,CAAC,EAAA,mBAAoB,EAAA,OAAe,CAC9C,CAAC"}