ngwr 11.2.0 → 12.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/README.md +7 -7
  2. package/burger/styles/_index.scss +1 -1
  3. package/button/styles/_index.scss +2 -2
  4. package/calendar/styles/_index.scss +32 -12
  5. package/calendar-heatmap/styles/_index.scss +20 -0
  6. package/carousel/styles/_index.scss +23 -1
  7. package/cascader/styles/_index.scss +15 -3
  8. package/checkbox/styles/_index.scss +2 -2
  9. package/collapse/styles/_index.scss +2 -2
  10. package/color-picker/styles/_index.scss +3 -3
  11. package/compare/styles/_index.scss +18 -2
  12. package/context-menu/styles/_index.scss +21 -2
  13. package/date-picker/styles/_index.scss +5 -5
  14. package/descriptions/styles/_index.scss +1 -1
  15. package/drag-drop/styles/_index.scss +28 -1
  16. package/drawer/styles/_index.scss +37 -0
  17. package/dropdown/styles/_index.scss +2 -2
  18. package/event-calendar/styles/_index.scss +67 -10
  19. package/fesm2022/ngwr-action-sheet.mjs +3 -3
  20. package/fesm2022/ngwr-affix.mjs +3 -3
  21. package/fesm2022/ngwr-alert.mjs +3 -3
  22. package/fesm2022/ngwr-anchor.mjs +3 -3
  23. package/fesm2022/ngwr-aurora.mjs +3 -3
  24. package/fesm2022/ngwr-avatar.mjs +3 -3
  25. package/fesm2022/ngwr-back-top.mjs +3 -3
  26. package/fesm2022/ngwr-badge.mjs +6 -6
  27. package/fesm2022/ngwr-bar-chart-testing.mjs +20 -2
  28. package/fesm2022/ngwr-bar-chart-testing.mjs.map +1 -1
  29. package/fesm2022/ngwr-bar-chart.mjs +3 -3
  30. package/fesm2022/ngwr-blur-text.mjs +3 -3
  31. package/fesm2022/ngwr-blur-text.mjs.map +1 -1
  32. package/fesm2022/ngwr-border-glow.mjs +4 -4
  33. package/fesm2022/ngwr-border-glow.mjs.map +1 -1
  34. package/fesm2022/ngwr-breadcrumbs.mjs +29 -17
  35. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  36. package/fesm2022/ngwr-burger.mjs +3 -3
  37. package/fesm2022/ngwr-button.mjs +6 -6
  38. package/fesm2022/ngwr-calendar-heatmap.mjs +17 -5
  39. package/fesm2022/ngwr-calendar-heatmap.mjs.map +1 -1
  40. package/fesm2022/ngwr-calendar-testing.mjs +1 -1
  41. package/fesm2022/ngwr-calendar-testing.mjs.map +1 -1
  42. package/fesm2022/ngwr-calendar.mjs +187 -9
  43. package/fesm2022/ngwr-calendar.mjs.map +1 -1
  44. package/fesm2022/ngwr-card.mjs +10 -10
  45. package/fesm2022/ngwr-card.mjs.map +1 -1
  46. package/fesm2022/ngwr-carousel.mjs +124 -24
  47. package/fesm2022/ngwr-carousel.mjs.map +1 -1
  48. package/fesm2022/ngwr-cascader.mjs +48 -5
  49. package/fesm2022/ngwr-cascader.mjs.map +1 -1
  50. package/fesm2022/ngwr-checkbox.mjs +6 -6
  51. package/fesm2022/ngwr-circular-text.mjs +4 -4
  52. package/fesm2022/ngwr-circular-text.mjs.map +1 -1
  53. package/fesm2022/ngwr-click-spark.mjs +27 -7
  54. package/fesm2022/ngwr-click-spark.mjs.map +1 -1
  55. package/fesm2022/ngwr-clipboard.mjs +3 -3
  56. package/fesm2022/ngwr-collapse.mjs +9 -9
  57. package/fesm2022/ngwr-color-picker-testing.mjs +26 -6
  58. package/fesm2022/ngwr-color-picker-testing.mjs.map +1 -1
  59. package/fesm2022/ngwr-color-picker.mjs +98 -8
  60. package/fesm2022/ngwr-color-picker.mjs.map +1 -1
  61. package/fesm2022/ngwr-command-palette.mjs +14 -5
  62. package/fesm2022/ngwr-command-palette.mjs.map +1 -1
  63. package/fesm2022/ngwr-compare.mjs +3 -3
  64. package/fesm2022/ngwr-confetti.mjs +31 -6
  65. package/fesm2022/ngwr-confetti.mjs.map +1 -1
  66. package/fesm2022/ngwr-config.mjs +9 -0
  67. package/fesm2022/ngwr-config.mjs.map +1 -1
  68. package/fesm2022/ngwr-context-menu-testing.mjs +30 -11
  69. package/fesm2022/ngwr-context-menu-testing.mjs.map +1 -1
  70. package/fesm2022/ngwr-context-menu.mjs +333 -43
  71. package/fesm2022/ngwr-context-menu.mjs.map +1 -1
  72. package/fesm2022/ngwr-cookie.mjs +3 -3
  73. package/fesm2022/ngwr-counter.mjs +25 -9
  74. package/fesm2022/ngwr-counter.mjs.map +1 -1
  75. package/fesm2022/{ngwr-date-adapter-fns.mjs → ngwr-date-adapters-fns.mjs} +37 -10
  76. package/fesm2022/ngwr-date-adapters-fns.mjs.map +1 -0
  77. package/fesm2022/{ngwr-date-adapter-luxon.mjs → ngwr-date-adapters-luxon.mjs} +5 -5
  78. package/fesm2022/ngwr-date-adapters-luxon.mjs.map +1 -0
  79. package/fesm2022/ngwr-date-picker-testing.mjs +11 -6
  80. package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -1
  81. package/fesm2022/ngwr-date-picker.mjs +119 -37
  82. package/fesm2022/ngwr-date-picker.mjs.map +1 -1
  83. package/fesm2022/{ngwr-date-adapter.mjs → ngwr-date.mjs} +36 -10
  84. package/fesm2022/ngwr-date.mjs.map +1 -0
  85. package/fesm2022/ngwr-decrypt-text.mjs +3 -3
  86. package/fesm2022/ngwr-density.mjs +6 -6
  87. package/fesm2022/ngwr-descriptions.mjs +6 -6
  88. package/fesm2022/ngwr-dialog.mjs +73 -18
  89. package/fesm2022/ngwr-dialog.mjs.map +1 -1
  90. package/fesm2022/ngwr-directives.mjs +12 -12
  91. package/fesm2022/ngwr-divider.mjs +52 -5
  92. package/fesm2022/ngwr-divider.mjs.map +1 -1
  93. package/fesm2022/ngwr-donut-chart-testing.mjs +8 -4
  94. package/fesm2022/ngwr-donut-chart-testing.mjs.map +1 -1
  95. package/fesm2022/ngwr-donut-chart.mjs +7 -7
  96. package/fesm2022/ngwr-donut-chart.mjs.map +1 -1
  97. package/fesm2022/ngwr-drag-drop.mjs +160 -11
  98. package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
  99. package/fesm2022/ngwr-drawer.mjs +121 -58
  100. package/fesm2022/ngwr-drawer.mjs.map +1 -1
  101. package/fesm2022/ngwr-dropdown.mjs +26 -10
  102. package/fesm2022/ngwr-dropdown.mjs.map +1 -1
  103. package/fesm2022/ngwr-empty.mjs +3 -3
  104. package/fesm2022/ngwr-event-calendar.mjs +28 -15
  105. package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
  106. package/fesm2022/ngwr-falling-text-testing.mjs +5 -3
  107. package/fesm2022/ngwr-falling-text-testing.mjs.map +1 -1
  108. package/fesm2022/ngwr-falling-text.mjs +75 -46
  109. package/fesm2022/ngwr-falling-text.mjs.map +1 -1
  110. package/fesm2022/ngwr-file-upload.mjs +3 -3
  111. package/fesm2022/ngwr-form.mjs +32 -15
  112. package/fesm2022/ngwr-form.mjs.map +1 -1
  113. package/fesm2022/ngwr-fuzzy-text.mjs +21 -11
  114. package/fesm2022/ngwr-fuzzy-text.mjs.map +1 -1
  115. package/fesm2022/ngwr-gauge.mjs +3 -3
  116. package/fesm2022/ngwr-glitch-text-testing.mjs +51 -17
  117. package/fesm2022/ngwr-glitch-text-testing.mjs.map +1 -1
  118. package/fesm2022/ngwr-glitch-text.mjs +7 -7
  119. package/fesm2022/ngwr-glitch-text.mjs.map +1 -1
  120. package/fesm2022/ngwr-gradient-text-testing.mjs +46 -11
  121. package/fesm2022/ngwr-gradient-text-testing.mjs.map +1 -1
  122. package/fesm2022/ngwr-gradient-text.mjs +4 -4
  123. package/fesm2022/ngwr-gradient-text.mjs.map +1 -1
  124. package/fesm2022/ngwr-hotkey.mjs +24 -9
  125. package/fesm2022/ngwr-hotkey.mjs.map +1 -1
  126. package/fesm2022/ngwr-i18n-en.mjs +73 -1
  127. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  128. package/fesm2022/ngwr-i18n-ru.mjs +55 -1
  129. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  130. package/fesm2022/ngwr-i18n.mjs +26 -15
  131. package/fesm2022/ngwr-i18n.mjs.map +1 -1
  132. package/fesm2022/ngwr-icon.mjs +6 -6
  133. package/fesm2022/ngwr-image-cropper-testing.mjs +53 -9
  134. package/fesm2022/ngwr-image-cropper-testing.mjs.map +1 -1
  135. package/fesm2022/ngwr-image-cropper.mjs +150 -12
  136. package/fesm2022/ngwr-image-cropper.mjs.map +1 -1
  137. package/fesm2022/ngwr-input-number-testing.mjs +15 -10
  138. package/fesm2022/ngwr-input-number-testing.mjs.map +1 -1
  139. package/fesm2022/ngwr-input-number.mjs +17 -10
  140. package/fesm2022/ngwr-input-number.mjs.map +1 -1
  141. package/fesm2022/ngwr-input-otp-testing.mjs +12 -6
  142. package/fesm2022/ngwr-input-otp-testing.mjs.map +1 -1
  143. package/fesm2022/ngwr-input-otp.mjs +24 -5
  144. package/fesm2022/ngwr-input-otp.mjs.map +1 -1
  145. package/fesm2022/ngwr-input.mjs +15 -15
  146. package/fesm2022/ngwr-keyboard.mjs +3 -3
  147. package/fesm2022/ngwr-knob.mjs +3 -3
  148. package/fesm2022/ngwr-layout.mjs +15 -15
  149. package/fesm2022/ngwr-lightbox.mjs +4 -4
  150. package/fesm2022/ngwr-lightbox.mjs.map +1 -1
  151. package/fesm2022/ngwr-line-chart.mjs +73 -26
  152. package/fesm2022/ngwr-line-chart.mjs.map +1 -1
  153. package/fesm2022/ngwr-list.mjs +7 -7
  154. package/fesm2022/ngwr-list.mjs.map +1 -1
  155. package/fesm2022/ngwr-loading-bar.mjs +7 -7
  156. package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
  157. package/fesm2022/ngwr-markdown.mjs +66 -8
  158. package/fesm2022/ngwr-markdown.mjs.map +1 -1
  159. package/fesm2022/ngwr-marquee.mjs +4 -4
  160. package/fesm2022/ngwr-marquee.mjs.map +1 -1
  161. package/fesm2022/ngwr-media.mjs +3 -3
  162. package/fesm2022/ngwr-mention.mjs +108 -13
  163. package/fesm2022/ngwr-mention.mjs.map +1 -1
  164. package/fesm2022/ngwr-meta.mjs +6 -6
  165. package/fesm2022/ngwr-meter-group-testing.mjs +26 -6
  166. package/fesm2022/ngwr-meter-group-testing.mjs.map +1 -1
  167. package/fesm2022/ngwr-meter-group.mjs +3 -3
  168. package/fesm2022/ngwr-overlay.mjs +6 -6
  169. package/fesm2022/ngwr-page-header.mjs +3 -3
  170. package/fesm2022/ngwr-pagination-testing.mjs +6 -6
  171. package/fesm2022/ngwr-pagination-testing.mjs.map +1 -1
  172. package/fesm2022/ngwr-pagination.mjs +17 -13
  173. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  174. package/fesm2022/ngwr-pipes.mjs +39 -26
  175. package/fesm2022/ngwr-pipes.mjs.map +1 -1
  176. package/fesm2022/ngwr-platform.mjs +9 -9
  177. package/fesm2022/ngwr-popconfirm.mjs +6 -6
  178. package/fesm2022/ngwr-popover-testing.mjs +7 -2
  179. package/fesm2022/ngwr-popover-testing.mjs.map +1 -1
  180. package/fesm2022/ngwr-popover.mjs +155 -33
  181. package/fesm2022/ngwr-popover.mjs.map +1 -1
  182. package/fesm2022/ngwr-progress.mjs +3 -3
  183. package/fesm2022/ngwr-pull-to-refresh.mjs +3 -3
  184. package/fesm2022/ngwr-qr.mjs +42 -5
  185. package/fesm2022/ngwr-qr.mjs.map +1 -1
  186. package/fesm2022/ngwr-radio.mjs +6 -6
  187. package/fesm2022/ngwr-rating.mjs +3 -3
  188. package/fesm2022/ngwr-result.mjs +21 -30
  189. package/fesm2022/ngwr-result.mjs.map +1 -1
  190. package/fesm2022/ngwr-rotating-text.mjs +3 -3
  191. package/fesm2022/ngwr-scroll.mjs +3 -3
  192. package/fesm2022/ngwr-segmented-testing.mjs +14 -1
  193. package/fesm2022/ngwr-segmented-testing.mjs.map +1 -1
  194. package/fesm2022/ngwr-segmented.mjs +29 -6
  195. package/fesm2022/ngwr-segmented.mjs.map +1 -1
  196. package/fesm2022/ngwr-select.mjs +159 -34
  197. package/fesm2022/ngwr-select.mjs.map +1 -1
  198. package/fesm2022/ngwr-shiny-text.mjs +11 -9
  199. package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
  200. package/fesm2022/ngwr-sidebar.mjs +31 -16
  201. package/fesm2022/ngwr-sidebar.mjs.map +1 -1
  202. package/fesm2022/ngwr-skeleton.mjs +3 -3
  203. package/fesm2022/ngwr-slider.mjs +100 -9
  204. package/fesm2022/ngwr-slider.mjs.map +1 -1
  205. package/fesm2022/ngwr-sparkline.mjs +3 -3
  206. package/fesm2022/ngwr-speed-dial-testing.mjs +15 -13
  207. package/fesm2022/ngwr-speed-dial-testing.mjs.map +1 -1
  208. package/fesm2022/ngwr-speed-dial.mjs +27 -12
  209. package/fesm2022/ngwr-speed-dial.mjs.map +1 -1
  210. package/fesm2022/ngwr-spinner.mjs +3 -3
  211. package/fesm2022/ngwr-splash-cursor.mjs +3 -3
  212. package/fesm2022/ngwr-split-text.mjs +17 -5
  213. package/fesm2022/ngwr-split-text.mjs.map +1 -1
  214. package/fesm2022/ngwr-splitter.mjs +3 -3
  215. package/fesm2022/ngwr-spotlight-card.mjs +7 -7
  216. package/fesm2022/ngwr-spotlight-card.mjs.map +1 -1
  217. package/fesm2022/ngwr-squircle.mjs +6 -6
  218. package/fesm2022/ngwr-star-border.mjs +4 -4
  219. package/fesm2022/ngwr-star-border.mjs.map +1 -1
  220. package/fesm2022/ngwr-statistic.mjs +9 -9
  221. package/fesm2022/ngwr-stepper-testing.mjs +8 -8
  222. package/fesm2022/ngwr-stepper-testing.mjs.map +1 -1
  223. package/fesm2022/ngwr-stepper.mjs +33 -8
  224. package/fesm2022/ngwr-stepper.mjs.map +1 -1
  225. package/fesm2022/ngwr-storage.mjs +17 -5
  226. package/fesm2022/ngwr-storage.mjs.map +1 -1
  227. package/fesm2022/ngwr-switch.mjs +3 -3
  228. package/fesm2022/ngwr-table.mjs +99 -39
  229. package/fesm2022/ngwr-table.mjs.map +1 -1
  230. package/fesm2022/ngwr-tabs-testing.mjs +11 -10
  231. package/fesm2022/ngwr-tabs-testing.mjs.map +1 -1
  232. package/fesm2022/ngwr-tabs.mjs +36 -10
  233. package/fesm2022/ngwr-tabs.mjs.map +1 -1
  234. package/fesm2022/ngwr-textarea.mjs +3 -3
  235. package/fesm2022/ngwr-theme.mjs +181 -4
  236. package/fesm2022/ngwr-theme.mjs.map +1 -1
  237. package/fesm2022/ngwr-tilt-card.mjs +7 -7
  238. package/fesm2022/ngwr-tilt-card.mjs.map +1 -1
  239. package/fesm2022/ngwr-timeline.mjs +6 -6
  240. package/fesm2022/ngwr-toast.mjs +9 -9
  241. package/fesm2022/ngwr-toolbar.mjs +3 -3
  242. package/fesm2022/ngwr-tour.mjs +34 -10
  243. package/fesm2022/ngwr-tour.mjs.map +1 -1
  244. package/fesm2022/ngwr-transfer.mjs +3 -3
  245. package/fesm2022/ngwr-tree.mjs +28 -10
  246. package/fesm2022/ngwr-tree.mjs.map +1 -1
  247. package/fesm2022/ngwr-typewriter-testing.mjs +15 -1
  248. package/fesm2022/ngwr-typewriter-testing.mjs.map +1 -1
  249. package/fesm2022/ngwr-typewriter.mjs +13 -5
  250. package/fesm2022/ngwr-typewriter.mjs.map +1 -1
  251. package/fesm2022/ngwr-typography.mjs +3 -3
  252. package/fesm2022/ngwr-utils.mjs +5 -2
  253. package/fesm2022/ngwr-utils.mjs.map +1 -1
  254. package/fesm2022/ngwr-version.mjs +1 -1
  255. package/fesm2022/ngwr-version.mjs.map +1 -1
  256. package/fesm2022/ngwr-virtual-scroll.mjs +4 -4
  257. package/fesm2022/ngwr-virtual-scroll.mjs.map +1 -1
  258. package/fesm2022/ngwr-waves.mjs +4 -4
  259. package/fesm2022/ngwr-waves.mjs.map +1 -1
  260. package/fesm2022/ngwr-window.mjs +94 -24
  261. package/fesm2022/ngwr-window.mjs.map +1 -1
  262. package/file-upload/styles/_index.scss +2 -2
  263. package/glitch-text/styles/_index.scss +27 -12
  264. package/i18n/en.json +56 -6
  265. package/i18n/ru.json +56 -6
  266. package/image-cropper/styles/_index.scss +19 -0
  267. package/input/styles/_index.scss +2 -2
  268. package/input-number/styles/_index.scss +2 -2
  269. package/input-otp/styles/_index.scss +9 -2
  270. package/knob/styles/_index.scss +1 -1
  271. package/lightbox/styles/_index.scss +16 -2
  272. package/line-chart/styles/_index.scss +28 -14
  273. package/list/styles/_index.scss +1 -1
  274. package/llms-full.txt +18 -23
  275. package/llms.txt +22 -6
  276. package/markdown/styles/_index.scss +3 -3
  277. package/mcp/api.js +6 -4
  278. package/mcp/catalog.js +40 -3
  279. package/mcp/providers.js +16 -3
  280. package/mcp/tools.js +21 -1
  281. package/meter-group/styles/_index.scss +1 -1
  282. package/overlay/styles/_index.scss +33 -1
  283. package/package.json +17 -12
  284. package/pagination/styles/_index.scss +23 -1
  285. package/popover/styles/_index.scss +16 -7
  286. package/progress/styles/_index.scss +1 -1
  287. package/radio/styles/_index.scss +2 -2
  288. package/rating/styles/_index.scss +1 -1
  289. package/schematics/collection.json +1 -1
  290. package/schematics/migrations/v12/index.js +65 -0
  291. package/schematics/migrations/v7/index.js +12 -1
  292. package/schematics/migrations/v8/index.js +10 -1
  293. package/schematics/migrations.json +5 -0
  294. package/schematics/ng-add/index.js +8 -3
  295. package/schematics/provider/index.js +80 -13
  296. package/schematics/use/index.js +9 -129
  297. package/schematics/use/rule.js +206 -0
  298. package/schematics/use/symbol-map.json +4 -4
  299. package/segmented/styles/_index.scss +14 -3
  300. package/select/styles/_index.scss +14 -2
  301. package/sidebar/styles/_index.scss +12 -10
  302. package/skills/ngwr/SKILL.md +7 -5
  303. package/skills/ngwr/references/catalog.md +9 -9
  304. package/skills/ngwr/references/setup.md +10 -5
  305. package/slider/styles/_index.scss +1 -1
  306. package/speed-dial/styles/_index.scss +1 -1
  307. package/stepper/styles/_index.scss +12 -3
  308. package/switch/styles/_index.scss +2 -2
  309. package/table/styles/_index.scss +61 -12
  310. package/tabs/styles/_index.scss +1 -1
  311. package/textarea/styles/_index.scss +1 -1
  312. package/theme/styles/_colors.scss +142 -56
  313. package/theme/styles/_dark.scss +46 -3
  314. package/theme/styles/_focus.scss +28 -5
  315. package/theme/styles/_mixins.scss +1 -1
  316. package/theme/styles/_variables.scss +67 -0
  317. package/transfer/styles/_index.scss +26 -3
  318. package/tree/styles/_index.scss +29 -6
  319. package/types/ngwr-bar-chart-testing.d.ts +9 -1
  320. package/types/ngwr-blur-text.d.ts +8 -4
  321. package/types/ngwr-breadcrumbs.d.ts +17 -10
  322. package/types/ngwr-calendar-testing.d.ts +1 -1
  323. package/types/ngwr-calendar.d.ts +66 -0
  324. package/types/ngwr-carousel.d.ts +85 -6
  325. package/types/ngwr-cascader.d.ts +17 -0
  326. package/types/ngwr-click-spark.d.ts +4 -0
  327. package/types/ngwr-color-picker-testing.d.ts +8 -0
  328. package/types/ngwr-color-picker.d.ts +21 -1
  329. package/types/ngwr-confetti.d.ts +8 -0
  330. package/types/ngwr-config.d.ts +10 -1
  331. package/types/ngwr-context-menu-testing.d.ts +24 -11
  332. package/types/ngwr-context-menu.d.ts +56 -0
  333. package/types/ngwr-counter.d.ts +1 -0
  334. package/types/{ngwr-date-adapter-fns.d.ts → ngwr-date-adapters-fns.d.ts} +33 -6
  335. package/types/{ngwr-date-adapter-luxon.d.ts → ngwr-date-adapters-luxon.d.ts} +1 -1
  336. package/types/ngwr-date-picker-testing.d.ts +11 -6
  337. package/types/ngwr-date-picker.d.ts +8 -4
  338. package/types/{ngwr-date-adapter.d.ts → ngwr-date.d.ts} +24 -5
  339. package/types/ngwr-dialog.d.ts +38 -1
  340. package/types/ngwr-divider.d.ts +25 -0
  341. package/types/ngwr-donut-chart-testing.d.ts +7 -3
  342. package/types/ngwr-donut-chart.d.ts +3 -3
  343. package/types/ngwr-drag-drop.d.ts +56 -2
  344. package/types/ngwr-drawer.d.ts +56 -5
  345. package/types/ngwr-event-calendar.d.ts +8 -2
  346. package/types/ngwr-falling-text-testing.d.ts +5 -3
  347. package/types/ngwr-falling-text.d.ts +1 -1
  348. package/types/ngwr-form.d.ts +12 -2
  349. package/types/ngwr-glitch-text-testing.d.ts +37 -8
  350. package/types/ngwr-gradient-text-testing.d.ts +16 -3
  351. package/types/ngwr-i18n.d.ts +13 -4
  352. package/types/ngwr-image-cropper-testing.d.ts +49 -10
  353. package/types/ngwr-image-cropper.d.ts +70 -5
  354. package/types/ngwr-input-number-testing.d.ts +15 -10
  355. package/types/ngwr-input-number.d.ts +11 -4
  356. package/types/ngwr-input-otp-testing.d.ts +12 -6
  357. package/types/ngwr-input-otp.d.ts +15 -1
  358. package/types/ngwr-line-chart.d.ts +20 -3
  359. package/types/ngwr-markdown.d.ts +11 -0
  360. package/types/ngwr-mention.d.ts +32 -0
  361. package/types/ngwr-meter-group-testing.d.ts +12 -2
  362. package/types/ngwr-overlay.d.ts +1 -0
  363. package/types/ngwr-pagination-testing.d.ts +4 -4
  364. package/types/ngwr-pipes.d.ts +1 -1
  365. package/types/ngwr-popover-testing.d.ts +7 -2
  366. package/types/ngwr-popover.d.ts +1 -0
  367. package/types/ngwr-result.d.ts +6 -0
  368. package/types/ngwr-segmented-testing.d.ts +9 -0
  369. package/types/ngwr-segmented.d.ts +21 -1
  370. package/types/ngwr-select.d.ts +68 -6
  371. package/types/ngwr-shiny-text.d.ts +4 -2
  372. package/types/ngwr-sidebar.d.ts +14 -5
  373. package/types/ngwr-slider.d.ts +69 -1
  374. package/types/ngwr-speed-dial-testing.d.ts +15 -11
  375. package/types/ngwr-speed-dial.d.ts +22 -3
  376. package/types/ngwr-split-text.d.ts +9 -0
  377. package/types/ngwr-stepper-testing.d.ts +5 -5
  378. package/types/ngwr-stepper.d.ts +8 -0
  379. package/types/ngwr-storage.d.ts +1 -0
  380. package/types/ngwr-table.d.ts +104 -11
  381. package/types/ngwr-tabs-testing.d.ts +11 -10
  382. package/types/ngwr-tabs.d.ts +2 -0
  383. package/types/ngwr-theme.d.ts +82 -1
  384. package/types/ngwr-tour.d.ts +22 -4
  385. package/types/ngwr-tree.d.ts +5 -4
  386. package/types/ngwr-typewriter-testing.d.ts +10 -0
  387. package/types/ngwr-utils.d.ts +5 -2
  388. package/types/ngwr-version.d.ts +1 -1
  389. package/types/ngwr-window.d.ts +39 -11
  390. package/typography/styles/_index.scss +1 -1
  391. package/window/styles/_index.scss +72 -26
  392. package/fesm2022/ngwr-date-adapter-fns.mjs.map +0 -1
  393. package/fesm2022/ngwr-date-adapter-luxon.mjs.map +0 -1
  394. package/fesm2022/ngwr-date-adapter.mjs.map +0 -1
