@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":"skeleton.js","names":[],"sources":["../src/feedback/skeleton/skeleton.ts"],"sourcesContent":["import { bind, define, getHost, html, onCleanup, onMounted, prop } from '@vielzeug/ore';\nimport { computed, fromSubscribable, signal, watch } from '@vielzeug/ripple';\nimport { createIntersection } from '@vielzeug/sentinel';\nimport { sizableBundle } from '../../shared';\nimport { reducedMotionMixin } from '../../styles';\nimport type { ComponentSize } from '../../types';\nimport { safeCSSLength } from '../../utils';\nimport componentStyles from './skeleton.css?inline';\n\n/** Skeleton loader component properties */\nexport type OreSkeletonProps = {\n /** Toggle shimmer animation */\n animated?: boolean;\n /** Height override (e.g. '1rem', '3rem') */\n height?: string;\n /** Number of text lines for `variant='text'` */\n lines?: number;\n /** Radius override (e.g. '9999px', 'var(--rounded-xl)') */\n radius?: string;\n /** Size preset controlling line height and circle size */\n size?: ComponentSize;\n /** Render diagonal stripes instead of the shimmer — useful as a design-mode placeholder */\n striped?: boolean;\n /** Visual variant: 'rect' (default), 'circle', or 'text' */\n variant?: 'rect' | 'circle' | 'text';\n /** Width override (e.g. '12rem', '70%') */\n width?: string;\n};\n\n/**\n * A shimmer placeholder that represents loading content.\n * Control dimensions via the `--skeleton-width` and `--skeleton-height` CSS custom properties,\n * or via `width` / `height` inline styles. Rectangle and circle bones fill the rendered host box.\n *\n * @element ore-skeleton\n *\n * @attr {string} variant - Shape: 'rect' (default) | 'circle' | 'text'\n * @attr {string} size - Height/circle preset: 'sm' | 'md' | 'lg'\n * @attr {string} width - Width override (CSS length/percentage)\n * @attr {string} height - Height override (CSS length)\n * @attr {string} radius - Radius override\n * @attr {boolean} animated - Disable with `animated=\"false\"`\n * @attr {number} lines - Text line count (only for `variant='text'`)\n * @attr {boolean} striped - Replace shimmer with diagonal stripes\n *\n * @cssprop --skeleton-bg - Base shimmer color\n * @cssprop --skeleton-highlight - Shimmer highlight color\n * @cssprop --skeleton-radius - Border radius\n * @cssprop --skeleton-size - Circle fallback size\n * @cssprop --skeleton-width - Width (default: 100%)\n * @cssprop --skeleton-height - Height (default: var(--size-4))\n * @cssprop --skeleton-line-gap - Vertical gap between text lines\n * @cssprop --skeleton-last-line-width - Width of the final text line\n * @cssprop --skeleton-duration - Shimmer animation duration\n * @cssprop --skeleton-stripe-size - Width of each diagonal stripe (default: 6px)\n *\n * @part stack - Stack container.\n * @part bone - Skeleton bone element.\n * @example\n * ```html\n * <!-- Paragraph lines -->\n * <ore-skeleton variant=\"text\" lines=\"3\" width=\"100%\"></ore-skeleton>\n *\n * <!-- Avatar -->\n * <ore-skeleton variant=\"circle\" size=\"md\"></ore-skeleton>\n *\n * <!-- Card image -->\n * <ore-skeleton width=\"100%\" height=\"10rem\"></ore-skeleton>\n * ```\n */\nexport const SKELETON_TAG = 'ore-skeleton' as const;\ndefine<OreSkeletonProps>(SKELETON_TAG, {\n props: {\n ...sizableBundle,\n animated: prop.bool(true),\n height: prop.string(),\n lines: prop.number(1),\n radius: prop.string(),\n striped: prop.bool(false),\n variant: prop.oneOf(['rect', 'circle', 'text'] as const, 'rect'),\n width: prop.string(),\n },\n setup(props) {\n const el = getHost();\n\n const isPaused = signal(false);\n const lineCount = () => {\n const value = Math.floor(Number(props.lines.value));\n\n return Number.isFinite(value) && value > 0 ? value : 1;\n };\n const renderLineCount = () => (props.variant.value === 'text' ? lineCount() : 1);\n const styleDeps = () =>\n `${props.width.value ?? ''}|${props.height.value ?? ''}|${props.radius.value ?? ''}|${props.animated.value === false ? '0' : '1'}`;\n\n watch(\n computed(styleDeps),\n () => {\n const safeWidth = safeCSSLength(props.width.value);\n const safeHeight = safeCSSLength(props.height.value);\n const safeRadius = safeCSSLength(props.radius.value);\n\n if (safeWidth) el.style.setProperty('--skeleton-width', safeWidth);\n else el.style.removeProperty('--skeleton-width');\n\n if (safeHeight) el.style.setProperty('--skeleton-height', safeHeight);\n else el.style.removeProperty('--skeleton-height');\n\n if (safeRadius) el.style.setProperty('--skeleton-radius', safeRadius);\n else el.style.removeProperty('--skeleton-radius');\n\n const rawAnimated = el.getAttribute('animated');\n const isAnimated = rawAnimated !== 'false' && props.animated.value !== false;\n\n el.setAttribute('data-animated', isAnimated ? 'true' : 'false');\n },\n { immediate: true },\n );\n\n bind({\n attr: {\n 'data-paused': () => (isPaused.value ? true : undefined),\n },\n });\n\n onMounted(() => {\n const intersection = createIntersection(el, { threshold: 0 });\n\n const intersectionState = fromSubscribable(intersection, { signal: intersection.disposalSignal });\n const intersectionWatch = watch(intersectionState, (state) => {\n const paused = state === null ? false : !state.isIntersecting;\n isPaused.value = paused;\n });\n\n onCleanup(() => {\n intersectionWatch.dispose();\n intersection.dispose();\n });\n });\n\n return html`\n <div class=\"stack\" part=\"stack\">\n ${() =>\n Array.from({ length: renderLineCount() }, (_, index) => {\n const isLastLine =\n props.variant.value === 'text' && renderLineCount() > 1 && index === renderLineCount() - 1;\n\n return html`\n <div class=\"bone\" part=\"bone\" aria-hidden=\"true\" data-last=\"${() => (isLastLine ? 'true' : null)}\"></div>\n `;\n })}\n </div>\n `;\n },\n styles: [reducedMotionMixin, componentStyles],\n});\n"],"mappings":"seAsEA,IAAa,EAAe,eAC5B,EAAyB,EAAc,CACrC,MAAO,CACL,GAAG,EACH,SAAU,EAAK,KAAK,EAAI,EACxB,OAAQ,EAAK,OAAO,EACpB,MAAO,EAAK,OAAO,CAAC,EACpB,OAAQ,EAAK,OAAO,EACpB,QAAS,EAAK,KAAK,EAAK,EACxB,QAAS,EAAK,MAAM,CAAC,OAAQ,SAAU,MAAM,EAAY,MAAM,EAC/D,MAAO,EAAK,OAAO,CACrB,EACA,MAAM,EAAO,CACX,IAAM,EAAK,EAAQ,EAEb,EAAW,EAAO,EAAK,EACvB,cAAkB,CACtB,IAAM,EAAQ,KAAK,MAAM,OAAO,EAAM,MAAM,KAAK,CAAC,EAElD,OAAO,OAAO,SAAS,CAAK,GAAK,EAAQ,EAAI,EAAQ,CACvD,EACM,oBAAyB,EAAM,QAAQ,QAAU,OAAS,UAAU,EAAI,EACxE,cACJ,GAAG,EAAM,MAAM,OAAS,GAAG,GAAG,EAAM,OAAO,OAAS,GAAG,GAAG,EAAM,OAAO,OAAS,GAAG,GAAG,EAAM,SAAS,QAAU,GAAQ,IAAM,MA+C/H,OA7CA,EACE,EAAS,SAAS,MACZ,CACJ,IAAM,EAAY,EAAc,EAAM,MAAM,KAAK,EAC3C,EAAa,EAAc,EAAM,OAAO,KAAK,EAC7C,EAAa,EAAc,EAAM,OAAO,KAAK,EAE/C,EAAW,EAAG,MAAM,YAAY,mBAAoB,CAAS,EAC5D,EAAG,MAAM,eAAe,kBAAkB,EAE3C,EAAY,EAAG,MAAM,YAAY,oBAAqB,CAAU,EAC/D,EAAG,MAAM,eAAe,mBAAmB,EAE5C,EAAY,EAAG,MAAM,YAAY,oBAAqB,CAAU,EAC/D,EAAG,MAAM,eAAe,mBAAmB,EAGhD,IAAM,EADc,EAAG,aAAa,UACjB,IAAgB,SAAW,EAAM,SAAS,QAAU,GAEvE,EAAG,aAAa,gBAAiB,EAAa,OAAS,OAAO,CAChE,EACA,CAAE,UAAW,EAAK,CACpB,EAEA,EAAK,CACH,KAAM,CACJ,kBAAsB,EAAS,MAAQ,GAAO,IAAA,EAChD,CACF,CAAC,EAED,MAAgB,CACd,IAAM,EAAe,EAAmB,EAAI,CAAE,UAAW,CAAE,CAAC,EAEtD,EAAoB,EAAiB,EAAc,CAAE,OAAQ,EAAa,cAAe,CAAC,EAC1F,EAAoB,EAAM,EAAoB,GAAU,CAC5D,IAAM,EAAS,IAAU,MAAe,CAAC,EAAM,eAC/C,EAAS,MAAQ,CACnB,CAAC,EAED,MAAgB,CACd,EAAkB,QAAQ,EAC1B,EAAa,QAAQ,CACvB,CAAC,CACH,CAAC,EAEM,CAAI;;cAGL,MAAM,KAAK,CAAE,OAAQ,gBAAgB,CAAE,GAAI,EAAG,IAAU,CACtD,IAAM,EACJ,EAAM,QAAQ,QAAU,QAAU,gBAAgB,EAAI,GAAK,IAAU,gBAAgB,EAAI,EAE3F,MAAO,EAAI;gFAC4D,EAAa,OAAS,KAAM;aAErG,CAAC,EAAE;;KAGX,EACA,OAAQ,CAAC,EAAoB,CAAe,CAC9C,CAAC"}
1
+ {"version":3,"file":"skeleton.js","names":[],"sources":["../src/feedback/skeleton/skeleton.ts"],"sourcesContent":["import { bind, define, getHost, html, onCleanup, onMounted, prop } from '@vielzeug/ore';\nimport { computed, fromSubscribable, signal, watch } from '@vielzeug/ripple';\nimport { createIntersection } from '@vielzeug/sentinel';\nimport { sizableBundle } from '../../shared';\nimport { reducedMotionMixin } from '../../styles';\nimport type { ComponentSize } from '../../types';\nimport { safeCSSLength } from '../../utils';\nimport componentStyles from './skeleton.css?inline';\n\n/** Skeleton loader component properties */\nexport type OreSkeletonProps = {\n /** Toggle shimmer animation */\n animated?: boolean;\n /** Height override (e.g. '1rem', '3rem') */\n height?: string;\n /** Number of text lines for `variant='text'` */\n lines?: number;\n /** Radius override (e.g. '9999px', 'var(--rounded-xl)') */\n radius?: string;\n /** Size preset controlling line height and circle size */\n size?: ComponentSize;\n /** Render diagonal stripes instead of the shimmer: useful as a design-mode placeholder */\n striped?: boolean;\n /** Visual variant: 'rect' (default), 'circle', or 'text' */\n variant?: 'rect' | 'circle' | 'text';\n /** Width override (e.g. '12rem', '70%') */\n width?: string;\n};\n\n/**\n * A shimmer placeholder that represents loading content.\n * Control dimensions via the `--skeleton-width` and `--skeleton-height` CSS custom properties,\n * or via `width` / `height` inline styles. Rectangle and circle bones fill the rendered host box.\n *\n * @element ore-skeleton\n *\n * @attr {string} variant - Shape: 'rect' (default) | 'circle' | 'text'\n * @attr {string} size - Height/circle preset: 'sm' | 'md' | 'lg'\n * @attr {string} width - Width override (CSS length/percentage)\n * @attr {string} height - Height override (CSS length)\n * @attr {string} radius - Radius override\n * @attr {boolean} animated - Disable with `animated=\"false\"`\n * @attr {number} lines - Text line count (only for `variant='text'`)\n * @attr {boolean} striped - Replace shimmer with diagonal stripes\n *\n * @cssprop --skeleton-bg - Base shimmer color\n * @cssprop --skeleton-highlight - Shimmer highlight color\n * @cssprop --skeleton-radius - Border radius\n * @cssprop --skeleton-size - Circle fallback size\n * @cssprop --skeleton-width - Width (default: 100%)\n * @cssprop --skeleton-height - Height (default: var(--size-4))\n * @cssprop --skeleton-line-gap - Vertical gap between text lines\n * @cssprop --skeleton-last-line-width - Width of the final text line\n * @cssprop --skeleton-duration - Shimmer animation duration\n * @cssprop --skeleton-stripe-size - Width of each diagonal stripe (default: 6px)\n *\n * @part stack - Stack container.\n * @part bone - Skeleton bone element.\n * @example\n * ```html\n * <!-- Paragraph lines -->\n * <ore-skeleton variant=\"text\" lines=\"3\" width=\"100%\"></ore-skeleton>\n *\n * <!-- Avatar -->\n * <ore-skeleton variant=\"circle\" size=\"md\"></ore-skeleton>\n *\n * <!-- Card image -->\n * <ore-skeleton width=\"100%\" height=\"10rem\"></ore-skeleton>\n * ```\n */\nexport const SKELETON_TAG = 'ore-skeleton' as const;\ndefine<OreSkeletonProps>(SKELETON_TAG, {\n props: {\n ...sizableBundle,\n animated: prop.bool(true),\n height: prop.string(),\n lines: prop.number(1),\n radius: prop.string(),\n striped: prop.bool(false),\n variant: prop.oneOf(['rect', 'circle', 'text'] as const, 'rect'),\n width: prop.string(),\n },\n setup(props) {\n const el = getHost();\n\n const isPaused = signal(false);\n const lineCount = () => {\n const value = Math.floor(Number(props.lines.value));\n\n return Number.isFinite(value) && value > 0 ? value : 1;\n };\n const renderLineCount = () => (props.variant.value === 'text' ? lineCount() : 1);\n const styleDeps = () =>\n `${props.width.value ?? ''}|${props.height.value ?? ''}|${props.radius.value ?? ''}|${props.animated.value === false ? '0' : '1'}`;\n\n watch(\n computed(styleDeps),\n () => {\n const safeWidth = safeCSSLength(props.width.value);\n const safeHeight = safeCSSLength(props.height.value);\n const safeRadius = safeCSSLength(props.radius.value);\n\n if (safeWidth) el.style.setProperty('--skeleton-width', safeWidth);\n else el.style.removeProperty('--skeleton-width');\n\n if (safeHeight) el.style.setProperty('--skeleton-height', safeHeight);\n else el.style.removeProperty('--skeleton-height');\n\n if (safeRadius) el.style.setProperty('--skeleton-radius', safeRadius);\n else el.style.removeProperty('--skeleton-radius');\n\n const rawAnimated = el.getAttribute('animated');\n const isAnimated = rawAnimated !== 'false' && props.animated.value !== false;\n\n el.setAttribute('data-animated', isAnimated ? 'true' : 'false');\n },\n { immediate: true },\n );\n\n bind({\n attr: {\n 'data-paused': () => (isPaused.value ? true : undefined),\n },\n });\n\n onMounted(() => {\n const intersection = createIntersection(el, { threshold: 0 });\n\n const intersectionState = fromSubscribable(intersection, { signal: intersection.disposalSignal });\n const intersectionWatch = watch(intersectionState, (state) => {\n const paused = state === null ? false : !state.isIntersecting;\n isPaused.value = paused;\n });\n\n onCleanup(() => {\n intersectionWatch.dispose();\n intersection.dispose();\n });\n });\n\n return html`\n <div class=\"stack\" part=\"stack\">\n ${() =>\n Array.from({ length: renderLineCount() }, (_, index) => {\n const isLastLine =\n props.variant.value === 'text' && renderLineCount() > 1 && index === renderLineCount() - 1;\n\n return html`\n <div class=\"bone\" part=\"bone\" aria-hidden=\"true\" data-last=\"${() => (isLastLine ? 'true' : null)}\"></div>\n `;\n })}\n </div>\n `;\n },\n styles: [reducedMotionMixin, componentStyles],\n});\n"],"mappings":"seAsEA,IAAa,EAAe,eAC5B,EAAyB,EAAc,CACrC,MAAO,CACL,GAAG,EACH,SAAU,EAAK,KAAK,EAAI,EACxB,OAAQ,EAAK,OAAO,EACpB,MAAO,EAAK,OAAO,CAAC,EACpB,OAAQ,EAAK,OAAO,EACpB,QAAS,EAAK,KAAK,EAAK,EACxB,QAAS,EAAK,MAAM,CAAC,OAAQ,SAAU,MAAM,EAAY,MAAM,EAC/D,MAAO,EAAK,OAAO,CACrB,EACA,MAAM,EAAO,CACX,IAAM,EAAK,EAAQ,EAEb,EAAW,EAAO,EAAK,EACvB,cAAkB,CACtB,IAAM,EAAQ,KAAK,MAAM,OAAO,EAAM,MAAM,KAAK,CAAC,EAElD,OAAO,OAAO,SAAS,CAAK,GAAK,EAAQ,EAAI,EAAQ,CACvD,EACM,oBAAyB,EAAM,QAAQ,QAAU,OAAS,UAAU,EAAI,EACxE,cACJ,GAAG,EAAM,MAAM,OAAS,GAAG,GAAG,EAAM,OAAO,OAAS,GAAG,GAAG,EAAM,OAAO,OAAS,GAAG,GAAG,EAAM,SAAS,QAAU,GAAQ,IAAM,MA+C/H,OA7CA,EACE,EAAS,SAAS,MACZ,CACJ,IAAM,EAAY,EAAc,EAAM,MAAM,KAAK,EAC3C,EAAa,EAAc,EAAM,OAAO,KAAK,EAC7C,EAAa,EAAc,EAAM,OAAO,KAAK,EAE/C,EAAW,EAAG,MAAM,YAAY,mBAAoB,CAAS,EAC5D,EAAG,MAAM,eAAe,kBAAkB,EAE3C,EAAY,EAAG,MAAM,YAAY,oBAAqB,CAAU,EAC/D,EAAG,MAAM,eAAe,mBAAmB,EAE5C,EAAY,EAAG,MAAM,YAAY,oBAAqB,CAAU,EAC/D,EAAG,MAAM,eAAe,mBAAmB,EAGhD,IAAM,EADc,EAAG,aAAa,UACjB,IAAgB,SAAW,EAAM,SAAS,QAAU,GAEvE,EAAG,aAAa,gBAAiB,EAAa,OAAS,OAAO,CAChE,EACA,CAAE,UAAW,EAAK,CACpB,EAEA,EAAK,CACH,KAAM,CACJ,kBAAsB,EAAS,MAAQ,GAAO,IAAA,EAChD,CACF,CAAC,EAED,MAAgB,CACd,IAAM,EAAe,EAAmB,EAAI,CAAE,UAAW,CAAE,CAAC,EAEtD,EAAoB,EAAiB,EAAc,CAAE,OAAQ,EAAa,cAAe,CAAC,EAC1F,EAAoB,EAAM,EAAoB,GAAU,CAC5D,IAAM,EAAS,IAAU,MAAe,CAAC,EAAM,eAC/C,EAAS,MAAQ,CACnB,CAAC,EAED,MAAgB,CACd,EAAkB,QAAQ,EAC1B,EAAa,QAAQ,CACvB,CAAC,CACH,CAAC,EAEM,CAAI;;cAGL,MAAM,KAAK,CAAE,OAAQ,gBAAgB,CAAE,GAAI,EAAG,IAAU,CACtD,IAAM,EACJ,EAAM,QAAQ,QAAU,QAAU,gBAAgB,EAAI,GAAK,IAAU,gBAAgB,EAAI,EAE3F,MAAO,EAAI;gFAC4D,EAAa,OAAS,KAAM;aAErG,CAAC,EAAE;;KAGX,EACA,OAAQ,CAAC,EAAoB,CAAe,CAC9C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"slider.cjs","names":[],"sources":["../src/inputs/slider/slider.ts"],"sourcesContent":["import {\n bind,\n createId,\n define,\n getHost,\n html,\n onCleanup,\n onEvent,\n onMounted,\n prop,\n ref,\n useField,\n useSlots,\n} from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\nimport { createErrorHelperState, createSliderControl } from '../../core';\nimport { disablableBundle, SLIDER_SIZE_PRESET, sizableBundle, themableBundle } from '../../shared';\nimport { colorThemeMixin, disabledStateMixin, reducedMotionMixin, sizeVariantMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor } from '../../types';\nimport { defineFieldValue, dispatchNativeFieldEvent, setFieldValue } from '../shared/native-field-event';\nimport { renderHelperRegion } from '../shared/templates';\nimport componentStyles from './slider.css?inline';\n\nconst guard =\n <E extends Event = Event>(condition: () => unknown, handler: (e: E) => void): ((e: E) => void) =>\n (e) => {\n if (condition()) handler(e);\n };\n\n/** Slider component properties */\n\nexport type OreSliderEvents = {\n change: Event;\n input: Event;\n};\n\nexport type OreSliderProps = {\n /** Theme color */\n color?: ThemeColor;\n /** Disable interaction */\n disabled?: boolean;\n /** Error message — marks the field as invalid (fallback when the `error` slot is empty) */\n error?: string;\n /** Range mode: lower bound */\n from?: number | string;\n /** Range mode a11y label for the start thumb (e.g. \"$20\") */\n 'from-value-text'?: string;\n /** Stretch to full width of container */\n fullwidth?: boolean;\n /** Helper text displayed below the slider (fallback when the `helper` slot is empty) */\n helper?: string;\n /** Maximum value */\n max?: number | string;\n /** Minimum value */\n min?: number | string;\n /** Single-value mode: form field name */\n name?: string;\n /** Activate two-thumb range selection */\n range?: boolean;\n /** Component size */\n size?: ComponentSize;\n /** Step increment */\n step?: number | string;\n /** Range mode: upper bound */\n to?: number | string;\n /** Range mode a11y label for the end thumb (e.g. \"$80\") */\n 'to-value-text'?: string;\n /** Single-value mode: current value */\n value?: number | string;\n /** Single-value mode a11y label override (e.g. \"75%\"). Overrides raw aria-valuenow. */\n 'value-text'?: string;\n};\n\n/**\n * A slider for selecting a single numeric value or a numeric range.\n *\n * Add the boolean `range` attribute to activate two-thumb range mode.\n *\n * @element ore-slider\n *\n * @attr {number} min - Minimum value (default: 0)\n * @attr {number} max - Maximum value (default: 100)\n * @attr {number} step - Step increment (default: 1)\n * @attr {number} value - Current value (single mode)\n * @attr {number} from - Lower bound (range mode)\n * @attr {number} to - Upper bound (range mode)\n * @attr {boolean} range - Activate range mode\n * @attr {boolean} disabled - Disable interaction\n * @attr {string} name - Form field name (single mode)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {string} helper - Helper text below the slider\n * @attr {string} error - Error message below the slider\n * @attr {boolean} fullwidth - Stretch to full width of container\n *\n * @fires input - Emitted when the slider value changes.\n * @fires change - Emitted when the slider value changes.\n *\n * @slot - Slider label text\n *\n * @part slider - Slider container\n * @part track - Track element\n * @part fill - Fill element\n * @part thumb - Single-value thumb\n * @part thumb-start - Range start thumb\n * @part thumb-end - Range end thumb\n * @part label - Label element\n * @part helper-text - The helper/error text element\n *\n * @cssprop --slider-height - Track height\n * @cssprop --slider-size - Thumb dimensions\n * @cssprop --slider-track-bg - Track background color\n * @cssprop --slider-fill - Active fill color\n * @cssprop --slider-thumb-bg - Thumb background color\n *\n * @example\n * ```html\n * <ore-slider value=\"50\" name=\"volume\">Volume</ore-slider>\n * <ore-slider range from=\"20\" to=\"80\" color=\"primary\">Price range</ore-slider>\n * ```\n */\nexport const SLIDER_TAG = 'ore-slider' as const;\ndefine<OreSliderProps>(SLIDER_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n error: prop.string(),\n from: prop.number(0),\n 'from-value-text': prop.string(),\n fullwidth: prop.bool(false),\n helper: prop.string(),\n max: prop.number(100),\n min: prop.number(0),\n name: prop.string(),\n range: prop.bool(false),\n step: prop.number(1),\n to: prop.number(100),\n 'to-value-text': prop.string(),\n value: prop.number(0),\n 'value-text': prop.string(),\n },\n setup(props) {\n const el = getHost();\n const slots = useSlots();\n\n // Treat `range` as static — determined at first render\n const isRange = props.range.value;\n // ── Shared helpers ────────────────────────────────────────────\n const sliderControl = createSliderControl({\n max: props.max,\n min: props.min,\n step: props.step,\n });\n // ── Single-value state ────────────────────────────────────────\n const isDragging = signal(false);\n const isDisabled = computed(() => Boolean(props.disabled.value));\n const labelledById = signal<string | undefined>(undefined);\n const assistiveId = createId('helper');\n const assistive = createErrorHelperState({ error: props.error, helper: props.helper });\n const errorText = computed(() => assistive.value.errorText);\n const helperText = computed(() => assistive.value.helperText);\n const ariaDescribedBy = computed(() => (errorText.value || helperText.value ? assistiveId : null));\n\n bind({\n attr: {\n 'data-dragging': () => (isDragging.value ? true : undefined),\n size: props.size,\n },\n });\n\n const valueSignal = signal('0');\n\n if (!isRange) {\n useField({ disabled: isDisabled, value: valueSignal });\n watch(\n props.value,\n (v) => {\n valueSignal.value = String(v);\n },\n { immediate: true },\n );\n bind({\n attr: {\n 'aria-describedby': () => ariaDescribedBy.value ?? null,\n 'aria-disabled': () => (isDisabled.value ? 'true' : null),\n 'aria-labelledby': () => labelledById.value ?? null,\n 'aria-valuemax': () => sliderControl.max(),\n 'aria-valuemin': () => sliderControl.min(),\n 'aria-valuenow': () => Number(valueSignal.value || 0),\n 'aria-valuetext': () => props['value-text'].value ?? null,\n role: () => 'slider',\n tabindex: () => (isDisabled.value ? null : '0'),\n },\n });\n }\n\n // ── Range state ───────────────────────────────────────────────\n const startVal = signal(0);\n const endVal = signal(100);\n\n defineFieldValue(\n el,\n () => (isRange ? `${startVal.value},${endVal.value}` : valueSignal.value),\n (value) => {\n if (isRange) {\n const [from, to] = value.split(',').map(Number);\n\n if (Number.isFinite(from)) startVal.value = sliderControl.snap(from);\n\n if (Number.isFinite(to)) endVal.value = sliderControl.snap(to);\n\n return;\n }\n\n const next = Number(value);\n\n if (Number.isFinite(next)) valueSignal.value = String(sliderControl.snap(next));\n },\n );\n\n if (isRange) {\n useField<{\n from: number;\n to: number;\n }>({\n disabled: isDisabled,\n toFormValue: ({ from, to }) => {\n const name = props.name.value;\n\n if (!name) return null;\n\n const fd = new FormData();\n\n fd.append(`${name}[from]`, String(from));\n fd.append(`${name}[to]`, String(to));\n\n return fd;\n },\n value: computed(() => ({ from: startVal.value, to: endVal.value })),\n });\n watch(\n props.from,\n (v) => {\n startVal.value = sliderControl.snap(Number(v));\n },\n { immediate: true },\n );\n watch(\n props.to,\n (v) => {\n endVal.value = sliderControl.snap(Number(v));\n },\n { immediate: true },\n );\n }\n\n // ── Refs ──────────────────────────────────────────────────────\n const containerRef = ref<HTMLDivElement>();\n const labelRef = ref<HTMLSpanElement>();\n const trackRef = ref<HTMLDivElement>();\n const thumbStartRef = ref<HTMLDivElement>();\n const thumbEndRef = ref<HTMLDivElement>();\n const startId = createId('slider-start');\n const endId = createId('slider-end');\n // ── CSS update helpers ────────────────────────────────────────\n const updateSingleCSS = (value: number) => {\n const pct = sliderControl.toPercent(value);\n\n el.style.setProperty('--_thumb-pos', `${pct}%`);\n el.style.setProperty('--_fill-start', '0%');\n el.style.setProperty('--_fill-width', `${pct}%`);\n };\n const updateRangeCSS = () => {\n const s = sliderControl.toPercent(startVal.value);\n const e = sliderControl.toPercent(endVal.value);\n\n el.style.setProperty('--_thumb-start', `${s}%`);\n el.style.setProperty('--_thumb-end', `${e}%`);\n el.style.setProperty('--_fill-start', `${s}%`);\n el.style.setProperty('--_fill-width', `${e - s}%`);\n };\n const emitValueChange = () => {\n const value = isRange ? `${startVal.value},${endVal.value}` : valueSignal.value;\n\n setFieldValue(el, value);\n dispatchNativeFieldEvent(el, 'input');\n dispatchNativeFieldEvent(el, 'change');\n };\n\n // ── Range mode setup ──────────────────────────────────────────\n const setupRangeMode = (container: HTMLDivElement, track: HTMLDivElement) => {\n updateRangeCSS();\n\n const clientToValue = (clientX: number) => {\n const rect = track.getBoundingClientRect();\n\n return sliderControl.fromClientX(clientX, rect);\n };\n let dragging: 'start' | 'end' | null = null;\n const applyDrag = (val: number) => {\n if (dragging === 'start') startVal.value = Math.min(sliderControl.snap(val), endVal.value);\n else if (dragging === 'end') endVal.value = Math.max(sliderControl.snap(val), startVal.value);\n\n startVal.value = sliderControl.clamp(startVal.value);\n endVal.value = sliderControl.clamp(endVal.value);\n updateRangeCSS();\n emitValueChange();\n };\n\n onEvent(\n container,\n 'pointerdown',\n guard(\n () => !isDisabled.value,\n (e: PointerEvent) => {\n e.preventDefault();\n\n const val = clientToValue(e.clientX);\n\n dragging = Math.abs(val - startVal.value) <= Math.abs(val - endVal.value) ? 'start' : 'end';\n (e.target as Element).setPointerCapture(e.pointerId);\n applyDrag(val);\n },\n ),\n );\n onEvent(\n container,\n 'pointermove',\n guard(\n () => !!dragging,\n (e: PointerEvent) => {\n e.preventDefault();\n\n if (!isDragging.value) isDragging.value = true;\n\n applyDrag(clientToValue(e.clientX));\n },\n ),\n );\n onEvent(\n container,\n 'pointerup',\n guard(\n () => !!dragging,\n (e: PointerEvent) => {\n e.preventDefault();\n dragging = null;\n isDragging.value = false;\n (e.target as Element).releasePointerCapture(e.pointerId);\n },\n ),\n );\n\n const makeThumbKeydown = (getVal: () => number, setVal: (v: number) => void) => (e: KeyboardEvent) => {\n if (isDisabled.value) return;\n\n const next = sliderControl.nextFromKey(e.key, getVal());\n\n if (next === null) return;\n\n e.preventDefault();\n setVal(sliderControl.snap(next));\n updateRangeCSS();\n emitValueChange();\n };\n const thumbStartEl = thumbStartRef.value;\n const thumbEndEl = thumbEndRef.value;\n\n if (thumbStartEl) {\n onEvent(\n thumbStartEl,\n 'keydown',\n makeThumbKeydown(\n () => startVal.value,\n (v) => {\n startVal.value = Math.min(v, endVal.value);\n },\n ),\n );\n onCleanup(\n bind(\n {\n attr: {\n 'aria-label': 'Range start',\n 'aria-valuemax': () => endVal.value,\n 'aria-valuemin': () => sliderControl.min(),\n 'aria-valuenow': () => startVal.value,\n 'aria-valuetext': () => props['from-value-text'].value ?? null,\n },\n },\n { target: thumbStartEl },\n ),\n );\n }\n\n if (thumbEndEl) {\n onEvent(\n thumbEndEl,\n 'keydown',\n makeThumbKeydown(\n () => endVal.value,\n (v) => {\n endVal.value = Math.max(v, startVal.value);\n },\n ),\n );\n onCleanup(\n bind(\n {\n attr: {\n 'aria-label': 'Range end',\n 'aria-valuemax': () => sliderControl.max(),\n 'aria-valuemin': () => startVal.value,\n 'aria-valuenow': () => endVal.value,\n 'aria-valuetext': () => props['to-value-text'].value ?? null,\n },\n },\n { target: thumbEndEl },\n ),\n );\n }\n };\n // ── Single-value mode setup ───────────────────────────────────\n const setupSingleMode = (container: HTMLDivElement, track: HTMLDivElement) => {\n updateSingleCSS(Number(valueSignal.value));\n\n const updateValue = (clientX: number) => {\n if (isDisabled.value) return;\n\n const rect = track.getBoundingClientRect();\n const newValue = sliderControl.fromClientX(clientX, rect);\n\n if (Number(valueSignal.value) !== newValue) {\n valueSignal.value = newValue.toString();\n updateSingleCSS(newValue);\n emitValueChange();\n }\n };\n let isPointerDragging = false;\n\n onEvent(\n container,\n 'pointerdown',\n guard(\n () => !isDisabled.value,\n (e: PointerEvent) => {\n e.preventDefault();\n isPointerDragging = true;\n updateValue(e.clientX);\n (e.target as Element).setPointerCapture(e.pointerId);\n },\n ),\n );\n onEvent(\n container,\n 'pointermove',\n guard(\n () => isPointerDragging,\n (e: PointerEvent) => {\n e.preventDefault();\n\n if (!isDragging.value) isDragging.value = true;\n\n updateValue(e.clientX);\n },\n ),\n );\n onEvent(\n container,\n 'pointerup',\n guard(\n () => isPointerDragging,\n (e: PointerEvent) => {\n e.preventDefault();\n isPointerDragging = false;\n isDragging.value = false;\n (e.target as Element).releasePointerCapture(e.pointerId);\n },\n ),\n );\n onEvent(\n el,\n 'keydown',\n guard(\n () => !isDisabled.value,\n (e: KeyboardEvent) => {\n const val = Number(valueSignal.value || 0);\n const next = sliderControl.nextFromKey(e.key, val);\n\n if (next === null) return;\n\n e.preventDefault();\n\n const newValue = sliderControl.clamp(next);\n\n if (newValue !== val) {\n valueSignal.value = newValue.toString();\n updateSingleCSS(newValue);\n emitValueChange();\n }\n },\n ),\n );\n };\n\n onMounted(() => {\n const container = containerRef.value;\n const track = trackRef.value;\n\n if (!container || !track) return;\n\n if (slots.has().value && labelRef.value) {\n const labelId = createId('slider-label');\n\n labelRef.value.id = labelId;\n\n if (!isRange) labelledById.value = labelId;\n }\n\n if (isRange) setupRangeMode(container, track);\n else setupSingleMode(container, track);\n });\n\n return html`\n <div class=\"slider-container\" part=\"slider\" ref=${containerRef}>\n <div class=\"slider-track\" part=\"track\" ref=${trackRef}>\n <div class=\"slider-fill\" part=\"fill\"></div>\n <div class=\"slider-thumb slider-thumb-sole\" part=\"thumb\"></div>\n <div\n class=\"slider-thumb slider-thumb-start\"\n part=\"thumb-start\"\n ref=${thumbStartRef}\n role=\"slider\"\n tabindex=\"${() => (isDisabled.value ? '-1' : '0')}\"\n aria-describedby=\"${ariaDescribedBy}\"\n id=\"${startId}\"></div>\n <div\n class=\"slider-thumb slider-thumb-end\"\n part=\"thumb-end\"\n ref=${thumbEndRef}\n role=\"slider\"\n tabindex=\"${() => (isDisabled.value ? '-1' : '0')}\"\n aria-describedby=\"${ariaDescribedBy}\"\n id=\"${endId}\"></div>\n </div>\n </div>\n <span class=\"label\" part=\"label\" ref=${labelRef}><slot></slot></span>\n ${renderHelperRegion(assistiveId, errorText, helperText)}\n `;\n },\n styles: [\n disabledStateMixin,\n colorThemeMixin,\n sizeVariantMixin(SLIDER_SIZE_PRESET),\n componentStyles,\n reducedMotionMixin,\n ],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"slider.cjs","names":[],"sources":["../src/inputs/slider/slider.ts"],"sourcesContent":["import {\n bind,\n createId,\n define,\n getHost,\n html,\n onCleanup,\n onEvent,\n onMounted,\n prop,\n ref,\n useField,\n useSlots,\n} from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\nimport { createErrorHelperState, createSliderControl } from '../../core';\nimport { disablableBundle, SLIDER_SIZE_PRESET, sizableBundle, themableBundle } from '../../shared';\nimport { colorThemeMixin, disabledStateMixin, reducedMotionMixin, sizeVariantMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor } from '../../types';\nimport { defineFieldValue, dispatchNativeFieldEvent, setFieldValue } from '../shared/native-field-event';\nimport { renderHelperRegion } from '../shared/templates';\nimport componentStyles from './slider.css?inline';\n\nconst guard =\n <E extends Event = Event>(condition: () => unknown, handler: (e: E) => void): ((e: E) => void) =>\n (e) => {\n if (condition()) handler(e);\n };\n\n/** Slider component properties */\n\nexport type OreSliderEvents = {\n change: Event;\n input: Event;\n};\n\nexport type OreSliderProps = {\n /** Theme color */\n color?: ThemeColor;\n /** Disable interaction */\n disabled?: boolean;\n /** Error message: marks the field as invalid (fallback when the `error` slot is empty) */\n error?: string;\n /** Range mode: lower bound */\n from?: number | string;\n /** Range mode a11y label for the start thumb (e.g. \"$20\") */\n 'from-value-text'?: string;\n /** Stretch to full width of container */\n fullwidth?: boolean;\n /** Helper text displayed below the slider (fallback when the `helper` slot is empty) */\n helper?: string;\n /** Maximum value */\n max?: number | string;\n /** Minimum value */\n min?: number | string;\n /** Single-value mode: form field name */\n name?: string;\n /** Activate two-thumb range selection */\n range?: boolean;\n /** Component size */\n size?: ComponentSize;\n /** Step increment */\n step?: number | string;\n /** Range mode: upper bound */\n to?: number | string;\n /** Range mode a11y label for the end thumb (e.g. \"$80\") */\n 'to-value-text'?: string;\n /** Single-value mode: current value */\n value?: number | string;\n /** Single-value mode a11y label override (e.g. \"75%\"). Overrides raw aria-valuenow. */\n 'value-text'?: string;\n};\n\n/**\n * A slider for selecting a single numeric value or a numeric range.\n *\n * Add the boolean `range` attribute to activate two-thumb range mode.\n *\n * @element ore-slider\n *\n * @attr {number} min - Minimum value (default: 0)\n * @attr {number} max - Maximum value (default: 100)\n * @attr {number} step - Step increment (default: 1)\n * @attr {number} value - Current value (single mode)\n * @attr {number} from - Lower bound (range mode)\n * @attr {number} to - Upper bound (range mode)\n * @attr {boolean} range - Activate range mode\n * @attr {boolean} disabled - Disable interaction\n * @attr {string} name - Form field name (single mode)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {string} helper - Helper text below the slider\n * @attr {string} error - Error message below the slider\n * @attr {boolean} fullwidth - Stretch to full width of container\n *\n * @fires input - Emitted when the slider value changes.\n * @fires change - Emitted when the slider value changes.\n *\n * @slot - Slider label text\n *\n * @part slider - Slider container\n * @part track - Track element\n * @part fill - Fill element\n * @part thumb - Single-value thumb\n * @part thumb-start - Range start thumb\n * @part thumb-end - Range end thumb\n * @part label - Label element\n * @part helper-text - The helper/error text element\n *\n * @cssprop --slider-height - Track height\n * @cssprop --slider-size - Thumb dimensions\n * @cssprop --slider-track-bg - Track background color\n * @cssprop --slider-fill - Active fill color\n * @cssprop --slider-thumb-bg - Thumb background color\n *\n * @example\n * ```html\n * <ore-slider value=\"50\" name=\"volume\">Volume</ore-slider>\n * <ore-slider range from=\"20\" to=\"80\" color=\"primary\">Price range</ore-slider>\n * ```\n */\nexport const SLIDER_TAG = 'ore-slider' as const;\ndefine<OreSliderProps>(SLIDER_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n error: prop.string(),\n from: prop.number(0),\n 'from-value-text': prop.string(),\n fullwidth: prop.bool(false),\n helper: prop.string(),\n max: prop.number(100),\n min: prop.number(0),\n name: prop.string(),\n range: prop.bool(false),\n step: prop.number(1),\n to: prop.number(100),\n 'to-value-text': prop.string(),\n value: prop.number(0),\n 'value-text': prop.string(),\n },\n setup(props) {\n const el = getHost();\n const slots = useSlots();\n\n // Treat `range` as static: determined at first render\n const isRange = props.range.value;\n // ── Shared helpers ────────────────────────────────────────────\n const sliderControl = createSliderControl({\n max: props.max,\n min: props.min,\n step: props.step,\n });\n // ── Single-value state ────────────────────────────────────────\n const isDragging = signal(false);\n const isDisabled = computed(() => Boolean(props.disabled.value));\n const labelledById = signal<string | undefined>(undefined);\n const assistiveId = createId('helper');\n const assistive = createErrorHelperState({ error: props.error, helper: props.helper });\n const errorText = computed(() => assistive.value.errorText);\n const helperText = computed(() => assistive.value.helperText);\n const ariaDescribedBy = computed(() => (errorText.value || helperText.value ? assistiveId : null));\n\n bind({\n attr: {\n 'data-dragging': () => (isDragging.value ? true : undefined),\n size: props.size,\n },\n });\n\n const valueSignal = signal('0');\n\n if (!isRange) {\n useField({ disabled: isDisabled, value: valueSignal });\n watch(\n props.value,\n (v) => {\n valueSignal.value = String(v);\n },\n { immediate: true },\n );\n bind({\n attr: {\n 'aria-describedby': () => ariaDescribedBy.value ?? null,\n 'aria-disabled': () => (isDisabled.value ? 'true' : null),\n 'aria-labelledby': () => labelledById.value ?? null,\n 'aria-valuemax': () => sliderControl.max(),\n 'aria-valuemin': () => sliderControl.min(),\n 'aria-valuenow': () => Number(valueSignal.value || 0),\n 'aria-valuetext': () => props['value-text'].value ?? null,\n role: () => 'slider',\n tabindex: () => (isDisabled.value ? null : '0'),\n },\n });\n }\n\n // ── Range state ───────────────────────────────────────────────\n const startVal = signal(0);\n const endVal = signal(100);\n\n defineFieldValue(\n el,\n () => (isRange ? `${startVal.value},${endVal.value}` : valueSignal.value),\n (value) => {\n if (isRange) {\n const [from, to] = value.split(',').map(Number);\n\n if (Number.isFinite(from)) startVal.value = sliderControl.snap(from);\n\n if (Number.isFinite(to)) endVal.value = sliderControl.snap(to);\n\n return;\n }\n\n const next = Number(value);\n\n if (Number.isFinite(next)) valueSignal.value = String(sliderControl.snap(next));\n },\n );\n\n if (isRange) {\n useField<{\n from: number;\n to: number;\n }>({\n disabled: isDisabled,\n toFormValue: ({ from, to }) => {\n const name = props.name.value;\n\n if (!name) return null;\n\n const fd = new FormData();\n\n fd.append(`${name}[from]`, String(from));\n fd.append(`${name}[to]`, String(to));\n\n return fd;\n },\n value: computed(() => ({ from: startVal.value, to: endVal.value })),\n });\n watch(\n props.from,\n (v) => {\n startVal.value = sliderControl.snap(Number(v));\n },\n { immediate: true },\n );\n watch(\n props.to,\n (v) => {\n endVal.value = sliderControl.snap(Number(v));\n },\n { immediate: true },\n );\n }\n\n // ── Refs ──────────────────────────────────────────────────────\n const containerRef = ref<HTMLDivElement>();\n const labelRef = ref<HTMLSpanElement>();\n const trackRef = ref<HTMLDivElement>();\n const thumbStartRef = ref<HTMLDivElement>();\n const thumbEndRef = ref<HTMLDivElement>();\n const startId = createId('slider-start');\n const endId = createId('slider-end');\n // ── CSS update helpers ────────────────────────────────────────\n const updateSingleCSS = (value: number) => {\n const pct = sliderControl.toPercent(value);\n\n el.style.setProperty('--_thumb-pos', `${pct}%`);\n el.style.setProperty('--_fill-start', '0%');\n el.style.setProperty('--_fill-width', `${pct}%`);\n };\n const updateRangeCSS = () => {\n const s = sliderControl.toPercent(startVal.value);\n const e = sliderControl.toPercent(endVal.value);\n\n el.style.setProperty('--_thumb-start', `${s}%`);\n el.style.setProperty('--_thumb-end', `${e}%`);\n el.style.setProperty('--_fill-start', `${s}%`);\n el.style.setProperty('--_fill-width', `${e - s}%`);\n };\n const emitValueChange = () => {\n const value = isRange ? `${startVal.value},${endVal.value}` : valueSignal.value;\n\n setFieldValue(el, value);\n dispatchNativeFieldEvent(el, 'input');\n dispatchNativeFieldEvent(el, 'change');\n };\n\n // ── Range mode setup ──────────────────────────────────────────\n const setupRangeMode = (container: HTMLDivElement, track: HTMLDivElement) => {\n updateRangeCSS();\n\n const clientToValue = (clientX: number) => {\n const rect = track.getBoundingClientRect();\n\n return sliderControl.fromClientX(clientX, rect);\n };\n let dragging: 'start' | 'end' | null = null;\n const applyDrag = (val: number) => {\n if (dragging === 'start') startVal.value = Math.min(sliderControl.snap(val), endVal.value);\n else if (dragging === 'end') endVal.value = Math.max(sliderControl.snap(val), startVal.value);\n\n startVal.value = sliderControl.clamp(startVal.value);\n endVal.value = sliderControl.clamp(endVal.value);\n updateRangeCSS();\n emitValueChange();\n };\n\n onEvent(\n container,\n 'pointerdown',\n guard(\n () => !isDisabled.value,\n (e: PointerEvent) => {\n e.preventDefault();\n\n const val = clientToValue(e.clientX);\n\n dragging = Math.abs(val - startVal.value) <= Math.abs(val - endVal.value) ? 'start' : 'end';\n (e.target as Element).setPointerCapture(e.pointerId);\n applyDrag(val);\n },\n ),\n );\n onEvent(\n container,\n 'pointermove',\n guard(\n () => !!dragging,\n (e: PointerEvent) => {\n e.preventDefault();\n\n if (!isDragging.value) isDragging.value = true;\n\n applyDrag(clientToValue(e.clientX));\n },\n ),\n );\n onEvent(\n container,\n 'pointerup',\n guard(\n () => !!dragging,\n (e: PointerEvent) => {\n e.preventDefault();\n dragging = null;\n isDragging.value = false;\n (e.target as Element).releasePointerCapture(e.pointerId);\n },\n ),\n );\n\n const makeThumbKeydown = (getVal: () => number, setVal: (v: number) => void) => (e: KeyboardEvent) => {\n if (isDisabled.value) return;\n\n const next = sliderControl.nextFromKey(e.key, getVal());\n\n if (next === null) return;\n\n e.preventDefault();\n setVal(sliderControl.snap(next));\n updateRangeCSS();\n emitValueChange();\n };\n const thumbStartEl = thumbStartRef.value;\n const thumbEndEl = thumbEndRef.value;\n\n if (thumbStartEl) {\n onEvent(\n thumbStartEl,\n 'keydown',\n makeThumbKeydown(\n () => startVal.value,\n (v) => {\n startVal.value = Math.min(v, endVal.value);\n },\n ),\n );\n onCleanup(\n bind(\n {\n attr: {\n 'aria-label': 'Range start',\n 'aria-valuemax': () => endVal.value,\n 'aria-valuemin': () => sliderControl.min(),\n 'aria-valuenow': () => startVal.value,\n 'aria-valuetext': () => props['from-value-text'].value ?? null,\n },\n },\n { target: thumbStartEl },\n ),\n );\n }\n\n if (thumbEndEl) {\n onEvent(\n thumbEndEl,\n 'keydown',\n makeThumbKeydown(\n () => endVal.value,\n (v) => {\n endVal.value = Math.max(v, startVal.value);\n },\n ),\n );\n onCleanup(\n bind(\n {\n attr: {\n 'aria-label': 'Range end',\n 'aria-valuemax': () => sliderControl.max(),\n 'aria-valuemin': () => startVal.value,\n 'aria-valuenow': () => endVal.value,\n 'aria-valuetext': () => props['to-value-text'].value ?? null,\n },\n },\n { target: thumbEndEl },\n ),\n );\n }\n };\n // ── Single-value mode setup ───────────────────────────────────\n const setupSingleMode = (container: HTMLDivElement, track: HTMLDivElement) => {\n updateSingleCSS(Number(valueSignal.value));\n\n const updateValue = (clientX: number) => {\n if (isDisabled.value) return;\n\n const rect = track.getBoundingClientRect();\n const newValue = sliderControl.fromClientX(clientX, rect);\n\n if (Number(valueSignal.value) !== newValue) {\n valueSignal.value = newValue.toString();\n updateSingleCSS(newValue);\n emitValueChange();\n }\n };\n let isPointerDragging = false;\n\n onEvent(\n container,\n 'pointerdown',\n guard(\n () => !isDisabled.value,\n (e: PointerEvent) => {\n e.preventDefault();\n isPointerDragging = true;\n updateValue(e.clientX);\n (e.target as Element).setPointerCapture(e.pointerId);\n },\n ),\n );\n onEvent(\n container,\n 'pointermove',\n guard(\n () => isPointerDragging,\n (e: PointerEvent) => {\n e.preventDefault();\n\n if (!isDragging.value) isDragging.value = true;\n\n updateValue(e.clientX);\n },\n ),\n );\n onEvent(\n container,\n 'pointerup',\n guard(\n () => isPointerDragging,\n (e: PointerEvent) => {\n e.preventDefault();\n isPointerDragging = false;\n isDragging.value = false;\n (e.target as Element).releasePointerCapture(e.pointerId);\n },\n ),\n );\n onEvent(\n el,\n 'keydown',\n guard(\n () => !isDisabled.value,\n (e: KeyboardEvent) => {\n const val = Number(valueSignal.value || 0);\n const next = sliderControl.nextFromKey(e.key, val);\n\n if (next === null) return;\n\n e.preventDefault();\n\n const newValue = sliderControl.clamp(next);\n\n if (newValue !== val) {\n valueSignal.value = newValue.toString();\n updateSingleCSS(newValue);\n emitValueChange();\n }\n },\n ),\n );\n };\n\n onMounted(() => {\n const container = containerRef.value;\n const track = trackRef.value;\n\n if (!container || !track) return;\n\n if (slots.has().value && labelRef.value) {\n const labelId = createId('slider-label');\n\n labelRef.value.id = labelId;\n\n if (!isRange) labelledById.value = labelId;\n }\n\n if (isRange) setupRangeMode(container, track);\n else setupSingleMode(container, track);\n });\n\n return html`\n <div class=\"slider-container\" part=\"slider\" ref=${containerRef}>\n <div class=\"slider-track\" part=\"track\" ref=${trackRef}>\n <div class=\"slider-fill\" part=\"fill\"></div>\n <div class=\"slider-thumb slider-thumb-sole\" part=\"thumb\"></div>\n <div\n class=\"slider-thumb slider-thumb-start\"\n part=\"thumb-start\"\n ref=${thumbStartRef}\n role=\"slider\"\n tabindex=\"${() => (isDisabled.value ? '-1' : '0')}\"\n aria-describedby=\"${ariaDescribedBy}\"\n id=\"${startId}\"></div>\n <div\n class=\"slider-thumb slider-thumb-end\"\n part=\"thumb-end\"\n ref=${thumbEndRef}\n role=\"slider\"\n tabindex=\"${() => (isDisabled.value ? '-1' : '0')}\"\n aria-describedby=\"${ariaDescribedBy}\"\n id=\"${endId}\"></div>\n </div>\n </div>\n <span class=\"label\" part=\"label\" ref=${labelRef}><slot></slot></span>\n ${renderHelperRegion(assistiveId, errorText, helperText)}\n `;\n },\n styles: [\n disabledStateMixin,\n colorThemeMixin,\n sizeVariantMixin(SLIDER_SIZE_PRESET),\n componentStyles,\n reducedMotionMixin,\n ],\n});\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"slider.js","names":[],"sources":["../src/inputs/slider/slider.ts"],"sourcesContent":["import {\n bind,\n createId,\n define,\n getHost,\n html,\n onCleanup,\n onEvent,\n onMounted,\n prop,\n ref,\n useField,\n useSlots,\n} from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\nimport { createErrorHelperState, createSliderControl } from '../../core';\nimport { disablableBundle, SLIDER_SIZE_PRESET, sizableBundle, themableBundle } from '../../shared';\nimport { colorThemeMixin, disabledStateMixin, reducedMotionMixin, sizeVariantMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor } from '../../types';\nimport { defineFieldValue, dispatchNativeFieldEvent, setFieldValue } from '../shared/native-field-event';\nimport { renderHelperRegion } from '../shared/templates';\nimport componentStyles from './slider.css?inline';\n\nconst guard =\n <E extends Event = Event>(condition: () => unknown, handler: (e: E) => void): ((e: E) => void) =>\n (e) => {\n if (condition()) handler(e);\n };\n\n/** Slider component properties */\n\nexport type OreSliderEvents = {\n change: Event;\n input: Event;\n};\n\nexport type OreSliderProps = {\n /** Theme color */\n color?: ThemeColor;\n /** Disable interaction */\n disabled?: boolean;\n /** Error message — marks the field as invalid (fallback when the `error` slot is empty) */\n error?: string;\n /** Range mode: lower bound */\n from?: number | string;\n /** Range mode a11y label for the start thumb (e.g. \"$20\") */\n 'from-value-text'?: string;\n /** Stretch to full width of container */\n fullwidth?: boolean;\n /** Helper text displayed below the slider (fallback when the `helper` slot is empty) */\n helper?: string;\n /** Maximum value */\n max?: number | string;\n /** Minimum value */\n min?: number | string;\n /** Single-value mode: form field name */\n name?: string;\n /** Activate two-thumb range selection */\n range?: boolean;\n /** Component size */\n size?: ComponentSize;\n /** Step increment */\n step?: number | string;\n /** Range mode: upper bound */\n to?: number | string;\n /** Range mode a11y label for the end thumb (e.g. \"$80\") */\n 'to-value-text'?: string;\n /** Single-value mode: current value */\n value?: number | string;\n /** Single-value mode a11y label override (e.g. \"75%\"). Overrides raw aria-valuenow. */\n 'value-text'?: string;\n};\n\n/**\n * A slider for selecting a single numeric value or a numeric range.\n *\n * Add the boolean `range` attribute to activate two-thumb range mode.\n *\n * @element ore-slider\n *\n * @attr {number} min - Minimum value (default: 0)\n * @attr {number} max - Maximum value (default: 100)\n * @attr {number} step - Step increment (default: 1)\n * @attr {number} value - Current value (single mode)\n * @attr {number} from - Lower bound (range mode)\n * @attr {number} to - Upper bound (range mode)\n * @attr {boolean} range - Activate range mode\n * @attr {boolean} disabled - Disable interaction\n * @attr {string} name - Form field name (single mode)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {string} helper - Helper text below the slider\n * @attr {string} error - Error message below the slider\n * @attr {boolean} fullwidth - Stretch to full width of container\n *\n * @fires input - Emitted when the slider value changes.\n * @fires change - Emitted when the slider value changes.\n *\n * @slot - Slider label text\n *\n * @part slider - Slider container\n * @part track - Track element\n * @part fill - Fill element\n * @part thumb - Single-value thumb\n * @part thumb-start - Range start thumb\n * @part thumb-end - Range end thumb\n * @part label - Label element\n * @part helper-text - The helper/error text element\n *\n * @cssprop --slider-height - Track height\n * @cssprop --slider-size - Thumb dimensions\n * @cssprop --slider-track-bg - Track background color\n * @cssprop --slider-fill - Active fill color\n * @cssprop --slider-thumb-bg - Thumb background color\n *\n * @example\n * ```html\n * <ore-slider value=\"50\" name=\"volume\">Volume</ore-slider>\n * <ore-slider range from=\"20\" to=\"80\" color=\"primary\">Price range</ore-slider>\n * ```\n */\nexport const SLIDER_TAG = 'ore-slider' as const;\ndefine<OreSliderProps>(SLIDER_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n error: prop.string(),\n from: prop.number(0),\n 'from-value-text': prop.string(),\n fullwidth: prop.bool(false),\n helper: prop.string(),\n max: prop.number(100),\n min: prop.number(0),\n name: prop.string(),\n range: prop.bool(false),\n step: prop.number(1),\n to: prop.number(100),\n 'to-value-text': prop.string(),\n value: prop.number(0),\n 'value-text': prop.string(),\n },\n setup(props) {\n const el = getHost();\n const slots = useSlots();\n\n // Treat `range` as static — determined at first render\n const isRange = props.range.value;\n // ── Shared helpers ────────────────────────────────────────────\n const sliderControl = createSliderControl({\n max: props.max,\n min: props.min,\n step: props.step,\n });\n // ── Single-value state ────────────────────────────────────────\n const isDragging = signal(false);\n const isDisabled = computed(() => Boolean(props.disabled.value));\n const labelledById = signal<string | undefined>(undefined);\n const assistiveId = createId('helper');\n const assistive = createErrorHelperState({ error: props.error, helper: props.helper });\n const errorText = computed(() => assistive.value.errorText);\n const helperText = computed(() => assistive.value.helperText);\n const ariaDescribedBy = computed(() => (errorText.value || helperText.value ? assistiveId : null));\n\n bind({\n attr: {\n 'data-dragging': () => (isDragging.value ? true : undefined),\n size: props.size,\n },\n });\n\n const valueSignal = signal('0');\n\n if (!isRange) {\n useField({ disabled: isDisabled, value: valueSignal });\n watch(\n props.value,\n (v) => {\n valueSignal.value = String(v);\n },\n { immediate: true },\n );\n bind({\n attr: {\n 'aria-describedby': () => ariaDescribedBy.value ?? null,\n 'aria-disabled': () => (isDisabled.value ? 'true' : null),\n 'aria-labelledby': () => labelledById.value ?? null,\n 'aria-valuemax': () => sliderControl.max(),\n 'aria-valuemin': () => sliderControl.min(),\n 'aria-valuenow': () => Number(valueSignal.value || 0),\n 'aria-valuetext': () => props['value-text'].value ?? null,\n role: () => 'slider',\n tabindex: () => (isDisabled.value ? null : '0'),\n },\n });\n }\n\n // ── Range state ───────────────────────────────────────────────\n const startVal = signal(0);\n const endVal = signal(100);\n\n defineFieldValue(\n el,\n () => (isRange ? `${startVal.value},${endVal.value}` : valueSignal.value),\n (value) => {\n if (isRange) {\n const [from, to] = value.split(',').map(Number);\n\n if (Number.isFinite(from)) startVal.value = sliderControl.snap(from);\n\n if (Number.isFinite(to)) endVal.value = sliderControl.snap(to);\n\n return;\n }\n\n const next = Number(value);\n\n if (Number.isFinite(next)) valueSignal.value = String(sliderControl.snap(next));\n },\n );\n\n if (isRange) {\n useField<{\n from: number;\n to: number;\n }>({\n disabled: isDisabled,\n toFormValue: ({ from, to }) => {\n const name = props.name.value;\n\n if (!name) return null;\n\n const fd = new FormData();\n\n fd.append(`${name}[from]`, String(from));\n fd.append(`${name}[to]`, String(to));\n\n return fd;\n },\n value: computed(() => ({ from: startVal.value, to: endVal.value })),\n });\n watch(\n props.from,\n (v) => {\n startVal.value = sliderControl.snap(Number(v));\n },\n { immediate: true },\n );\n watch(\n props.to,\n (v) => {\n endVal.value = sliderControl.snap(Number(v));\n },\n { immediate: true },\n );\n }\n\n // ── Refs ──────────────────────────────────────────────────────\n const containerRef = ref<HTMLDivElement>();\n const labelRef = ref<HTMLSpanElement>();\n const trackRef = ref<HTMLDivElement>();\n const thumbStartRef = ref<HTMLDivElement>();\n const thumbEndRef = ref<HTMLDivElement>();\n const startId = createId('slider-start');\n const endId = createId('slider-end');\n // ── CSS update helpers ────────────────────────────────────────\n const updateSingleCSS = (value: number) => {\n const pct = sliderControl.toPercent(value);\n\n el.style.setProperty('--_thumb-pos', `${pct}%`);\n el.style.setProperty('--_fill-start', '0%');\n el.style.setProperty('--_fill-width', `${pct}%`);\n };\n const updateRangeCSS = () => {\n const s = sliderControl.toPercent(startVal.value);\n const e = sliderControl.toPercent(endVal.value);\n\n el.style.setProperty('--_thumb-start', `${s}%`);\n el.style.setProperty('--_thumb-end', `${e}%`);\n el.style.setProperty('--_fill-start', `${s}%`);\n el.style.setProperty('--_fill-width', `${e - s}%`);\n };\n const emitValueChange = () => {\n const value = isRange ? `${startVal.value},${endVal.value}` : valueSignal.value;\n\n setFieldValue(el, value);\n dispatchNativeFieldEvent(el, 'input');\n dispatchNativeFieldEvent(el, 'change');\n };\n\n // ── Range mode setup ──────────────────────────────────────────\n const setupRangeMode = (container: HTMLDivElement, track: HTMLDivElement) => {\n updateRangeCSS();\n\n const clientToValue = (clientX: number) => {\n const rect = track.getBoundingClientRect();\n\n return sliderControl.fromClientX(clientX, rect);\n };\n let dragging: 'start' | 'end' | null = null;\n const applyDrag = (val: number) => {\n if (dragging === 'start') startVal.value = Math.min(sliderControl.snap(val), endVal.value);\n else if (dragging === 'end') endVal.value = Math.max(sliderControl.snap(val), startVal.value);\n\n startVal.value = sliderControl.clamp(startVal.value);\n endVal.value = sliderControl.clamp(endVal.value);\n updateRangeCSS();\n emitValueChange();\n };\n\n onEvent(\n container,\n 'pointerdown',\n guard(\n () => !isDisabled.value,\n (e: PointerEvent) => {\n e.preventDefault();\n\n const val = clientToValue(e.clientX);\n\n dragging = Math.abs(val - startVal.value) <= Math.abs(val - endVal.value) ? 'start' : 'end';\n (e.target as Element).setPointerCapture(e.pointerId);\n applyDrag(val);\n },\n ),\n );\n onEvent(\n container,\n 'pointermove',\n guard(\n () => !!dragging,\n (e: PointerEvent) => {\n e.preventDefault();\n\n if (!isDragging.value) isDragging.value = true;\n\n applyDrag(clientToValue(e.clientX));\n },\n ),\n );\n onEvent(\n container,\n 'pointerup',\n guard(\n () => !!dragging,\n (e: PointerEvent) => {\n e.preventDefault();\n dragging = null;\n isDragging.value = false;\n (e.target as Element).releasePointerCapture(e.pointerId);\n },\n ),\n );\n\n const makeThumbKeydown = (getVal: () => number, setVal: (v: number) => void) => (e: KeyboardEvent) => {\n if (isDisabled.value) return;\n\n const next = sliderControl.nextFromKey(e.key, getVal());\n\n if (next === null) return;\n\n e.preventDefault();\n setVal(sliderControl.snap(next));\n updateRangeCSS();\n emitValueChange();\n };\n const thumbStartEl = thumbStartRef.value;\n const thumbEndEl = thumbEndRef.value;\n\n if (thumbStartEl) {\n onEvent(\n thumbStartEl,\n 'keydown',\n makeThumbKeydown(\n () => startVal.value,\n (v) => {\n startVal.value = Math.min(v, endVal.value);\n },\n ),\n );\n onCleanup(\n bind(\n {\n attr: {\n 'aria-label': 'Range start',\n 'aria-valuemax': () => endVal.value,\n 'aria-valuemin': () => sliderControl.min(),\n 'aria-valuenow': () => startVal.value,\n 'aria-valuetext': () => props['from-value-text'].value ?? null,\n },\n },\n { target: thumbStartEl },\n ),\n );\n }\n\n if (thumbEndEl) {\n onEvent(\n thumbEndEl,\n 'keydown',\n makeThumbKeydown(\n () => endVal.value,\n (v) => {\n endVal.value = Math.max(v, startVal.value);\n },\n ),\n );\n onCleanup(\n bind(\n {\n attr: {\n 'aria-label': 'Range end',\n 'aria-valuemax': () => sliderControl.max(),\n 'aria-valuemin': () => startVal.value,\n 'aria-valuenow': () => endVal.value,\n 'aria-valuetext': () => props['to-value-text'].value ?? null,\n },\n },\n { target: thumbEndEl },\n ),\n );\n }\n };\n // ── Single-value mode setup ───────────────────────────────────\n const setupSingleMode = (container: HTMLDivElement, track: HTMLDivElement) => {\n updateSingleCSS(Number(valueSignal.value));\n\n const updateValue = (clientX: number) => {\n if (isDisabled.value) return;\n\n const rect = track.getBoundingClientRect();\n const newValue = sliderControl.fromClientX(clientX, rect);\n\n if (Number(valueSignal.value) !== newValue) {\n valueSignal.value = newValue.toString();\n updateSingleCSS(newValue);\n emitValueChange();\n }\n };\n let isPointerDragging = false;\n\n onEvent(\n container,\n 'pointerdown',\n guard(\n () => !isDisabled.value,\n (e: PointerEvent) => {\n e.preventDefault();\n isPointerDragging = true;\n updateValue(e.clientX);\n (e.target as Element).setPointerCapture(e.pointerId);\n },\n ),\n );\n onEvent(\n container,\n 'pointermove',\n guard(\n () => isPointerDragging,\n (e: PointerEvent) => {\n e.preventDefault();\n\n if (!isDragging.value) isDragging.value = true;\n\n updateValue(e.clientX);\n },\n ),\n );\n onEvent(\n container,\n 'pointerup',\n guard(\n () => isPointerDragging,\n (e: PointerEvent) => {\n e.preventDefault();\n isPointerDragging = false;\n isDragging.value = false;\n (e.target as Element).releasePointerCapture(e.pointerId);\n },\n ),\n );\n onEvent(\n el,\n 'keydown',\n guard(\n () => !isDisabled.value,\n (e: KeyboardEvent) => {\n const val = Number(valueSignal.value || 0);\n const next = sliderControl.nextFromKey(e.key, val);\n\n if (next === null) return;\n\n e.preventDefault();\n\n const newValue = sliderControl.clamp(next);\n\n if (newValue !== val) {\n valueSignal.value = newValue.toString();\n updateSingleCSS(newValue);\n emitValueChange();\n }\n },\n ),\n );\n };\n\n onMounted(() => {\n const container = containerRef.value;\n const track = trackRef.value;\n\n if (!container || !track) return;\n\n if (slots.has().value && labelRef.value) {\n const labelId = createId('slider-label');\n\n labelRef.value.id = labelId;\n\n if (!isRange) labelledById.value = labelId;\n }\n\n if (isRange) setupRangeMode(container, track);\n else setupSingleMode(container, track);\n });\n\n return html`\n <div class=\"slider-container\" part=\"slider\" ref=${containerRef}>\n <div class=\"slider-track\" part=\"track\" ref=${trackRef}>\n <div class=\"slider-fill\" part=\"fill\"></div>\n <div class=\"slider-thumb slider-thumb-sole\" part=\"thumb\"></div>\n <div\n class=\"slider-thumb slider-thumb-start\"\n part=\"thumb-start\"\n ref=${thumbStartRef}\n role=\"slider\"\n tabindex=\"${() => (isDisabled.value ? '-1' : '0')}\"\n aria-describedby=\"${ariaDescribedBy}\"\n id=\"${startId}\"></div>\n <div\n class=\"slider-thumb slider-thumb-end\"\n part=\"thumb-end\"\n ref=${thumbEndRef}\n role=\"slider\"\n tabindex=\"${() => (isDisabled.value ? '-1' : '0')}\"\n aria-describedby=\"${ariaDescribedBy}\"\n id=\"${endId}\"></div>\n </div>\n </div>\n <span class=\"label\" part=\"label\" ref=${labelRef}><slot></slot></span>\n ${renderHelperRegion(assistiveId, errorText, helperText)}\n `;\n },\n styles: [\n disabledStateMixin,\n colorThemeMixin,\n sizeVariantMixin(SLIDER_SIZE_PRESET),\n componentStyles,\n reducedMotionMixin,\n ],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"slider.js","names":[],"sources":["../src/inputs/slider/slider.ts"],"sourcesContent":["import {\n bind,\n createId,\n define,\n getHost,\n html,\n onCleanup,\n onEvent,\n onMounted,\n prop,\n ref,\n useField,\n useSlots,\n} from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\nimport { createErrorHelperState, createSliderControl } from '../../core';\nimport { disablableBundle, SLIDER_SIZE_PRESET, sizableBundle, themableBundle } from '../../shared';\nimport { colorThemeMixin, disabledStateMixin, reducedMotionMixin, sizeVariantMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor } from '../../types';\nimport { defineFieldValue, dispatchNativeFieldEvent, setFieldValue } from '../shared/native-field-event';\nimport { renderHelperRegion } from '../shared/templates';\nimport componentStyles from './slider.css?inline';\n\nconst guard =\n <E extends Event = Event>(condition: () => unknown, handler: (e: E) => void): ((e: E) => void) =>\n (e) => {\n if (condition()) handler(e);\n };\n\n/** Slider component properties */\n\nexport type OreSliderEvents = {\n change: Event;\n input: Event;\n};\n\nexport type OreSliderProps = {\n /** Theme color */\n color?: ThemeColor;\n /** Disable interaction */\n disabled?: boolean;\n /** Error message: marks the field as invalid (fallback when the `error` slot is empty) */\n error?: string;\n /** Range mode: lower bound */\n from?: number | string;\n /** Range mode a11y label for the start thumb (e.g. \"$20\") */\n 'from-value-text'?: string;\n /** Stretch to full width of container */\n fullwidth?: boolean;\n /** Helper text displayed below the slider (fallback when the `helper` slot is empty) */\n helper?: string;\n /** Maximum value */\n max?: number | string;\n /** Minimum value */\n min?: number | string;\n /** Single-value mode: form field name */\n name?: string;\n /** Activate two-thumb range selection */\n range?: boolean;\n /** Component size */\n size?: ComponentSize;\n /** Step increment */\n step?: number | string;\n /** Range mode: upper bound */\n to?: number | string;\n /** Range mode a11y label for the end thumb (e.g. \"$80\") */\n 'to-value-text'?: string;\n /** Single-value mode: current value */\n value?: number | string;\n /** Single-value mode a11y label override (e.g. \"75%\"). Overrides raw aria-valuenow. */\n 'value-text'?: string;\n};\n\n/**\n * A slider for selecting a single numeric value or a numeric range.\n *\n * Add the boolean `range` attribute to activate two-thumb range mode.\n *\n * @element ore-slider\n *\n * @attr {number} min - Minimum value (default: 0)\n * @attr {number} max - Maximum value (default: 100)\n * @attr {number} step - Step increment (default: 1)\n * @attr {number} value - Current value (single mode)\n * @attr {number} from - Lower bound (range mode)\n * @attr {number} to - Upper bound (range mode)\n * @attr {boolean} range - Activate range mode\n * @attr {boolean} disabled - Disable interaction\n * @attr {string} name - Form field name (single mode)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {string} helper - Helper text below the slider\n * @attr {string} error - Error message below the slider\n * @attr {boolean} fullwidth - Stretch to full width of container\n *\n * @fires input - Emitted when the slider value changes.\n * @fires change - Emitted when the slider value changes.\n *\n * @slot - Slider label text\n *\n * @part slider - Slider container\n * @part track - Track element\n * @part fill - Fill element\n * @part thumb - Single-value thumb\n * @part thumb-start - Range start thumb\n * @part thumb-end - Range end thumb\n * @part label - Label element\n * @part helper-text - The helper/error text element\n *\n * @cssprop --slider-height - Track height\n * @cssprop --slider-size - Thumb dimensions\n * @cssprop --slider-track-bg - Track background color\n * @cssprop --slider-fill - Active fill color\n * @cssprop --slider-thumb-bg - Thumb background color\n *\n * @example\n * ```html\n * <ore-slider value=\"50\" name=\"volume\">Volume</ore-slider>\n * <ore-slider range from=\"20\" to=\"80\" color=\"primary\">Price range</ore-slider>\n * ```\n */\nexport const SLIDER_TAG = 'ore-slider' as const;\ndefine<OreSliderProps>(SLIDER_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n error: prop.string(),\n from: prop.number(0),\n 'from-value-text': prop.string(),\n fullwidth: prop.bool(false),\n helper: prop.string(),\n max: prop.number(100),\n min: prop.number(0),\n name: prop.string(),\n range: prop.bool(false),\n step: prop.number(1),\n to: prop.number(100),\n 'to-value-text': prop.string(),\n value: prop.number(0),\n 'value-text': prop.string(),\n },\n setup(props) {\n const el = getHost();\n const slots = useSlots();\n\n // Treat `range` as static: determined at first render\n const isRange = props.range.value;\n // ── Shared helpers ────────────────────────────────────────────\n const sliderControl = createSliderControl({\n max: props.max,\n min: props.min,\n step: props.step,\n });\n // ── Single-value state ────────────────────────────────────────\n const isDragging = signal(false);\n const isDisabled = computed(() => Boolean(props.disabled.value));\n const labelledById = signal<string | undefined>(undefined);\n const assistiveId = createId('helper');\n const assistive = createErrorHelperState({ error: props.error, helper: props.helper });\n const errorText = computed(() => assistive.value.errorText);\n const helperText = computed(() => assistive.value.helperText);\n const ariaDescribedBy = computed(() => (errorText.value || helperText.value ? assistiveId : null));\n\n bind({\n attr: {\n 'data-dragging': () => (isDragging.value ? true : undefined),\n size: props.size,\n },\n });\n\n const valueSignal = signal('0');\n\n if (!isRange) {\n useField({ disabled: isDisabled, value: valueSignal });\n watch(\n props.value,\n (v) => {\n valueSignal.value = String(v);\n },\n { immediate: true },\n );\n bind({\n attr: {\n 'aria-describedby': () => ariaDescribedBy.value ?? null,\n 'aria-disabled': () => (isDisabled.value ? 'true' : null),\n 'aria-labelledby': () => labelledById.value ?? null,\n 'aria-valuemax': () => sliderControl.max(),\n 'aria-valuemin': () => sliderControl.min(),\n 'aria-valuenow': () => Number(valueSignal.value || 0),\n 'aria-valuetext': () => props['value-text'].value ?? null,\n role: () => 'slider',\n tabindex: () => (isDisabled.value ? null : '0'),\n },\n });\n }\n\n // ── Range state ───────────────────────────────────────────────\n const startVal = signal(0);\n const endVal = signal(100);\n\n defineFieldValue(\n el,\n () => (isRange ? `${startVal.value},${endVal.value}` : valueSignal.value),\n (value) => {\n if (isRange) {\n const [from, to] = value.split(',').map(Number);\n\n if (Number.isFinite(from)) startVal.value = sliderControl.snap(from);\n\n if (Number.isFinite(to)) endVal.value = sliderControl.snap(to);\n\n return;\n }\n\n const next = Number(value);\n\n if (Number.isFinite(next)) valueSignal.value = String(sliderControl.snap(next));\n },\n );\n\n if (isRange) {\n useField<{\n from: number;\n to: number;\n }>({\n disabled: isDisabled,\n toFormValue: ({ from, to }) => {\n const name = props.name.value;\n\n if (!name) return null;\n\n const fd = new FormData();\n\n fd.append(`${name}[from]`, String(from));\n fd.append(`${name}[to]`, String(to));\n\n return fd;\n },\n value: computed(() => ({ from: startVal.value, to: endVal.value })),\n });\n watch(\n props.from,\n (v) => {\n startVal.value = sliderControl.snap(Number(v));\n },\n { immediate: true },\n );\n watch(\n props.to,\n (v) => {\n endVal.value = sliderControl.snap(Number(v));\n },\n { immediate: true },\n );\n }\n\n // ── Refs ──────────────────────────────────────────────────────\n const containerRef = ref<HTMLDivElement>();\n const labelRef = ref<HTMLSpanElement>();\n const trackRef = ref<HTMLDivElement>();\n const thumbStartRef = ref<HTMLDivElement>();\n const thumbEndRef = ref<HTMLDivElement>();\n const startId = createId('slider-start');\n const endId = createId('slider-end');\n // ── CSS update helpers ────────────────────────────────────────\n const updateSingleCSS = (value: number) => {\n const pct = sliderControl.toPercent(value);\n\n el.style.setProperty('--_thumb-pos', `${pct}%`);\n el.style.setProperty('--_fill-start', '0%');\n el.style.setProperty('--_fill-width', `${pct}%`);\n };\n const updateRangeCSS = () => {\n const s = sliderControl.toPercent(startVal.value);\n const e = sliderControl.toPercent(endVal.value);\n\n el.style.setProperty('--_thumb-start', `${s}%`);\n el.style.setProperty('--_thumb-end', `${e}%`);\n el.style.setProperty('--_fill-start', `${s}%`);\n el.style.setProperty('--_fill-width', `${e - s}%`);\n };\n const emitValueChange = () => {\n const value = isRange ? `${startVal.value},${endVal.value}` : valueSignal.value;\n\n setFieldValue(el, value);\n dispatchNativeFieldEvent(el, 'input');\n dispatchNativeFieldEvent(el, 'change');\n };\n\n // ── Range mode setup ──────────────────────────────────────────\n const setupRangeMode = (container: HTMLDivElement, track: HTMLDivElement) => {\n updateRangeCSS();\n\n const clientToValue = (clientX: number) => {\n const rect = track.getBoundingClientRect();\n\n return sliderControl.fromClientX(clientX, rect);\n };\n let dragging: 'start' | 'end' | null = null;\n const applyDrag = (val: number) => {\n if (dragging === 'start') startVal.value = Math.min(sliderControl.snap(val), endVal.value);\n else if (dragging === 'end') endVal.value = Math.max(sliderControl.snap(val), startVal.value);\n\n startVal.value = sliderControl.clamp(startVal.value);\n endVal.value = sliderControl.clamp(endVal.value);\n updateRangeCSS();\n emitValueChange();\n };\n\n onEvent(\n container,\n 'pointerdown',\n guard(\n () => !isDisabled.value,\n (e: PointerEvent) => {\n e.preventDefault();\n\n const val = clientToValue(e.clientX);\n\n dragging = Math.abs(val - startVal.value) <= Math.abs(val - endVal.value) ? 'start' : 'end';\n (e.target as Element).setPointerCapture(e.pointerId);\n applyDrag(val);\n },\n ),\n );\n onEvent(\n container,\n 'pointermove',\n guard(\n () => !!dragging,\n (e: PointerEvent) => {\n e.preventDefault();\n\n if (!isDragging.value) isDragging.value = true;\n\n applyDrag(clientToValue(e.clientX));\n },\n ),\n );\n onEvent(\n container,\n 'pointerup',\n guard(\n () => !!dragging,\n (e: PointerEvent) => {\n e.preventDefault();\n dragging = null;\n isDragging.value = false;\n (e.target as Element).releasePointerCapture(e.pointerId);\n },\n ),\n );\n\n const makeThumbKeydown = (getVal: () => number, setVal: (v: number) => void) => (e: KeyboardEvent) => {\n if (isDisabled.value) return;\n\n const next = sliderControl.nextFromKey(e.key, getVal());\n\n if (next === null) return;\n\n e.preventDefault();\n setVal(sliderControl.snap(next));\n updateRangeCSS();\n emitValueChange();\n };\n const thumbStartEl = thumbStartRef.value;\n const thumbEndEl = thumbEndRef.value;\n\n if (thumbStartEl) {\n onEvent(\n thumbStartEl,\n 'keydown',\n makeThumbKeydown(\n () => startVal.value,\n (v) => {\n startVal.value = Math.min(v, endVal.value);\n },\n ),\n );\n onCleanup(\n bind(\n {\n attr: {\n 'aria-label': 'Range start',\n 'aria-valuemax': () => endVal.value,\n 'aria-valuemin': () => sliderControl.min(),\n 'aria-valuenow': () => startVal.value,\n 'aria-valuetext': () => props['from-value-text'].value ?? null,\n },\n },\n { target: thumbStartEl },\n ),\n );\n }\n\n if (thumbEndEl) {\n onEvent(\n thumbEndEl,\n 'keydown',\n makeThumbKeydown(\n () => endVal.value,\n (v) => {\n endVal.value = Math.max(v, startVal.value);\n },\n ),\n );\n onCleanup(\n bind(\n {\n attr: {\n 'aria-label': 'Range end',\n 'aria-valuemax': () => sliderControl.max(),\n 'aria-valuemin': () => startVal.value,\n 'aria-valuenow': () => endVal.value,\n 'aria-valuetext': () => props['to-value-text'].value ?? null,\n },\n },\n { target: thumbEndEl },\n ),\n );\n }\n };\n // ── Single-value mode setup ───────────────────────────────────\n const setupSingleMode = (container: HTMLDivElement, track: HTMLDivElement) => {\n updateSingleCSS(Number(valueSignal.value));\n\n const updateValue = (clientX: number) => {\n if (isDisabled.value) return;\n\n const rect = track.getBoundingClientRect();\n const newValue = sliderControl.fromClientX(clientX, rect);\n\n if (Number(valueSignal.value) !== newValue) {\n valueSignal.value = newValue.toString();\n updateSingleCSS(newValue);\n emitValueChange();\n }\n };\n let isPointerDragging = false;\n\n onEvent(\n container,\n 'pointerdown',\n guard(\n () => !isDisabled.value,\n (e: PointerEvent) => {\n e.preventDefault();\n isPointerDragging = true;\n updateValue(e.clientX);\n (e.target as Element).setPointerCapture(e.pointerId);\n },\n ),\n );\n onEvent(\n container,\n 'pointermove',\n guard(\n () => isPointerDragging,\n (e: PointerEvent) => {\n e.preventDefault();\n\n if (!isDragging.value) isDragging.value = true;\n\n updateValue(e.clientX);\n },\n ),\n );\n onEvent(\n container,\n 'pointerup',\n guard(\n () => isPointerDragging,\n (e: PointerEvent) => {\n e.preventDefault();\n isPointerDragging = false;\n isDragging.value = false;\n (e.target as Element).releasePointerCapture(e.pointerId);\n },\n ),\n );\n onEvent(\n el,\n 'keydown',\n guard(\n () => !isDisabled.value,\n (e: KeyboardEvent) => {\n const val = Number(valueSignal.value || 0);\n const next = sliderControl.nextFromKey(e.key, val);\n\n if (next === null) return;\n\n e.preventDefault();\n\n const newValue = sliderControl.clamp(next);\n\n if (newValue !== val) {\n valueSignal.value = newValue.toString();\n updateSingleCSS(newValue);\n emitValueChange();\n }\n },\n ),\n );\n };\n\n onMounted(() => {\n const container = containerRef.value;\n const track = trackRef.value;\n\n if (!container || !track) return;\n\n if (slots.has().value && labelRef.value) {\n const labelId = createId('slider-label');\n\n labelRef.value.id = labelId;\n\n if (!isRange) labelledById.value = labelId;\n }\n\n if (isRange) setupRangeMode(container, track);\n else setupSingleMode(container, track);\n });\n\n return html`\n <div class=\"slider-container\" part=\"slider\" ref=${containerRef}>\n <div class=\"slider-track\" part=\"track\" ref=${trackRef}>\n <div class=\"slider-fill\" part=\"fill\"></div>\n <div class=\"slider-thumb slider-thumb-sole\" part=\"thumb\"></div>\n <div\n class=\"slider-thumb slider-thumb-start\"\n part=\"thumb-start\"\n ref=${thumbStartRef}\n role=\"slider\"\n tabindex=\"${() => (isDisabled.value ? '-1' : '0')}\"\n aria-describedby=\"${ariaDescribedBy}\"\n id=\"${startId}\"></div>\n <div\n class=\"slider-thumb slider-thumb-end\"\n part=\"thumb-end\"\n ref=${thumbEndRef}\n role=\"slider\"\n tabindex=\"${() => (isDisabled.value ? '-1' : '0')}\"\n aria-describedby=\"${ariaDescribedBy}\"\n id=\"${endId}\"></div>\n </div>\n </div>\n <span class=\"label\" part=\"label\" ref=${labelRef}><slot></slot></span>\n ${renderHelperRegion(assistiveId, errorText, helperText)}\n `;\n },\n styles: [\n disabledStateMixin,\n colorThemeMixin,\n sizeVariantMixin(SLIDER_SIZE_PRESET),\n componentStyles,\n reducedMotionMixin,\n ],\n});\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"speech-player.cjs","names":[],"sources":["../src/content/speech-player/speech-player.ts"],"sourcesContent":["import { bind, define, getHost, html, onCleanup, prop, useEmit } from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\nimport { srOnlyMixin } from '../../styles';\n\nimport componentStyles from './speech-player.css?inline';\n\nexport { ICON_TAG } from '../icon/icon';\n\n/** The player's reflected state — `unsupported` when the Web Speech API is missing. */\nexport type SpeechPlayerState = 'idle' | 'playing' | 'paused' | 'unsupported';\n\n/** The rate steps the speed control cycles through — normal, slow, fast. */\nexport const SPEECH_RATES = [1, 0.85, 1.15] as const;\nexport type SpeechRate = (typeof SPEECH_RATES)[number];\n\n/** Every user-facing string the component renders or announces. Override the `labels`\n * property to localize; English is the default. */\nexport type OreSpeechPlayerLabels = {\n /** Announced when the engine fails mid-read. */\n error: string;\n /** The toggle's accessible name while playing. */\n pause: string;\n /** Announced when reading is paused. */\n paused: string;\n /** The toggle's accessible name while idle. */\n read: string;\n /** The toggle's accessible name while paused. */\n resume: string;\n /** Announced when reading resumes. */\n resumed: string;\n /** Announced when reading starts. */\n started: string;\n /** The speed control's accessible-name stem; the current step's name follows it. */\n speed: string;\n /** The fast speed step's name. */\n speedFast: string;\n /** The normal speed step's name. */\n speedNormal: string;\n /** The slow speed step's name. */\n speedSlow: string;\n /** The stop control's accessible name. */\n stop: string;\n /** Announced when reading stops before finishing. */\n stopped: string;\n /** Announced when reading finishes on its own. */\n finished: string;\n /** The toggle's accessible name when speech synthesis is unavailable. */\n unsupported: string;\n};\n\nconst DEFAULT_LABELS: OreSpeechPlayerLabels = {\n error: 'Reading was interrupted.',\n finished: 'Reading finished.',\n pause: 'Pause reading',\n paused: 'Reading paused.',\n read: 'Read aloud',\n resume: 'Resume reading',\n resumed: 'Reading resumed.',\n speed: 'Reading speed',\n speedFast: 'fast',\n speedNormal: 'normal',\n speedSlow: 'slow',\n started: 'Reading aloud.',\n stop: 'Stop reading',\n stopped: 'Reading stopped.',\n unsupported: 'Text-to-speech is unavailable',\n};\n\n/** Speech player component properties */\nexport type OreSpeechPlayerProps = {\n /** Labels for the controls and announcements — override to localize. */\n labels?: Partial<OreSpeechPlayerLabels>;\n /** Playback rate — bind with `rate-change` to persist the listener's choice; unbound,\n * the speed control cycles on its own. Values outside `SPEECH_RATES` snap into the\n * cycle at the control's first use. */\n rate?: number;\n /** The sentence index playback begins from — bind a persisted position so \"resume\"\n * survives stops, collapses, and page loads. */\n 'resume-at'?: number;\n /** The text to read aloud. */\n text?: string;\n};\n\n/** Speech player component events */\nexport type OreSpeechPlayerEvents = {\n /** Reading finished on its own. */\n end: undefined;\n /** The engine failed mid-read — reading stopped, but not by the listener. */\n error: undefined;\n /** Reading started — from the control or a `play()` call. */\n play: undefined;\n /** A sentence began — carry `sentence` of `total` to the surfaces that follow the voice. */\n progress: { sentence: number; total: number };\n /** The speed control moved to a new step. */\n 'rate-change': { rate: number };\n /** Reading stopped before finishing — from the control or a `stop()` call. */\n stop: undefined;\n};\n\n/** Element interface exposing the imperative API for `ore-speech-player`. */\nexport interface SpeechPlayerElement extends HTMLElement, OreSpeechPlayerProps {\n /** Pauses the current reading. */\n pause(): void;\n /** Starts reading — from `resume-at` when bound, else from the beginning.\n * Any player already reading stops first. */\n play(): void;\n /** Stops reading and cancels pending speech. */\n stop(): void;\n}\n\n/** Chrome cuts a single utterance off after roughly fifteen seconds of speech; sentence\n * pieces under this length stay safely inside that window at any of the rate steps. */\nconst UTTERANCE_LIMIT = 220;\n\n/** The entry's sentences — prose ends . ! or ?, closing quotes belong to the sentence\n * they end, and an unterminated tail is still a sentence. Consumers that highlight what\n * is being read build their display from this same list, so `progress` sentence indexes\n * map onto their own. */\nexport function speechSentences(text: string): string[] {\n const normalized = text.replace(/\\s+/g, ' ').trim();\n const sentences: string[] = normalized.match(/[^.!?]+[.!?]+[\"'”’»]*/g) ?? [];\n const consumed = sentences.join('').length;\n if (consumed < normalized.length) sentences.push(normalized.slice(consumed));\n return sentences.flatMap((sentence) => {\n const trimmed = sentence.trim();\n return trimmed ? [trimmed] : [];\n });\n}\n\n/** Breaks a sentence longer than `limit` at word boundaries (falling back to a hard cut). */\nfunction hardSplit(sentence: string, limit: number): string[] {\n const pieces: string[] = [];\n let rest = sentence;\n while (rest.length > limit) {\n const cut = rest.lastIndexOf(' ', limit);\n const at = cut > 0 ? cut : limit;\n pieces.push(rest.slice(0, at));\n rest = rest.slice(at).trimStart();\n }\n if (rest) pieces.push(rest);\n return pieces;\n}\n\n/** One spoken unit: a sentence, or a piece of one that alone exceeds the engine limit —\n * pieces keep their sentence's index, so `progress` never skips a highlight. */\ntype SpeechPiece = { sentence: number; text: string };\n\nfunction piecesOf(sentences: string[], limit = UTTERANCE_LIMIT): SpeechPiece[] {\n return sentences.flatMap((sentence, index) =>\n (sentence.length > limit ? hardSplit(sentence, limit) : [sentence]).map((text) => ({\n sentence: index,\n text,\n })),\n );\n}\n\n/**\n * A text-to-speech player control — reads its `text` aloud through the Web Speech API.\n *\n * Renders a labeled play/pause toggle (the label folds away once engaged), a stop\n * control beside it that appears while reading, and a speed control cycling\n * normal/slow/fast. The text is spoken sentence by sentence (long sentences split into pieces under the\n * engine's truncation window), the `state` attribute reflects playback, a\n * `role=\"status\"` live region announces transitions, and `progress` events name the\n * sentence being read so a consumer can highlight it. Only one player speaks at a\n * time: starting one stops any other. `resume-at` names the sentence to begin from,\n * so a persisted position makes resume mean it.\n *\n * @element ore-speech-player\n *\n * @attr {string} text - The text to read aloud (also settable as a JavaScript property)\n * @attr {string} lang - BCP-47 tag of `text`'s language, e.g. `en` or `de-DE`\n * @attr {string} state - Reflected playback state: 'idle' | 'playing' | 'paused' | 'unsupported'\n * @attr {number} rate - Playback rate; the speed control cycles 1 → 0.85 → 1.15\n * @attr {number} resume-at - The sentence index playback begins from\n *\n * @fires play - Reading started. No detail.\n * @fires progress - A sentence began. detail: { sentence, total }\n * @fires stop - Reading stopped before finishing. No detail.\n * @fires end - Reading finished on its own. No detail.\n * @fires error - The engine failed mid-read. No detail.\n * @fires rate-change - The speed control moved to a new step. detail: { rate }\n *\n * @cssprop --speech-player-gap - Gap between the controls\n * @cssprop --speech-player-control-size - Each control's hit-area size (the toggle grows with its label)\n * @cssprop --speech-player-icon-color - Control icon color at rest\n * @cssprop --speech-player-active-color - Toggle color while reading (playing or paused)\n * @cssprop --speech-player-hover-bg - Control background on hover\n *\n * @part player - Outer control group\n * @part play - The play/pause toggle button\n * @part rate - The speed control\n * @part stop - The stop button (hidden while idle)\n *\n * @example\n * ```html\n * <ore-speech-player text=\"The valley sleeps under ash.\"></ore-speech-player>\n *\n * <ore-speech-player lang=\"de\" text=\"Das Tal schläft unter Asche.\"></ore-speech-player>\n * ```\n */\nexport const SPEECH_PLAYER_TAG = 'ore-speech-player' as const;\n\n/** Stops whichever player is currently reading — speechSynthesis is one global voice, so a\n * new reader cancels the engine and the old player needs to settle to idle. */\nlet stopActivePlayer: (() => void) | null = null;\n\ndefine<OreSpeechPlayerProps>(SPEECH_PLAYER_TAG, {\n props: {\n labels: prop.data<Partial<OreSpeechPlayerLabels>>({}),\n rate: prop.number(1),\n 'resume-at': prop.number(),\n text: prop.string(''),\n },\n\n setup(props) {\n const host = getHost() as SpeechPlayerElement;\n const emit = useEmit<OreSpeechPlayerEvents>();\n\n const supported = typeof window !== 'undefined' && typeof window.speechSynthesis !== 'undefined';\n const state = signal<SpeechPlayerState>(supported ? 'idle' : 'unsupported');\n const announcement = signal('');\n\n // The reading stream is sentence-addressed: pieces carry their sentence's index, so\n // progress and highlighting never drift apart.\n const sentences = computed(() => speechSentences(props.text.value ?? ''));\n const pieces = computed(() => piecesOf(sentences.value));\n const hasText = computed(() => (props.text.value ?? '').trim().length > 0);\n const label = (key: keyof OreSpeechPlayerLabels): string => props.labels.value?.[key] ?? DEFAULT_LABELS[key];\n const toggleLabel = computed(() => {\n if (state.value === 'unsupported') return label('unsupported');\n if (state.value === 'playing') return label('pause');\n if (state.value === 'paused') return label('resume');\n return label('read');\n });\n\n // The rate is the consumer's to persist: bind `rate` with `rate-change` and every\n // instance follows the listener's choice; unbound, the control cycles internally.\n const rate = signal<number>(props.rate.value ?? 1);\n watch(props.rate, (value) => {\n if (value !== undefined) rate.value = value;\n });\n const speedName = (value: number): string => {\n if (value === 1) return label('speedNormal');\n if (value === 0.85) return label('speedSlow');\n if (value === 1.15) return label('speedFast');\n return `${value}×`;\n };\n const speedLabel = computed(() => `${label('speed')}: ${speedName(rate.value)}`);\n\n // Reflect playback so CSS can react to it; the aria contract lives on the controls.\n bind({ attr: { state } });\n\n const labelOf = (key: keyof OreSpeechPlayerLabels): void => {\n announcement.value = label(key);\n };\n\n /** Utterance callbacks capture the generation they belong to; a stop or a later play\n * bumps it, so stale events from a canceled utterance cannot chain or settle. */\n let generation = 0;\n /** Bumped by any engine callback on the live utterance — liveness evidence for the\n * resume watchdog. */\n let enginePulse = 0;\n let current = 0;\n let disposed = false;\n\n const speakPiece = (index: number): void => {\n current = index;\n const piece = pieces.value[index]!;\n const utterance = new SpeechSynthesisUtterance(piece.text);\n utterance.rate = rate.value;\n // The native `lang` attribute names the text's language — the standard HTML\n // semantic — and hints voice selection when no explicit voice matches yet.\n const lang = host.getAttribute('lang');\n if (lang) {\n utterance.lang = lang;\n const voice = window.speechSynthesis\n .getVoices()\n .find((candidate) => candidate.lang.toLowerCase().startsWith(lang.toLowerCase()));\n if (voice) utterance.voice = voice;\n }\n const token = generation;\n const alive = (): void => {\n if (token === generation) enginePulse += 1;\n };\n utterance.onstart = alive;\n utterance.onboundary = alive;\n utterance.onend = () => {\n alive();\n if (token !== generation) return;\n if (index + 1 >= pieces.value.length) {\n releaseActive();\n state.value = 'idle';\n labelOf('finished');\n emit('end');\n } else {\n speakPiece(index + 1);\n }\n };\n utterance.onerror = () => {\n alive();\n if (token !== generation) return;\n releaseActive();\n state.value = 'idle';\n labelOf('error');\n emit('error');\n };\n emit('progress', { sentence: piece.sentence, total: sentences.value.length });\n window.speechSynthesis.speak(utterance);\n };\n\n const releaseActive = (): void => {\n if (stopActivePlayer === stop) stopActivePlayer = null;\n };\n\n /** The first piece of the sentence to begin from — clamped into the text. */\n const pieceIndexFor = (sentence: number | undefined): number => {\n const total = sentences.value.length;\n if (sentence === undefined || total === 0) return 0;\n const begin = Math.min(Math.max(sentence, 0), total - 1);\n const index = pieces.value.findIndex((piece) => piece.sentence >= begin);\n return index === -1 ? 0 : index;\n };\n\n const start = (): void => {\n if (state.value !== 'idle' || !hasText.value) return;\n stopActivePlayer?.();\n window.speechSynthesis.cancel();\n generation += 1;\n stopActivePlayer = stop;\n state.value = 'playing';\n labelOf('started');\n emit('play');\n speakPiece(pieceIndexFor(props['resume-at'].value));\n };\n\n let pausedAt = 0;\n\n const pause = (): void => {\n if (state.value !== 'playing') return;\n window.speechSynthesis.pause();\n state.value = 'paused';\n pausedAt = performance.now();\n labelOf('paused');\n };\n\n const resume = (): void => {\n if (state.value !== 'paused') return;\n window.speechSynthesis.resume();\n state.value = 'playing';\n labelOf('resumed');\n // Some Chromium builds resume a long-held pause into silence: no error, no\n // event, the playing state a lie. Within ten seconds the engine is trusted;\n // beyond it, demand a pulse within a second or restart the current sentence —\n // a fresh utterance always speaks. Boundary-less engines only pay this on long holds.\n if (performance.now() - pausedAt < 10_000) return;\n const pulse = enginePulse;\n window.setTimeout(() => {\n if (disposed || state.value !== 'playing' || enginePulse !== pulse) return;\n generation += 1;\n window.speechSynthesis.cancel();\n speakPiece(current);\n }, 1000);\n };\n\n function stop(): void {\n if (state.value === 'idle' || state.value === 'unsupported') return;\n generation += 1;\n releaseActive();\n window.speechSynthesis.cancel();\n state.value = 'idle';\n labelOf('stopped');\n emit('stop');\n }\n\n /** One control, three meanings by state — play, pause, resume. */\n const toggle = (): void => {\n if (state.value === 'playing') pause();\n else if (state.value === 'paused') resume();\n else start();\n };\n\n const cycleRate = (): void => {\n const index = SPEECH_RATES.indexOf(rate.value as SpeechRate);\n rate.value = SPEECH_RATES[(index + 1) % SPEECH_RATES.length] ?? 1;\n announcement.value = speedLabel.value;\n emit('rate-change', { rate: rate.value });\n // A rate change while reading applies at once: the current sentence restarts at\n // the new pace. Paused or idle readings pick it up with the next sentence.\n if (state.value === 'playing') {\n generation += 1;\n window.speechSynthesis.cancel();\n speakPiece(current);\n }\n };\n\n host.play = start;\n host.pause = pause;\n host.stop = stop;\n\n onCleanup(() => {\n disposed = true;\n generation += 1;\n releaseActive();\n // The engine itself may already be gone in a tearing-down context.\n if (window.speechSynthesis) window.speechSynthesis.cancel();\n });\n\n return html`\n <div class=\"player\" part=\"player\">\n <button\n class=\"control control--toggle\"\n part=\"play\"\n type=\"button\"\n ?disabled=\"${() => state.value === 'unsupported' || !hasText.value}\"\n aria-label=\"${toggleLabel}\"\n @click=${toggle}\n >\n ${() =>\n state.value === 'playing'\n ? html`<ore-icon name=\"pause\" size=\"16\" aria-hidden=\"true\"></ore-icon>`\n : html`<ore-icon name=\"play\" size=\"16\" aria-hidden=\"true\"></ore-icon>`}\n <span class=\"toggle-label\" ?hidden=\"${() => state.value === 'playing' || state.value === 'paused'}\">${() => label('read')}</span>\n </button>\n <button\n class=\"control\"\n part=\"stop\"\n type=\"button\"\n ?hidden=\"${() => state.value === 'idle' || state.value === 'unsupported'}\"\n aria-label=\"${() => label('stop')}\"\n @click=${stop}\n >\n <ore-icon name=\"square\" size=\"16\" aria-hidden=\"true\"></ore-icon>\n </button>\n <button\n class=\"control control--rate\"\n part=\"rate\"\n type=\"button\"\n aria-label=\"${speedLabel}\"\n @click=${cycleRate}\n >\n <ore-icon name=\"gauge\" size=\"16\" aria-hidden=\"true\"></ore-icon>\n <span class=\"rate-text\">${() => `${rate.value}×`}</span>\n </button>\n </div>\n <div class=\"sr-only\" role=\"status\">${announcement}</div>\n `;\n },\n\n styles: [srOnlyMixin, componentStyles],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"speech-player.cjs","names":[],"sources":["../src/content/speech-player/speech-player.ts"],"sourcesContent":["import { bind, define, getHost, html, onCleanup, prop, useEmit } from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\nimport { srOnlyMixin } from '../../styles';\n\nimport componentStyles from './speech-player.css?inline';\n\nexport { ICON_TAG } from '../icon/icon';\n\n/** The player's reflected state: `unsupported` when the Web Speech API is missing. */\nexport type SpeechPlayerState = 'idle' | 'playing' | 'paused' | 'unsupported';\n\n/** The rate steps the speed control cycles through: normal, slow, fast. */\nexport const SPEECH_RATES = [1, 0.85, 1.15] as const;\nexport type SpeechRate = (typeof SPEECH_RATES)[number];\n\n/** Every user-facing string the component renders or announces. Override the `labels`\n * property to localize; English is the default. */\nexport type OreSpeechPlayerLabels = {\n /** Announced when the engine fails mid-read. */\n error: string;\n /** The toggle's accessible name while playing. */\n pause: string;\n /** Announced when reading is paused. */\n paused: string;\n /** The toggle's accessible name while idle. */\n read: string;\n /** The toggle's accessible name while paused. */\n resume: string;\n /** Announced when reading resumes. */\n resumed: string;\n /** Announced when reading starts. */\n started: string;\n /** The speed control's accessible-name stem; the current step's name follows it. */\n speed: string;\n /** The fast speed step's name. */\n speedFast: string;\n /** The normal speed step's name. */\n speedNormal: string;\n /** The slow speed step's name. */\n speedSlow: string;\n /** The stop control's accessible name. */\n stop: string;\n /** Announced when reading stops before finishing. */\n stopped: string;\n /** Announced when reading finishes on its own. */\n finished: string;\n /** The toggle's accessible name when speech synthesis is unavailable. */\n unsupported: string;\n};\n\nconst DEFAULT_LABELS: OreSpeechPlayerLabels = {\n error: 'Reading was interrupted.',\n finished: 'Reading finished.',\n pause: 'Pause reading',\n paused: 'Reading paused.',\n read: 'Read aloud',\n resume: 'Resume reading',\n resumed: 'Reading resumed.',\n speed: 'Reading speed',\n speedFast: 'fast',\n speedNormal: 'normal',\n speedSlow: 'slow',\n started: 'Reading aloud.',\n stop: 'Stop reading',\n stopped: 'Reading stopped.',\n unsupported: 'Text-to-speech is unavailable',\n};\n\n/** Speech player component properties */\nexport type OreSpeechPlayerProps = {\n /** Labels for the controls and announcements: override to localize. */\n labels?: Partial<OreSpeechPlayerLabels>;\n /** Playback rate: bind with `rate-change` to persist the listener's choice; unbound,\n * the speed control cycles on its own. Values outside `SPEECH_RATES` snap into the\n * cycle at the control's first use. */\n rate?: number;\n /** The sentence index playback begins from: bind a persisted position so \"resume\"\n * survives stops, collapses, and page loads. */\n 'resume-at'?: number;\n /** The text to read aloud. */\n text?: string;\n};\n\n/** Speech player component events */\nexport type OreSpeechPlayerEvents = {\n /** Reading finished on its own. */\n end: undefined;\n /** The engine failed mid-read: reading stopped, but not by the listener. */\n error: undefined;\n /** Reading started: from the control or a `play()` call. */\n play: undefined;\n /** A sentence began: carry `sentence` of `total` to the surfaces that follow the voice. */\n progress: { sentence: number; total: number };\n /** The speed control moved to a new step. */\n 'rate-change': { rate: number };\n /** Reading stopped before finishing: from the control or a `stop()` call. */\n stop: undefined;\n};\n\n/** Element interface exposing the imperative API for `ore-speech-player`. */\nexport interface SpeechPlayerElement extends HTMLElement, OreSpeechPlayerProps {\n /** Pauses the current reading. */\n pause(): void;\n /** Starts reading: from `resume-at` when bound, else from the beginning.\n * Any player already reading stops first. */\n play(): void;\n /** Stops reading and cancels pending speech. */\n stop(): void;\n}\n\n/** Chrome cuts a single utterance off after roughly fifteen seconds of speech; sentence\n * pieces under this length stay safely inside that window at any of the rate steps. */\nconst UTTERANCE_LIMIT = 220;\n\n/** The entry's sentences: prose ends . ! or ?, closing quotes belong to the sentence\n * they end, and an unterminated tail is still a sentence. Consumers that highlight what\n * is being read build their display from this same list, so `progress` sentence indexes\n * map onto their own. */\nexport function speechSentences(text: string): string[] {\n const normalized = text.replace(/\\s+/g, ' ').trim();\n const sentences: string[] = normalized.match(/[^.!?]+[.!?]+[\"'”’»]*/g) ?? [];\n const consumed = sentences.join('').length;\n if (consumed < normalized.length) sentences.push(normalized.slice(consumed));\n return sentences.flatMap((sentence) => {\n const trimmed = sentence.trim();\n return trimmed ? [trimmed] : [];\n });\n}\n\n/** Breaks a sentence longer than `limit` at word boundaries (falling back to a hard cut). */\nfunction hardSplit(sentence: string, limit: number): string[] {\n const pieces: string[] = [];\n let rest = sentence;\n while (rest.length > limit) {\n const cut = rest.lastIndexOf(' ', limit);\n const at = cut > 0 ? cut : limit;\n pieces.push(rest.slice(0, at));\n rest = rest.slice(at).trimStart();\n }\n if (rest) pieces.push(rest);\n return pieces;\n}\n\n/** One spoken unit: a sentence, or a piece of one that alone exceeds the engine limit :\n * pieces keep their sentence's index, so `progress` never skips a highlight. */\ntype SpeechPiece = { sentence: number; text: string };\n\nfunction piecesOf(sentences: string[], limit = UTTERANCE_LIMIT): SpeechPiece[] {\n return sentences.flatMap((sentence, index) =>\n (sentence.length > limit ? hardSplit(sentence, limit) : [sentence]).map((text) => ({\n sentence: index,\n text,\n })),\n );\n}\n\n/**\n * A text-to-speech player control: reads its `text` aloud through the Web Speech API.\n *\n * Renders a labeled play/pause toggle (the label folds away once engaged), a stop\n * control beside it that appears while reading, and a speed control cycling\n * normal/slow/fast. The text is spoken sentence by sentence (long sentences split into pieces under the\n * engine's truncation window), the `state` attribute reflects playback, a\n * `role=\"status\"` live region announces transitions, and `progress` events name the\n * sentence being read so a consumer can highlight it. Only one player speaks at a\n * time: starting one stops any other. `resume-at` names the sentence to begin from,\n * so a persisted position makes resume mean it.\n *\n * @element ore-speech-player\n *\n * @attr {string} text - The text to read aloud (also settable as a JavaScript property)\n * @attr {string} lang - BCP-47 tag of `text`'s language, e.g. `en` or `de-DE`\n * @attr {string} state - Reflected playback state: 'idle' | 'playing' | 'paused' | 'unsupported'\n * @attr {number} rate - Playback rate; the speed control cycles 1 → 0.85 → 1.15\n * @attr {number} resume-at - The sentence index playback begins from\n *\n * @fires play - Reading started. No detail.\n * @fires progress - A sentence began. detail: { sentence, total }\n * @fires stop - Reading stopped before finishing. No detail.\n * @fires end - Reading finished on its own. No detail.\n * @fires error - The engine failed mid-read. No detail.\n * @fires rate-change - The speed control moved to a new step. detail: { rate }\n *\n * @cssprop --speech-player-gap - Gap between the controls\n * @cssprop --speech-player-control-size - Each control's hit-area size (the toggle grows with its label)\n * @cssprop --speech-player-icon-color - Control icon color at rest\n * @cssprop --speech-player-active-color - Toggle color while reading (playing or paused)\n * @cssprop --speech-player-hover-bg - Control background on hover\n *\n * @part player - Outer control group\n * @part play - The play/pause toggle button\n * @part rate - The speed control\n * @part stop - The stop button (hidden while idle)\n *\n * @example\n * ```html\n * <ore-speech-player text=\"The valley sleeps under ash.\"></ore-speech-player>\n *\n * <ore-speech-player lang=\"de\" text=\"Das Tal schläft unter Asche.\"></ore-speech-player>\n * ```\n */\nexport const SPEECH_PLAYER_TAG = 'ore-speech-player' as const;\n\n/** Stops whichever player is currently reading: speechSynthesis is one global voice, so a\n * new reader cancels the engine and the old player needs to settle to idle. */\nlet stopActivePlayer: (() => void) | null = null;\n\ndefine<OreSpeechPlayerProps>(SPEECH_PLAYER_TAG, {\n props: {\n labels: prop.data<Partial<OreSpeechPlayerLabels>>({}),\n rate: prop.number(1),\n 'resume-at': prop.number(),\n text: prop.string(''),\n },\n\n setup(props) {\n const host = getHost() as SpeechPlayerElement;\n const emit = useEmit<OreSpeechPlayerEvents>();\n\n const supported = typeof window !== 'undefined' && typeof window.speechSynthesis !== 'undefined';\n const state = signal<SpeechPlayerState>(supported ? 'idle' : 'unsupported');\n const announcement = signal('');\n\n // The reading stream is sentence-addressed: pieces carry their sentence's index, so\n // progress and highlighting never drift apart.\n const sentences = computed(() => speechSentences(props.text.value ?? ''));\n const pieces = computed(() => piecesOf(sentences.value));\n const hasText = computed(() => (props.text.value ?? '').trim().length > 0);\n const label = (key: keyof OreSpeechPlayerLabels): string => props.labels.value?.[key] ?? DEFAULT_LABELS[key];\n const toggleLabel = computed(() => {\n if (state.value === 'unsupported') return label('unsupported');\n if (state.value === 'playing') return label('pause');\n if (state.value === 'paused') return label('resume');\n return label('read');\n });\n\n // The rate is the consumer's to persist: bind `rate` with `rate-change` and every\n // instance follows the listener's choice; unbound, the control cycles internally.\n const rate = signal<number>(props.rate.value ?? 1);\n watch(props.rate, (value) => {\n if (value !== undefined) rate.value = value;\n });\n const speedName = (value: number): string => {\n if (value === 1) return label('speedNormal');\n if (value === 0.85) return label('speedSlow');\n if (value === 1.15) return label('speedFast');\n return `${value}×`;\n };\n const speedLabel = computed(() => `${label('speed')}: ${speedName(rate.value)}`);\n\n // Reflect playback so CSS can react to it; the aria contract lives on the controls.\n bind({ attr: { state } });\n\n const labelOf = (key: keyof OreSpeechPlayerLabels): void => {\n announcement.value = label(key);\n };\n\n /** Utterance callbacks capture the generation they belong to; a stop or a later play\n * bumps it, so stale events from a canceled utterance cannot chain or settle. */\n let generation = 0;\n /** Bumped by any engine callback on the live utterance: liveness evidence for the\n * resume watchdog. */\n let enginePulse = 0;\n let current = 0;\n let disposed = false;\n\n const speakPiece = (index: number): void => {\n current = index;\n const piece = pieces.value[index]!;\n const utterance = new SpeechSynthesisUtterance(piece.text);\n utterance.rate = rate.value;\n // The native `lang` attribute names the text's language: the standard HTML\n // semantic, and hints voice selection when no explicit voice matches yet.\n const lang = host.getAttribute('lang');\n if (lang) {\n utterance.lang = lang;\n const voice = window.speechSynthesis\n .getVoices()\n .find((candidate) => candidate.lang.toLowerCase().startsWith(lang.toLowerCase()));\n if (voice) utterance.voice = voice;\n }\n const token = generation;\n const alive = (): void => {\n if (token === generation) enginePulse += 1;\n };\n utterance.onstart = alive;\n utterance.onboundary = alive;\n utterance.onend = () => {\n alive();\n if (token !== generation) return;\n if (index + 1 >= pieces.value.length) {\n releaseActive();\n state.value = 'idle';\n labelOf('finished');\n emit('end');\n } else {\n speakPiece(index + 1);\n }\n };\n utterance.onerror = () => {\n alive();\n if (token !== generation) return;\n releaseActive();\n state.value = 'idle';\n labelOf('error');\n emit('error');\n };\n emit('progress', { sentence: piece.sentence, total: sentences.value.length });\n window.speechSynthesis.speak(utterance);\n };\n\n const releaseActive = (): void => {\n if (stopActivePlayer === stop) stopActivePlayer = null;\n };\n\n /** The first piece of the sentence to begin from: clamped into the text. */\n const pieceIndexFor = (sentence: number | undefined): number => {\n const total = sentences.value.length;\n if (sentence === undefined || total === 0) return 0;\n const begin = Math.min(Math.max(sentence, 0), total - 1);\n const index = pieces.value.findIndex((piece) => piece.sentence >= begin);\n return index === -1 ? 0 : index;\n };\n\n const start = (): void => {\n if (state.value !== 'idle' || !hasText.value) return;\n stopActivePlayer?.();\n window.speechSynthesis.cancel();\n generation += 1;\n stopActivePlayer = stop;\n state.value = 'playing';\n labelOf('started');\n emit('play');\n speakPiece(pieceIndexFor(props['resume-at'].value));\n };\n\n let pausedAt = 0;\n\n const pause = (): void => {\n if (state.value !== 'playing') return;\n window.speechSynthesis.pause();\n state.value = 'paused';\n pausedAt = performance.now();\n labelOf('paused');\n };\n\n const resume = (): void => {\n if (state.value !== 'paused') return;\n window.speechSynthesis.resume();\n state.value = 'playing';\n labelOf('resumed');\n // Some Chromium builds resume a long-held pause into silence: no error, no\n // event, the playing state a lie. Within ten seconds the engine is trusted;\n // beyond it, demand a pulse within a second or restart the current sentence :\n // a fresh utterance always speaks. Boundary-less engines only pay this on long holds.\n if (performance.now() - pausedAt < 10_000) return;\n const pulse = enginePulse;\n window.setTimeout(() => {\n if (disposed || state.value !== 'playing' || enginePulse !== pulse) return;\n generation += 1;\n window.speechSynthesis.cancel();\n speakPiece(current);\n }, 1000);\n };\n\n function stop(): void {\n if (state.value === 'idle' || state.value === 'unsupported') return;\n generation += 1;\n releaseActive();\n window.speechSynthesis.cancel();\n state.value = 'idle';\n labelOf('stopped');\n emit('stop');\n }\n\n /** One control, three meanings by state: play, pause, resume. */\n const toggle = (): void => {\n if (state.value === 'playing') pause();\n else if (state.value === 'paused') resume();\n else start();\n };\n\n const cycleRate = (): void => {\n const index = SPEECH_RATES.indexOf(rate.value as SpeechRate);\n rate.value = SPEECH_RATES[(index + 1) % SPEECH_RATES.length] ?? 1;\n announcement.value = speedLabel.value;\n emit('rate-change', { rate: rate.value });\n // A rate change while reading applies at once: the current sentence restarts at\n // the new pace. Paused or idle readings pick it up with the next sentence.\n if (state.value === 'playing') {\n generation += 1;\n window.speechSynthesis.cancel();\n speakPiece(current);\n }\n };\n\n host.play = start;\n host.pause = pause;\n host.stop = stop;\n\n onCleanup(() => {\n disposed = true;\n generation += 1;\n releaseActive();\n // The engine itself may already be gone in a tearing-down context.\n if (window.speechSynthesis) window.speechSynthesis.cancel();\n });\n\n return html`\n <div class=\"player\" part=\"player\">\n <button\n class=\"control control--toggle\"\n part=\"play\"\n type=\"button\"\n ?disabled=\"${() => state.value === 'unsupported' || !hasText.value}\"\n aria-label=\"${toggleLabel}\"\n @click=${toggle}\n >\n ${() =>\n state.value === 'playing'\n ? html`<ore-icon name=\"pause\" size=\"16\" aria-hidden=\"true\"></ore-icon>`\n : html`<ore-icon name=\"play\" size=\"16\" aria-hidden=\"true\"></ore-icon>`}\n <span class=\"toggle-label\" ?hidden=\"${() => state.value === 'playing' || state.value === 'paused'}\">${() => label('read')}</span>\n </button>\n <button\n class=\"control\"\n part=\"stop\"\n type=\"button\"\n ?hidden=\"${() => state.value === 'idle' || state.value === 'unsupported'}\"\n aria-label=\"${() => label('stop')}\"\n @click=${stop}\n >\n <ore-icon name=\"square\" size=\"16\" aria-hidden=\"true\"></ore-icon>\n </button>\n <button\n class=\"control control--rate\"\n part=\"rate\"\n type=\"button\"\n aria-label=\"${speedLabel}\"\n @click=${cycleRate}\n >\n <ore-icon name=\"gauge\" size=\"16\" aria-hidden=\"true\"></ore-icon>\n <span class=\"rate-text\">${() => `${rate.value}×`}</span>\n </button>\n </div>\n <div class=\"sr-only\" role=\"status\">${announcement}</div>\n `;\n },\n\n styles: [srOnlyMixin, componentStyles],\n});\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"speech-player.js","names":[],"sources":["../src/content/speech-player/speech-player.ts"],"sourcesContent":["import { bind, define, getHost, html, onCleanup, prop, useEmit } from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\nimport { srOnlyMixin } from '../../styles';\n\nimport componentStyles from './speech-player.css?inline';\n\nexport { ICON_TAG } from '../icon/icon';\n\n/** The player's reflected state — `unsupported` when the Web Speech API is missing. */\nexport type SpeechPlayerState = 'idle' | 'playing' | 'paused' | 'unsupported';\n\n/** The rate steps the speed control cycles through — normal, slow, fast. */\nexport const SPEECH_RATES = [1, 0.85, 1.15] as const;\nexport type SpeechRate = (typeof SPEECH_RATES)[number];\n\n/** Every user-facing string the component renders or announces. Override the `labels`\n * property to localize; English is the default. */\nexport type OreSpeechPlayerLabels = {\n /** Announced when the engine fails mid-read. */\n error: string;\n /** The toggle's accessible name while playing. */\n pause: string;\n /** Announced when reading is paused. */\n paused: string;\n /** The toggle's accessible name while idle. */\n read: string;\n /** The toggle's accessible name while paused. */\n resume: string;\n /** Announced when reading resumes. */\n resumed: string;\n /** Announced when reading starts. */\n started: string;\n /** The speed control's accessible-name stem; the current step's name follows it. */\n speed: string;\n /** The fast speed step's name. */\n speedFast: string;\n /** The normal speed step's name. */\n speedNormal: string;\n /** The slow speed step's name. */\n speedSlow: string;\n /** The stop control's accessible name. */\n stop: string;\n /** Announced when reading stops before finishing. */\n stopped: string;\n /** Announced when reading finishes on its own. */\n finished: string;\n /** The toggle's accessible name when speech synthesis is unavailable. */\n unsupported: string;\n};\n\nconst DEFAULT_LABELS: OreSpeechPlayerLabels = {\n error: 'Reading was interrupted.',\n finished: 'Reading finished.',\n pause: 'Pause reading',\n paused: 'Reading paused.',\n read: 'Read aloud',\n resume: 'Resume reading',\n resumed: 'Reading resumed.',\n speed: 'Reading speed',\n speedFast: 'fast',\n speedNormal: 'normal',\n speedSlow: 'slow',\n started: 'Reading aloud.',\n stop: 'Stop reading',\n stopped: 'Reading stopped.',\n unsupported: 'Text-to-speech is unavailable',\n};\n\n/** Speech player component properties */\nexport type OreSpeechPlayerProps = {\n /** Labels for the controls and announcements — override to localize. */\n labels?: Partial<OreSpeechPlayerLabels>;\n /** Playback rate — bind with `rate-change` to persist the listener's choice; unbound,\n * the speed control cycles on its own. Values outside `SPEECH_RATES` snap into the\n * cycle at the control's first use. */\n rate?: number;\n /** The sentence index playback begins from — bind a persisted position so \"resume\"\n * survives stops, collapses, and page loads. */\n 'resume-at'?: number;\n /** The text to read aloud. */\n text?: string;\n};\n\n/** Speech player component events */\nexport type OreSpeechPlayerEvents = {\n /** Reading finished on its own. */\n end: undefined;\n /** The engine failed mid-read — reading stopped, but not by the listener. */\n error: undefined;\n /** Reading started — from the control or a `play()` call. */\n play: undefined;\n /** A sentence began — carry `sentence` of `total` to the surfaces that follow the voice. */\n progress: { sentence: number; total: number };\n /** The speed control moved to a new step. */\n 'rate-change': { rate: number };\n /** Reading stopped before finishing — from the control or a `stop()` call. */\n stop: undefined;\n};\n\n/** Element interface exposing the imperative API for `ore-speech-player`. */\nexport interface SpeechPlayerElement extends HTMLElement, OreSpeechPlayerProps {\n /** Pauses the current reading. */\n pause(): void;\n /** Starts reading — from `resume-at` when bound, else from the beginning.\n * Any player already reading stops first. */\n play(): void;\n /** Stops reading and cancels pending speech. */\n stop(): void;\n}\n\n/** Chrome cuts a single utterance off after roughly fifteen seconds of speech; sentence\n * pieces under this length stay safely inside that window at any of the rate steps. */\nconst UTTERANCE_LIMIT = 220;\n\n/** The entry's sentences — prose ends . ! or ?, closing quotes belong to the sentence\n * they end, and an unterminated tail is still a sentence. Consumers that highlight what\n * is being read build their display from this same list, so `progress` sentence indexes\n * map onto their own. */\nexport function speechSentences(text: string): string[] {\n const normalized = text.replace(/\\s+/g, ' ').trim();\n const sentences: string[] = normalized.match(/[^.!?]+[.!?]+[\"'”’»]*/g) ?? [];\n const consumed = sentences.join('').length;\n if (consumed < normalized.length) sentences.push(normalized.slice(consumed));\n return sentences.flatMap((sentence) => {\n const trimmed = sentence.trim();\n return trimmed ? [trimmed] : [];\n });\n}\n\n/** Breaks a sentence longer than `limit` at word boundaries (falling back to a hard cut). */\nfunction hardSplit(sentence: string, limit: number): string[] {\n const pieces: string[] = [];\n let rest = sentence;\n while (rest.length > limit) {\n const cut = rest.lastIndexOf(' ', limit);\n const at = cut > 0 ? cut : limit;\n pieces.push(rest.slice(0, at));\n rest = rest.slice(at).trimStart();\n }\n if (rest) pieces.push(rest);\n return pieces;\n}\n\n/** One spoken unit: a sentence, or a piece of one that alone exceeds the engine limit —\n * pieces keep their sentence's index, so `progress` never skips a highlight. */\ntype SpeechPiece = { sentence: number; text: string };\n\nfunction piecesOf(sentences: string[], limit = UTTERANCE_LIMIT): SpeechPiece[] {\n return sentences.flatMap((sentence, index) =>\n (sentence.length > limit ? hardSplit(sentence, limit) : [sentence]).map((text) => ({\n sentence: index,\n text,\n })),\n );\n}\n\n/**\n * A text-to-speech player control — reads its `text` aloud through the Web Speech API.\n *\n * Renders a labeled play/pause toggle (the label folds away once engaged), a stop\n * control beside it that appears while reading, and a speed control cycling\n * normal/slow/fast. The text is spoken sentence by sentence (long sentences split into pieces under the\n * engine's truncation window), the `state` attribute reflects playback, a\n * `role=\"status\"` live region announces transitions, and `progress` events name the\n * sentence being read so a consumer can highlight it. Only one player speaks at a\n * time: starting one stops any other. `resume-at` names the sentence to begin from,\n * so a persisted position makes resume mean it.\n *\n * @element ore-speech-player\n *\n * @attr {string} text - The text to read aloud (also settable as a JavaScript property)\n * @attr {string} lang - BCP-47 tag of `text`'s language, e.g. `en` or `de-DE`\n * @attr {string} state - Reflected playback state: 'idle' | 'playing' | 'paused' | 'unsupported'\n * @attr {number} rate - Playback rate; the speed control cycles 1 → 0.85 → 1.15\n * @attr {number} resume-at - The sentence index playback begins from\n *\n * @fires play - Reading started. No detail.\n * @fires progress - A sentence began. detail: { sentence, total }\n * @fires stop - Reading stopped before finishing. No detail.\n * @fires end - Reading finished on its own. No detail.\n * @fires error - The engine failed mid-read. No detail.\n * @fires rate-change - The speed control moved to a new step. detail: { rate }\n *\n * @cssprop --speech-player-gap - Gap between the controls\n * @cssprop --speech-player-control-size - Each control's hit-area size (the toggle grows with its label)\n * @cssprop --speech-player-icon-color - Control icon color at rest\n * @cssprop --speech-player-active-color - Toggle color while reading (playing or paused)\n * @cssprop --speech-player-hover-bg - Control background on hover\n *\n * @part player - Outer control group\n * @part play - The play/pause toggle button\n * @part rate - The speed control\n * @part stop - The stop button (hidden while idle)\n *\n * @example\n * ```html\n * <ore-speech-player text=\"The valley sleeps under ash.\"></ore-speech-player>\n *\n * <ore-speech-player lang=\"de\" text=\"Das Tal schläft unter Asche.\"></ore-speech-player>\n * ```\n */\nexport const SPEECH_PLAYER_TAG = 'ore-speech-player' as const;\n\n/** Stops whichever player is currently reading — speechSynthesis is one global voice, so a\n * new reader cancels the engine and the old player needs to settle to idle. */\nlet stopActivePlayer: (() => void) | null = null;\n\ndefine<OreSpeechPlayerProps>(SPEECH_PLAYER_TAG, {\n props: {\n labels: prop.data<Partial<OreSpeechPlayerLabels>>({}),\n rate: prop.number(1),\n 'resume-at': prop.number(),\n text: prop.string(''),\n },\n\n setup(props) {\n const host = getHost() as SpeechPlayerElement;\n const emit = useEmit<OreSpeechPlayerEvents>();\n\n const supported = typeof window !== 'undefined' && typeof window.speechSynthesis !== 'undefined';\n const state = signal<SpeechPlayerState>(supported ? 'idle' : 'unsupported');\n const announcement = signal('');\n\n // The reading stream is sentence-addressed: pieces carry their sentence's index, so\n // progress and highlighting never drift apart.\n const sentences = computed(() => speechSentences(props.text.value ?? ''));\n const pieces = computed(() => piecesOf(sentences.value));\n const hasText = computed(() => (props.text.value ?? '').trim().length > 0);\n const label = (key: keyof OreSpeechPlayerLabels): string => props.labels.value?.[key] ?? DEFAULT_LABELS[key];\n const toggleLabel = computed(() => {\n if (state.value === 'unsupported') return label('unsupported');\n if (state.value === 'playing') return label('pause');\n if (state.value === 'paused') return label('resume');\n return label('read');\n });\n\n // The rate is the consumer's to persist: bind `rate` with `rate-change` and every\n // instance follows the listener's choice; unbound, the control cycles internally.\n const rate = signal<number>(props.rate.value ?? 1);\n watch(props.rate, (value) => {\n if (value !== undefined) rate.value = value;\n });\n const speedName = (value: number): string => {\n if (value === 1) return label('speedNormal');\n if (value === 0.85) return label('speedSlow');\n if (value === 1.15) return label('speedFast');\n return `${value}×`;\n };\n const speedLabel = computed(() => `${label('speed')}: ${speedName(rate.value)}`);\n\n // Reflect playback so CSS can react to it; the aria contract lives on the controls.\n bind({ attr: { state } });\n\n const labelOf = (key: keyof OreSpeechPlayerLabels): void => {\n announcement.value = label(key);\n };\n\n /** Utterance callbacks capture the generation they belong to; a stop or a later play\n * bumps it, so stale events from a canceled utterance cannot chain or settle. */\n let generation = 0;\n /** Bumped by any engine callback on the live utterance — liveness evidence for the\n * resume watchdog. */\n let enginePulse = 0;\n let current = 0;\n let disposed = false;\n\n const speakPiece = (index: number): void => {\n current = index;\n const piece = pieces.value[index]!;\n const utterance = new SpeechSynthesisUtterance(piece.text);\n utterance.rate = rate.value;\n // The native `lang` attribute names the text's language — the standard HTML\n // semantic — and hints voice selection when no explicit voice matches yet.\n const lang = host.getAttribute('lang');\n if (lang) {\n utterance.lang = lang;\n const voice = window.speechSynthesis\n .getVoices()\n .find((candidate) => candidate.lang.toLowerCase().startsWith(lang.toLowerCase()));\n if (voice) utterance.voice = voice;\n }\n const token = generation;\n const alive = (): void => {\n if (token === generation) enginePulse += 1;\n };\n utterance.onstart = alive;\n utterance.onboundary = alive;\n utterance.onend = () => {\n alive();\n if (token !== generation) return;\n if (index + 1 >= pieces.value.length) {\n releaseActive();\n state.value = 'idle';\n labelOf('finished');\n emit('end');\n } else {\n speakPiece(index + 1);\n }\n };\n utterance.onerror = () => {\n alive();\n if (token !== generation) return;\n releaseActive();\n state.value = 'idle';\n labelOf('error');\n emit('error');\n };\n emit('progress', { sentence: piece.sentence, total: sentences.value.length });\n window.speechSynthesis.speak(utterance);\n };\n\n const releaseActive = (): void => {\n if (stopActivePlayer === stop) stopActivePlayer = null;\n };\n\n /** The first piece of the sentence to begin from — clamped into the text. */\n const pieceIndexFor = (sentence: number | undefined): number => {\n const total = sentences.value.length;\n if (sentence === undefined || total === 0) return 0;\n const begin = Math.min(Math.max(sentence, 0), total - 1);\n const index = pieces.value.findIndex((piece) => piece.sentence >= begin);\n return index === -1 ? 0 : index;\n };\n\n const start = (): void => {\n if (state.value !== 'idle' || !hasText.value) return;\n stopActivePlayer?.();\n window.speechSynthesis.cancel();\n generation += 1;\n stopActivePlayer = stop;\n state.value = 'playing';\n labelOf('started');\n emit('play');\n speakPiece(pieceIndexFor(props['resume-at'].value));\n };\n\n let pausedAt = 0;\n\n const pause = (): void => {\n if (state.value !== 'playing') return;\n window.speechSynthesis.pause();\n state.value = 'paused';\n pausedAt = performance.now();\n labelOf('paused');\n };\n\n const resume = (): void => {\n if (state.value !== 'paused') return;\n window.speechSynthesis.resume();\n state.value = 'playing';\n labelOf('resumed');\n // Some Chromium builds resume a long-held pause into silence: no error, no\n // event, the playing state a lie. Within ten seconds the engine is trusted;\n // beyond it, demand a pulse within a second or restart the current sentence —\n // a fresh utterance always speaks. Boundary-less engines only pay this on long holds.\n if (performance.now() - pausedAt < 10_000) return;\n const pulse = enginePulse;\n window.setTimeout(() => {\n if (disposed || state.value !== 'playing' || enginePulse !== pulse) return;\n generation += 1;\n window.speechSynthesis.cancel();\n speakPiece(current);\n }, 1000);\n };\n\n function stop(): void {\n if (state.value === 'idle' || state.value === 'unsupported') return;\n generation += 1;\n releaseActive();\n window.speechSynthesis.cancel();\n state.value = 'idle';\n labelOf('stopped');\n emit('stop');\n }\n\n /** One control, three meanings by state — play, pause, resume. */\n const toggle = (): void => {\n if (state.value === 'playing') pause();\n else if (state.value === 'paused') resume();\n else start();\n };\n\n const cycleRate = (): void => {\n const index = SPEECH_RATES.indexOf(rate.value as SpeechRate);\n rate.value = SPEECH_RATES[(index + 1) % SPEECH_RATES.length] ?? 1;\n announcement.value = speedLabel.value;\n emit('rate-change', { rate: rate.value });\n // A rate change while reading applies at once: the current sentence restarts at\n // the new pace. Paused or idle readings pick it up with the next sentence.\n if (state.value === 'playing') {\n generation += 1;\n window.speechSynthesis.cancel();\n speakPiece(current);\n }\n };\n\n host.play = start;\n host.pause = pause;\n host.stop = stop;\n\n onCleanup(() => {\n disposed = true;\n generation += 1;\n releaseActive();\n // The engine itself may already be gone in a tearing-down context.\n if (window.speechSynthesis) window.speechSynthesis.cancel();\n });\n\n return html`\n <div class=\"player\" part=\"player\">\n <button\n class=\"control control--toggle\"\n part=\"play\"\n type=\"button\"\n ?disabled=\"${() => state.value === 'unsupported' || !hasText.value}\"\n aria-label=\"${toggleLabel}\"\n @click=${toggle}\n >\n ${() =>\n state.value === 'playing'\n ? html`<ore-icon name=\"pause\" size=\"16\" aria-hidden=\"true\"></ore-icon>`\n : html`<ore-icon name=\"play\" size=\"16\" aria-hidden=\"true\"></ore-icon>`}\n <span class=\"toggle-label\" ?hidden=\"${() => state.value === 'playing' || state.value === 'paused'}\">${() => label('read')}</span>\n </button>\n <button\n class=\"control\"\n part=\"stop\"\n type=\"button\"\n ?hidden=\"${() => state.value === 'idle' || state.value === 'unsupported'}\"\n aria-label=\"${() => label('stop')}\"\n @click=${stop}\n >\n <ore-icon name=\"square\" size=\"16\" aria-hidden=\"true\"></ore-icon>\n </button>\n <button\n class=\"control control--rate\"\n part=\"rate\"\n type=\"button\"\n aria-label=\"${speedLabel}\"\n @click=${cycleRate}\n >\n <ore-icon name=\"gauge\" size=\"16\" aria-hidden=\"true\"></ore-icon>\n <span class=\"rate-text\">${() => `${rate.value}×`}</span>\n </button>\n </div>\n <div class=\"sr-only\" role=\"status\">${announcement}</div>\n `;\n },\n\n styles: [srOnlyMixin, componentStyles],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"speech-player.js","names":[],"sources":["../src/content/speech-player/speech-player.ts"],"sourcesContent":["import { bind, define, getHost, html, onCleanup, prop, useEmit } from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\nimport { srOnlyMixin } from '../../styles';\n\nimport componentStyles from './speech-player.css?inline';\n\nexport { ICON_TAG } from '../icon/icon';\n\n/** The player's reflected state: `unsupported` when the Web Speech API is missing. */\nexport type SpeechPlayerState = 'idle' | 'playing' | 'paused' | 'unsupported';\n\n/** The rate steps the speed control cycles through: normal, slow, fast. */\nexport const SPEECH_RATES = [1, 0.85, 1.15] as const;\nexport type SpeechRate = (typeof SPEECH_RATES)[number];\n\n/** Every user-facing string the component renders or announces. Override the `labels`\n * property to localize; English is the default. */\nexport type OreSpeechPlayerLabels = {\n /** Announced when the engine fails mid-read. */\n error: string;\n /** The toggle's accessible name while playing. */\n pause: string;\n /** Announced when reading is paused. */\n paused: string;\n /** The toggle's accessible name while idle. */\n read: string;\n /** The toggle's accessible name while paused. */\n resume: string;\n /** Announced when reading resumes. */\n resumed: string;\n /** Announced when reading starts. */\n started: string;\n /** The speed control's accessible-name stem; the current step's name follows it. */\n speed: string;\n /** The fast speed step's name. */\n speedFast: string;\n /** The normal speed step's name. */\n speedNormal: string;\n /** The slow speed step's name. */\n speedSlow: string;\n /** The stop control's accessible name. */\n stop: string;\n /** Announced when reading stops before finishing. */\n stopped: string;\n /** Announced when reading finishes on its own. */\n finished: string;\n /** The toggle's accessible name when speech synthesis is unavailable. */\n unsupported: string;\n};\n\nconst DEFAULT_LABELS: OreSpeechPlayerLabels = {\n error: 'Reading was interrupted.',\n finished: 'Reading finished.',\n pause: 'Pause reading',\n paused: 'Reading paused.',\n read: 'Read aloud',\n resume: 'Resume reading',\n resumed: 'Reading resumed.',\n speed: 'Reading speed',\n speedFast: 'fast',\n speedNormal: 'normal',\n speedSlow: 'slow',\n started: 'Reading aloud.',\n stop: 'Stop reading',\n stopped: 'Reading stopped.',\n unsupported: 'Text-to-speech is unavailable',\n};\n\n/** Speech player component properties */\nexport type OreSpeechPlayerProps = {\n /** Labels for the controls and announcements: override to localize. */\n labels?: Partial<OreSpeechPlayerLabels>;\n /** Playback rate: bind with `rate-change` to persist the listener's choice; unbound,\n * the speed control cycles on its own. Values outside `SPEECH_RATES` snap into the\n * cycle at the control's first use. */\n rate?: number;\n /** The sentence index playback begins from: bind a persisted position so \"resume\"\n * survives stops, collapses, and page loads. */\n 'resume-at'?: number;\n /** The text to read aloud. */\n text?: string;\n};\n\n/** Speech player component events */\nexport type OreSpeechPlayerEvents = {\n /** Reading finished on its own. */\n end: undefined;\n /** The engine failed mid-read: reading stopped, but not by the listener. */\n error: undefined;\n /** Reading started: from the control or a `play()` call. */\n play: undefined;\n /** A sentence began: carry `sentence` of `total` to the surfaces that follow the voice. */\n progress: { sentence: number; total: number };\n /** The speed control moved to a new step. */\n 'rate-change': { rate: number };\n /** Reading stopped before finishing: from the control or a `stop()` call. */\n stop: undefined;\n};\n\n/** Element interface exposing the imperative API for `ore-speech-player`. */\nexport interface SpeechPlayerElement extends HTMLElement, OreSpeechPlayerProps {\n /** Pauses the current reading. */\n pause(): void;\n /** Starts reading: from `resume-at` when bound, else from the beginning.\n * Any player already reading stops first. */\n play(): void;\n /** Stops reading and cancels pending speech. */\n stop(): void;\n}\n\n/** Chrome cuts a single utterance off after roughly fifteen seconds of speech; sentence\n * pieces under this length stay safely inside that window at any of the rate steps. */\nconst UTTERANCE_LIMIT = 220;\n\n/** The entry's sentences: prose ends . ! or ?, closing quotes belong to the sentence\n * they end, and an unterminated tail is still a sentence. Consumers that highlight what\n * is being read build their display from this same list, so `progress` sentence indexes\n * map onto their own. */\nexport function speechSentences(text: string): string[] {\n const normalized = text.replace(/\\s+/g, ' ').trim();\n const sentences: string[] = normalized.match(/[^.!?]+[.!?]+[\"'”’»]*/g) ?? [];\n const consumed = sentences.join('').length;\n if (consumed < normalized.length) sentences.push(normalized.slice(consumed));\n return sentences.flatMap((sentence) => {\n const trimmed = sentence.trim();\n return trimmed ? [trimmed] : [];\n });\n}\n\n/** Breaks a sentence longer than `limit` at word boundaries (falling back to a hard cut). */\nfunction hardSplit(sentence: string, limit: number): string[] {\n const pieces: string[] = [];\n let rest = sentence;\n while (rest.length > limit) {\n const cut = rest.lastIndexOf(' ', limit);\n const at = cut > 0 ? cut : limit;\n pieces.push(rest.slice(0, at));\n rest = rest.slice(at).trimStart();\n }\n if (rest) pieces.push(rest);\n return pieces;\n}\n\n/** One spoken unit: a sentence, or a piece of one that alone exceeds the engine limit :\n * pieces keep their sentence's index, so `progress` never skips a highlight. */\ntype SpeechPiece = { sentence: number; text: string };\n\nfunction piecesOf(sentences: string[], limit = UTTERANCE_LIMIT): SpeechPiece[] {\n return sentences.flatMap((sentence, index) =>\n (sentence.length > limit ? hardSplit(sentence, limit) : [sentence]).map((text) => ({\n sentence: index,\n text,\n })),\n );\n}\n\n/**\n * A text-to-speech player control: reads its `text` aloud through the Web Speech API.\n *\n * Renders a labeled play/pause toggle (the label folds away once engaged), a stop\n * control beside it that appears while reading, and a speed control cycling\n * normal/slow/fast. The text is spoken sentence by sentence (long sentences split into pieces under the\n * engine's truncation window), the `state` attribute reflects playback, a\n * `role=\"status\"` live region announces transitions, and `progress` events name the\n * sentence being read so a consumer can highlight it. Only one player speaks at a\n * time: starting one stops any other. `resume-at` names the sentence to begin from,\n * so a persisted position makes resume mean it.\n *\n * @element ore-speech-player\n *\n * @attr {string} text - The text to read aloud (also settable as a JavaScript property)\n * @attr {string} lang - BCP-47 tag of `text`'s language, e.g. `en` or `de-DE`\n * @attr {string} state - Reflected playback state: 'idle' | 'playing' | 'paused' | 'unsupported'\n * @attr {number} rate - Playback rate; the speed control cycles 1 → 0.85 → 1.15\n * @attr {number} resume-at - The sentence index playback begins from\n *\n * @fires play - Reading started. No detail.\n * @fires progress - A sentence began. detail: { sentence, total }\n * @fires stop - Reading stopped before finishing. No detail.\n * @fires end - Reading finished on its own. No detail.\n * @fires error - The engine failed mid-read. No detail.\n * @fires rate-change - The speed control moved to a new step. detail: { rate }\n *\n * @cssprop --speech-player-gap - Gap between the controls\n * @cssprop --speech-player-control-size - Each control's hit-area size (the toggle grows with its label)\n * @cssprop --speech-player-icon-color - Control icon color at rest\n * @cssprop --speech-player-active-color - Toggle color while reading (playing or paused)\n * @cssprop --speech-player-hover-bg - Control background on hover\n *\n * @part player - Outer control group\n * @part play - The play/pause toggle button\n * @part rate - The speed control\n * @part stop - The stop button (hidden while idle)\n *\n * @example\n * ```html\n * <ore-speech-player text=\"The valley sleeps under ash.\"></ore-speech-player>\n *\n * <ore-speech-player lang=\"de\" text=\"Das Tal schläft unter Asche.\"></ore-speech-player>\n * ```\n */\nexport const SPEECH_PLAYER_TAG = 'ore-speech-player' as const;\n\n/** Stops whichever player is currently reading: speechSynthesis is one global voice, so a\n * new reader cancels the engine and the old player needs to settle to idle. */\nlet stopActivePlayer: (() => void) | null = null;\n\ndefine<OreSpeechPlayerProps>(SPEECH_PLAYER_TAG, {\n props: {\n labels: prop.data<Partial<OreSpeechPlayerLabels>>({}),\n rate: prop.number(1),\n 'resume-at': prop.number(),\n text: prop.string(''),\n },\n\n setup(props) {\n const host = getHost() as SpeechPlayerElement;\n const emit = useEmit<OreSpeechPlayerEvents>();\n\n const supported = typeof window !== 'undefined' && typeof window.speechSynthesis !== 'undefined';\n const state = signal<SpeechPlayerState>(supported ? 'idle' : 'unsupported');\n const announcement = signal('');\n\n // The reading stream is sentence-addressed: pieces carry their sentence's index, so\n // progress and highlighting never drift apart.\n const sentences = computed(() => speechSentences(props.text.value ?? ''));\n const pieces = computed(() => piecesOf(sentences.value));\n const hasText = computed(() => (props.text.value ?? '').trim().length > 0);\n const label = (key: keyof OreSpeechPlayerLabels): string => props.labels.value?.[key] ?? DEFAULT_LABELS[key];\n const toggleLabel = computed(() => {\n if (state.value === 'unsupported') return label('unsupported');\n if (state.value === 'playing') return label('pause');\n if (state.value === 'paused') return label('resume');\n return label('read');\n });\n\n // The rate is the consumer's to persist: bind `rate` with `rate-change` and every\n // instance follows the listener's choice; unbound, the control cycles internally.\n const rate = signal<number>(props.rate.value ?? 1);\n watch(props.rate, (value) => {\n if (value !== undefined) rate.value = value;\n });\n const speedName = (value: number): string => {\n if (value === 1) return label('speedNormal');\n if (value === 0.85) return label('speedSlow');\n if (value === 1.15) return label('speedFast');\n return `${value}×`;\n };\n const speedLabel = computed(() => `${label('speed')}: ${speedName(rate.value)}`);\n\n // Reflect playback so CSS can react to it; the aria contract lives on the controls.\n bind({ attr: { state } });\n\n const labelOf = (key: keyof OreSpeechPlayerLabels): void => {\n announcement.value = label(key);\n };\n\n /** Utterance callbacks capture the generation they belong to; a stop or a later play\n * bumps it, so stale events from a canceled utterance cannot chain or settle. */\n let generation = 0;\n /** Bumped by any engine callback on the live utterance: liveness evidence for the\n * resume watchdog. */\n let enginePulse = 0;\n let current = 0;\n let disposed = false;\n\n const speakPiece = (index: number): void => {\n current = index;\n const piece = pieces.value[index]!;\n const utterance = new SpeechSynthesisUtterance(piece.text);\n utterance.rate = rate.value;\n // The native `lang` attribute names the text's language: the standard HTML\n // semantic, and hints voice selection when no explicit voice matches yet.\n const lang = host.getAttribute('lang');\n if (lang) {\n utterance.lang = lang;\n const voice = window.speechSynthesis\n .getVoices()\n .find((candidate) => candidate.lang.toLowerCase().startsWith(lang.toLowerCase()));\n if (voice) utterance.voice = voice;\n }\n const token = generation;\n const alive = (): void => {\n if (token === generation) enginePulse += 1;\n };\n utterance.onstart = alive;\n utterance.onboundary = alive;\n utterance.onend = () => {\n alive();\n if (token !== generation) return;\n if (index + 1 >= pieces.value.length) {\n releaseActive();\n state.value = 'idle';\n labelOf('finished');\n emit('end');\n } else {\n speakPiece(index + 1);\n }\n };\n utterance.onerror = () => {\n alive();\n if (token !== generation) return;\n releaseActive();\n state.value = 'idle';\n labelOf('error');\n emit('error');\n };\n emit('progress', { sentence: piece.sentence, total: sentences.value.length });\n window.speechSynthesis.speak(utterance);\n };\n\n const releaseActive = (): void => {\n if (stopActivePlayer === stop) stopActivePlayer = null;\n };\n\n /** The first piece of the sentence to begin from: clamped into the text. */\n const pieceIndexFor = (sentence: number | undefined): number => {\n const total = sentences.value.length;\n if (sentence === undefined || total === 0) return 0;\n const begin = Math.min(Math.max(sentence, 0), total - 1);\n const index = pieces.value.findIndex((piece) => piece.sentence >= begin);\n return index === -1 ? 0 : index;\n };\n\n const start = (): void => {\n if (state.value !== 'idle' || !hasText.value) return;\n stopActivePlayer?.();\n window.speechSynthesis.cancel();\n generation += 1;\n stopActivePlayer = stop;\n state.value = 'playing';\n labelOf('started');\n emit('play');\n speakPiece(pieceIndexFor(props['resume-at'].value));\n };\n\n let pausedAt = 0;\n\n const pause = (): void => {\n if (state.value !== 'playing') return;\n window.speechSynthesis.pause();\n state.value = 'paused';\n pausedAt = performance.now();\n labelOf('paused');\n };\n\n const resume = (): void => {\n if (state.value !== 'paused') return;\n window.speechSynthesis.resume();\n state.value = 'playing';\n labelOf('resumed');\n // Some Chromium builds resume a long-held pause into silence: no error, no\n // event, the playing state a lie. Within ten seconds the engine is trusted;\n // beyond it, demand a pulse within a second or restart the current sentence :\n // a fresh utterance always speaks. Boundary-less engines only pay this on long holds.\n if (performance.now() - pausedAt < 10_000) return;\n const pulse = enginePulse;\n window.setTimeout(() => {\n if (disposed || state.value !== 'playing' || enginePulse !== pulse) return;\n generation += 1;\n window.speechSynthesis.cancel();\n speakPiece(current);\n }, 1000);\n };\n\n function stop(): void {\n if (state.value === 'idle' || state.value === 'unsupported') return;\n generation += 1;\n releaseActive();\n window.speechSynthesis.cancel();\n state.value = 'idle';\n labelOf('stopped');\n emit('stop');\n }\n\n /** One control, three meanings by state: play, pause, resume. */\n const toggle = (): void => {\n if (state.value === 'playing') pause();\n else if (state.value === 'paused') resume();\n else start();\n };\n\n const cycleRate = (): void => {\n const index = SPEECH_RATES.indexOf(rate.value as SpeechRate);\n rate.value = SPEECH_RATES[(index + 1) % SPEECH_RATES.length] ?? 1;\n announcement.value = speedLabel.value;\n emit('rate-change', { rate: rate.value });\n // A rate change while reading applies at once: the current sentence restarts at\n // the new pace. Paused or idle readings pick it up with the next sentence.\n if (state.value === 'playing') {\n generation += 1;\n window.speechSynthesis.cancel();\n speakPiece(current);\n }\n };\n\n host.play = start;\n host.pause = pause;\n host.stop = stop;\n\n onCleanup(() => {\n disposed = true;\n generation += 1;\n releaseActive();\n // The engine itself may already be gone in a tearing-down context.\n if (window.speechSynthesis) window.speechSynthesis.cancel();\n });\n\n return html`\n <div class=\"player\" part=\"player\">\n <button\n class=\"control control--toggle\"\n part=\"play\"\n type=\"button\"\n ?disabled=\"${() => state.value === 'unsupported' || !hasText.value}\"\n aria-label=\"${toggleLabel}\"\n @click=${toggle}\n >\n ${() =>\n state.value === 'playing'\n ? html`<ore-icon name=\"pause\" size=\"16\" aria-hidden=\"true\"></ore-icon>`\n : html`<ore-icon name=\"play\" size=\"16\" aria-hidden=\"true\"></ore-icon>`}\n <span class=\"toggle-label\" ?hidden=\"${() => state.value === 'playing' || state.value === 'paused'}\">${() => label('read')}</span>\n </button>\n <button\n class=\"control\"\n part=\"stop\"\n type=\"button\"\n ?hidden=\"${() => state.value === 'idle' || state.value === 'unsupported'}\"\n aria-label=\"${() => label('stop')}\"\n @click=${stop}\n >\n <ore-icon name=\"square\" size=\"16\" aria-hidden=\"true\"></ore-icon>\n </button>\n <button\n class=\"control control--rate\"\n part=\"rate\"\n type=\"button\"\n aria-label=\"${speedLabel}\"\n @click=${cycleRate}\n >\n <ore-icon name=\"gauge\" size=\"16\" aria-hidden=\"true\"></ore-icon>\n <span class=\"rate-text\">${() => `${rate.value}×`}</span>\n </button>\n </div>\n <div class=\"sr-only\" role=\"status\">${announcement}</div>\n `;\n },\n\n styles: [srOnlyMixin, componentStyles],\n});\n"],"mappings":"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"}