@@ -1,7 +1,9 @@
1
1
  import { Directionality } from '@angular/cdk/bidi';
2
2
  import { coerceNumberProperty, coerceBooleanProperty } from '@angular/cdk/coercion';
3
3
  import * as i0 from '@angular/core';
4
- import { input, computed, model, output, signal, viewChild, inject, effect, untracked, ViewEncapsulation, Component } from '@angular/core';
4
+ import { input, computed, inject, model, output, signal, viewChild, effect, untracked, ViewEncapsulation, Component } from '@angular/core';
5
+ import { WR_FORM_FIELD } from 'ngwr/form';
6
+ import { useI18nText } from 'ngwr/i18n';
5
7
  import { clamp, round } from 'ngwr/utils';
6
8
 
7
9
  /**
@@ -73,6 +75,69 @@ class WrSlider {
73
75
  disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
74
76
  /** Render the current value below the track. @default true */
75
77
  showLabel = input(true, { ...(ngDevMode ? { debugName: "showLabel" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
78
+ /**
79
+ * Accessible name of the thumb — the single one, or the LOWER one in range
80
+ * mode. Falls back to `slider.label` / `slider.lower`.
81
+ */
82
+ ariaLabel = input(null, /* @ts-ignore */
83
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
84
+ /** Accessible name of the upper thumb in range mode. Falls back to `slider.upper`. */
85
+ upperLabel = input(null, /* @ts-ignore */
86
+ ...(ngDevMode ? [{ debugName: "upperLabel" }] : /* istanbul ignore next */ []));
87
+ /**
88
+ * A thumb projects no text, so these ARE the accessible names — and they were
89
+ * hard-coded English with no key behind them, so every slider on a page
90
+ * announced the same untranslated "Value" whatever it controlled.
91
+ *
92
+ * Range mode reads `ariaLabel` as the LOWER end rather than as the whole
93
+ * control: a name given once must land on something, and naming both thumbs
94
+ * the same thing would read two different values under one name.
95
+ */
96
+ singleLabel = useI18nText(this.ariaLabel, 'slider.label', 'Value');
97
+ lowerLabel = useI18nText(this.ariaLabel, 'slider.lower', 'Lower value');
98
+ resolvedUpperLabel = useI18nText(this.upperLabel, 'slider.upper', 'Upper value');
99
+ resolvedLowLabel = computed(() => (this.range() ? this.lowerLabel() : this.singleLabel()), /* @ts-ignore */
100
+ ...(ngDevMode ? [{ debugName: "resolvedLowLabel" }] : /* istanbul ignore next */ []));
101
+ field = inject(WR_FORM_FIELD, { optional: true });
102
+ /**
103
+ * Id the surrounding `<wr-form-field>`'s `<label for>` points at, put on the
104
+ * single thumb — the LOWER one in range mode, the same end `ariaLabel` names.
105
+ *
106
+ * The field renders its label before it can see what was projected into it, so
107
+ * the id travels the other way and the control adopts it, exactly as
108
+ * `[wrInput]` does. Without this the `for` named an element that was nowhere
109
+ * in the document: clicking the label did nothing, and the field's own text
110
+ * reached the thumb through no path at all.
111
+ *
112
+ * It lands on the THUMB rather than on the host because only a labelable
113
+ * element can be a label's target, and `<wr-slider>` is not one — a `for`
114
+ * pointing at the host would resolve and still name nothing. The thumb is a
115
+ * real `<button>`, so it is.
116
+ *
117
+ * What this does NOT change is the thumb's name: an `aria-label` outranks a
118
+ * `<label>` in the accname order, and the thumb keeps its own because the
119
+ * field cannot promise it has one to give — `<wr-form-field>` renders a label
120
+ * only when `label` is set, and a slider that traded a generic name for no
121
+ * name would be the worse bug. Set `[ariaLabel]` to the field's label where
122
+ * the two should read alike.
123
+ */
124
+ thumbId = computed(() => this.field?.controlId() ?? null, /* @ts-ignore */
125
+ ...(ngDevMode ? [{ debugName: "thumbId" }] : /* istanbul ignore next */ []));
126
+ /**
127
+ * The field's error message, wired to the thumb the same way `wrInput` wires it
128
+ * to its native element.
129
+ *
130
+ * Without this the messages `<wr-form-field>` renders are visible and nothing
131
+ * else: a screen reader on the thumb never learns the field is invalid, nor
132
+ * what the message says. `aria-invalid` is keyed on the message EXISTING rather
133
+ * than on `errorKeys()`, because the field only publishes an id once it is
134
+ * showing something — announcing "invalid" while pointing at nothing is worse
135
+ * than staying quiet.
136
+ */
137
+ describedBy = computed(() => this.field?.describedBy() ?? null, /* @ts-ignore */
138
+ ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
139
+ ariaInvalid = computed(() => (this.field?.describedBy() ? 'true' : null), /* @ts-ignore */
140
+ ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
76
141
  /**
77
142
  * Current value. Bound by `[formField]`, or two-way via `[(value)]`. Shape
78
143
  * follows `range`: a plain `number`, or `[low, high]` in range mode.
@@ -178,15 +243,26 @@ class WrSlider {
178
243
  event.preventDefault();
179
244
  const target = event.currentTarget;
180
245
  target.setPointerCapture(event.pointerId);
246
+ // `preventDefault` above also suppresses the default focus, so the arrows
247
+ // did nothing after a mouse drag until the thumb was found again with Tab —
248
+ // while a click on the bare TRACK focused it. Synchronously, not through
249
+ // `focusThumb`: `event.currentTarget` is already the thumb, so that
250
+ // helper's `queueMicrotask` would only defer a focus nothing is waiting on.
251
+ target.focus();
181
252
  const move = (e) => this.updateFromEvent(e, thumb);
182
- const up = (e) => {
253
+ const cleanup = (e) => {
183
254
  target.releasePointerCapture(e.pointerId);
184
255
  target.removeEventListener('pointermove', move);
185
- target.removeEventListener('pointerup', up);
256
+ target.removeEventListener('pointerup', cleanup);
257
+ target.removeEventListener('pointercancel', cleanup);
186
258
  this.touch.emit();
187
259
  };
188
260
  target.addEventListener('pointermove', move);
189
- target.addEventListener('pointerup', up);
261
+ target.addEventListener('pointerup', cleanup);
262
+ // `pointercancel` is never followed by a `pointerup`, so without this the
263
+ // move listener outlived the drag: the thumb kept following a pointer that
264
+ // was merely hovering over it, with no button held.
265
+ target.addEventListener('pointercancel', cleanup);
190
266
  this.updateFromEvent(event, thumb);
191
267
  }
192
268
  onTrackPointerDown(event) {
@@ -237,6 +313,21 @@ class WrSlider {
237
313
  this.emitChange();
238
314
  this.touch.emit();
239
315
  }
316
+ /**
317
+ * A bound field learns it may show its validation copy from `touch`, and
318
+ * pointer-up plus a value-changing keypress cannot be the only sources: a
319
+ * slider tabbed into and out of without an arrow press left the field
320
+ * untouched forever — which is exactly the case `touched` exists for, and
321
+ * what this output has always documented itself as doing. `wr-knob` and
322
+ * `wr-rating` bind `(blur)` on the one element they own; this component has
323
+ * two thumbs, so it listens for the bubbling `focusout` on its host instead
324
+ * — and ignores the one that only moves focus from one thumb to the other.
325
+ */
326
+ onFocusOut(event) {
327
+ if (this.track().nativeElement.contains(event.relatedTarget))
328
+ return;
329
+ this.touch.emit();
330
+ }
240
331
  // Internals
241
332
  isRtl() {
242
333
  return this.dir?.value === 'rtl';
@@ -306,13 +397,13 @@ class WrSlider {
306
397
  const value = this.range() ? [this.low(), this.high()] : this.low();
307
398
  this.value.set(value);
308
399
  }
309
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrSlider, deps: [], target: i0.ɵɵFactoryTarget.Component });
310
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: WrSlider, isStandalone: true, selector: "wr-slider", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, range: { classPropertyName: "range", publicName: "range", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showLabel: { classPropertyName: "showLabel", publicName: "showLabel", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, viewQueries: [{ propertyName: "track", first: true, predicate: ["track"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #track class=\"wr-slider__track\" (pointerdown)=\"onTrackPointerDown($event)\">\n <!--\n Positioned on the INLINE axis, not with `left`: `inset-inline-start` is the\n same edge in LTR and mirrors under `dir=\"rtl\"`, so the track paints itself\n the right way round without the component reading a direction to draw.\n -->\n <span class=\"wr-slider__fill\" [style.inset-inline-start.%]=\"fillStart()\" [style.width.%]=\"fillWidth()\"></span>\n\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--low\"\n role=\"slider\"\n [style.inset-inline-start.%]=\"lowPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"range() ? high() : max()\"\n [attr.aria-valuenow]=\"low()\"\n [attr.aria-label]=\"range() ? 'Lower value' : 'Value'\"\n (pointerdown)=\"onPointerDown($event, 'low')\"\n (keydown)=\"onKey($event, 'low')\"\n ></button>\n\n @if (range()) {\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--high\"\n role=\"slider\"\n [style.inset-inline-start.%]=\"highPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"low()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"high()\"\n aria-label=\"Upper value\"\n (pointerdown)=\"onPointerDown($event, 'high')\"\n (keydown)=\"onKey($event, 'high')\"\n ></button>\n }\n</div>\n\n@if (showLabel()) {\n <div class=\"wr-slider__label\">{{ label() }}</div>\n}\n", encapsulation: i0.ViewEncapsulation.None });
400
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrSlider, deps: [], target: i0.ɵɵFactoryTarget.Component });
401
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: WrSlider, isStandalone: true, selector: "wr-slider", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, range: { classPropertyName: "range", publicName: "range", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showLabel: { classPropertyName: "showLabel", publicName: "showLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, upperLabel: { classPropertyName: "upperLabel", publicName: "upperLabel", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { listeners: { "focusout": "onFocusOut($event)" }, properties: { "class": "classes()" } }, viewQueries: [{ propertyName: "track", first: true, predicate: ["track"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #track class=\"wr-slider__track\" (pointerdown)=\"onTrackPointerDown($event)\">\n <!--\n Positioned on the INLINE axis, not with `left`: `inset-inline-start` is the\n same edge in LTR and mirrors under `dir=\"rtl\"`, so the track paints itself\n the right way round without the component reading a direction to draw.\n -->\n <span class=\"wr-slider__fill\" [style.inset-inline-start.%]=\"fillStart()\" [style.width.%]=\"fillWidth()\"></span>\n\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--low\"\n role=\"slider\"\n [attr.id]=\"thumbId()\"\n [style.inset-inline-start.%]=\"lowPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"range() ? high() : max()\"\n [attr.aria-valuenow]=\"low()\"\n [attr.aria-label]=\"resolvedLowLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n (pointerdown)=\"onPointerDown($event, 'low')\"\n (keydown)=\"onKey($event, 'low')\"\n ></button>\n\n @if (range()) {\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--high\"\n role=\"slider\"\n [style.inset-inline-start.%]=\"highPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"low()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"high()\"\n [attr.aria-label]=\"resolvedUpperLabel()\"\n (pointerdown)=\"onPointerDown($event, 'high')\"\n (keydown)=\"onKey($event, 'high')\"\n ></button>\n }\n</div>\n\n@if (showLabel()) {\n <div class=\"wr-slider__label\">{{ label() }}</div>\n}\n", encapsulation: i0.ViewEncapsulation.None });
311
402
  }
312
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrSlider, decorators: [{
403
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrSlider, decorators: [{
313
404
  type: Component,
314
- args: [{ selector: 'wr-slider', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, template: "<div #track class=\"wr-slider__track\" (pointerdown)=\"onTrackPointerDown($event)\">\n <!--\n Positioned on the INLINE axis, not with `left`: `inset-inline-start` is the\n same edge in LTR and mirrors under `dir=\"rtl\"`, so the track paints itself\n the right way round without the component reading a direction to draw.\n -->\n <span class=\"wr-slider__fill\" [style.inset-inline-start.%]=\"fillStart()\" [style.width.%]=\"fillWidth()\"></span>\n\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--low\"\n role=\"slider\"\n [style.inset-inline-start.%]=\"lowPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"range() ? high() : max()\"\n [attr.aria-valuenow]=\"low()\"\n [attr.aria-label]=\"range() ? 'Lower value' : 'Value'\"\n (pointerdown)=\"onPointerDown($event, 'low')\"\n (keydown)=\"onKey($event, 'low')\"\n ></button>\n\n @if (range()) {\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--high\"\n role=\"slider\"\n [style.inset-inline-start.%]=\"highPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"low()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"high()\"\n aria-label=\"Upper value\"\n (pointerdown)=\"onPointerDown($event, 'high')\"\n (keydown)=\"onKey($event, 'high')\"\n ></button>\n }\n</div>\n\n@if (showLabel()) {\n <div class=\"wr-slider__label\">{{ label() }}</div>\n}\n" }]
315
- }], ctorParameters: () => [], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLabel", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }], track: [{ type: i0.ViewChild, args: ['track', { isSignal: true }] }] } });
405
+ args: [{ selector: 'wr-slider', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()', '(focusout)': 'onFocusOut($event)' }, template: "<div #track class=\"wr-slider__track\" (pointerdown)=\"onTrackPointerDown($event)\">\n <!--\n Positioned on the INLINE axis, not with `left`: `inset-inline-start` is the\n same edge in LTR and mirrors under `dir=\"rtl\"`, so the track paints itself\n the right way round without the component reading a direction to draw.\n -->\n <span class=\"wr-slider__fill\" [style.inset-inline-start.%]=\"fillStart()\" [style.width.%]=\"fillWidth()\"></span>\n\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--low\"\n role=\"slider\"\n [attr.id]=\"thumbId()\"\n [style.inset-inline-start.%]=\"lowPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"range() ? high() : max()\"\n [attr.aria-valuenow]=\"low()\"\n [attr.aria-label]=\"resolvedLowLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n (pointerdown)=\"onPointerDown($event, 'low')\"\n (keydown)=\"onKey($event, 'low')\"\n ></button>\n\n @if (range()) {\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--high\"\n role=\"slider\"\n [style.inset-inline-start.%]=\"highPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"low()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"high()\"\n [attr.aria-label]=\"resolvedUpperLabel()\"\n (pointerdown)=\"onPointerDown($event, 'high')\"\n (keydown)=\"onKey($event, 'high')\"\n ></button>\n }\n</div>\n\n@if (showLabel()) {\n <div class=\"wr-slider__label\">{{ label() }}</div>\n}\n" }]
406
+ }], ctorParameters: () => [], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLabel", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], upperLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "upperLabel", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }], track: [{ type: i0.ViewChild, args: ['track', { isSignal: true }] }] } });
316
407
 
317
408
  /**
318
409
  * Generated bundle index. Do not edit.
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-slider.mjs","sources":["../../../projects/lib/slider/slider.ts","../../../projects/lib/slider/slider.html","../../../projects/lib/slider/ngwr-slider.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Directionality } from '@angular/cdk/bidi';\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport type { ElementRef } from '@angular/core';\nimport {\n Component,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { clamp, round } from 'ngwr/utils';\n\nimport type { WrSliderValue } from './interfaces';\n\n/** Trim float drift from step calculations. */\n\n/**\n * Numeric slider with optional dual-thumb range mode.\n *\n * A signal-forms native control: it implements `FormValueControl<WrSliderValue>`,\n * so `[formField]` binds straight to its `value` model — no\n * `ControlValueAccessor` in between. `[(value)]` works standalone. The value\n * shape depends on `range`: `number` (default) or `[number, number]` when\n * `range=\"true\"`.\n *\n * Keyboard: ← / → adjust by `step`; ↑ / ↓ same; Shift+arrow by `step × 10`;\n * Home / End jump to min / max; PageUp / PageDown by `step × 10`.\n *\n * Reading direction: the arrows follow the VISUAL track, so under `dir=\"rtl\"`\n * → moves toward the lower end and ← toward the higher one, and a drag reads\n * its ratio from the right edge. ↑ / ↓, PageUp / PageDown and Home / End are\n * unchanged — they name a value, not a side of the screen.\n *\n * @example\n * ```html\n * <!-- signal forms -->\n * <wr-slider [formField]=\"form.volume\" min=\"0\" max=\"100\" step=\"5\" />\n *\n * <!-- standalone two-way binding -->\n * <wr-slider [(value)]=\"range\" range min=\"0\" max=\"1000\" step=\"10\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/slider\n */\n@Component({\n selector: 'wr-slider',\n templateUrl: './slider.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()' },\n})\nexport class WrSlider implements FormValueControl<WrSliderValue> {\n // Typed `WrSliderValue | undefined` (not `number`) to satisfy the reserved\n // `FormUiControl` min/max slots, which are keyed to the control's value type.\n // The transform still coerces to a plain number, so the bounds stay numeric\n // at runtime; `minValue`/`maxValue` narrow them back for arithmetic.\n /** Lower bound. @default 0 */\n readonly min = input<WrSliderValue | undefined>(0, { transform: (v: unknown): number => coerceNumberProperty(v, 0) });\n /** Upper bound. @default 100 */\n readonly max = input<WrSliderValue | undefined>(100, {\n transform: (v: unknown): number => coerceNumberProperty(v, 100),\n });\n\n /** Resolved numeric lower bound for internal arithmetic. */\n private readonly minValue = computed(() => {\n const m = this.min();\n return typeof m === 'number' ? m : 0;\n });\n /** Resolved numeric upper bound for internal arithmetic. */\n private readonly maxValue = computed(() => {\n const m = this.max();\n return typeof m === 'number' ? m : 100;\n });\n\n /** Step size for keyboard and drag. @default 1 */\n readonly step = input(1, { transform: (v: unknown): number => Math.max(0.0001, coerceNumberProperty(v, 1)) });\n /** Render two thumbs and emit `[low, high]`. @default false */\n readonly range = input(false, { transform: coerceBooleanProperty });\n /**\n * Disable interaction. Bound automatically from the field's disabled state\n * when used with `[formField]`.\n *\n * @default false\n */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n /** Render the current value below the track. @default true */\n readonly showLabel = input(true, { transform: coerceBooleanProperty });\n\n /**\n * Current value. Bound by `[formField]`, or two-way via `[(value)]`. Shape\n * follows `range`: a plain `number`, or `[low, high]` in range mode.\n */\n readonly value = model<WrSliderValue>(0);\n\n /** Emitted on blur so a bound field can mark itself touched. */\n readonly touch = output<void>();\n\n protected readonly low = signal(0);\n /**\n * The high thumb's cell. Its literal default stands in for `maxValue()`,\n * which cannot be read at field-initialisation time — see `highSeeded` and\n * the constructor effect.\n */\n protected readonly high = signal(100);\n\n /**\n * Whether `high` has ever been told a real value by the model.\n *\n * `<wr-slider range [min]=\"200\" [max]=\"300\" />` binds no value, so the model\n * holds its own scalar default and the tuple branch below never runs: `high`\n * kept the literal 100, which is not inside every `[min, max]`. The thumb\n * rendered off the track and the first interaction committed `[low, 100]` —\n * a value the slider itself calls out of range.\n */\n private highSeeded = false;\n\n protected readonly track = viewChild.required<ElementRef<HTMLElement>>('track');\n\n /**\n * Ambient reading direction. Optional so a bare `TestBed` — or any consumer\n * that never set a direction — needs no provider; `Directionality` is\n * root-provided, so `null` only ever means \"nobody asked\", which is LTR.\n *\n * Nothing caches a direction-derived value: the thumbs are painted with\n * `inset-inline-start`, so the track mirrors from CSS alone, and `isRtl()` is\n * read inside the event handlers. A runtime flip therefore needs no\n * subscription to `Directionality.change`.\n */\n private readonly dir = inject(Directionality, { optional: true });\n\n protected readonly classes = computed(() => {\n const parts = ['wr-slider'];\n if (this.range()) parts.push('wr-slider--range');\n if (this.disabled()) parts.push('wr-slider--disabled');\n return parts.join(' ');\n });\n\n protected readonly lowPercent = computed(() => this.percentOf(this.low()));\n protected readonly highPercent = computed(() => this.percentOf(this.high()));\n\n /** Offset of the fill from the track's START edge — the left one in LTR, the right one in RTL. */\n protected readonly fillStart = computed(() => (this.range() ? this.lowPercent() : 0));\n protected readonly fillWidth = computed(() =>\n this.range() ? this.highPercent() - this.lowPercent() : this.lowPercent()\n );\n\n protected readonly label = computed(() => (this.range() ? `${this.low()} – ${this.high()}` : `${this.low()}`));\n\n constructor() {\n // Keep the internal thumbs in sync with external writes to `value`\n // (the old `writeValue`): split the tuple / clamp into the low & high cells.\n // The cells are clamped and the MODEL IS NOT TOUCHED, and the asymmetry is\n // deliberate. A thumb cannot render outside its own track, so the display\n // has no choice — but writing that clamp back into `value` would erase the\n // error it represents: under `[formField]` the bounds reach this component\n // only through the schema's `min()` / `max()` rules (binding `[min]` beside\n // `[formField]` is an NG8022 compile error), so the write-back deleted the\n // out-of-range value those rules exist to report, and marked a pristine\n // form dirty on first paint besides. The first real interaction emits an\n // in-range number through `emitChange` and the two agree from then on.\n effect(() => {\n const v = this.value();\n\n if (Array.isArray(v)) {\n const tuple = v as readonly [number, number];\n const lo = this.clampToBounds(tuple[0]);\n const hi = this.clampToBounds(tuple[1]);\n this.low.set(lo);\n this.high.set(hi);\n this.highSeeded = true;\n return;\n }\n\n if (typeof v !== 'number') return;\n const lo = this.clampToBounds(v);\n this.low.set(lo);\n\n if (!this.range()) return;\n\n // Range mode holding a scalar: the high thumb has never been told\n // anything, and its literal default is not inside every `[min, max]`.\n // Seed it at the top of the range — which is what the 100 meant back when\n // the bounds were 0–100. The MODEL is left alone here too; the shape it\n // holds is the consumer's business until they move a thumb.\n this.high.set(this.highSeeded ? this.clampToBounds(untracked(this.high)) : this.maxValue());\n this.highSeeded = true;\n });\n }\n\n // Interaction\n\n protected onPointerDown(event: PointerEvent, thumb: 'low' | 'high'): void {\n if (this.disabled()) return;\n event.preventDefault();\n const target = event.currentTarget as HTMLElement;\n target.setPointerCapture(event.pointerId);\n\n const move = (e: PointerEvent): void => this.updateFromEvent(e, thumb);\n const up = (e: PointerEvent): void => {\n target.releasePointerCapture(e.pointerId);\n target.removeEventListener('pointermove', move);\n target.removeEventListener('pointerup', up);\n this.touch.emit();\n };\n\n target.addEventListener('pointermove', move);\n target.addEventListener('pointerup', up);\n this.updateFromEvent(event, thumb);\n }\n\n protected onTrackPointerDown(event: PointerEvent): void {\n if (this.disabled()) return;\n if ((event.target as HTMLElement).closest('.wr-slider__thumb')) return;\n const thumb = this.nearestThumb(event);\n this.updateFromEvent(event, thumb);\n this.focusThumb(thumb);\n }\n\n protected onKey(event: KeyboardEvent, thumb: 'low' | 'high'): void {\n if (this.disabled()) return;\n const big = event.shiftKey || event.key === 'PageUp' || event.key === 'PageDown' ? 10 : 1;\n const delta = this.step() * big;\n const current = thumb === 'low' ? this.low() : this.high();\n // The horizontal arrows name a SIDE of the track, so they follow the\n // reading direction; every other key names a value and does not.\n const inline = this.isRtl() ? -delta : delta;\n let next: number | null = null;\n\n switch (event.key) {\n case 'ArrowRight':\n next = current + inline;\n break;\n case 'ArrowLeft':\n next = current - inline;\n break;\n case 'ArrowUp':\n case 'PageUp':\n next = current + delta;\n break;\n case 'ArrowDown':\n case 'PageDown':\n next = current - delta;\n break;\n case 'Home':\n next = this.minValue();\n break;\n case 'End':\n next = this.maxValue();\n break;\n }\n\n if (next === null) return;\n event.preventDefault();\n this.setThumb(thumb, next);\n this.emitChange();\n this.touch.emit();\n }\n\n // Internals\n\n private isRtl(): boolean {\n return this.dir?.value === 'rtl';\n }\n\n /**\n * The value under a pointer, measured along the track's INLINE axis.\n *\n * The offset is taken from whichever edge the track starts at — the left one\n * in LTR, the right one in RTL — so the whole component needs the direction\n * in exactly one place. Measuring from `rect.left` regardless would read 0 at\n * the visual maximum of a mirrored track.\n */\n private valueAt(event: PointerEvent): number {\n const rect = this.track().nativeElement.getBoundingClientRect();\n const offset = this.isRtl() ? rect.right - event.clientX : event.clientX - rect.left;\n const ratio = clamp(offset / rect.width, 0, 1);\n return this.minValue() + ratio * (this.maxValue() - this.minValue());\n }\n\n private updateFromEvent(event: PointerEvent, thumb: 'low' | 'high'): void {\n this.setThumb(thumb, this.valueAt(event));\n this.emitChange();\n }\n\n /**\n * Which thumb a track click grabs: the one nearest the pointer ON SCREEN.\n *\n * Value distance is the same thing as visual distance once {@link valueAt}\n * has mirrored the ratio — in RTL the low thumb is the RIGHT one, and a click\n * near the right edge lands close to `low` in value space too.\n */\n private nearestThumb(event: PointerEvent): 'low' | 'high' {\n if (!this.range()) return 'low';\n const raw = this.valueAt(event);\n return Math.abs(raw - this.low()) <= Math.abs(raw - this.high()) ? 'low' : 'high';\n }\n\n private focusThumb(thumb: 'low' | 'high'): void {\n queueMicrotask(() => {\n const root = this.track().nativeElement;\n const el = root.querySelector<HTMLElement>(`.wr-slider__thumb--${thumb}`);\n el?.focus();\n });\n }\n\n private setThumb(thumb: 'low' | 'high', raw: number): void {\n const stepped = this.snap(raw);\n if (thumb === 'low') {\n const next = this.range() ? Math.min(stepped, this.high()) : stepped;\n this.low.set(next);\n } else {\n this.high.set(Math.max(stepped, this.low()));\n }\n }\n\n private snap(value: number): number {\n const stepped = Math.round((value - this.minValue()) / this.step()) * this.step() + this.minValue();\n return this.clampToBounds(round(stepped, 6));\n }\n\n private clampToBounds(v: number): number {\n return clamp(v, this.minValue(), this.maxValue());\n }\n\n private percentOf(v: number): number {\n const span = this.maxValue() - this.minValue();\n if (span <= 0) return 0;\n return ((v - this.minValue()) / span) * 100;\n }\n\n private emitChange(): void {\n const value: WrSliderValue = this.range() ? [this.low(), this.high()] : this.low();\n this.value.set(value);\n }\n}\n","<div #track class=\"wr-slider__track\" (pointerdown)=\"onTrackPointerDown($event)\">\n <!--\n Positioned on the INLINE axis, not with `left`: `inset-inline-start` is the\n same edge in LTR and mirrors under `dir=\"rtl\"`, so the track paints itself\n the right way round without the component reading a direction to draw.\n -->\n <span class=\"wr-slider__fill\" [style.inset-inline-start.%]=\"fillStart()\" [style.width.%]=\"fillWidth()\"></span>\n\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--low\"\n role=\"slider\"\n [style.inset-inline-start.%]=\"lowPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"range() ? high() : max()\"\n [attr.aria-valuenow]=\"low()\"\n [attr.aria-label]=\"range() ? 'Lower value' : 'Value'\"\n (pointerdown)=\"onPointerDown($event, 'low')\"\n (keydown)=\"onKey($event, 'low')\"\n ></button>\n\n @if (range()) {\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--high\"\n role=\"slider\"\n [style.inset-inline-start.%]=\"highPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"low()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"high()\"\n aria-label=\"Upper value\"\n (pointerdown)=\"onPointerDown($event, 'high')\"\n (keydown)=\"onKey($event, 'high')\"\n ></button>\n }\n</div>\n\n@if (showLabel()) {\n <div class=\"wr-slider__label\">{{ label() }}</div>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-slider.mjs","sources":["../../../projects/lib/slider/slider.ts","../../../projects/lib/slider/slider.html","../../../projects/lib/slider/ngwr-slider.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Directionality } from '@angular/cdk/bidi';\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport type { ElementRef } from '@angular/core';\nimport {\n Component,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { WR_FORM_FIELD } from 'ngwr/form';\nimport { useI18nText } from 'ngwr/i18n';\nimport { clamp, round } from 'ngwr/utils';\n\nimport type { WrSliderValue } from './interfaces';\n\n/** Trim float drift from step calculations. */\n\n/**\n * Numeric slider with optional dual-thumb range mode.\n *\n * A signal-forms native control: it implements `FormValueControl<WrSliderValue>`,\n * so `[formField]` binds straight to its `value` model — no\n * `ControlValueAccessor` in between. `[(value)]` works standalone. The value\n * shape depends on `range`: `number` (default) or `[number, number]` when\n * `range=\"true\"`.\n *\n * Keyboard: ← / → adjust by `step`; ↑ / ↓ same; Shift+arrow by `step × 10`;\n * Home / End jump to min / max; PageUp / PageDown by `step × 10`.\n *\n * Reading direction: the arrows follow the VISUAL track, so under `dir=\"rtl\"`\n * → moves toward the lower end and ← toward the higher one, and a drag reads\n * its ratio from the right edge. ↑ / ↓, PageUp / PageDown and Home / End are\n * unchanged — they name a value, not a side of the screen.\n *\n * @example\n * ```html\n * <!-- signal forms -->\n * <wr-slider [formField]=\"form.volume\" min=\"0\" max=\"100\" step=\"5\" />\n *\n * <!-- standalone two-way binding -->\n * <wr-slider [(value)]=\"range\" range min=\"0\" max=\"1000\" step=\"10\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/slider\n */\n@Component({\n selector: 'wr-slider',\n templateUrl: './slider.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()', '(focusout)': 'onFocusOut($event)' },\n})\nexport class WrSlider implements FormValueControl<WrSliderValue> {\n // Typed `WrSliderValue | undefined` (not `number`) to satisfy the reserved\n // `FormUiControl` min/max slots, which are keyed to the control's value type.\n // The transform still coerces to a plain number, so the bounds stay numeric\n // at runtime; `minValue`/`maxValue` narrow them back for arithmetic.\n /** Lower bound. @default 0 */\n readonly min = input<WrSliderValue | undefined>(0, { transform: (v: unknown): number => coerceNumberProperty(v, 0) });\n /** Upper bound. @default 100 */\n readonly max = input<WrSliderValue | undefined>(100, {\n transform: (v: unknown): number => coerceNumberProperty(v, 100),\n });\n\n /** Resolved numeric lower bound for internal arithmetic. */\n private readonly minValue = computed(() => {\n const m = this.min();\n return typeof m === 'number' ? m : 0;\n });\n /** Resolved numeric upper bound for internal arithmetic. */\n private readonly maxValue = computed(() => {\n const m = this.max();\n return typeof m === 'number' ? m : 100;\n });\n\n /** Step size for keyboard and drag. @default 1 */\n readonly step = input(1, { transform: (v: unknown): number => Math.max(0.0001, coerceNumberProperty(v, 1)) });\n /** Render two thumbs and emit `[low, high]`. @default false */\n readonly range = input(false, { transform: coerceBooleanProperty });\n /**\n * Disable interaction. Bound automatically from the field's disabled state\n * when used with `[formField]`.\n *\n * @default false\n */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n /** Render the current value below the track. @default true */\n readonly showLabel = input(true, { transform: coerceBooleanProperty });\n\n /**\n * Accessible name of the thumb — the single one, or the LOWER one in range\n * mode. Falls back to `slider.label` / `slider.lower`.\n */\n readonly ariaLabel = input<string | null>(null);\n\n /** Accessible name of the upper thumb in range mode. Falls back to `slider.upper`. */\n readonly upperLabel = input<string | null>(null);\n\n /**\n * A thumb projects no text, so these ARE the accessible names — and they were\n * hard-coded English with no key behind them, so every slider on a page\n * announced the same untranslated \"Value\" whatever it controlled.\n *\n * Range mode reads `ariaLabel` as the LOWER end rather than as the whole\n * control: a name given once must land on something, and naming both thumbs\n * the same thing would read two different values under one name.\n */\n private readonly singleLabel = useI18nText(this.ariaLabel, 'slider.label', 'Value');\n private readonly lowerLabel = useI18nText(this.ariaLabel, 'slider.lower', 'Lower value');\n protected readonly resolvedUpperLabel = useI18nText(this.upperLabel, 'slider.upper', 'Upper value');\n protected readonly resolvedLowLabel = computed(() => (this.range() ? this.lowerLabel() : this.singleLabel()));\n\n private readonly field = inject(WR_FORM_FIELD, { optional: true });\n\n /**\n * Id the surrounding `<wr-form-field>`'s `<label for>` points at, put on the\n * single thumb — the LOWER one in range mode, the same end `ariaLabel` names.\n *\n * The field renders its label before it can see what was projected into it, so\n * the id travels the other way and the control adopts it, exactly as\n * `[wrInput]` does. Without this the `for` named an element that was nowhere\n * in the document: clicking the label did nothing, and the field's own text\n * reached the thumb through no path at all.\n *\n * It lands on the THUMB rather than on the host because only a labelable\n * element can be a label's target, and `<wr-slider>` is not one — a `for`\n * pointing at the host would resolve and still name nothing. The thumb is a\n * real `<button>`, so it is.\n *\n * What this does NOT change is the thumb's name: an `aria-label` outranks a\n * `<label>` in the accname order, and the thumb keeps its own because the\n * field cannot promise it has one to give — `<wr-form-field>` renders a label\n * only when `label` is set, and a slider that traded a generic name for no\n * name would be the worse bug. Set `[ariaLabel]` to the field's label where\n * the two should read alike.\n */\n protected readonly thumbId = computed(() => this.field?.controlId() ?? null);\n\n /**\n * The field's error message, wired to the thumb the same way `wrInput` wires it\n * to its native element.\n *\n * Without this the messages `<wr-form-field>` renders are visible and nothing\n * else: a screen reader on the thumb never learns the field is invalid, nor\n * what the message says. `aria-invalid` is keyed on the message EXISTING rather\n * than on `errorKeys()`, because the field only publishes an id once it is\n * showing something — announcing \"invalid\" while pointing at nothing is worse\n * than staying quiet.\n */\n protected readonly describedBy = computed(() => this.field?.describedBy() ?? null);\n protected readonly ariaInvalid = computed(() => (this.field?.describedBy() ? 'true' : null));\n\n /**\n * Current value. Bound by `[formField]`, or two-way via `[(value)]`. Shape\n * follows `range`: a plain `number`, or `[low, high]` in range mode.\n */\n readonly value = model<WrSliderValue>(0);\n\n /** Emitted on blur so a bound field can mark itself touched. */\n readonly touch = output<void>();\n\n protected readonly low = signal(0);\n /**\n * The high thumb's cell. Its literal default stands in for `maxValue()`,\n * which cannot be read at field-initialisation time — see `highSeeded` and\n * the constructor effect.\n */\n protected readonly high = signal(100);\n\n /**\n * Whether `high` has ever been told a real value by the model.\n *\n * `<wr-slider range [min]=\"200\" [max]=\"300\" />` binds no value, so the model\n * holds its own scalar default and the tuple branch below never runs: `high`\n * kept the literal 100, which is not inside every `[min, max]`. The thumb\n * rendered off the track and the first interaction committed `[low, 100]` —\n * a value the slider itself calls out of range.\n */\n private highSeeded = false;\n\n protected readonly track = viewChild.required<ElementRef<HTMLElement>>('track');\n\n /**\n * Ambient reading direction. Optional so a bare `TestBed` — or any consumer\n * that never set a direction — needs no provider; `Directionality` is\n * root-provided, so `null` only ever means \"nobody asked\", which is LTR.\n *\n * Nothing caches a direction-derived value: the thumbs are painted with\n * `inset-inline-start`, so the track mirrors from CSS alone, and `isRtl()` is\n * read inside the event handlers. A runtime flip therefore needs no\n * subscription to `Directionality.change`.\n */\n private readonly dir = inject(Directionality, { optional: true });\n\n protected readonly classes = computed(() => {\n const parts = ['wr-slider'];\n if (this.range()) parts.push('wr-slider--range');\n if (this.disabled()) parts.push('wr-slider--disabled');\n return parts.join(' ');\n });\n\n protected readonly lowPercent = computed(() => this.percentOf(this.low()));\n protected readonly highPercent = computed(() => this.percentOf(this.high()));\n\n /** Offset of the fill from the track's START edge — the left one in LTR, the right one in RTL. */\n protected readonly fillStart = computed(() => (this.range() ? this.lowPercent() : 0));\n protected readonly fillWidth = computed(() =>\n this.range() ? this.highPercent() - this.lowPercent() : this.lowPercent()\n );\n\n protected readonly label = computed(() => (this.range() ? `${this.low()} – ${this.high()}` : `${this.low()}`));\n\n constructor() {\n // Keep the internal thumbs in sync with external writes to `value`\n // (the old `writeValue`): split the tuple / clamp into the low & high cells.\n // The cells are clamped and the MODEL IS NOT TOUCHED, and the asymmetry is\n // deliberate. A thumb cannot render outside its own track, so the display\n // has no choice — but writing that clamp back into `value` would erase the\n // error it represents: under `[formField]` the bounds reach this component\n // only through the schema's `min()` / `max()` rules (binding `[min]` beside\n // `[formField]` is an NG8022 compile error), so the write-back deleted the\n // out-of-range value those rules exist to report, and marked a pristine\n // form dirty on first paint besides. The first real interaction emits an\n // in-range number through `emitChange` and the two agree from then on.\n effect(() => {\n const v = this.value();\n\n if (Array.isArray(v)) {\n const tuple = v as readonly [number, number];\n const lo = this.clampToBounds(tuple[0]);\n const hi = this.clampToBounds(tuple[1]);\n this.low.set(lo);\n this.high.set(hi);\n this.highSeeded = true;\n return;\n }\n\n if (typeof v !== 'number') return;\n const lo = this.clampToBounds(v);\n this.low.set(lo);\n\n if (!this.range()) return;\n\n // Range mode holding a scalar: the high thumb has never been told\n // anything, and its literal default is not inside every `[min, max]`.\n // Seed it at the top of the range — which is what the 100 meant back when\n // the bounds were 0–100. The MODEL is left alone here too; the shape it\n // holds is the consumer's business until they move a thumb.\n this.high.set(this.highSeeded ? this.clampToBounds(untracked(this.high)) : this.maxValue());\n this.highSeeded = true;\n });\n }\n\n // Interaction\n\n protected onPointerDown(event: PointerEvent, thumb: 'low' | 'high'): void {\n if (this.disabled()) return;\n event.preventDefault();\n const target = event.currentTarget as HTMLElement;\n target.setPointerCapture(event.pointerId);\n // `preventDefault` above also suppresses the default focus, so the arrows\n // did nothing after a mouse drag until the thumb was found again with Tab —\n // while a click on the bare TRACK focused it. Synchronously, not through\n // `focusThumb`: `event.currentTarget` is already the thumb, so that\n // helper's `queueMicrotask` would only defer a focus nothing is waiting on.\n target.focus();\n\n const move = (e: PointerEvent): void => this.updateFromEvent(e, thumb);\n const cleanup = (e: PointerEvent): void => {\n target.releasePointerCapture(e.pointerId);\n target.removeEventListener('pointermove', move);\n target.removeEventListener('pointerup', cleanup);\n target.removeEventListener('pointercancel', cleanup);\n this.touch.emit();\n };\n\n target.addEventListener('pointermove', move);\n target.addEventListener('pointerup', cleanup);\n // `pointercancel` is never followed by a `pointerup`, so without this the\n // move listener outlived the drag: the thumb kept following a pointer that\n // was merely hovering over it, with no button held.\n target.addEventListener('pointercancel', cleanup);\n this.updateFromEvent(event, thumb);\n }\n\n protected onTrackPointerDown(event: PointerEvent): void {\n if (this.disabled()) return;\n if ((event.target as HTMLElement).closest('.wr-slider__thumb')) return;\n const thumb = this.nearestThumb(event);\n this.updateFromEvent(event, thumb);\n this.focusThumb(thumb);\n }\n\n protected onKey(event: KeyboardEvent, thumb: 'low' | 'high'): void {\n if (this.disabled()) return;\n const big = event.shiftKey || event.key === 'PageUp' || event.key === 'PageDown' ? 10 : 1;\n const delta = this.step() * big;\n const current = thumb === 'low' ? this.low() : this.high();\n // The horizontal arrows name a SIDE of the track, so they follow the\n // reading direction; every other key names a value and does not.\n const inline = this.isRtl() ? -delta : delta;\n let next: number | null = null;\n\n switch (event.key) {\n case 'ArrowRight':\n next = current + inline;\n break;\n case 'ArrowLeft':\n next = current - inline;\n break;\n case 'ArrowUp':\n case 'PageUp':\n next = current + delta;\n break;\n case 'ArrowDown':\n case 'PageDown':\n next = current - delta;\n break;\n case 'Home':\n next = this.minValue();\n break;\n case 'End':\n next = this.maxValue();\n break;\n }\n\n if (next === null) return;\n event.preventDefault();\n this.setThumb(thumb, next);\n this.emitChange();\n this.touch.emit();\n }\n\n /**\n * A bound field learns it may show its validation copy from `touch`, and\n * pointer-up plus a value-changing keypress cannot be the only sources: a\n * slider tabbed into and out of without an arrow press left the field\n * untouched forever — which is exactly the case `touched` exists for, and\n * what this output has always documented itself as doing. `wr-knob` and\n * `wr-rating` bind `(blur)` on the one element they own; this component has\n * two thumbs, so it listens for the bubbling `focusout` on its host instead\n * — and ignores the one that only moves focus from one thumb to the other.\n */\n protected onFocusOut(event: FocusEvent): void {\n if (this.track().nativeElement.contains(event.relatedTarget as Node | null)) return;\n this.touch.emit();\n }\n\n // Internals\n\n private isRtl(): boolean {\n return this.dir?.value === 'rtl';\n }\n\n /**\n * The value under a pointer, measured along the track's INLINE axis.\n *\n * The offset is taken from whichever edge the track starts at — the left one\n * in LTR, the right one in RTL — so the whole component needs the direction\n * in exactly one place. Measuring from `rect.left` regardless would read 0 at\n * the visual maximum of a mirrored track.\n */\n private valueAt(event: PointerEvent): number {\n const rect = this.track().nativeElement.getBoundingClientRect();\n const offset = this.isRtl() ? rect.right - event.clientX : event.clientX - rect.left;\n const ratio = clamp(offset / rect.width, 0, 1);\n return this.minValue() + ratio * (this.maxValue() - this.minValue());\n }\n\n private updateFromEvent(event: PointerEvent, thumb: 'low' | 'high'): void {\n this.setThumb(thumb, this.valueAt(event));\n this.emitChange();\n }\n\n /**\n * Which thumb a track click grabs: the one nearest the pointer ON SCREEN.\n *\n * Value distance is the same thing as visual distance once {@link valueAt}\n * has mirrored the ratio — in RTL the low thumb is the RIGHT one, and a click\n * near the right edge lands close to `low` in value space too.\n */\n private nearestThumb(event: PointerEvent): 'low' | 'high' {\n if (!this.range()) return 'low';\n const raw = this.valueAt(event);\n return Math.abs(raw - this.low()) <= Math.abs(raw - this.high()) ? 'low' : 'high';\n }\n\n private focusThumb(thumb: 'low' | 'high'): void {\n queueMicrotask(() => {\n const root = this.track().nativeElement;\n const el = root.querySelector<HTMLElement>(`.wr-slider__thumb--${thumb}`);\n el?.focus();\n });\n }\n\n private setThumb(thumb: 'low' | 'high', raw: number): void {\n const stepped = this.snap(raw);\n if (thumb === 'low') {\n const next = this.range() ? Math.min(stepped, this.high()) : stepped;\n this.low.set(next);\n } else {\n this.high.set(Math.max(stepped, this.low()));\n }\n }\n\n private snap(value: number): number {\n const stepped = Math.round((value - this.minValue()) / this.step()) * this.step() + this.minValue();\n return this.clampToBounds(round(stepped, 6));\n }\n\n private clampToBounds(v: number): number {\n return clamp(v, this.minValue(), this.maxValue());\n }\n\n private percentOf(v: number): number {\n const span = this.maxValue() - this.minValue();\n if (span <= 0) return 0;\n return ((v - this.minValue()) / span) * 100;\n }\n\n private emitChange(): void {\n const value: WrSliderValue = this.range() ? [this.low(), this.high()] : this.low();\n this.value.set(value);\n }\n}\n","<div #track class=\"wr-slider__track\" (pointerdown)=\"onTrackPointerDown($event)\">\n <!--\n Positioned on the INLINE axis, not with `left`: `inset-inline-start` is the\n same edge in LTR and mirrors under `dir=\"rtl\"`, so the track paints itself\n the right way round without the component reading a direction to draw.\n -->\n <span class=\"wr-slider__fill\" [style.inset-inline-start.%]=\"fillStart()\" [style.width.%]=\"fillWidth()\"></span>\n\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--low\"\n role=\"slider\"\n [attr.id]=\"thumbId()\"\n [style.inset-inline-start.%]=\"lowPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"range() ? high() : max()\"\n [attr.aria-valuenow]=\"low()\"\n [attr.aria-label]=\"resolvedLowLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n (pointerdown)=\"onPointerDown($event, 'low')\"\n (keydown)=\"onKey($event, 'low')\"\n ></button>\n\n @if (range()) {\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--high\"\n role=\"slider\"\n [style.inset-inline-start.%]=\"highPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"low()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"high()\"\n [attr.aria-label]=\"resolvedUpperLabel()\"\n (pointerdown)=\"onPointerDown($event, 'high')\"\n (keydown)=\"onKey($event, 'high')\"\n ></button>\n }\n</div>\n\n@if (showLabel()) {\n <div class=\"wr-slider__label\">{{ label() }}</div>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -108,10 +108,10 @@ class WrSparkline {
108
108
  }, /* @ts-ignore */
109
109
  ...(ngDevMode ? [{ debugName: "tipPoint" }] : /* istanbul ignore next */ []));
110
110
  viewBox = `0 0 ${this.vbW} ${this.vbH}`;
111
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrSparkline, deps: [], target: i0.ɵɵFactoryTarget.Component });
112
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: WrSparkline, isStandalone: true, selector: "wr-sparkline", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, showArea: { classPropertyName: "showArea", publicName: "showArea", isSignal: true, isRequired: false, transformFunction: null }, showTip: { classPropertyName: "showTip", publicName: "showTip", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "wr-sparkline" }, ngImport: i0, template: "<svg\n [attr.viewBox]=\"viewBox\"\n [style.width]=\"width()\"\n [style.height]=\"height()\"\n [attr.role]=\"ariaLabel() ? 'img' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-hidden]=\"ariaLabel() ? null : 'true'\"\n preserveAspectRatio=\"none\"\n>\n @if (showArea() && areaPath()) {\n <path class=\"wr-sparkline__area\" [attr.d]=\"areaPath()\" [attr.fill]=\"color()\" fill-opacity=\"0.15\" stroke=\"none\" />\n }\n @if (linePath()) {\n <path\n class=\"wr-sparkline__line\"\n [attr.d]=\"linePath()\"\n fill=\"none\"\n [attr.stroke]=\"color()\"\n [attr.stroke-width]=\"strokeWidth()\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n vector-effect=\"non-scaling-stroke\"\n />\n }\n @if (showTip() && tipPoint(); as tip) {\n <circle class=\"wr-sparkline__tip\" [attr.cx]=\"tip.x\" [attr.cy]=\"tip.y\" r=\"1.5\" [attr.fill]=\"color()\" />\n }\n</svg>\n", encapsulation: i0.ViewEncapsulation.None });
111
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrSparkline, deps: [], target: i0.ɵɵFactoryTarget.Component });
112
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: WrSparkline, isStandalone: true, selector: "wr-sparkline", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, showArea: { classPropertyName: "showArea", publicName: "showArea", isSignal: true, isRequired: false, transformFunction: null }, showTip: { classPropertyName: "showTip", publicName: "showTip", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "wr-sparkline" }, ngImport: i0, template: "<svg\n [attr.viewBox]=\"viewBox\"\n [style.width]=\"width()\"\n [style.height]=\"height()\"\n [attr.role]=\"ariaLabel() ? 'img' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-hidden]=\"ariaLabel() ? null : 'true'\"\n preserveAspectRatio=\"none\"\n>\n @if (showArea() && areaPath()) {\n <path class=\"wr-sparkline__area\" [attr.d]=\"areaPath()\" [attr.fill]=\"color()\" fill-opacity=\"0.15\" stroke=\"none\" />\n }\n @if (linePath()) {\n <path\n class=\"wr-sparkline__line\"\n [attr.d]=\"linePath()\"\n fill=\"none\"\n [attr.stroke]=\"color()\"\n [attr.stroke-width]=\"strokeWidth()\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n vector-effect=\"non-scaling-stroke\"\n />\n }\n @if (showTip() && tipPoint(); as tip) {\n <circle class=\"wr-sparkline__tip\" [attr.cx]=\"tip.x\" [attr.cy]=\"tip.y\" r=\"1.5\" [attr.fill]=\"color()\" />\n }\n</svg>\n", encapsulation: i0.ViewEncapsulation.None });
113
113
  }
114
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrSparkline, decorators: [{
114
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrSparkline, decorators: [{
115
115
  type: Component,
116
116
  args: [{ selector: 'wr-sparkline', encapsulation: ViewEncapsulation.None, host: { class: 'wr-sparkline' }, template: "<svg\n [attr.viewBox]=\"viewBox\"\n [style.width]=\"width()\"\n [style.height]=\"height()\"\n [attr.role]=\"ariaLabel() ? 'img' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-hidden]=\"ariaLabel() ? null : 'true'\"\n preserveAspectRatio=\"none\"\n>\n @if (showArea() && areaPath()) {\n <path class=\"wr-sparkline__area\" [attr.d]=\"areaPath()\" [attr.fill]=\"color()\" fill-opacity=\"0.15\" stroke=\"none\" />\n }\n @if (linePath()) {\n <path\n class=\"wr-sparkline__line\"\n [attr.d]=\"linePath()\"\n fill=\"none\"\n [attr.stroke]=\"color()\"\n [attr.stroke-width]=\"strokeWidth()\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n vector-effect=\"non-scaling-stroke\"\n />\n }\n @if (showTip() && tipPoint(); as tip) {\n <circle class=\"wr-sparkline__tip\" [attr.cx]=\"tip.x\" [attr.cy]=\"tip.y\" r=\"1.5\" [attr.fill]=\"color()\" />\n }\n</svg>\n" }]
117
117
  }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], showArea: [{ type: i0.Input, args: [{ isSignal: true, alias: "showArea", required: false }] }], showTip: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTip", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }] } });
@@ -18,7 +18,7 @@ import { ComponentHarness, HarnessPredicate, TestKey } from '@angular/cdk/testin
18
18
  * @see https://ngwr.dev/guides/testing
19
19
  */
20
20
  class WrSpeedDialActionHarness extends ComponentHarness {
21
- /** The action button. `role="menuitem"` sits on this element too. */
21
+ /** The action button. */
22
22
  static hostSelector = '.wr-speed-dial__action';
23
23
  /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */
24
24
  static with(options = {}) {
@@ -30,10 +30,6 @@ class WrSpeedDialActionHarness extends ComponentHarness {
30
30
  async getLabel() {
31
31
  return (await this.host()).getAttribute('aria-label');
32
32
  }
33
- /** The role the action announces — `menuitem`. */
34
- async getRole() {
35
- return (await this.host()).getAttribute('role');
36
- }
37
33
  /** Whether the action draws an icon rather than a letter. */
38
34
  async hasIcon() {
39
35
  return (await this.locatorForOptional('wr-icon')()) !== null;
@@ -162,17 +158,24 @@ class WrSpeedDialHarness extends ComponentHarness {
162
158
  async hasSafeArea() {
163
159
  return (await this.host()).hasClass('wr-speed-dial--safe-area');
164
160
  }
165
- /** The role the action list announces — `menu`. */
161
+ /**
162
+ * The role the action list announces — `list`.
163
+ *
164
+ * Worth asking despite being a plain `<ul>`: the list is styled `list-style: none`,
165
+ * which drops the implicit list semantics in Safari, so the role is written out. It
166
+ * is deliberately NOT `menu` — that role promises arrows, Home/End and a single tab
167
+ * stop, none of which this component has.
168
+ */
166
169
  async getMenuRole() {
167
170
  return (await this.menu()).getAttribute('role');
168
171
  }
169
172
  /**
170
- * Whether the trigger's `aria-controls` names THIS dial's menu, and nothing else on
171
- * the page.
173
+ * Whether the trigger's `aria-controls` names THIS dial's action list, and nothing
174
+ * else on the page.
172
175
  *
173
176
  * Both halves matter. The id is resolved inside the host first, so a trigger
174
- * pointing at a SIBLING dial's menu — which resolves perfectly well from the
175
- * document — fails here. Then it is counted across the document, because two menus
177
+ * pointing at a SIBLING dial's list — which resolves perfectly well from the
178
+ * document — fails here. Then it is counted across the document, because two lists
176
179
  * answering to one id hand every reference to whichever comes first.
177
180
  */
178
181
  async isMenuBound() {
@@ -247,9 +250,8 @@ class WrSpeedDialHarness extends ComponentHarness {
247
250
  * Press Escape — closing the dial and putting focus back on the trigger.
248
251
  *
249
252
  * Sent to the host, which is where the component listens. The focus return is the
250
- * half worth asserting: `role="menu"` promises a way out, and the actions are
251
- * ordinary tab stops, so without it a keyboard user has to walk through every
252
- * action to leave.
253
+ * half worth asserting: the actions are ordinary tab stops, so without it a keyboard
254
+ * user has to walk through every action to leave.
253
255
  */
254
256
  async sendEscape() {
255
257
  await (await this.host()).sendKeys(TestKey.ESCAPE);
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-speed-dial-testing.mjs","sources":["../../../projects/lib/speed-dial/testing/wr-speed-dial-action-harness.ts","../../../projects/lib/speed-dial/testing/wr-speed-dial-harness.ts","../../../projects/lib/speed-dial/testing/ngwr-speed-dial-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrSpeedDialActionHarnessFilters } from './interfaces';\n\n/**\n * Test harness for one action inside a {@link WrSpeedDialHarness}.\n *\n * The label is read from `aria-label` rather than from the button's text, and that\n * is the whole point of the control: an action renders an ICON, or — with no icon —\n * the first glyph of its label. Its visible text is therefore `\"S\"` for \"Share\", or\n * nothing at all. {@link getInitial} answers what is drawn; {@link getLabel} answers\n * what it is.\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrSpeedDialActionHarness extends ComponentHarness {\n /** The action button. `role=\"menuitem\"` sits on this element too. */\n static hostSelector = '.wr-speed-dial__action';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrSpeedDialActionHarnessFilters = {}): HarnessPredicate<WrSpeedDialActionHarness> {\n return new HarnessPredicate(WrSpeedDialActionHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);\n }\n\n /** The action's accessible name — what a screen reader reads, and what `title` shows. */\n async getLabel(): Promise<string | null> {\n return (await this.host()).getAttribute('aria-label');\n }\n\n /** The role the action announces — `menuitem`. */\n async getRole(): Promise<string | null> {\n return (await this.host()).getAttribute('role');\n }\n\n /** Whether the action draws an icon rather than a letter. */\n async hasIcon(): Promise<boolean> {\n return (await this.locatorForOptional('wr-icon')()) !== null;\n }\n\n /** The icon's registered name, from `wr-icon`'s reflected `data-icon`, or `null`. */\n async getIconName(): Promise<string | null> {\n const icon = await this.locatorForOptional('wr-icon')();\n return icon ? icon.getAttribute('data-icon') : null;\n }\n\n /**\n * The single glyph drawn in place of a missing icon, or `null` when there is one.\n *\n * A GLYPH, not a character: the component splits the label by code point, so an\n * emoji label draws the whole emoji rather than the broken half of a surrogate pair.\n */\n async getInitial(): Promise<string | null> {\n if (await this.hasIcon()) return null;\n return (await this.host()).text();\n }\n\n /**\n * Whether the action refuses interaction.\n *\n * Every action follows the DIAL's `disabled` — there is no per-action flag — so\n * this is really \"is the dial disabled\", asked at the action. Worth having anyway:\n * it is what the DOM and the CDK consult before delivering a click.\n */\n async isDisabled(): Promise<boolean> {\n return (await this.host()).getProperty<boolean>('disabled');\n }\n\n /** Pick this action — emitting `pick` and closing the dial. */\n async click(): Promise<void> {\n if (await this.isDisabled()) {\n throw new Error(\n `WrSpeedDialActionHarness.click(): \"${await this.getLabel()}\" is disabled. The DOM swallows the click ` +\n 'and the component checks `disabled` again before emitting, so nothing would happen.'\n );\n }\n await (await this.host()).click();\n }\n\n /** Move keyboard focus to the action. */\n async focus(): Promise<void> {\n return (await this.host()).focus();\n }\n\n /** Whether the action currently holds focus. */\n async isFocused(): Promise<boolean> {\n return (await this.host()).isFocused();\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate, TestKey } from '@angular/cdk/testing';\n\nimport type { WrSpeedDialDirection } from 'ngwr/speed-dial';\n\nimport type { WrSpeedDialActionHarnessFilters, WrSpeedDialHarnessFilters } from './interfaces';\nimport { WrSpeedDialActionHarness } from './wr-speed-dial-action-harness';\n\nconst DIRECTIONS: readonly WrSpeedDialDirection[] = ['up', 'down', 'left', 'right'];\n\n/**\n * Test harness for `<wr-speed-dial>` — the floating trigger and the actions that fan\n * out of it.\n *\n * **The actions never leave the DOM.** They are hidden with `visibility`, which is\n * what actually takes them out of the tab order and the accessibility tree —\n * `opacity: 0` alone would leave them focusable — and CSS is exactly what a unit\n * test does not have. So a closed dial's buttons are queryable, clickable and\n * readable from a spec while being unreachable in a browser. {@link getActions} and\n * {@link pick} therefore REFUSE while the dial is closed rather than answering with\n * elements nobody can see; open it first, which is what a user has to do too.\n *\n * @example\n * ```ts\n * const dial = await loader.getHarness(WrSpeedDialHarness);\n *\n * await dial.open();\n * expect(await dial.getActionLabels()).toEqual(['Share', 'Copy link']);\n *\n * await dial.pick({ label: 'Share' });\n * expect(await dial.isOpen()).toBe(false);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrSpeedDialHarness extends ComponentHarness {\n static hostSelector = 'wr-speed-dial';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrSpeedDialHarnessFilters = {}): HarnessPredicate<WrSpeedDialHarness> {\n return new HarnessPredicate(WrSpeedDialHarness, options)\n .addOption('triggerLabel', options.triggerLabel, (harness, label) =>\n HarnessPredicate.stringMatches(harness.getTriggerLabel(), label)\n )\n .addOption(\n 'direction',\n options.direction,\n async (harness, direction) => (await harness.getDirection()) === direction\n )\n .addOption('open', options.open, async (harness, open) => (await harness.isOpen()) === open)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);\n }\n\n private readonly trigger = this.locatorFor('.wr-speed-dial__trigger');\n private readonly menu = this.locatorFor('.wr-speed-dial__actions');\n\n /** Whether the dial is fanned out, from the trigger's `aria-expanded`. */\n async isOpen(): Promise<boolean> {\n return (await (await this.trigger()).getAttribute('aria-expanded')) === 'true';\n }\n\n /** The direction the actions travel, from the host modifier. */\n async getDirection(): Promise<WrSpeedDialDirection> {\n const host = await this.host();\n for (const direction of DIRECTIONS) {\n if (await host.hasClass(`wr-speed-dial--${direction}`)) return direction;\n }\n throw new Error(\n 'WrSpeedDialHarness.getDirection(): this host carries no `wr-speed-dial--<direction>` class, which ' +\n 'every dial sets — the element matched is probably not an ngwr speed dial.'\n );\n }\n\n /**\n * The trigger's accessible name.\n *\n * The trigger is icon-only, so this is its ONLY name — there is no text to fall\n * back on, and a dial that loses it is announced as an unnamed button.\n */\n async getTriggerLabel(): Promise<string | null> {\n return (await this.trigger()).getAttribute('aria-label');\n }\n\n /** The trigger icon's registered name, from `wr-icon`'s reflected `data-icon`. */\n async getTriggerIcon(): Promise<string | null> {\n const icon = await this.locatorForOptional('.wr-speed-dial__trigger wr-icon')();\n return icon ? icon.getAttribute('data-icon') : null;\n }\n\n /** Whether the dial refuses interaction, from the trigger's own `disabled` property. */\n async isDisabled(): Promise<boolean> {\n return (await this.trigger()).getProperty<boolean>('disabled');\n }\n\n /** Whether the host is padded for the device's safe area (`safeArea`). */\n async hasSafeArea(): Promise<boolean> {\n return (await this.host()).hasClass('wr-speed-dial--safe-area');\n }\n\n /** The role the action list announces — `menu`. */\n async getMenuRole(): Promise<string | null> {\n return (await this.menu()).getAttribute('role');\n }\n\n /**\n * Whether the trigger's `aria-controls` names THIS dial's menu, and nothing else on\n * the page.\n *\n * Both halves matter. The id is resolved inside the host first, so a trigger\n * pointing at a SIBLING dial's menu — which resolves perfectly well from the\n * document — fails here. Then it is counted across the document, because two menus\n * answering to one id hand every reference to whichever comes first.\n */\n async isMenuBound(): Promise<boolean> {\n const id = await (await this.trigger()).getAttribute('aria-controls');\n if (!id) return false;\n\n const own = await this.locatorForOptional(`.wr-speed-dial__actions#${id}`)();\n if (!own) return false;\n\n const everywhere = await this.documentRootLocatorFactory().locatorForAll(`#${id}`)();\n return everywhere.length === 1;\n }\n\n /**\n * The actions, in DOM order.\n *\n * Throws while the dial is closed. The buttons are still in the DOM then — they\n * have to be, for the fan-out to animate — and jsdom applies no `visibility`, so\n * they would come back looking perfectly reachable when a real user cannot tab to\n * one. A spec that read them closed would pass on a dial that never opens.\n */\n async getActions(filters: WrSpeedDialActionHarnessFilters = {}): Promise<WrSpeedDialActionHarness[]> {\n await this.requireOpen('getActions');\n return this.locatorForAll(WrSpeedDialActionHarness.with(filters))();\n }\n\n /** The accessible names of the actions, in DOM order. Throws while closed. */\n async getActionLabels(): Promise<(string | null)[]> {\n const actions = await this.getActions();\n return Promise.all(actions.map(action => action.getLabel()));\n }\n\n /** How many actions the dial holds — readable while closed, since it is not a reach. */\n async getActionCount(): Promise<number> {\n return (await this.locatorForAll('.wr-speed-dial__action')()).length;\n }\n\n /** Open the dial. An already-open one is left alone; a disabled one throws. */\n async open(): Promise<void> {\n if (await this.isOpen()) return;\n await this.toggle();\n\n if (!(await this.isOpen())) {\n throw new Error(\n 'WrSpeedDialHarness.open(): the dial did not open. A disabled trigger refuses the click twice over — ' +\n 'the DOM swallows it, and the component checks `disabled` again.'\n );\n }\n }\n\n /** Close the dial. An already-closed one is left alone. */\n async close(): Promise<void> {\n if (!(await this.isOpen())) return;\n await this.toggle();\n }\n\n /** Click the trigger once, whichever way that takes it. */\n async toggle(): Promise<void> {\n return (await this.trigger()).click();\n }\n\n /**\n * Pick the first action matching the filters — emitting `pick` and closing the dial.\n *\n * Opens first if it has to, the way `WrDropdownHarness.clickItem` does: a spec that\n * says \"pick Share\" means the whole gesture. Throws when nothing matches, naming\n * what the dial does offer.\n */\n async pick(filters: WrSpeedDialActionHarnessFilters): Promise<void> {\n await this.open();\n\n const [action] = await this.getActions(filters);\n if (!action) {\n const offered = await this.getActionLabels();\n throw new Error(\n `WrSpeedDialHarness.pick(): no action matched ${JSON.stringify(filters)}. ` +\n `The dial offers: ${offered.join(', ')}.`\n );\n }\n await action.click();\n }\n\n /**\n * Press Escape — closing the dial and putting focus back on the trigger.\n *\n * Sent to the host, which is where the component listens. The focus return is the\n * half worth asserting: `role=\"menu\"` promises a way out, and the actions are\n * ordinary tab stops, so without it a keyboard user has to walk through every\n * action to leave.\n */\n async sendEscape(): Promise<void> {\n await (await this.host()).sendKeys(TestKey.ESCAPE);\n }\n\n /** Move keyboard focus to the trigger. */\n async focusTrigger(): Promise<void> {\n return (await this.trigger()).focus();\n }\n\n /** Whether the trigger currently holds focus. */\n async isTriggerFocused(): Promise<boolean> {\n return (await this.trigger()).isFocused();\n }\n\n private async requireOpen(method: string): Promise<void> {\n if (await this.isOpen()) return;\n throw new Error(\n `WrSpeedDialHarness.${method}(): the dial is closed. Its actions stay in the DOM while collapsed — the ` +\n 'fan-out animates them — and only `visibility` hides them, which a unit test does not apply. Reading ' +\n 'them now would assert buttons nobody can reach. Call open() first, or getActionCount().'\n );\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-speed-dial-testing.mjs","sources":["../../../projects/lib/speed-dial/testing/wr-speed-dial-action-harness.ts","../../../projects/lib/speed-dial/testing/wr-speed-dial-harness.ts","../../../projects/lib/speed-dial/testing/ngwr-speed-dial-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrSpeedDialActionHarnessFilters } from './interfaces';\n\n/**\n * Test harness for one action inside a {@link WrSpeedDialHarness}.\n *\n * The label is read from `aria-label` rather than from the button's text, and that\n * is the whole point of the control: an action renders an ICON, or — with no icon —\n * the first glyph of its label. Its visible text is therefore `\"S\"` for \"Share\", or\n * nothing at all. {@link getInitial} answers what is drawn; {@link getLabel} answers\n * what it is.\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrSpeedDialActionHarness extends ComponentHarness {\n /** The action button. */\n static hostSelector = '.wr-speed-dial__action';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrSpeedDialActionHarnessFilters = {}): HarnessPredicate<WrSpeedDialActionHarness> {\n return new HarnessPredicate(WrSpeedDialActionHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);\n }\n\n /** The action's accessible name — what a screen reader reads, and what `title` shows. */\n async getLabel(): Promise<string | null> {\n return (await this.host()).getAttribute('aria-label');\n }\n\n /** Whether the action draws an icon rather than a letter. */\n async hasIcon(): Promise<boolean> {\n return (await this.locatorForOptional('wr-icon')()) !== null;\n }\n\n /** The icon's registered name, from `wr-icon`'s reflected `data-icon`, or `null`. */\n async getIconName(): Promise<string | null> {\n const icon = await this.locatorForOptional('wr-icon')();\n return icon ? icon.getAttribute('data-icon') : null;\n }\n\n /**\n * The single glyph drawn in place of a missing icon, or `null` when there is one.\n *\n * A GLYPH, not a character: the component splits the label by code point, so an\n * emoji label draws the whole emoji rather than the broken half of a surrogate pair.\n */\n async getInitial(): Promise<string | null> {\n if (await this.hasIcon()) return null;\n return (await this.host()).text();\n }\n\n /**\n * Whether the action refuses interaction.\n *\n * Every action follows the DIAL's `disabled` — there is no per-action flag — so\n * this is really \"is the dial disabled\", asked at the action. Worth having anyway:\n * it is what the DOM and the CDK consult before delivering a click.\n */\n async isDisabled(): Promise<boolean> {\n return (await this.host()).getProperty<boolean>('disabled');\n }\n\n /** Pick this action — emitting `pick` and closing the dial. */\n async click(): Promise<void> {\n if (await this.isDisabled()) {\n throw new Error(\n `WrSpeedDialActionHarness.click(): \"${await this.getLabel()}\" is disabled. The DOM swallows the click ` +\n 'and the component checks `disabled` again before emitting, so nothing would happen.'\n );\n }\n await (await this.host()).click();\n }\n\n /** Move keyboard focus to the action. */\n async focus(): Promise<void> {\n return (await this.host()).focus();\n }\n\n /** Whether the action currently holds focus. */\n async isFocused(): Promise<boolean> {\n return (await this.host()).isFocused();\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate, TestKey } from '@angular/cdk/testing';\n\nimport type { WrSpeedDialDirection } from 'ngwr/speed-dial';\n\nimport type { WrSpeedDialActionHarnessFilters, WrSpeedDialHarnessFilters } from './interfaces';\nimport { WrSpeedDialActionHarness } from './wr-speed-dial-action-harness';\n\nconst DIRECTIONS: readonly WrSpeedDialDirection[] = ['up', 'down', 'left', 'right'];\n\n/**\n * Test harness for `<wr-speed-dial>` — the floating trigger and the actions that fan\n * out of it.\n *\n * **The actions never leave the DOM.** They are hidden with `visibility`, which is\n * what actually takes them out of the tab order and the accessibility tree —\n * `opacity: 0` alone would leave them focusable — and CSS is exactly what a unit\n * test does not have. So a closed dial's buttons are queryable, clickable and\n * readable from a spec while being unreachable in a browser. {@link getActions} and\n * {@link pick} therefore REFUSE while the dial is closed rather than answering with\n * elements nobody can see; open it first, which is what a user has to do too.\n *\n * @example\n * ```ts\n * const dial = await loader.getHarness(WrSpeedDialHarness);\n *\n * await dial.open();\n * expect(await dial.getActionLabels()).toEqual(['Share', 'Copy link']);\n *\n * await dial.pick({ label: 'Share' });\n * expect(await dial.isOpen()).toBe(false);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrSpeedDialHarness extends ComponentHarness {\n static hostSelector = 'wr-speed-dial';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrSpeedDialHarnessFilters = {}): HarnessPredicate<WrSpeedDialHarness> {\n return new HarnessPredicate(WrSpeedDialHarness, options)\n .addOption('triggerLabel', options.triggerLabel, (harness, label) =>\n HarnessPredicate.stringMatches(harness.getTriggerLabel(), label)\n )\n .addOption(\n 'direction',\n options.direction,\n async (harness, direction) => (await harness.getDirection()) === direction\n )\n .addOption('open', options.open, async (harness, open) => (await harness.isOpen()) === open)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);\n }\n\n private readonly trigger = this.locatorFor('.wr-speed-dial__trigger');\n private readonly menu = this.locatorFor('.wr-speed-dial__actions');\n\n /** Whether the dial is fanned out, from the trigger's `aria-expanded`. */\n async isOpen(): Promise<boolean> {\n return (await (await this.trigger()).getAttribute('aria-expanded')) === 'true';\n }\n\n /** The direction the actions travel, from the host modifier. */\n async getDirection(): Promise<WrSpeedDialDirection> {\n const host = await this.host();\n for (const direction of DIRECTIONS) {\n if (await host.hasClass(`wr-speed-dial--${direction}`)) return direction;\n }\n throw new Error(\n 'WrSpeedDialHarness.getDirection(): this host carries no `wr-speed-dial--<direction>` class, which ' +\n 'every dial sets — the element matched is probably not an ngwr speed dial.'\n );\n }\n\n /**\n * The trigger's accessible name.\n *\n * The trigger is icon-only, so this is its ONLY name — there is no text to fall\n * back on, and a dial that loses it is announced as an unnamed button.\n */\n async getTriggerLabel(): Promise<string | null> {\n return (await this.trigger()).getAttribute('aria-label');\n }\n\n /** The trigger icon's registered name, from `wr-icon`'s reflected `data-icon`. */\n async getTriggerIcon(): Promise<string | null> {\n const icon = await this.locatorForOptional('.wr-speed-dial__trigger wr-icon')();\n return icon ? icon.getAttribute('data-icon') : null;\n }\n\n /** Whether the dial refuses interaction, from the trigger's own `disabled` property. */\n async isDisabled(): Promise<boolean> {\n return (await this.trigger()).getProperty<boolean>('disabled');\n }\n\n /** Whether the host is padded for the device's safe area (`safeArea`). */\n async hasSafeArea(): Promise<boolean> {\n return (await this.host()).hasClass('wr-speed-dial--safe-area');\n }\n\n /**\n * The role the action list announces — `list`.\n *\n * Worth asking despite being a plain `<ul>`: the list is styled `list-style: none`,\n * which drops the implicit list semantics in Safari, so the role is written out. It\n * is deliberately NOT `menu` — that role promises arrows, Home/End and a single tab\n * stop, none of which this component has.\n */\n async getMenuRole(): Promise<string | null> {\n return (await this.menu()).getAttribute('role');\n }\n\n /**\n * Whether the trigger's `aria-controls` names THIS dial's action list, and nothing\n * else on the page.\n *\n * Both halves matter. The id is resolved inside the host first, so a trigger\n * pointing at a SIBLING dial's list — which resolves perfectly well from the\n * document — fails here. Then it is counted across the document, because two lists\n * answering to one id hand every reference to whichever comes first.\n */\n async isMenuBound(): Promise<boolean> {\n const id = await (await this.trigger()).getAttribute('aria-controls');\n if (!id) return false;\n\n const own = await this.locatorForOptional(`.wr-speed-dial__actions#${id}`)();\n if (!own) return false;\n\n const everywhere = await this.documentRootLocatorFactory().locatorForAll(`#${id}`)();\n return everywhere.length === 1;\n }\n\n /**\n * The actions, in DOM order.\n *\n * Throws while the dial is closed. The buttons are still in the DOM then — they\n * have to be, for the fan-out to animate — and jsdom applies no `visibility`, so\n * they would come back looking perfectly reachable when a real user cannot tab to\n * one. A spec that read them closed would pass on a dial that never opens.\n */\n async getActions(filters: WrSpeedDialActionHarnessFilters = {}): Promise<WrSpeedDialActionHarness[]> {\n await this.requireOpen('getActions');\n return this.locatorForAll(WrSpeedDialActionHarness.with(filters))();\n }\n\n /** The accessible names of the actions, in DOM order. Throws while closed. */\n async getActionLabels(): Promise<(string | null)[]> {\n const actions = await this.getActions();\n return Promise.all(actions.map(action => action.getLabel()));\n }\n\n /** How many actions the dial holds — readable while closed, since it is not a reach. */\n async getActionCount(): Promise<number> {\n return (await this.locatorForAll('.wr-speed-dial__action')()).length;\n }\n\n /** Open the dial. An already-open one is left alone; a disabled one throws. */\n async open(): Promise<void> {\n if (await this.isOpen()) return;\n await this.toggle();\n\n if (!(await this.isOpen())) {\n throw new Error(\n 'WrSpeedDialHarness.open(): the dial did not open. A disabled trigger refuses the click twice over — ' +\n 'the DOM swallows it, and the component checks `disabled` again.'\n );\n }\n }\n\n /** Close the dial. An already-closed one is left alone. */\n async close(): Promise<void> {\n if (!(await this.isOpen())) return;\n await this.toggle();\n }\n\n /** Click the trigger once, whichever way that takes it. */\n async toggle(): Promise<void> {\n return (await this.trigger()).click();\n }\n\n /**\n * Pick the first action matching the filters — emitting `pick` and closing the dial.\n *\n * Opens first if it has to, the way `WrDropdownHarness.clickItem` does: a spec that\n * says \"pick Share\" means the whole gesture. Throws when nothing matches, naming\n * what the dial does offer.\n */\n async pick(filters: WrSpeedDialActionHarnessFilters): Promise<void> {\n await this.open();\n\n const [action] = await this.getActions(filters);\n if (!action) {\n const offered = await this.getActionLabels();\n throw new Error(\n `WrSpeedDialHarness.pick(): no action matched ${JSON.stringify(filters)}. ` +\n `The dial offers: ${offered.join(', ')}.`\n );\n }\n await action.click();\n }\n\n /**\n * Press Escape — closing the dial and putting focus back on the trigger.\n *\n * Sent to the host, which is where the component listens. The focus return is the\n * half worth asserting: the actions are ordinary tab stops, so without it a keyboard\n * user has to walk through every action to leave.\n */\n async sendEscape(): Promise<void> {\n await (await this.host()).sendKeys(TestKey.ESCAPE);\n }\n\n /** Move keyboard focus to the trigger. */\n async focusTrigger(): Promise<void> {\n return (await this.trigger()).focus();\n }\n\n /** Whether the trigger currently holds focus. */\n async isTriggerFocused(): Promise<boolean> {\n return (await this.trigger()).isFocused();\n }\n\n private async requireOpen(method: string): Promise<void> {\n if (await this.isOpen()) return;\n throw new Error(\n `WrSpeedDialHarness.${method}(): the dial is closed. Its actions stay in the DOM while collapsed — the ` +\n 'fan-out animates them — and only `visibility` hides them, which a unit test does not apply. Reading ' +\n 'them now would assert buttons nobody can reach. Call open() first, or getActionCount().'\n );\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}