ngwr 14.3.1 → 14.5.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 (368) hide show
  1. package/README.md +4 -4
  2. package/avatar/styles/_index.scss +4 -2
  3. package/badge/styles/_index.scss +4 -1
  4. package/badge/styles/_tag.scss +3 -0
  5. package/cascader/styles/_rules.scss +3 -0
  6. package/command-palette/styles/_index.scss +23 -0
  7. package/context-menu/styles/_index.scss +13 -1
  8. package/fesm2022/ngwr-action-sheet.mjs +3 -3
  9. package/fesm2022/ngwr-affix.mjs +17 -4
  10. package/fesm2022/ngwr-affix.mjs.map +1 -1
  11. package/fesm2022/ngwr-alert.mjs +4 -4
  12. package/fesm2022/ngwr-alert.mjs.map +1 -1
  13. package/fesm2022/ngwr-anchor.mjs +3 -3
  14. package/fesm2022/ngwr-aurora.mjs +3 -3
  15. package/fesm2022/ngwr-avatar.mjs +3 -3
  16. package/fesm2022/ngwr-back-top.mjs +3 -3
  17. package/fesm2022/ngwr-badge.mjs +6 -6
  18. package/fesm2022/ngwr-bar-chart-testing.mjs +18 -2
  19. package/fesm2022/ngwr-bar-chart-testing.mjs.map +1 -1
  20. package/fesm2022/ngwr-bar-chart.mjs +3 -3
  21. package/fesm2022/ngwr-blur-text.mjs +3 -3
  22. package/fesm2022/ngwr-border-glow.mjs +3 -3
  23. package/fesm2022/ngwr-breadcrumbs.mjs +7 -7
  24. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  25. package/fesm2022/ngwr-burger.mjs +3 -3
  26. package/fesm2022/ngwr-button.mjs +6 -6
  27. package/fesm2022/ngwr-calendar-heatmap-testing.mjs +12 -9
  28. package/fesm2022/ngwr-calendar-heatmap-testing.mjs.map +1 -1
  29. package/fesm2022/ngwr-calendar-heatmap.mjs +35 -7
  30. package/fesm2022/ngwr-calendar-heatmap.mjs.map +1 -1
  31. package/fesm2022/ngwr-calendar.mjs +32 -4
  32. package/fesm2022/ngwr-calendar.mjs.map +1 -1
  33. package/fesm2022/ngwr-card.mjs +9 -9
  34. package/fesm2022/ngwr-carousel.mjs +6 -6
  35. package/fesm2022/ngwr-cascader-testing.mjs +87 -17
  36. package/fesm2022/ngwr-cascader-testing.mjs.map +1 -1
  37. package/fesm2022/ngwr-cascader.mjs +76 -5
  38. package/fesm2022/ngwr-cascader.mjs.map +1 -1
  39. package/fesm2022/ngwr-checkbox-testing.mjs +13 -2
  40. package/fesm2022/ngwr-checkbox-testing.mjs.map +1 -1
  41. package/fesm2022/ngwr-checkbox.mjs +42 -7
  42. package/fesm2022/ngwr-checkbox.mjs.map +1 -1
  43. package/fesm2022/ngwr-circular-text.mjs +3 -3
  44. package/fesm2022/ngwr-click-spark.mjs +3 -3
  45. package/fesm2022/ngwr-clipboard.mjs +3 -3
  46. package/fesm2022/ngwr-collapse.mjs +9 -9
  47. package/fesm2022/ngwr-color-picker.mjs +24 -8
  48. package/fesm2022/ngwr-color-picker.mjs.map +1 -1
  49. package/fesm2022/ngwr-command-palette.mjs +16 -3
  50. package/fesm2022/ngwr-command-palette.mjs.map +1 -1
  51. package/fesm2022/ngwr-compare.mjs +3 -3
  52. package/fesm2022/ngwr-confetti.mjs +3 -3
  53. package/fesm2022/ngwr-context-menu.mjs +47 -15
  54. package/fesm2022/ngwr-context-menu.mjs.map +1 -1
  55. package/fesm2022/ngwr-cookie.mjs +3 -3
  56. package/fesm2022/ngwr-counter.mjs +6 -6
  57. package/fesm2022/ngwr-date-adapters-fns.mjs +3 -3
  58. package/fesm2022/ngwr-date-adapters-luxon.mjs +3 -3
  59. package/fesm2022/ngwr-date-picker.mjs +111 -28
  60. package/fesm2022/ngwr-date-picker.mjs.map +1 -1
  61. package/fesm2022/ngwr-date.mjs +3 -3
  62. package/fesm2022/ngwr-decrypt-text.mjs +3 -3
  63. package/fesm2022/ngwr-density.mjs +6 -6
  64. package/fesm2022/ngwr-descriptions.mjs +6 -6
  65. package/fesm2022/ngwr-dialog.mjs +27 -16
  66. package/fesm2022/ngwr-dialog.mjs.map +1 -1
  67. package/fesm2022/ngwr-directives.mjs +12 -12
  68. package/fesm2022/ngwr-divider.mjs +3 -3
  69. package/fesm2022/ngwr-donut-chart-testing.mjs +18 -2
  70. package/fesm2022/ngwr-donut-chart-testing.mjs.map +1 -1
  71. package/fesm2022/ngwr-donut-chart.mjs +4 -4
  72. package/fesm2022/ngwr-donut-chart.mjs.map +1 -1
  73. package/fesm2022/ngwr-drag-drop.mjs +7 -7
  74. package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
  75. package/fesm2022/ngwr-drawer.mjs +66 -20
  76. package/fesm2022/ngwr-drawer.mjs.map +1 -1
  77. package/fesm2022/ngwr-dropdown.mjs +54 -16
  78. package/fesm2022/ngwr-dropdown.mjs.map +1 -1
  79. package/fesm2022/ngwr-empty.mjs +3 -3
  80. package/fesm2022/ngwr-event-calendar.mjs +6 -6
  81. package/fesm2022/ngwr-falling-text.mjs +3 -3
  82. package/fesm2022/ngwr-file-upload.mjs +18 -5
  83. package/fesm2022/ngwr-file-upload.mjs.map +1 -1
  84. package/fesm2022/ngwr-form.mjs +66 -14
  85. package/fesm2022/ngwr-form.mjs.map +1 -1
  86. package/fesm2022/ngwr-fuzzy-text.mjs +3 -3
  87. package/fesm2022/ngwr-gauge-testing.mjs +16 -2
  88. package/fesm2022/ngwr-gauge-testing.mjs.map +1 -1
  89. package/fesm2022/ngwr-gauge.mjs +3 -3
  90. package/fesm2022/ngwr-glitch-text.mjs +3 -3
  91. package/fesm2022/ngwr-gradient-text.mjs +3 -3
  92. package/fesm2022/ngwr-graph-testing.mjs +243 -0
  93. package/fesm2022/ngwr-graph-testing.mjs.map +1 -0
  94. package/fesm2022/ngwr-graph.mjs +1112 -0
  95. package/fesm2022/ngwr-graph.mjs.map +1 -0
  96. package/fesm2022/ngwr-hotkey.mjs +6 -6
  97. package/fesm2022/ngwr-i18n-ar.mjs +7 -0
  98. package/fesm2022/ngwr-i18n-ar.mjs.map +1 -1
  99. package/fesm2022/ngwr-i18n-cs.mjs +7 -0
  100. package/fesm2022/ngwr-i18n-cs.mjs.map +1 -1
  101. package/fesm2022/ngwr-i18n-de.mjs +7 -0
  102. package/fesm2022/ngwr-i18n-de.mjs.map +1 -1
  103. package/fesm2022/ngwr-i18n-en.mjs +23 -2
  104. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  105. package/fesm2022/ngwr-i18n-es.mjs +7 -0
  106. package/fesm2022/ngwr-i18n-es.mjs.map +1 -1
  107. package/fesm2022/ngwr-i18n-fr.mjs +7 -0
  108. package/fesm2022/ngwr-i18n-fr.mjs.map +1 -1
  109. package/fesm2022/ngwr-i18n-he.mjs +7 -0
  110. package/fesm2022/ngwr-i18n-he.mjs.map +1 -1
  111. package/fesm2022/ngwr-i18n-hi.mjs +7 -0
  112. package/fesm2022/ngwr-i18n-hi.mjs.map +1 -1
  113. package/fesm2022/ngwr-i18n-id.mjs +7 -0
  114. package/fesm2022/ngwr-i18n-id.mjs.map +1 -1
  115. package/fesm2022/ngwr-i18n-it.mjs +7 -0
  116. package/fesm2022/ngwr-i18n-it.mjs.map +1 -1
  117. package/fesm2022/ngwr-i18n-ja.mjs +7 -0
  118. package/fesm2022/ngwr-i18n-ja.mjs.map +1 -1
  119. package/fesm2022/ngwr-i18n-ko.mjs +7 -0
  120. package/fesm2022/ngwr-i18n-ko.mjs.map +1 -1
  121. package/fesm2022/ngwr-i18n-nl.mjs +7 -0
  122. package/fesm2022/ngwr-i18n-nl.mjs.map +1 -1
  123. package/fesm2022/ngwr-i18n-pl.mjs +7 -0
  124. package/fesm2022/ngwr-i18n-pl.mjs.map +1 -1
  125. package/fesm2022/ngwr-i18n-pt.mjs +7 -0
  126. package/fesm2022/ngwr-i18n-pt.mjs.map +1 -1
  127. package/fesm2022/ngwr-i18n-ru.mjs +7 -0
  128. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  129. package/fesm2022/ngwr-i18n-sv.mjs +7 -0
  130. package/fesm2022/ngwr-i18n-sv.mjs.map +1 -1
  131. package/fesm2022/ngwr-i18n-tr.mjs +7 -0
  132. package/fesm2022/ngwr-i18n-tr.mjs.map +1 -1
  133. package/fesm2022/ngwr-i18n-uk.mjs +7 -0
  134. package/fesm2022/ngwr-i18n-uk.mjs.map +1 -1
  135. package/fesm2022/ngwr-i18n-vi.mjs +7 -0
  136. package/fesm2022/ngwr-i18n-vi.mjs.map +1 -1
  137. package/fesm2022/ngwr-i18n-zh-TW.mjs +7 -0
  138. package/fesm2022/ngwr-i18n-zh-TW.mjs.map +1 -1
  139. package/fesm2022/ngwr-i18n-zh.mjs +7 -0
  140. package/fesm2022/ngwr-i18n-zh.mjs.map +1 -1
  141. package/fesm2022/ngwr-i18n.mjs +9 -9
  142. package/fesm2022/ngwr-icon.mjs +6 -6
  143. package/fesm2022/ngwr-image-cropper.mjs +3 -3
  144. package/fesm2022/ngwr-input-number.mjs +15 -6
  145. package/fesm2022/ngwr-input-number.mjs.map +1 -1
  146. package/fesm2022/ngwr-input-otp.mjs +38 -6
  147. package/fesm2022/ngwr-input-otp.mjs.map +1 -1
  148. package/fesm2022/ngwr-input.mjs +15 -15
  149. package/fesm2022/ngwr-keyboard.mjs +3 -3
  150. package/fesm2022/ngwr-knob-testing.mjs +11 -3
  151. package/fesm2022/ngwr-knob-testing.mjs.map +1 -1
  152. package/fesm2022/ngwr-knob.mjs +19 -4
  153. package/fesm2022/ngwr-knob.mjs.map +1 -1
  154. package/fesm2022/ngwr-layout.mjs +15 -15
  155. package/fesm2022/ngwr-lightbox.mjs +27 -6
  156. package/fesm2022/ngwr-lightbox.mjs.map +1 -1
  157. package/fesm2022/ngwr-line-chart-testing.mjs +18 -2
  158. package/fesm2022/ngwr-line-chart-testing.mjs.map +1 -1
  159. package/fesm2022/ngwr-line-chart.mjs +22 -4
  160. package/fesm2022/ngwr-line-chart.mjs.map +1 -1
  161. package/fesm2022/ngwr-list.mjs +7 -7
  162. package/fesm2022/ngwr-list.mjs.map +1 -1
  163. package/fesm2022/ngwr-loading-bar.mjs +6 -6
  164. package/fesm2022/ngwr-markdown.mjs +6 -6
  165. package/fesm2022/ngwr-marquee.mjs +3 -3
  166. package/fesm2022/ngwr-media.mjs +3 -3
  167. package/fesm2022/ngwr-mention.mjs +24 -8
  168. package/fesm2022/ngwr-mention.mjs.map +1 -1
  169. package/fesm2022/ngwr-meta.mjs +6 -6
  170. package/fesm2022/ngwr-meter-group.mjs +3 -3
  171. package/fesm2022/ngwr-overlay.mjs +125 -10
  172. package/fesm2022/ngwr-overlay.mjs.map +1 -1
  173. package/fesm2022/ngwr-page-header.mjs +3 -3
  174. package/fesm2022/ngwr-pagination.mjs +33 -6
  175. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  176. package/fesm2022/ngwr-pipes.mjs +21 -21
  177. package/fesm2022/ngwr-platform.mjs +9 -9
  178. package/fesm2022/ngwr-popconfirm.mjs +25 -9
  179. package/fesm2022/ngwr-popconfirm.mjs.map +1 -1
  180. package/fesm2022/ngwr-popover-testing.mjs +22 -5
  181. package/fesm2022/ngwr-popover-testing.mjs.map +1 -1
  182. package/fesm2022/ngwr-popover.mjs +243 -37
  183. package/fesm2022/ngwr-popover.mjs.map +1 -1
  184. package/fesm2022/ngwr-progress.mjs +3 -3
  185. package/fesm2022/ngwr-pull-to-refresh.mjs +3 -3
  186. package/fesm2022/ngwr-qr.mjs +3 -3
  187. package/fesm2022/ngwr-radio.mjs +6 -6
  188. package/fesm2022/ngwr-rating-testing.mjs +12 -3
  189. package/fesm2022/ngwr-rating-testing.mjs.map +1 -1
  190. package/fesm2022/ngwr-rating.mjs +19 -4
  191. package/fesm2022/ngwr-rating.mjs.map +1 -1
  192. package/fesm2022/ngwr-result.mjs +13 -13
  193. package/fesm2022/ngwr-result.mjs.map +1 -1
  194. package/fesm2022/ngwr-rotating-text.mjs +3 -3
  195. package/fesm2022/ngwr-schema-form.mjs +3 -3
  196. package/fesm2022/ngwr-scroll.mjs +3 -3
  197. package/fesm2022/ngwr-segmented.mjs +37 -4
  198. package/fesm2022/ngwr-segmented.mjs.map +1 -1
  199. package/fesm2022/ngwr-select.mjs +24 -10
  200. package/fesm2022/ngwr-select.mjs.map +1 -1
  201. package/fesm2022/ngwr-shiny-text.mjs +6 -6
  202. package/fesm2022/ngwr-sidebar.mjs +3 -3
  203. package/fesm2022/ngwr-skeleton.mjs +3 -3
  204. package/fesm2022/ngwr-slider.mjs +18 -5
  205. package/fesm2022/ngwr-slider.mjs.map +1 -1
  206. package/fesm2022/ngwr-sparkline.mjs +3 -3
  207. package/fesm2022/ngwr-speed-dial.mjs +3 -3
  208. package/fesm2022/ngwr-spinner.mjs +3 -3
  209. package/fesm2022/ngwr-splash-cursor.mjs +3 -3
  210. package/fesm2022/ngwr-split-text.mjs +3 -3
  211. package/fesm2022/ngwr-splitter.mjs +3 -3
  212. package/fesm2022/ngwr-spotlight-card.mjs +6 -6
  213. package/fesm2022/ngwr-squircle.mjs +28 -9
  214. package/fesm2022/ngwr-squircle.mjs.map +1 -1
  215. package/fesm2022/ngwr-star-border.mjs +3 -3
  216. package/fesm2022/ngwr-statistic.mjs +20 -13
  217. package/fesm2022/ngwr-statistic.mjs.map +1 -1
  218. package/fesm2022/ngwr-stepper.mjs +6 -6
  219. package/fesm2022/ngwr-storage.mjs +3 -3
  220. package/fesm2022/ngwr-switch.mjs +3 -3
  221. package/fesm2022/ngwr-table.mjs +18 -18
  222. package/fesm2022/ngwr-tabs-router.mjs +3 -3
  223. package/fesm2022/ngwr-tabs.mjs +6 -6
  224. package/fesm2022/ngwr-textarea.mjs +3 -3
  225. package/fesm2022/ngwr-theme.mjs +3 -3
  226. package/fesm2022/ngwr-tilt-card.mjs +6 -6
  227. package/fesm2022/ngwr-timeline.mjs +6 -6
  228. package/fesm2022/ngwr-toast.mjs +29 -12
  229. package/fesm2022/ngwr-toast.mjs.map +1 -1
  230. package/fesm2022/ngwr-toolbar.mjs +3 -3
  231. package/fesm2022/ngwr-tour.mjs +26 -12
  232. package/fesm2022/ngwr-tour.mjs.map +1 -1
  233. package/fesm2022/ngwr-transfer.mjs +88 -11
  234. package/fesm2022/ngwr-transfer.mjs.map +1 -1
  235. package/fesm2022/ngwr-tree-testing.mjs +24 -0
  236. package/fesm2022/ngwr-tree-testing.mjs.map +1 -1
  237. package/fesm2022/ngwr-tree.mjs +81 -9
  238. package/fesm2022/ngwr-tree.mjs.map +1 -1
  239. package/fesm2022/ngwr-typewriter.mjs +3 -3
  240. package/fesm2022/ngwr-typography.mjs +3 -3
  241. package/fesm2022/ngwr-version.mjs +1 -1
  242. package/fesm2022/ngwr-version.mjs.map +1 -1
  243. package/fesm2022/ngwr-virtual-scroll.mjs +3 -3
  244. package/fesm2022/ngwr-waves.mjs +3 -3
  245. package/fesm2022/ngwr-window.mjs +30 -13
  246. package/fesm2022/ngwr-window.mjs.map +1 -1
  247. package/form/styles/_index.scss +63 -0
  248. package/graph/styles/_index.scss +113 -0
  249. package/i18n/ar.json +9 -2
  250. package/i18n/cs.json +9 -2
  251. package/i18n/de.json +9 -2
  252. package/i18n/en.json +11 -4
  253. package/i18n/es.json +9 -2
  254. package/i18n/fr.json +9 -2
  255. package/i18n/he.json +9 -2
  256. package/i18n/hi.json +9 -2
  257. package/i18n/id.json +9 -2
  258. package/i18n/it.json +9 -2
  259. package/i18n/ja.json +9 -2
  260. package/i18n/ko.json +9 -2
  261. package/i18n/nl.json +9 -2
  262. package/i18n/pl.json +9 -2
  263. package/i18n/pt.json +9 -2
  264. package/i18n/ru.json +9 -2
  265. package/i18n/sv.json +9 -2
  266. package/i18n/tr.json +9 -2
  267. package/i18n/uk.json +9 -2
  268. package/i18n/vi.json +9 -2
  269. package/i18n/zh-TW.json +9 -2
  270. package/i18n/zh.json +9 -2
  271. package/input-otp/styles/_index.scss +5 -1
  272. package/line-chart/styles/_index.scss +5 -3
  273. package/llms-full.txt +13 -3
  274. package/llms.txt +8 -6
  275. package/markdown/styles/_index.scss +4 -1
  276. package/mention/styles/_index.scss +22 -1
  277. package/package.json +10 -1
  278. package/radio/styles/_index.scss +3 -0
  279. package/result/styles/_index.scss +12 -0
  280. package/schematics/migrations/v14/index.js +6 -3
  281. package/schematics/migrations.json +1 -1
  282. package/schematics/ng-add/index.js +34 -5
  283. package/schematics/use/symbol-map.json +2 -0
  284. package/segmented/styles/_index.scss +12 -1
  285. package/select/styles/_index.scss +20 -1
  286. package/skills/ngwr/SKILL.md +1 -1
  287. package/skills/ngwr/references/catalog.md +2 -0
  288. package/statistic/styles/_index.scss +4 -0
  289. package/stepper/styles/_index.scss +36 -0
  290. package/styles.scss +1 -0
  291. package/switch/styles/_index.scss +3 -0
  292. package/table/styles/_index.scss +12 -0
  293. package/tabs/styles/_index.scss +4 -1
  294. package/textarea/styles/_index.scss +6 -1
  295. package/theme/styles/_colors.scss +11 -0
  296. package/theme/styles/_contrast.scss +136 -0
  297. package/theme/styles/_focus.scss +7 -0
  298. package/theme/styles/_index.scss +1 -0
  299. package/theme/styles/_mixins.scss +15 -8
  300. package/timeline/styles/_index.scss +28 -1
  301. package/toast/styles/_index.scss +11 -0
  302. package/tree/styles/_index.scss +27 -1
  303. package/types/ngwr-badge.d.ts +1 -1
  304. package/types/ngwr-bar-chart-testing.d.ts +11 -1
  305. package/types/ngwr-button.d.ts +1 -1
  306. package/types/ngwr-calendar-heatmap-testing.d.ts +10 -5
  307. package/types/ngwr-calendar-heatmap.d.ts +22 -0
  308. package/types/ngwr-calendar.d.ts +21 -0
  309. package/types/ngwr-cascader-testing.d.ts +74 -17
  310. package/types/ngwr-cascader.d.ts +35 -0
  311. package/types/ngwr-checkbox-testing.d.ts +12 -1
  312. package/types/ngwr-checkbox.d.ts +10 -0
  313. package/types/ngwr-clipboard.d.ts +1 -1
  314. package/types/ngwr-color-picker.d.ts +8 -0
  315. package/types/ngwr-confetti.d.ts +1 -1
  316. package/types/ngwr-context-menu.d.ts +10 -0
  317. package/types/ngwr-cookie.d.ts +1 -1
  318. package/types/ngwr-date-adapters-fns.d.ts +1 -1
  319. package/types/ngwr-date-adapters-luxon.d.ts +1 -1
  320. package/types/ngwr-date-picker.d.ts +16 -0
  321. package/types/ngwr-date.d.ts +9 -3
  322. package/types/ngwr-density.d.ts +1 -1
  323. package/types/ngwr-dialog.d.ts +2 -1
  324. package/types/ngwr-donut-chart-testing.d.ts +11 -1
  325. package/types/ngwr-drawer.d.ts +29 -1
  326. package/types/ngwr-dropdown.d.ts +25 -0
  327. package/types/ngwr-file-upload.d.ts +11 -0
  328. package/types/ngwr-form.d.ts +25 -0
  329. package/types/ngwr-gauge-testing.d.ts +9 -1
  330. package/types/ngwr-graph-testing.d.ts +190 -0
  331. package/types/ngwr-graph.d.ts +203 -0
  332. package/types/ngwr-hotkey.d.ts +1 -1
  333. package/types/ngwr-i18n.d.ts +1 -1
  334. package/types/ngwr-input-number.d.ts +12 -2
  335. package/types/ngwr-input-otp.d.ts +12 -0
  336. package/types/ngwr-knob-testing.d.ts +11 -3
  337. package/types/ngwr-knob.d.ts +13 -0
  338. package/types/ngwr-lightbox.d.ts +15 -0
  339. package/types/ngwr-line-chart-testing.d.ts +11 -1
  340. package/types/ngwr-line-chart.d.ts +18 -0
  341. package/types/ngwr-loading-bar.d.ts +1 -1
  342. package/types/ngwr-markdown.d.ts +1 -1
  343. package/types/ngwr-media.d.ts +1 -1
  344. package/types/ngwr-mention.d.ts +7 -0
  345. package/types/ngwr-meta.d.ts +1 -1
  346. package/types/ngwr-overlay.d.ts +64 -6
  347. package/types/ngwr-pagination.d.ts +16 -0
  348. package/types/ngwr-platform.d.ts +3 -3
  349. package/types/ngwr-popconfirm.d.ts +1 -0
  350. package/types/ngwr-popover-testing.d.ts +22 -5
  351. package/types/ngwr-popover.d.ts +44 -8
  352. package/types/ngwr-rating-testing.d.ts +12 -3
  353. package/types/ngwr-rating.d.ts +13 -0
  354. package/types/ngwr-scroll.d.ts +1 -1
  355. package/types/ngwr-segmented.d.ts +16 -0
  356. package/types/ngwr-select.d.ts +7 -0
  357. package/types/ngwr-squircle.d.ts +22 -3
  358. package/types/ngwr-statistic.d.ts +0 -1
  359. package/types/ngwr-storage.d.ts +1 -1
  360. package/types/ngwr-theme.d.ts +1 -1
  361. package/types/ngwr-toast.d.ts +3 -1
  362. package/types/ngwr-tour.d.ts +7 -1
  363. package/types/ngwr-transfer.d.ts +52 -0
  364. package/types/ngwr-tree-testing.d.ts +20 -0
  365. package/types/ngwr-tree.d.ts +43 -1
  366. package/types/ngwr-version.d.ts +1 -1
  367. package/types/ngwr-window.d.ts +2 -1
  368. package/typography/styles/_index.scss +26 -4
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-tour.mjs","sources":["../../../projects/lib/tour/tour-popup.ts","../../../projects/lib/tour/tour-popup.html","../../../projects/lib/tour/tour.ts","../../../projects/lib/tour/ngwr-tour.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 { Component, InjectionToken, ViewEncapsulation, computed, inject } from '@angular/core';\n\nimport { WrButton } from 'ngwr/button';\nimport { readI18nText, useI18nFormatter } from 'ngwr/i18n';\n\nimport type { WrTourStep } from './interfaces';\n\n/** What the popup needs from the running tour, without importing the service (cycle). */\nexport interface WrTourController {\n readonly index: () => number;\n readonly total: () => number;\n readonly step: () => WrTourStep | null;\n /**\n * Whether this is the last step the user will actually see — the service looks\n * ahead for a reachable one, because a trailing step whose target is missing gets\n * skipped and the raw count would call the final card \"Next\".\n */\n readonly isLast: () => boolean;\n /**\n * Whether this is the first step the user will actually see — the service looks\n * BEHIND for a reachable one, for the mirror reason: a leading step whose target\n * is missing gets skipped, so the tour opens on a later index and the raw one\n * would put a Back button on the very first card.\n */\n readonly isFirst: () => boolean;\n next(): void;\n prev(): void;\n stop(): void;\n}\n\n/** Provided by `WrTour` when it attaches the popup portal. */\nexport const WR_TOUR_STEP = new InjectionToken<WrTourController>('WR_TOUR_STEP');\n\n/**\n * The card a tour shows next to the spotlit element. Rendered by `WrTour` into\n * a CDK overlay — not something you place yourself.\n *\n * @see https://ngwr.dev/reference/services/tour\n */\n@Component({\n selector: 'wr-tour-popup',\n templateUrl: './tour-popup.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-tour-popup', role: 'dialog', 'aria-modal': 'true', '[attr.aria-label]': 'ariaLabel()' },\n imports: [WrButton],\n})\nexport class WrTourPopup {\n protected readonly tour = inject(WR_TOUR_STEP);\n\n // `readI18nText`, not `useI18nText`: these have no consumer-facing input to\n // forward. Both helpers are reactive.\n protected readonly nextLabel = readI18nText('tour.next', 'Next');\n protected readonly backLabel = readI18nText('tour.back', 'Back');\n protected readonly doneLabel = readI18nText('tour.done', 'Done');\n protected readonly skipLabel = readI18nText('tour.skip', 'Skip tour');\n\n /** `Step {{current}} of {{total}}` — also the dialog's accessible name. */\n protected readonly progress = useI18nFormatter('tour.progress', 'Step {{current}} of {{total}}');\n\n // From the service, not `index() >= total() - 1`: a trailing step whose target is\n // missing gets skipped, so the raw count called the final card \"Next\" and then\n // ended the tour when it was pressed.\n protected readonly isLast = computed(() => this.tour.isLast());\n // Same story at the other end, and the same fix: `index() <= 0` rendered a Back\n // button on the first card of a tour whose leading step had been skipped, and\n // `prev()` walks off the start and returns — so the button did nothing at all.\n protected readonly isFirst = computed(() => this.tour.isFirst());\n\n protected readonly progressText = computed(() =>\n this.progress({ current: this.tour.index() + 1, total: this.tour.total() })\n );\n\n protected readonly ariaLabel = computed(() => {\n const title = this.tour.step()?.title;\n return title ? `${title} — ${this.progressText()}` : this.progressText();\n });\n}\n","@let step = tour.step();\n\n<p class=\"wr-tour-popup__progress\">{{ progressText() }}</p>\n\n@if (step?.title; as title) {\n <h2 class=\"wr-tour-popup__title\">{{ title }}</h2>\n}\n\n<p class=\"wr-tour-popup__content\">{{ step?.content }}</p>\n\n<div class=\"wr-tour-popup__actions\">\n <!-- The three buttons are told apart by a class rather than by their position in\n the row: the back button is conditional, so an index means one thing on the\n first step and another everywhere else — and the labels are translated. -->\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"medium\"\n outlined\n class=\"wr-tour-popup__action wr-tour-popup__action--skip\"\n (click)=\"tour.stop()\"\n >\n {{ skipLabel() }}\n </button>\n\n <span class=\"wr-tour-popup__spacer\"></span>\n\n @if (!isFirst()) {\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n outlined\n class=\"wr-tour-popup__action wr-tour-popup__action--back\"\n (click)=\"tour.prev()\"\n >\n {{ backLabel() }}\n </button>\n }\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n class=\"wr-tour-popup__action wr-tour-popup__action--next\"\n (click)=\"tour.next()\"\n >\n {{ isLast() ? doneLabel() : nextLabel() }}\n </button>\n</div>\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 { type ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';\nimport { Directionality } from '@angular/cdk/bidi';\nimport { type OverlayRef, ScrollStrategyOptions } from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n DOCUMENT,\n DestroyRef,\n EnvironmentInjector,\n Injector,\n PLATFORM_ID,\n Service,\n computed,\n inject,\n signal,\n} from '@angular/core';\n\nimport { WR_OVERLAY, wrMirrorOffsets } from 'ngwr/overlay';\n\nimport type { WrTourPlacement, WrTourStep } from './interfaces';\nimport { WR_TOUR_STEP, WrTourPopup } from './tour-popup';\n\n/** Fallback order per preferred side — the opposite side first, then the sides. */\nconst FALLBACKS: Record<WrTourPlacement, readonly WrTourPlacement[]> = {\n bottom: ['bottom', 'top', 'right', 'left'],\n top: ['top', 'bottom', 'right', 'left'],\n left: ['left', 'right', 'bottom', 'top'],\n right: ['right', 'left', 'bottom', 'top'],\n};\n\nconst CONNECTED: Record<\n WrTourPlacement,\n {\n originX: 'start' | 'center' | 'end';\n originY: 'top' | 'center' | 'bottom';\n overlayX: 'start' | 'center' | 'end';\n overlayY: 'top' | 'center' | 'bottom';\n }\n> = {\n bottom: { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top' },\n top: { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom' },\n left: { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center' },\n right: { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center' },\n};\n\n/**\n * Guided product tour: a spotlight over one element at a time plus a popup\n * explaining it.\n *\n * Steps are data, not markup — call `start()` with an array and the service owns\n * the overlay, the cut-out, focus and the keyboard. A step whose target is not\n * on the page is skipped rather than shown floating, so a tour survives a\n * feature being hidden behind a flag or a permission.\n *\n * @example\n * ```ts\n * private readonly tour = inject(WrTour);\n *\n * showMeAround(): void {\n * this.tour.start([\n * { target: '[data-tour=\"search\"]', title: 'Search', content: 'Find anything by name.' },\n * { target: '[data-tour=\"filters\"]', content: 'Narrow the list down.', placement: 'right' },\n * ]);\n * }\n * ```\n *\n * @see https://ngwr.dev/reference/services/tour\n */\n@Service()\nexport class WrTour {\n private readonly overlay = inject(WR_OVERLAY);\n private readonly dir = inject(Directionality, { optional: true });\n private readonly scrollStrategies = inject(ScrollStrategyOptions);\n private readonly injector = inject(EnvironmentInjector);\n private readonly focusTrapFactory = inject(ConfigurableFocusTrapFactory);\n private readonly document = inject(DOCUMENT);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private readonly steps = signal<readonly WrTourStep[]>([]);\n private readonly cursor = signal(-1);\n\n private overlayRef: OverlayRef | null = null;\n private focusTrap: ConfigurableFocusTrap | null = null;\n private spotlight: HTMLElement | null = null;\n private detachKeydown: (() => void) | null = null;\n private detachReposition: (() => void) | null = null;\n private restoreFocus: HTMLElement | null = null;\n\n /** Whether a tour is running. */\n readonly active = computed(() => this.cursor() >= 0);\n\n /** Zero-based index of the current step, `-1` when idle. */\n readonly index = computed(() => this.cursor());\n\n /** How many steps this tour has. */\n readonly total = computed(() => this.steps().length);\n\n /** The step being shown, or `null` when idle. */\n readonly step = computed<WrTourStep | null>(() => this.steps()[this.cursor()] ?? null);\n\n // Whether any step AFTER the current one can actually be shown. Settled once per\n // move, in `goTo`, because reachability is a DOM question and not a signal.\n private readonly noStepAfter = signal(false);\n\n // The same question looking the other way. Settled in `goTo` for the same reason.\n private readonly noStepBefore = signal(true);\n\n /**\n * Whether this is the last step the user will see. Not `index() === total() - 1`:\n * a trailing step whose target is missing is skipped, so comparing against the\n * raw count left the primary button reading \"Next\" on the final card and then\n * ending the tour when it was pressed.\n */\n readonly isLast = computed(() => this.noStepAfter());\n\n /**\n * Whether this is the first step the user will see. Not `index() === 0`, for the\n * mirror of the reason above: a LEADING step whose target is missing is skipped,\n * so the tour opens on a later index and the raw one put a Back button on the\n * first card — one that did nothing at all, because `prev()` walks off the start\n * and returns without moving.\n */\n readonly isFirst = computed(() => this.noStepBefore());\n\n constructor() {\n // Everything this service owns lives OUTSIDE its own injector: a CDK overlay, a\n // div appended to `document.body`, a document keydown listener and two window\n // listeners. Nothing removes them when the injector goes, so a tour running at\n // teardown left its cut-out on the page.\n inject(DestroyRef).onDestroy(() => this.stop());\n }\n\n /**\n * Begin a tour. Restarts if one is already running. A no-op under SSR — a\n * tour is a client-side affordance and there is nothing to spotlight on the\n * server.\n */\n start(steps: readonly WrTourStep[]): void {\n if (!this.isBrowser || steps.length === 0) return;\n this.stop();\n this.steps.set(steps);\n this.restoreFocus = this.document.activeElement as HTMLElement | null;\n this.goTo(0, 1);\n }\n\n /** Advance; finishes the tour past the last step. */\n next(): void {\n this.goTo(this.cursor() + 1, 1);\n }\n\n /** Go back; stays put on the first step. */\n prev(): void {\n this.goTo(this.cursor() - 1, -1);\n }\n\n /** End the tour and return focus where it was. */\n stop(): void {\n this.teardownStep();\n this.steps.set([]);\n this.cursor.set(-1);\n this.restoreFocus?.focus?.();\n this.restoreFocus = null;\n }\n\n /**\n * Move to `index`, skipping steps whose target is not in the DOM. `direction`\n * says which way to keep skipping, so a missing step never traps the tour\n * between two dead ends.\n */\n private goTo(index: number, direction: 1 | -1): void {\n const steps = this.steps();\n let i = index;\n while (i >= 0 && i < steps.length && this.resolve(steps[i]) === null) i += direction;\n\n if (i < 0) {\n // Ran off the start while going back — stay where we were.\n return;\n }\n if (i >= steps.length) {\n this.stop();\n return;\n }\n\n this.teardownStep();\n this.cursor.set(i);\n this.noStepAfter.set(!this.anyResolvableAfter(i));\n this.noStepBefore.set(!this.anyResolvableBefore(i));\n this.openStep(steps[i]);\n }\n\n /** Whether the tour has a step past `index` that can actually be shown. */\n private anyResolvableAfter(index: number): boolean {\n const steps = this.steps();\n for (let i = index + 1; i < steps.length; i += 1) {\n if (this.resolve(steps[i]) !== null) return true;\n }\n return false;\n }\n\n /** Whether the tour has a step before `index` that can actually be shown. */\n private anyResolvableBefore(index: number): boolean {\n const steps = this.steps();\n for (let i = index - 1; i >= 0; i -= 1) {\n if (this.resolve(steps[i]) !== null) return true;\n }\n return false;\n }\n\n private resolve(step: WrTourStep): HTMLElement | null {\n if (typeof step.target !== 'string') return step.target.isConnected ? step.target : null;\n return this.document.querySelector<HTMLElement>(step.target);\n }\n\n private openStep(step: WrTourStep): void {\n const target = this.resolve(step);\n if (!target) return;\n\n target.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'smooth' });\n\n // The cut-out is a bare element sized to the target with an enormous spread\n // shadow: everything outside it dims, the target itself stays untouched.\n // Cheaper and far more robust than a clip-path polygon, and it needs no\n // recompute when the page reflows beyond moving the box.\n const spotlight = this.document.createElement('div');\n spotlight.className = 'wr-tour__spotlight';\n spotlight.setAttribute('aria-hidden', 'true');\n this.document.body.appendChild(spotlight);\n this.spotlight = spotlight;\n this.placeSpotlight(target);\n\n const placement = step.placement ?? 'bottom';\n const positionStrategy = this.overlay\n .position()\n .flexibleConnectedTo(target)\n .withPositions(\n wrMirrorOffsets(\n FALLBACKS[placement].map(side => ({\n ...CONNECTED[side],\n offsetY: side === 'bottom' ? 12 : side === 'top' ? -12 : 0,\n offsetX: side === 'right' ? 12 : side === 'left' ? -12 : 0,\n })),\n this.isRtl()\n )\n )\n .withPush(true);\n\n this.overlayRef = this.overlay.create({\n positionStrategy,\n scrollStrategy: this.scrollStrategies.reposition(),\n panelClass: 'wr-tour-overlay',\n });\n\n const portal = new ComponentPortal(\n WrTourPopup,\n null,\n Injector.create({ parent: this.injector, providers: [{ provide: WR_TOUR_STEP, useValue: this }] })\n );\n const ref = this.overlayRef.attach(portal);\n\n // Kept on the instance, not in a local: a trap registers with CDK's\n // `FocusTrapManager` and installs a capture-phase document focus listener, so an\n // undestroyed one leaks per STEP and every tour left a stack of them behind.\n this.focusTrap = this.focusTrapFactory.create(ref.location.nativeElement as HTMLElement);\n void this.focusTrap.focusInitialElementWhenReady();\n\n const onKey = (event: KeyboardEvent): void => {\n // The listener is on the document, so a step shown over a dialog would see the\n // Escape that dialog just handled and close the tour too.\n if (event.key === 'Escape' && !event.defaultPrevented) {\n event.preventDefault();\n this.stop();\n }\n };\n this.document.addEventListener('keydown', onKey);\n this.detachKeydown = () => this.document.removeEventListener('keydown', onKey);\n\n const reposition = (): void => this.placeSpotlight(target);\n window.addEventListener('scroll', reposition, true);\n window.addEventListener('resize', reposition);\n this.detachReposition = () => {\n window.removeEventListener('scroll', reposition, true);\n window.removeEventListener('resize', reposition);\n };\n }\n\n private placeSpotlight(target: HTMLElement): void {\n const el = this.spotlight;\n if (!el) return;\n const r = target.getBoundingClientRect();\n const pad = 6;\n el.style.top = `${r.top - pad}px`;\n el.style.left = `${r.left - pad}px`;\n el.style.width = `${r.width + pad * 2}px`;\n el.style.height = `${r.height + pad * 2}px`;\n }\n\n private teardownStep(): void {\n this.focusTrap?.destroy();\n this.focusTrap = null;\n this.detachKeydown?.();\n this.detachKeydown = null;\n this.detachReposition?.();\n this.detachReposition = null;\n this.overlayRef?.dispose();\n this.overlayRef = null;\n this.spotlight?.remove();\n this.spotlight = null;\n }\n\n /**\n * Whether the app reads right-to-left.\n *\n * Optional inject: `Directionality` is root-provided, so this is never null in\n * an app — but a bare `TestBed` that provides nothing should still get a\n * component that works, and defaulting to LTR is the honest fallback.\n */\n private isRtl(): boolean {\n return this.dir?.value === 'rtl';\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-tour.mjs","sources":["../../../projects/lib/tour/tour-popup.ts","../../../projects/lib/tour/tour-popup.html","../../../projects/lib/tour/tour.ts","../../../projects/lib/tour/ngwr-tour.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 { Component, InjectionToken, ViewEncapsulation, computed, inject } from '@angular/core';\n\nimport { WrButton } from 'ngwr/button';\nimport { readI18nText, useI18nFormatter } from 'ngwr/i18n';\n\nimport type { WrTourStep } from './interfaces';\n\n/** What the popup needs from the running tour, without importing the service (cycle). */\nexport interface WrTourController {\n readonly index: () => number;\n readonly total: () => number;\n readonly step: () => WrTourStep | null;\n /**\n * Whether this is the last step the user will actually see — the service looks\n * ahead for a reachable one, because a trailing step whose target is missing gets\n * skipped and the raw count would call the final card \"Next\".\n */\n readonly isLast: () => boolean;\n /**\n * Whether this is the first step the user will actually see — the service looks\n * BEHIND for a reachable one, for the mirror reason: a leading step whose target\n * is missing gets skipped, so the tour opens on a later index and the raw one\n * would put a Back button on the very first card.\n */\n readonly isFirst: () => boolean;\n next(): void;\n prev(): void;\n stop(): void;\n}\n\n/** Provided by `WrTour` when it attaches the popup portal. */\nexport const WR_TOUR_STEP = new InjectionToken<WrTourController>('WR_TOUR_STEP');\n\n/**\n * The card a tour shows next to the spotlit element. Rendered by `WrTour` into\n * a CDK overlay — not something you place yourself.\n *\n * @see https://ngwr.dev/reference/services/tour\n */\n@Component({\n selector: 'wr-tour-popup',\n templateUrl: './tour-popup.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-tour-popup', role: 'dialog', 'aria-modal': 'true', '[attr.aria-label]': 'ariaLabel()' },\n imports: [WrButton],\n})\nexport class WrTourPopup {\n protected readonly tour = inject(WR_TOUR_STEP);\n\n // `readI18nText`, not `useI18nText`: these have no consumer-facing input to\n // forward. Both helpers are reactive.\n protected readonly nextLabel = readI18nText('tour.next', 'Next');\n protected readonly backLabel = readI18nText('tour.back', 'Back');\n protected readonly doneLabel = readI18nText('tour.done', 'Done');\n protected readonly skipLabel = readI18nText('tour.skip', 'Skip tour');\n\n /** `Step {{current}} of {{total}}` — also the dialog's accessible name. */\n protected readonly progress = useI18nFormatter('tour.progress', 'Step {{current}} of {{total}}');\n\n // From the service, not `index() >= total() - 1`: a trailing step whose target is\n // missing gets skipped, so the raw count called the final card \"Next\" and then\n // ended the tour when it was pressed.\n protected readonly isLast = computed(() => this.tour.isLast());\n // Same story at the other end, and the same fix: `index() <= 0` rendered a Back\n // button on the first card of a tour whose leading step had been skipped, and\n // `prev()` walks off the start and returns — so the button did nothing at all.\n protected readonly isFirst = computed(() => this.tour.isFirst());\n\n protected readonly progressText = computed(() =>\n this.progress({ current: this.tour.index() + 1, total: this.tour.total() })\n );\n\n protected readonly ariaLabel = computed(() => {\n const title = this.tour.step()?.title;\n return title ? `${title} — ${this.progressText()}` : this.progressText();\n });\n}\n","@let step = tour.step();\n\n<p class=\"wr-tour-popup__progress\">{{ progressText() }}</p>\n\n@if (step?.title; as title) {\n <h2 class=\"wr-tour-popup__title\">{{ title }}</h2>\n}\n\n<p class=\"wr-tour-popup__content\">{{ step?.content }}</p>\n\n<div class=\"wr-tour-popup__actions\">\n <!-- The three buttons are told apart by a class rather than by their position in\n the row: the back button is conditional, so an index means one thing on the\n first step and another everywhere else — and the labels are translated. -->\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"medium\"\n outlined\n class=\"wr-tour-popup__action wr-tour-popup__action--skip\"\n (click)=\"tour.stop()\"\n >\n {{ skipLabel() }}\n </button>\n\n <span class=\"wr-tour-popup__spacer\"></span>\n\n @if (!isFirst()) {\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n outlined\n class=\"wr-tour-popup__action wr-tour-popup__action--back\"\n (click)=\"tour.prev()\"\n >\n {{ backLabel() }}\n </button>\n }\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n class=\"wr-tour-popup__action wr-tour-popup__action--next\"\n (click)=\"tour.next()\"\n >\n {{ isLast() ? doneLabel() : nextLabel() }}\n </button>\n</div>\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 { type ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';\nimport { Directionality } from '@angular/cdk/bidi';\nimport { type ConnectedPosition, type OverlayRef, ScrollStrategyOptions } from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n DOCUMENT,\n DestroyRef,\n EnvironmentInjector,\n Injector,\n PLATFORM_ID,\n Service,\n computed,\n inject,\n signal,\n} from '@angular/core';\n\nimport { WR_OVERLAY, wrFollowDirection, wrMirrorOffsets } from 'ngwr/overlay';\n\nimport type { WrTourPlacement, WrTourStep } from './interfaces';\nimport { WR_TOUR_STEP, WrTourPopup } from './tour-popup';\n\n/** Fallback order per preferred side — the opposite side first, then the sides. */\nconst FALLBACKS: Record<WrTourPlacement, readonly WrTourPlacement[]> = {\n bottom: ['bottom', 'top', 'right', 'left'],\n top: ['top', 'bottom', 'right', 'left'],\n left: ['left', 'right', 'bottom', 'top'],\n right: ['right', 'left', 'bottom', 'top'],\n};\n\nconst CONNECTED: Record<\n WrTourPlacement,\n {\n originX: 'start' | 'center' | 'end';\n originY: 'top' | 'center' | 'bottom';\n overlayX: 'start' | 'center' | 'end';\n overlayY: 'top' | 'center' | 'bottom';\n }\n> = {\n bottom: { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top' },\n top: { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom' },\n left: { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center' },\n right: { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center' },\n};\n\n/**\n * Guided product tour: a spotlight over one element at a time plus a popup\n * explaining it.\n *\n * Steps are data, not markup — call `start()` with an array and the service owns\n * the overlay, the cut-out, focus and the keyboard. A step whose target is not\n * on the page is skipped rather than shown floating, so a tour survives a\n * feature being hidden behind a flag or a permission.\n *\n * @example\n * ```ts\n * private readonly tour = inject(WrTour);\n *\n * showMeAround(): void {\n * this.tour.start([\n * { target: '[data-tour=\"search\"]', title: 'Search', content: 'Find anything by name.' },\n * { target: '[data-tour=\"filters\"]', content: 'Narrow the list down.', placement: 'right' },\n * ]);\n * }\n * ```\n *\n * @see https://ngwr.dev/reference/services/tour\n */\n@Service()\nexport class WrTour {\n private readonly overlay = inject(WR_OVERLAY);\n private readonly dir = inject(Directionality, { optional: true });\n private readonly scrollStrategies = inject(ScrollStrategyOptions);\n private readonly injector = inject(EnvironmentInjector);\n private readonly focusTrapFactory = inject(ConfigurableFocusTrapFactory);\n private readonly document = inject(DOCUMENT);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private readonly steps = signal<readonly WrTourStep[]>([]);\n private readonly cursor = signal(-1);\n\n private overlayRef: OverlayRef | null = null;\n private focusTrap: ConfigurableFocusTrap | null = null;\n private spotlight: HTMLElement | null = null;\n private detachKeydown: (() => void) | null = null;\n private detachReposition: (() => void) | null = null;\n private restoreFocus: HTMLElement | null = null;\n\n /** Whether a tour is running. */\n readonly active = computed(() => this.cursor() >= 0);\n\n /** Zero-based index of the current step, `-1` when idle. */\n readonly index = computed(() => this.cursor());\n\n /** How many steps this tour has. */\n readonly total = computed(() => this.steps().length);\n\n /** The step being shown, or `null` when idle. */\n readonly step = computed<WrTourStep | null>(() => this.steps()[this.cursor()] ?? null);\n\n // Whether any step AFTER the current one can actually be shown. Settled once per\n // move, in `goTo`, because reachability is a DOM question and not a signal.\n private readonly noStepAfter = signal(false);\n\n // The same question looking the other way. Settled in `goTo` for the same reason.\n private readonly noStepBefore = signal(true);\n\n /**\n * Whether this is the last step the user will see. Not `index() === total() - 1`:\n * a trailing step whose target is missing is skipped, so comparing against the\n * raw count left the primary button reading \"Next\" on the final card and then\n * ending the tour when it was pressed.\n */\n readonly isLast = computed(() => this.noStepAfter());\n\n /**\n * Whether this is the first step the user will see. Not `index() === 0`, for the\n * mirror of the reason above: a LEADING step whose target is missing is skipped,\n * so the tour opens on a later index and the raw one put a Back button on the\n * first card — one that did nothing at all, because `prev()` walks off the start\n * and returns without moving.\n */\n readonly isFirst = computed(() => this.noStepBefore());\n\n constructor() {\n // Everything this service owns lives OUTSIDE its own injector: a CDK overlay, a\n // div appended to `document.body`, a document keydown listener and two window\n // listeners. Nothing removes them when the injector goes, so a tour running at\n // teardown left its cut-out on the page.\n inject(DestroyRef).onDestroy(() => this.stop());\n }\n\n /**\n * Begin a tour. Restarts if one is already running. A no-op under SSR — a\n * tour is a client-side affordance and there is nothing to spotlight on the\n * server.\n */\n start(steps: readonly WrTourStep[]): void {\n if (!this.isBrowser || steps.length === 0) return;\n this.stop();\n this.steps.set(steps);\n this.restoreFocus = this.document.activeElement as HTMLElement | null;\n this.goTo(0, 1);\n }\n\n /** Advance; finishes the tour past the last step. */\n next(): void {\n this.goTo(this.cursor() + 1, 1);\n }\n\n /** Go back; stays put on the first step. */\n prev(): void {\n this.goTo(this.cursor() - 1, -1);\n }\n\n /** End the tour and return focus where it was. */\n stop(): void {\n this.teardownStep();\n this.steps.set([]);\n this.cursor.set(-1);\n this.restoreFocus?.focus?.();\n this.restoreFocus = null;\n }\n\n /**\n * Move to `index`, skipping steps whose target is not in the DOM. `direction`\n * says which way to keep skipping, so a missing step never traps the tour\n * between two dead ends.\n */\n private goTo(index: number, direction: 1 | -1): void {\n const steps = this.steps();\n let i = index;\n while (i >= 0 && i < steps.length && this.resolve(steps[i]) === null) i += direction;\n\n if (i < 0) {\n // Ran off the start while going back — stay where we were.\n return;\n }\n if (i >= steps.length) {\n this.stop();\n return;\n }\n\n this.teardownStep();\n this.cursor.set(i);\n this.noStepAfter.set(!this.anyResolvableAfter(i));\n this.noStepBefore.set(!this.anyResolvableBefore(i));\n this.openStep(steps[i]);\n }\n\n /** Whether the tour has a step past `index` that can actually be shown. */\n private anyResolvableAfter(index: number): boolean {\n const steps = this.steps();\n for (let i = index + 1; i < steps.length; i += 1) {\n if (this.resolve(steps[i]) !== null) return true;\n }\n return false;\n }\n\n /** Whether the tour has a step before `index` that can actually be shown. */\n private anyResolvableBefore(index: number): boolean {\n const steps = this.steps();\n for (let i = index - 1; i >= 0; i -= 1) {\n if (this.resolve(steps[i]) !== null) return true;\n }\n return false;\n }\n\n private resolve(step: WrTourStep): HTMLElement | null {\n if (typeof step.target !== 'string') return step.target.isConnected ? step.target : null;\n return this.document.querySelector<HTMLElement>(step.target);\n }\n\n private openStep(step: WrTourStep): void {\n const target = this.resolve(step);\n if (!target) return;\n\n target.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'smooth' });\n\n // The cut-out is a bare element sized to the target with an enormous spread\n // shadow: everything outside it dims, the target itself stays untouched.\n // Cheaper and far more robust than a clip-path polygon, and it needs no\n // recompute when the page reflows beyond moving the box.\n const spotlight = this.document.createElement('div');\n spotlight.className = 'wr-tour__spotlight';\n spotlight.setAttribute('aria-hidden', 'true');\n this.document.body.appendChild(spotlight);\n this.spotlight = spotlight;\n this.placeSpotlight(target);\n\n const placement = step.placement ?? 'bottom';\n const positionStrategy = this.overlay\n .position()\n .flexibleConnectedTo(target)\n .withPositions(this.stepPositions(placement, this.isRtl()))\n .withPush(true);\n\n this.overlayRef = this.overlay.create({\n positionStrategy,\n scrollStrategy: this.scrollStrategies.reposition(),\n panelClass: 'wr-tour-overlay',\n });\n\n // The CDK read the direction once, just now. A tour is long-lived by nature\n // — a step can sit on screen while the user changes a language setting — and\n // without this the card keeps the direction the step opened in, with the\n // 12px gap that held it clear of the spotlight now pointing into it.\n // Per step, because every move disposes this ref and builds the next.\n wrFollowDirection(this.overlayRef, this.dir, this.injector, direction =>\n positionStrategy.withPositions(this.stepPositions(placement, direction === 'rtl'))\n );\n\n const portal = new ComponentPortal(\n WrTourPopup,\n null,\n Injector.create({ parent: this.injector, providers: [{ provide: WR_TOUR_STEP, useValue: this }] })\n );\n const ref = this.overlayRef.attach(portal);\n\n // Kept on the instance, not in a local: a trap registers with CDK's\n // `FocusTrapManager` and installs a capture-phase document focus listener, so an\n // undestroyed one leaks per STEP and every tour left a stack of them behind.\n this.focusTrap = this.focusTrapFactory.create(ref.location.nativeElement as HTMLElement);\n void this.focusTrap.focusInitialElementWhenReady();\n\n const onKey = (event: KeyboardEvent): void => {\n // The listener is on the document, so a step shown over a dialog would see the\n // Escape that dialog just handled and close the tour too.\n if (event.key === 'Escape' && !event.defaultPrevented) {\n event.preventDefault();\n this.stop();\n }\n };\n this.document.addEventListener('keydown', onKey);\n this.detachKeydown = () => this.document.removeEventListener('keydown', onKey);\n\n const reposition = (): void => this.placeSpotlight(target);\n window.addEventListener('scroll', reposition, true);\n window.addEventListener('resize', reposition);\n this.detachReposition = () => {\n window.removeEventListener('scroll', reposition, true);\n window.removeEventListener('resize', reposition);\n };\n }\n\n /**\n * The card's fallback table for one placement, with its inline offsets already\n * mirrored. Built per call rather than per step: the direction can change while\n * the step is on screen, and a table is what has to be rebuilt when it does.\n */\n private stepPositions(placement: WrTourPlacement, isRtl: boolean): ConnectedPosition[] {\n return wrMirrorOffsets(\n FALLBACKS[placement].map(side => ({\n ...CONNECTED[side],\n offsetY: side === 'bottom' ? 12 : side === 'top' ? -12 : 0,\n offsetX: side === 'right' ? 12 : side === 'left' ? -12 : 0,\n })),\n isRtl\n );\n }\n\n private placeSpotlight(target: HTMLElement): void {\n const el = this.spotlight;\n if (!el) return;\n const r = target.getBoundingClientRect();\n const pad = 6;\n el.style.top = `${r.top - pad}px`;\n el.style.left = `${r.left - pad}px`;\n el.style.width = `${r.width + pad * 2}px`;\n el.style.height = `${r.height + pad * 2}px`;\n }\n\n private teardownStep(): void {\n this.focusTrap?.destroy();\n this.focusTrap = null;\n this.detachKeydown?.();\n this.detachKeydown = null;\n this.detachReposition?.();\n this.detachReposition = null;\n this.overlayRef?.dispose();\n this.overlayRef = null;\n this.spotlight?.remove();\n this.spotlight = null;\n }\n\n /**\n * Whether the app reads right-to-left.\n *\n * Optional inject: `Directionality` is root-provided, so this is never null in\n * an app — but a bare `TestBed` that provides nothing should still get a\n * component that works, and defaulting to LTR is the honest fallback.\n */\n private isRtl(): boolean {\n return this.dir?.value === 'rtl';\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;;;;;AAAA;;;;;AAKG;AAgCH;AACO,MAAM,YAAY,GAAG,IAAI,cAAc,CAAmB,cAAc,CAAC;AAEhF;;;;;AAKG;MAQU,WAAW,CAAA;AACH,IAAA,IAAI,GAAG,MAAM,CAAC,YAAY,CAAC;;;AAI3B,IAAA,SAAS,GAAG,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC;AAC7C,IAAA,SAAS,GAAG,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC;AAC7C,IAAA,SAAS,GAAG,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC;AAC7C,IAAA,SAAS,GAAG,YAAY,CAAC,WAAW,EAAE,WAAW,CAAC;;AAGlD,IAAA,QAAQ,GAAG,gBAAgB,CAAC,eAAe,EAAE,+BAA+B,CAAC;;;;IAK7E,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;+EAAC;;;;IAI3C,OAAO,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;gFAAC;AAE7C,IAAA,YAAY,GAAG,QAAQ,CAAC,MACzC,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;qFAC5E;AAEkB,IAAA,SAAS,GAAG,QAAQ,CAAC,MAAK;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,KAAK;AACrC,QAAA,OAAO,KAAK,GAAG,CAAA,EAAG,KAAK,MAAM,IAAI,CAAC,YAAY,EAAE,CAAA,CAAE,GAAG,IAAI,CAAC,YAAY,EAAE;IAC1E,CAAC;kFAAC;uGA7BS,WAAW,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAX,WAAW,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,eAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,MAAA,EAAA,QAAA,EAAA,YAAA,EAAA,MAAA,EAAA,EAAA,UAAA,EAAA,EAAA,iBAAA,EAAA,aAAA,EAAA,EAAA,cAAA,EAAA,eAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECrDxB,6zCAmDA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EDAY,QAAQ,EAAA,QAAA,EAAA,mCAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,EAAA,cAAA,EAAA,UAAA,EAAA,UAAA,EAAA,OAAA,EAAA,SAAA,EAAA,qBAAA,CAAA,EAAA,CAAA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA;;2FAEP,WAAW,EAAA,UAAA,EAAA,CAAA;kBAPvB,SAAS;+BACE,eAAe,EAAA,aAAA,EAEV,iBAAiB,CAAC,IAAI,EAAA,IAAA,EAC/B,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,mBAAmB,EAAE,aAAa,EAAE,EAAA,OAAA,EACjG,CAAC,QAAQ,CAAC,EAAA,QAAA,EAAA,6zCAAA,EAAA;;;AEnDrB;;;;;AAKG;AAwBH;AACA,MAAM,SAAS,GAAwD;IACrE,MAAM,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC;IAC1C,GAAG,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC;IACvC,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC;IACxC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC;CAC1C;AAED,MAAM,SAAS,GAQX;AACF,IAAA,MAAM,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE;AACrF,IAAA,GAAG,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE;AAClF,IAAA,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE;AAClF,IAAA,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE;CACpF;AAED;;;;;;;;;;;;;;;;;;;;;;AAsBG;MAEU,MAAM,CAAA;AACA,IAAA,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC;IAC5B,GAAG,GAAG,MAAM,CAAC,cAAc,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAChD,IAAA,gBAAgB,GAAG,MAAM,CAAC,qBAAqB,CAAC;AAChD,IAAA,QAAQ,GAAG,MAAM,CAAC,mBAAmB,CAAC;AACtC,IAAA,gBAAgB,GAAG,MAAM,CAAC,4BAA4B,CAAC;AACvD,IAAA,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC3B,SAAS,GAAG,iBAAiB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAElD,KAAK,GAAG,MAAM,CAAwB,EAAE;8EAAC;AACzC,IAAA,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC;+EAAC;IAE5B,UAAU,GAAsB,IAAI;IACpC,SAAS,GAAiC,IAAI;IAC9C,SAAS,GAAuB,IAAI;IACpC,aAAa,GAAwB,IAAI;IACzC,gBAAgB,GAAwB,IAAI;IAC5C,YAAY,GAAuB,IAAI;;IAGtC,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;+EAAC;;IAG3C,KAAK,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE;8EAAC;;IAGrC,KAAK,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,MAAM;8EAAC;;AAG3C,IAAA,IAAI,GAAG,QAAQ,CAAoB,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,IAAI;6EAAC;;;IAIrE,WAAW,GAAG,MAAM,CAAC,KAAK;oFAAC;;IAG3B,YAAY,GAAG,MAAM,CAAC,IAAI;qFAAC;AAE5C;;;;;AAKG;IACM,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE;+EAAC;AAEpD;;;;;;AAMG;IACM,OAAO,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,YAAY,EAAE;gFAAC;AAEtD,IAAA,WAAA,GAAA;;;;;AAKE,QAAA,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;IACjD;AAEA;;;;AAIG;AACH,IAAA,KAAK,CAAC,KAA4B,EAAA;QAChC,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE;QAC3C,IAAI,CAAC,IAAI,EAAE;AACX,QAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;QACrB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,aAAmC;AACrE,QAAA,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IACjB;;IAGA,IAAI,GAAA;AACF,QAAA,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IACjC;;IAGA,IAAI,GAAA;AACF,QAAA,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAClC;;IAGA,IAAI,GAAA;QACF,IAAI,CAAC,YAAY,EAAE;AACnB,QAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAClB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACnB,QAAA,IAAI,CAAC,YAAY,EAAE,KAAK,IAAI;AAC5B,QAAA,IAAI,CAAC,YAAY,GAAG,IAAI;IAC1B;AAEA;;;;AAIG;IACK,IAAI,CAAC,KAAa,EAAE,SAAiB,EAAA;AAC3C,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE;QAC1B,IAAI,CAAC,GAAG,KAAK;QACb,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;YAAE,CAAC,IAAI,SAAS;AAEpF,QAAA,IAAI,CAAC,GAAG,CAAC,EAAE;;YAET;QACF;AACA,QAAA,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE;YACrB,IAAI,CAAC,IAAI,EAAE;YACX;QACF;QAEA,IAAI,CAAC,YAAY,EAAE;AACnB,QAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AAClB,QAAA,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;AACjD,QAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;QACnD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB;;AAGQ,IAAA,kBAAkB,CAAC,KAAa,EAAA;AACtC,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE;AAC1B,QAAA,KAAK,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE;YAChD,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;AAAE,gBAAA,OAAO,IAAI;QAClD;AACA,QAAA,OAAO,KAAK;IACd;;AAGQ,IAAA,mBAAmB,CAAC,KAAa,EAAA;AACvC,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE;AAC1B,QAAA,KAAK,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE;YACtC,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;AAAE,gBAAA,OAAO,IAAI;QAClD;AACA,QAAA,OAAO,KAAK;IACd;AAEQ,IAAA,OAAO,CAAC,IAAgB,EAAA;AAC9B,QAAA,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ;AAAE,YAAA,OAAO,IAAI,CAAC,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI;QACxF,OAAO,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAc,IAAI,CAAC,MAAM,CAAC;IAC9D;AAEQ,IAAA,QAAQ,CAAC,IAAgB,EAAA;QAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;AACjC,QAAA,IAAI,CAAC,MAAM;YAAE;AAEb,QAAA,MAAM,CAAC,cAAc,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;;;;;QAMjF,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC;AACpD,QAAA,SAAS,CAAC,SAAS,GAAG,oBAAoB;AAC1C,QAAA,SAAS,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC;QAC7C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC;AACzC,QAAA,IAAI,CAAC,SAAS,GAAG,SAAS;AAC1B,QAAA,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC;AAE3B,QAAA,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,QAAQ;AAC5C,QAAA,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAC3B,aAAA,QAAQ;aACR,mBAAmB,CAAC,MAAM;AAC1B,aAAA,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;aACzD,QAAQ,CAAC,IAAI,CAAC;QAEjB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;YACpC,gBAAgB;AAChB,YAAA,cAAc,EAAE,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE;AAClD,YAAA,UAAU,EAAE,iBAAiB;AAC9B,SAAA,CAAC;;;;;;AAOF,QAAA,iBAAiB,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,IACnE,gBAAgB,CAAC,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,SAAS,KAAK,KAAK,CAAC,CAAC,CACnF;AAED,QAAA,MAAM,MAAM,GAAG,IAAI,eAAe,CAChC,WAAW,EACX,IAAI,EACJ,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CACnG;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC;;;;AAK1C,QAAA,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,aAA4B,CAAC;AACxF,QAAA,KAAK,IAAI,CAAC,SAAS,CAAC,4BAA4B,EAAE;AAElD,QAAA,MAAM,KAAK,GAAG,CAAC,KAAoB,KAAU;;;YAG3C,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE;gBACrD,KAAK,CAAC,cAAc,EAAE;gBACtB,IAAI,CAAC,IAAI,EAAE;YACb;AACF,QAAA,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC;AAChD,QAAA,IAAI,CAAC,aAAa,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,KAAK,CAAC;QAE9E,MAAM,UAAU,GAAG,MAAY,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC;QAC1D,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,UAAU,EAAE,IAAI,CAAC;AACnD,QAAA,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,UAAU,CAAC;AAC7C,QAAA,IAAI,CAAC,gBAAgB,GAAG,MAAK;YAC3B,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,UAAU,EAAE,IAAI,CAAC;AACtD,YAAA,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,UAAU,CAAC;AAClD,QAAA,CAAC;IACH;AAEA;;;;AAIG;IACK,aAAa,CAAC,SAA0B,EAAE,KAAc,EAAA;AAC9D,QAAA,OAAO,eAAe,CACpB,SAAS,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK;YAChC,GAAG,SAAS,CAAC,IAAI,CAAC;YAClB,OAAO,EAAE,IAAI,KAAK,QAAQ,GAAG,EAAE,GAAG,IAAI,KAAK,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC;YAC1D,OAAO,EAAE,IAAI,KAAK,OAAO,GAAG,EAAE,GAAG,IAAI,KAAK,MAAM,GAAG,CAAC,EAAE,GAAG,CAAC;AAC3D,SAAA,CAAC,CAAC,EACH,KAAK,CACN;IACH;AAEQ,IAAA,cAAc,CAAC,MAAmB,EAAA;AACxC,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS;AACzB,QAAA,IAAI,CAAC,EAAE;YAAE;AACT,QAAA,MAAM,CAAC,GAAG,MAAM,CAAC,qBAAqB,EAAE;QACxC,MAAM,GAAG,GAAG,CAAC;AACb,QAAA,EAAE,CAAC,KAAK,CAAC,GAAG,GAAG,CAAA,EAAG,CAAC,CAAC,GAAG,GAAG,GAAG,CAAA,EAAA,CAAI;AACjC,QAAA,EAAE,CAAC,KAAK,CAAC,IAAI,GAAG,CAAA,EAAG,CAAC,CAAC,IAAI,GAAG,GAAG,CAAA,EAAA,CAAI;AACnC,QAAA,EAAE,CAAC,KAAK,CAAC,KAAK,GAAG,CAAA,EAAG,CAAC,CAAC,KAAK,GAAG,GAAG,GAAG,CAAC,IAAI;AACzC,QAAA,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAA,EAAG,CAAC,CAAC,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI;IAC7C;IAEQ,YAAY,GAAA;AAClB,QAAA,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE;AACzB,QAAA,IAAI,CAAC,SAAS,GAAG,IAAI;AACrB,QAAA,IAAI,CAAC,aAAa,IAAI;AACtB,QAAA,IAAI,CAAC,aAAa,GAAG,IAAI;AACzB,QAAA,IAAI,CAAC,gBAAgB,IAAI;AACzB,QAAA,IAAI,CAAC,gBAAgB,GAAG,IAAI;AAC5B,QAAA,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE;AAC1B,QAAA,IAAI,CAAC,UAAU,GAAG,IAAI;AACtB,QAAA,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE;AACxB,QAAA,IAAI,CAAC,SAAS,GAAG,IAAI;IACvB;AAEA;;;;;;AAMG;IACK,KAAK,GAAA;AACX,QAAA,OAAO,IAAI,CAAC,GAAG,EAAE,KAAK,KAAK,KAAK;IAClC;uGAzQW,MAAM,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,OAAA,EAAA,CAAA;wGAAN,MAAM,EAAA,CAAA;;2FAAN,MAAM,EAAA,UAAA,EAAA,CAAA;kBADlB;;;AC3ED;;AAEG;;;;"}
@@ -1,6 +1,6 @@
1
1
  import { coerceBooleanProperty } from '@angular/cdk/coercion';
2
2
  import * as i0 from '@angular/core';
3
- import { input, model, output, signal, computed, ViewEncapsulation, Component } from '@angular/core';
3
+ import { input, model, output, signal, computed, isDevMode, inject, ElementRef, Injector, afterNextRender, ViewEncapsulation, Component } from '@angular/core';
4
4
  import { WrButton } from 'ngwr/button';
5
5
  import { WrCheckbox } from 'ngwr/checkbox';
6
6
  import { useFormFieldAria, WR_FORM_FIELD } from 'ngwr/form';
@@ -102,6 +102,17 @@ class WrTransfer {
102
102
  resolvedSearch = useI18nText(this.searchPlaceholder, 'transfer.search', 'Search');
103
103
  resolvedEmpty = useI18nText(this.emptyText, 'transfer.empty', 'Nothing here');
104
104
  resolvedSelectAll = useI18nText(this.selectAllLabel, 'transfer.selectAll', 'Select all');
105
+ /**
106
+ * The select-all checkbox's accessible name, composed IN THE CATALOG.
107
+ *
108
+ * It used to be a template literal — `` `${title} — ${selectAll}` `` — which
109
+ * froze both the separator and the operand order into English. A translator
110
+ * could change each half and not the sentence they make, which is the exact
111
+ * defect the calendar and event-calendar catalogs were rewritten to remove.
112
+ * The key ships as `{{pane}} — {{action}}` in every locale, so nothing reads
113
+ * differently today and every locale can now reorder it without a code change.
114
+ */
115
+ selectAllAria = useI18nFormatter('transfer.selectAllAria', '{{pane}} — {{action}}');
105
116
  resolvedToTarget = useI18nText(this.toTargetLabel, 'transfer.toTarget', 'Move to selected');
106
117
  resolvedToSource = useI18nText(this.toSourceLabel, 'transfer.toSource', 'Move to available');
107
118
  /** `{{checked}} / {{total}}` under each heading. @internal */
@@ -129,13 +140,44 @@ class WrTransfer {
129
140
  ...(ngDevMode ? [{ debugName: "valueSet" }] : /* istanbul ignore next */ []));
130
141
  source = computed(() => this.pane(this.items().filter(item => !this.valueSet().has(item.value)), this.sourceQuery(), this.sourceChecked()), /* @ts-ignore */
131
142
  ...(ngDevMode ? [{ debugName: "source" }] : /* istanbul ignore next */ []));
132
- target = computed(() => this.pane(
133
- // Right-pane order follows `value`, not `items`: the order a user built is
134
- // the one they expect to read back.
135
- this.selected()
136
- .map(v => this.items().find(item => item.value === v))
137
- .filter((item) => item !== undefined), this.targetQuery(), this.targetChecked()), /* @ts-ignore */
143
+ target = computed(() => {
144
+ // Right-pane order follows `value`, not `items`: the order a user built is
145
+ // the one they expect to read back.
146
+ const rows = this.selected()
147
+ .map(v => this.items().find(item => item.value === v))
148
+ .filter((item) => item !== undefined);
149
+ if (isDevMode() && rows.length !== this.selected().length)
150
+ this.warnAboutOrphans();
151
+ return this.pane(rows, this.targetQuery(), this.targetChecked());
152
+ }, /* @ts-ignore */
138
153
  ...(ngDevMode ? [{ debugName: "target" }] : /* istanbul ignore next */ []));
154
+ /**
155
+ * Say something when `value` holds a value no `items` entry matches.
156
+ *
157
+ * Such a value is in NEITHER pane — the left one filters out anything already
158
+ * selected and the right one can only render rows it found — and the header
159
+ * counts the rows it shows, so nothing on screen contradicts anything. The
160
+ * result is a value the user can neither see nor remove, and no sign that it
161
+ * is there.
162
+ *
163
+ * A warning rather than an invented row, which matches how the library treats
164
+ * the same question elsewhere: `wr-select` renders a value outside its option
165
+ * list only under an explicit `freeText`, so an unmatched value is a mode a
166
+ * consumer opts into, not a shape a component guesses at. It also stays quiet
167
+ * about the common transient case — `items` arriving after `value` — because
168
+ * the message names the values and a developer can see them settle.
169
+ */
170
+ warnAboutOrphans() {
171
+ const known = new Set(this.items().map(item => item.value));
172
+ const orphans = this.selected().filter(v => !known.has(v));
173
+ if (orphans.length === 0)
174
+ return;
175
+ // eslint-disable-next-line no-console -- dev-mode validation
176
+ console.warn(`[NGWR] <wr-transfer>: ${orphans.length} value(s) in \`value\` match no \`items\` entry ` +
177
+ `and are rendered in neither pane, so a user can neither see nor remove them: ` +
178
+ `${orphans.map(v => JSON.stringify(v)).join(', ')}. ` +
179
+ `If \`items\` loads after \`value\`, this settles on its own.`);
180
+ }
139
181
  canMoveRight = computed(() => !this.disabled() && !this.readonly() && this.source().checkedCount > 0, /* @ts-ignore */
140
182
  ...(ngDevMode ? [{ debugName: "canMoveRight" }] : /* istanbul ignore next */ []));
141
183
  canMoveLeft = computed(() => !this.disabled() && !this.readonly() && this.target().checkedCount > 0, /* @ts-ignore */
@@ -183,6 +225,39 @@ class WrTransfer {
183
225
  const query = event.target?.value ?? '';
184
226
  (pane === 'source' ? this.sourceQuery : this.targetQuery).set(query);
185
227
  }
228
+ host = inject(ElementRef);
229
+ injector = inject(Injector);
230
+ /**
231
+ * Keep focus in the widget after a move.
232
+ *
233
+ * A move empties the pane's staging set, which is exactly what disables the
234
+ * button that was just pressed — so the element holding DOM focus went
235
+ * `disabled` as a RESULT of its own activation and the browser dropped focus
236
+ * to `<body>`. From there the next Tab restarts at the top of the document,
237
+ * which for a keyboard user is being thrown out of a control they were in the
238
+ * middle of using.
239
+ *
240
+ * The landing is the FIRST ROW OF THE PANE THE ROWS ARRIVED IN, and the
241
+ * obvious alternative does not work: after a move BOTH buttons are disabled,
242
+ * because each needs something ticked and the move cleared the tick on one
243
+ * side and arrived unticked on the other. The rows that just moved are where
244
+ * the user's attention is and where the next action lives, so that is where
245
+ * focus goes.
246
+ *
247
+ * `afterNextRender`, not `queueMicrotask`: under zoneless CD the microtask
248
+ * runs before the moved rows are in the DOM, so the focus call would land on
249
+ * the pane as it was before the move.
250
+ */
251
+ restoreFocusAfterMove(landing) {
252
+ afterNextRender(() => {
253
+ const root = this.host.nativeElement;
254
+ const pane = landing === 'target'
255
+ ? root.querySelector('.wr-transfer__pane--target')
256
+ : root.querySelector('.wr-transfer__pane:not(.wr-transfer__pane--target)');
257
+ const row = pane?.querySelector('.wr-transfer__item input:not([disabled])');
258
+ row?.focus();
259
+ }, { injector: this.injector });
260
+ }
186
261
  moveRight() {
187
262
  if (this.disabled() || this.readonly())
188
263
  return;
@@ -196,6 +271,7 @@ class WrTransfer {
196
271
  this.value.set([...this.selected(), ...moving]);
197
272
  this.sourceChecked.set([]);
198
273
  this.touch.emit();
274
+ this.restoreFocusAfterMove('target');
199
275
  }
200
276
  moveLeft() {
201
277
  if (this.disabled() || this.readonly())
@@ -206,6 +282,7 @@ class WrTransfer {
206
282
  this.value.set(this.selected().filter(v => !moving.has(v)));
207
283
  this.targetChecked.set([]);
208
284
  this.touch.emit();
285
+ this.restoreFocusAfterMove('source');
209
286
  }
210
287
  /** Filter, then derive the header state from what survived. */
211
288
  pane(rows, query, checked) {
@@ -223,10 +300,10 @@ class WrTransfer {
223
300
  someChecked: shown.length > 0 && shown.length < enabled.length,
224
301
  };
225
302
  }
226
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: WrTransfer, deps: [], target: i0.ɵɵFactoryTarget.Component });
227
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: WrTransfer, isStandalone: true, selector: "wr-transfer", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, sourceTitle: { classPropertyName: "sourceTitle", publicName: "sourceTitle", isSignal: true, isRequired: false, transformFunction: null }, targetTitle: { classPropertyName: "targetTitle", publicName: "targetTitle", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, selectAllLabel: { classPropertyName: "selectAllLabel", publicName: "selectAllLabel", isSignal: true, isRequired: false, transformFunction: null }, toTargetLabel: { classPropertyName: "toTargetLabel", publicName: "toTargetLabel", isSignal: true, isRequired: false, transformFunction: null }, toSourceLabel: { classPropertyName: "toSourceLabel", publicName: "toSourceLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { attributes: { "role": "group" }, properties: { "class": "classes()", "attr.aria-invalid": "fieldAria.ariaInvalid()", "attr.aria-describedby": "fieldAria.describedBy()" } }, providers: [{ provide: WR_FORM_FIELD, useValue: null }], ngImport: i0, template: "<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid \u2014 an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"`${title} \u2014 ${resolvedSelectAll()}`\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n size=\"sm\"\n type=\"search\"\n [placeholder]=\"resolvedSearch()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"`${title} \u2014 ${resolvedSearch()}`\"\n (input)=\"onSearch(pane, $event)\"\n />\n </div>\n }\n\n <ul class=\"wr-transfer__list\" [attr.aria-label]=\"title\">\n @for (item of state.rows; track item.value) {\n <li class=\"wr-transfer__item\" [class.wr-transfer__item--disabled]=\"item.disabled\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"isChecked(pane, item)\"\n [disabled]=\"disabled() || !!item.disabled\"\n [readonly]=\"readonly()\"\n (checkedChange)=\"toggle(pane, item, $event)\"\n (touch)=\"touch.emit()\"\n >\n {{ item.label }}\n </wr-checkbox>\n </li>\n } @empty {\n <li class=\"wr-transfer__empty\">{{ resolvedEmpty() }}</li>\n }\n </ul>\n </div>\n\n <!-- The buttons live between the panes, so they are emitted after the first. -->\n @if (pane === 'source') {\n <div class=\"wr-transfer__actions\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-target\"\n [disabled]=\"!canMoveRight()\"\n [attr.aria-label]=\"resolvedToTarget()\"\n (click)=\"moveRight()\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-source\"\n [disabled]=\"!canMoveLeft()\"\n [attr.aria-label]=\"resolvedToSource()\"\n (click)=\"moveLeft()\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n </div>\n }\n}\n", dependencies: [{ kind: "component", type: WrButton, selector: "wr-btn, button[wr-btn], a[wr-btn]", inputs: ["color", "size", "shape", "icon", "iconPosition", "disabled", "outlined", "block", "loading", "disabledWhenLoading"] }, { kind: "component", type: WrCheckbox, selector: "wr-checkbox", inputs: ["id", "checkboxValue", "ariaLabel", "checked", "disabled", "readonly", "size", "icon", "indeterminate"], outputs: ["checkedChange", "touch"] }, { kind: "directive", type: WrInput, selector: "input[wrInput], textarea[wrInput]", inputs: ["size", "rounded"] }], encapsulation: i0.ViewEncapsulation.None });
303
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrTransfer, deps: [], target: i0.ɵɵFactoryTarget.Component });
304
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: WrTransfer, isStandalone: true, selector: "wr-transfer", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, sourceTitle: { classPropertyName: "sourceTitle", publicName: "sourceTitle", isSignal: true, isRequired: false, transformFunction: null }, targetTitle: { classPropertyName: "targetTitle", publicName: "targetTitle", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, selectAllLabel: { classPropertyName: "selectAllLabel", publicName: "selectAllLabel", isSignal: true, isRequired: false, transformFunction: null }, toTargetLabel: { classPropertyName: "toTargetLabel", publicName: "toTargetLabel", isSignal: true, isRequired: false, transformFunction: null }, toSourceLabel: { classPropertyName: "toSourceLabel", publicName: "toSourceLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { attributes: { "role": "group" }, properties: { "class": "classes()", "attr.aria-invalid": "fieldAria.ariaInvalid()", "attr.aria-describedby": "fieldAria.describedBy()" } }, providers: [{ provide: WR_FORM_FIELD, useValue: null }], ngImport: i0, template: "<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid \u2014 an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"selectAllAria({ action: resolvedSelectAll(), pane: title })\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n size=\"sm\"\n type=\"search\"\n [placeholder]=\"resolvedSearch()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"`${title} \u2014 ${resolvedSearch()}`\"\n (input)=\"onSearch(pane, $event)\"\n />\n </div>\n }\n\n <ul class=\"wr-transfer__list\" [attr.aria-label]=\"title\">\n @for (item of state.rows; track item.value) {\n <li class=\"wr-transfer__item\" [class.wr-transfer__item--disabled]=\"item.disabled\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"isChecked(pane, item)\"\n [disabled]=\"disabled() || !!item.disabled\"\n [readonly]=\"readonly()\"\n (checkedChange)=\"toggle(pane, item, $event)\"\n (touch)=\"touch.emit()\"\n >\n {{ item.label }}\n </wr-checkbox>\n </li>\n } @empty {\n <li class=\"wr-transfer__empty\">{{ resolvedEmpty() }}</li>\n }\n </ul>\n </div>\n\n <!-- The buttons live between the panes, so they are emitted after the first. -->\n @if (pane === 'source') {\n <div class=\"wr-transfer__actions\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-target\"\n [disabled]=\"!canMoveRight()\"\n [attr.aria-label]=\"resolvedToTarget()\"\n (click)=\"moveRight()\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-source\"\n [disabled]=\"!canMoveLeft()\"\n [attr.aria-label]=\"resolvedToSource()\"\n (click)=\"moveLeft()\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n </div>\n }\n}\n", dependencies: [{ kind: "component", type: WrButton, selector: "wr-btn, button[wr-btn], a[wr-btn]", inputs: ["color", "size", "shape", "icon", "iconPosition", "disabled", "outlined", "block", "loading", "disabledWhenLoading"] }, { kind: "component", type: WrCheckbox, selector: "wr-checkbox", inputs: ["id", "checkboxValue", "ariaLabel", "checked", "disabled", "readonly", "size", "icon", "indeterminate"], outputs: ["checkedChange", "touch"] }, { kind: "directive", type: WrInput, selector: "input[wrInput], textarea[wrInput]", inputs: ["size", "rounded"] }], encapsulation: i0.ViewEncapsulation.None });
228
305
  }
229
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: WrTransfer, decorators: [{
306
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrTransfer, decorators: [{
230
307
  type: Component,
231
308
  args: [{ selector: 'wr-transfer', encapsulation: ViewEncapsulation.None, host: {
232
309
  '[class]': 'classes()',
@@ -236,7 +313,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
236
313
  role: 'group',
237
314
  '[attr.aria-invalid]': 'fieldAria.ariaInvalid()',
238
315
  '[attr.aria-describedby]': 'fieldAria.describedBy()',
239
- }, providers: [{ provide: WR_FORM_FIELD, useValue: null }], imports: [WrButton, WrCheckbox, WrInput], template: "<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid \u2014 an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"`${title} \u2014 ${resolvedSelectAll()}`\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n size=\"sm\"\n type=\"search\"\n [placeholder]=\"resolvedSearch()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"`${title} \u2014 ${resolvedSearch()}`\"\n (input)=\"onSearch(pane, $event)\"\n />\n </div>\n }\n\n <ul class=\"wr-transfer__list\" [attr.aria-label]=\"title\">\n @for (item of state.rows; track item.value) {\n <li class=\"wr-transfer__item\" [class.wr-transfer__item--disabled]=\"item.disabled\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"isChecked(pane, item)\"\n [disabled]=\"disabled() || !!item.disabled\"\n [readonly]=\"readonly()\"\n (checkedChange)=\"toggle(pane, item, $event)\"\n (touch)=\"touch.emit()\"\n >\n {{ item.label }}\n </wr-checkbox>\n </li>\n } @empty {\n <li class=\"wr-transfer__empty\">{{ resolvedEmpty() }}</li>\n }\n </ul>\n </div>\n\n <!-- The buttons live between the panes, so they are emitted after the first. -->\n @if (pane === 'source') {\n <div class=\"wr-transfer__actions\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-target\"\n [disabled]=\"!canMoveRight()\"\n [attr.aria-label]=\"resolvedToTarget()\"\n (click)=\"moveRight()\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-source\"\n [disabled]=\"!canMoveLeft()\"\n [attr.aria-label]=\"resolvedToSource()\"\n (click)=\"moveLeft()\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n </div>\n }\n}\n" }]
316
+ }, providers: [{ provide: WR_FORM_FIELD, useValue: null }], imports: [WrButton, WrCheckbox, WrInput], template: "<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid \u2014 an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"selectAllAria({ action: resolvedSelectAll(), pane: title })\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n size=\"sm\"\n type=\"search\"\n [placeholder]=\"resolvedSearch()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"`${title} \u2014 ${resolvedSearch()}`\"\n (input)=\"onSearch(pane, $event)\"\n />\n </div>\n }\n\n <ul class=\"wr-transfer__list\" [attr.aria-label]=\"title\">\n @for (item of state.rows; track item.value) {\n <li class=\"wr-transfer__item\" [class.wr-transfer__item--disabled]=\"item.disabled\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"isChecked(pane, item)\"\n [disabled]=\"disabled() || !!item.disabled\"\n [readonly]=\"readonly()\"\n (checkedChange)=\"toggle(pane, item, $event)\"\n (touch)=\"touch.emit()\"\n >\n {{ item.label }}\n </wr-checkbox>\n </li>\n } @empty {\n <li class=\"wr-transfer__empty\">{{ resolvedEmpty() }}</li>\n }\n </ul>\n </div>\n\n <!-- The buttons live between the panes, so they are emitted after the first. -->\n @if (pane === 'source') {\n <div class=\"wr-transfer__actions\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-target\"\n [disabled]=\"!canMoveRight()\"\n [attr.aria-label]=\"resolvedToTarget()\"\n (click)=\"moveRight()\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-source\"\n [disabled]=\"!canMoveLeft()\"\n [attr.aria-label]=\"resolvedToSource()\"\n (click)=\"moveLeft()\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n </div>\n }\n}\n" }]
240
317
  }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], sourceTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceTitle", required: false }] }], targetTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "targetTitle", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], selectAllLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectAllLabel", required: false }] }], toTargetLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "toTargetLabel", required: false }] }], toSourceLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "toSourceLabel", required: false }] }] } });
241
318
 
242
319
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-transfer.mjs","sources":["../../../projects/lib/transfer/transfer.ts","../../../projects/lib/transfer/transfer.html","../../../projects/lib/transfer/ngwr-transfer.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 { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, computed, input, model, output, signal } from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { WrButton } from 'ngwr/button';\nimport { WrCheckbox } from 'ngwr/checkbox';\nimport { WR_FORM_FIELD, useFormFieldAria } from 'ngwr/form';\nimport { useI18nFormatter, useI18nText } from 'ngwr/i18n';\nimport { WrInput } from 'ngwr/input';\n\nimport type { WrTransferItem } from './interfaces';\n\n/** A pane's rows plus the header state derived from them. */\ninterface PaneState {\n readonly rows: readonly WrTransferItem[];\n /**\n * The staged values this pane is actually SHOWING — visible and enabled. The\n * staging signals can hold more than that (a filter change hides rows without\n * unstaging them), and every consumer of the staged set has to agree on which\n * one it means: the count, the header checkbox and the move all read this.\n */\n readonly checked: readonly unknown[];\n readonly checkedCount: number;\n readonly enabledCount: number;\n readonly allChecked: boolean;\n readonly someChecked: boolean;\n}\n\n/**\n * Dual-listbox picker: two panes and the buttons that move rows between them.\n *\n * The full item set is `[items]` and the RIGHT pane is the value — `[(value)]`\n * holds the chosen `WrTransferItem['value']`s, everything else stays left. The\n * checked state inside each pane is transient staging, deliberately not part of\n * the value: what a form cares about is which rows ended up on the right.\n *\n * A signal-forms native control (`FormValueControl<readonly unknown[]>`), so\n * `[formField]` binds straight to `value`; `[(ngModel)]` and reactive forms keep\n * working through Angular's bridge, and `[(value)]` is the standalone shape.\n *\n * @example\n * ```html\n * <!-- Standalone -->\n * <wr-transfer [items]=\"permissions\" [(value)]=\"granted\" />\n *\n * <!-- Searchable, with pane titles -->\n * <wr-transfer\n * searchable\n * sourceTitle=\"Available\"\n * targetTitle=\"Granted\"\n * [items]=\"permissions\"\n * [formField]=\"form.granted\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/transfer\n */\n@Component({\n selector: 'wr-transfer',\n templateUrl: './transfer.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n // `role=\"group\"` so the error state below has somewhere legible to live: the\n // control is a composite with no single element carrying its semantics, and\n // `aria-invalid` on a bare `generic` host is announced by nothing.\n role: 'group',\n '[attr.aria-invalid]': 'fieldAria.ariaInvalid()',\n '[attr.aria-describedby]': 'fieldAria.describedBy()',\n },\n // The transfer is the control a `<wr-form-field>` wraps; the checkboxes and\n // the search inputs in its own template are parts, not controls. Without this\n // shield each of them found the field's token and announced the one error as\n // its own — which is why `fieldAria` below reads the field with `skipSelf`.\n providers: [{ provide: WR_FORM_FIELD, useValue: null }],\n imports: [WrButton, WrCheckbox, WrInput],\n})\nexport class WrTransfer implements FormValueControl<readonly unknown[]> {\n /** Every row, in either pane. Membership of the right pane is `value`. */\n readonly items = input<readonly WrTransferItem[]>([]);\n\n /**\n * Values currently in the RIGHT pane. Two-way bindable; bound automatically\n * by `[formField]` / `[(ngModel)]`.\n */\n readonly value = model<readonly unknown[]>([]);\n\n /** Emitted on blur / commit so a bound field marks itself touched. */\n readonly touch = output<void>();\n\n /** Disable the whole control. @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Refuse changes to the value while both panes stay focusable and readable.\n * Bound automatically from the field's readonly state when used with\n * `[formField]`.\n *\n * The move buttons go inert and the row checkboxes go read-only; SEARCH keeps\n * working, because filtering a pane changes what is shown and not what is\n * chosen. No `aria-readonly` on the host: role `group` does not support the\n * state, so each row's checkbox mirrors its own instead.\n *\n * @default false\n */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /** The surrounding `<wr-form-field>`'s error state. @internal */\n protected readonly fieldAria = useFormFieldAria({ skipSelf: true });\n\n /** Show a filter box above each pane. @default false */\n readonly searchable = input(false, { transform: coerceBooleanProperty });\n\n /** Heading above the left pane. Falls back to `transfer.source`. */\n readonly sourceTitle = input<string | null>(null);\n\n /** Heading above the right pane. Falls back to `transfer.target`. */\n readonly targetTitle = input<string | null>(null);\n\n /** Placeholder in both filter boxes. Falls back to `transfer.search`. */\n readonly searchPlaceholder = input<string | null>(null);\n\n /** Shown in a pane with no rows. Falls back to `transfer.empty`. */\n readonly emptyText = input<string | null>(null);\n\n /**\n * Accessible name of each pane's select-all checkbox, composed with the pane\n * heading. Falls back to `transfer.selectAll`.\n */\n readonly selectAllLabel = input<string | null>(null);\n\n /** Accessible name of the move-right button. Falls back to `transfer.toTarget`. */\n readonly toTargetLabel = input<string | null>(null);\n\n /** Accessible name of the move-left button. Falls back to `transfer.toSource`. */\n readonly toSourceLabel = input<string | null>(null);\n\n protected readonly resolvedSourceTitle = useI18nText(this.sourceTitle, 'transfer.source', 'Available');\n protected readonly resolvedTargetTitle = useI18nText(this.targetTitle, 'transfer.target', 'Selected');\n protected readonly resolvedSearch = useI18nText(this.searchPlaceholder, 'transfer.search', 'Search');\n protected readonly resolvedEmpty = useI18nText(this.emptyText, 'transfer.empty', 'Nothing here');\n protected readonly resolvedSelectAll = useI18nText(this.selectAllLabel, 'transfer.selectAll', 'Select all');\n protected readonly resolvedToTarget = useI18nText(this.toTargetLabel, 'transfer.toTarget', 'Move to selected');\n protected readonly resolvedToSource = useI18nText(this.toSourceLabel, 'transfer.toSource', 'Move to available');\n\n /** `{{checked}} / {{total}}` under each heading. @internal */\n protected readonly countLabel = useI18nFormatter('transfer.count', '{{checked}} / {{total}}');\n\n /** Pane order — the template iterates it so both panes share one block. */\n protected readonly panes = ['source', 'target'] as const;\n\n private readonly sourceQuery = signal('');\n private readonly targetQuery = signal('');\n\n /** Staged rows, per pane. Cleared as soon as a move commits them. */\n private readonly sourceChecked = signal<readonly unknown[]>([]);\n private readonly targetChecked = signal<readonly unknown[]>([]);\n\n // The value as a guaranteed array — a classic-forms binding can write null even\n // though the type is `readonly unknown[]`, so normalise every read. Same hazard\n // `wr-checkbox-group` guards; here an unguarded `.map` threw outright.\n private readonly selected = computed<readonly unknown[]>(() => {\n const v = this.value();\n return Array.isArray(v) ? v : [];\n });\n\n private readonly valueSet = computed(() => new Set(this.selected()));\n\n protected readonly source = computed<PaneState>(() =>\n this.pane(\n this.items().filter(item => !this.valueSet().has(item.value)),\n this.sourceQuery(),\n this.sourceChecked()\n )\n );\n\n protected readonly target = computed<PaneState>(() =>\n this.pane(\n // Right-pane order follows `value`, not `items`: the order a user built is\n // the one they expect to read back.\n this.selected()\n .map(v => this.items().find(item => item.value === v))\n .filter((item): item is WrTransferItem => item !== undefined),\n this.targetQuery(),\n this.targetChecked()\n )\n );\n\n protected readonly canMoveRight = computed(\n () => !this.disabled() && !this.readonly() && this.source().checkedCount > 0\n );\n protected readonly canMoveLeft = computed(\n () => !this.disabled() && !this.readonly() && this.target().checkedCount > 0\n );\n\n protected readonly classes = computed(() => {\n const parts = ['wr-transfer'];\n if (this.disabled()) parts.push('wr-transfer--disabled');\n else if (this.readonly()) parts.push('wr-transfer--readonly');\n if (this.searchable()) parts.push('wr-transfer--searchable');\n return parts.join(' ');\n });\n\n protected isChecked(pane: 'source' | 'target', item: WrTransferItem): boolean {\n return (pane === 'source' ? this.sourceChecked() : this.targetChecked()).includes(item.value);\n }\n\n protected toggle(pane: 'source' | 'target', item: WrTransferItem, checked: boolean): void {\n if (item.disabled || this.readonly()) return;\n const box = pane === 'source' ? this.sourceChecked : this.targetChecked;\n const current = box();\n if (checked) {\n box.set([...current, item.value]);\n return;\n }\n // A `Set` compares with SameValueZero, which is what `WrTransferItem['value']`\n // is documented to use and what `isChecked`'s `includes` already does. A `!==`\n // filter cannot remove `NaN` from its own list, so such a row drew itself\n // unchecked while staying staged.\n const next = new Set(current);\n next.delete(item.value);\n box.set([...next]);\n }\n\n /** Header checkbox — stages or clears every enabled row the filter shows. */\n protected toggleAll(pane: 'source' | 'target', checked: boolean): void {\n if (this.readonly()) return;\n const state = pane === 'source' ? this.source() : this.target();\n const box = pane === 'source' ? this.sourceChecked : this.targetChecked;\n box.set(checked ? state.rows.filter(item => !item.disabled).map(item => item.value) : []);\n }\n\n protected onSearch(pane: 'source' | 'target', event: Event): void {\n const query = (event.target as HTMLInputElement | null)?.value ?? '';\n (pane === 'source' ? this.sourceQuery : this.targetQuery).set(query);\n }\n\n protected moveRight(): void {\n if (this.disabled() || this.readonly()) return;\n // `source().checked`, not the raw staging box: rows a filter change hid are\n // neither counted nor ticked any more, so moving them would transfer rows the\n // pane never showed as chosen — and, since staging outlives an external write\n // to `value`, could land the same value on the right twice.\n const moving = this.source().checked;\n if (moving.length === 0) return;\n this.value.set([...this.selected(), ...moving]);\n this.sourceChecked.set([]);\n this.touch.emit();\n }\n\n protected moveLeft(): void {\n if (this.disabled() || this.readonly()) return;\n const moving = new Set(this.target().checked);\n if (moving.size === 0) return;\n this.value.set(this.selected().filter(v => !moving.has(v)));\n this.targetChecked.set([]);\n this.touch.emit();\n }\n\n /** Filter, then derive the header state from what survived. */\n private pane(rows: readonly WrTransferItem[], query: string, checked: readonly unknown[]): PaneState {\n const q = query.trim().toLowerCase();\n const visible = q ? rows.filter(item => item.label.toLowerCase().includes(q)) : rows;\n const enabled = visible.filter(item => !item.disabled);\n const staged = new Set(checked);\n const shown = enabled.filter(item => staged.has(item.value)).map(item => item.value);\n return {\n rows: visible,\n checked: shown,\n checkedCount: shown.length,\n enabledCount: enabled.length,\n allChecked: enabled.length > 0 && shown.length === enabled.length,\n someChecked: shown.length > 0 && shown.length < enabled.length,\n };\n }\n}\n","<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid — an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"`${title} — ${resolvedSelectAll()}`\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n size=\"sm\"\n type=\"search\"\n [placeholder]=\"resolvedSearch()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"`${title} — ${resolvedSearch()}`\"\n (input)=\"onSearch(pane, $event)\"\n />\n </div>\n }\n\n <ul class=\"wr-transfer__list\" [attr.aria-label]=\"title\">\n @for (item of state.rows; track item.value) {\n <li class=\"wr-transfer__item\" [class.wr-transfer__item--disabled]=\"item.disabled\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"isChecked(pane, item)\"\n [disabled]=\"disabled() || !!item.disabled\"\n [readonly]=\"readonly()\"\n (checkedChange)=\"toggle(pane, item, $event)\"\n (touch)=\"touch.emit()\"\n >\n {{ item.label }}\n </wr-checkbox>\n </li>\n } @empty {\n <li class=\"wr-transfer__empty\">{{ resolvedEmpty() }}</li>\n }\n </ul>\n </div>\n\n <!-- The buttons live between the panes, so they are emitted after the first. -->\n @if (pane === 'source') {\n <div class=\"wr-transfer__actions\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-target\"\n [disabled]=\"!canMoveRight()\"\n [attr.aria-label]=\"resolvedToTarget()\"\n (click)=\"moveRight()\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-source\"\n [disabled]=\"!canMoveLeft()\"\n [attr.aria-label]=\"resolvedToSource()\"\n (click)=\"moveLeft()\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n </div>\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-transfer.mjs","sources":["../../../projects/lib/transfer/transfer.ts","../../../projects/lib/transfer/transfer.html","../../../projects/lib/transfer/ngwr-transfer.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 { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n ElementRef,\n Injector,\n ViewEncapsulation,\n afterNextRender,\n computed,\n inject,\n input,\n isDevMode,\n model,\n output,\n signal,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { WrButton } from 'ngwr/button';\nimport { WrCheckbox } from 'ngwr/checkbox';\nimport { WR_FORM_FIELD, useFormFieldAria } from 'ngwr/form';\nimport { useI18nFormatter, useI18nText } from 'ngwr/i18n';\nimport { WrInput } from 'ngwr/input';\n\nimport type { WrTransferItem } from './interfaces';\n\n/** A pane's rows plus the header state derived from them. */\ninterface PaneState {\n readonly rows: readonly WrTransferItem[];\n /**\n * The staged values this pane is actually SHOWING — visible and enabled. The\n * staging signals can hold more than that (a filter change hides rows without\n * unstaging them), and every consumer of the staged set has to agree on which\n * one it means: the count, the header checkbox and the move all read this.\n */\n readonly checked: readonly unknown[];\n readonly checkedCount: number;\n readonly enabledCount: number;\n readonly allChecked: boolean;\n readonly someChecked: boolean;\n}\n\n/**\n * Dual-listbox picker: two panes and the buttons that move rows between them.\n *\n * The full item set is `[items]` and the RIGHT pane is the value — `[(value)]`\n * holds the chosen `WrTransferItem['value']`s, everything else stays left. The\n * checked state inside each pane is transient staging, deliberately not part of\n * the value: what a form cares about is which rows ended up on the right.\n *\n * A signal-forms native control (`FormValueControl<readonly unknown[]>`), so\n * `[formField]` binds straight to `value`; `[(ngModel)]` and reactive forms keep\n * working through Angular's bridge, and `[(value)]` is the standalone shape.\n *\n * @example\n * ```html\n * <!-- Standalone -->\n * <wr-transfer [items]=\"permissions\" [(value)]=\"granted\" />\n *\n * <!-- Searchable, with pane titles -->\n * <wr-transfer\n * searchable\n * sourceTitle=\"Available\"\n * targetTitle=\"Granted\"\n * [items]=\"permissions\"\n * [formField]=\"form.granted\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/transfer\n */\n@Component({\n selector: 'wr-transfer',\n templateUrl: './transfer.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n // `role=\"group\"` so the error state below has somewhere legible to live: the\n // control is a composite with no single element carrying its semantics, and\n // `aria-invalid` on a bare `generic` host is announced by nothing.\n role: 'group',\n '[attr.aria-invalid]': 'fieldAria.ariaInvalid()',\n '[attr.aria-describedby]': 'fieldAria.describedBy()',\n },\n // The transfer is the control a `<wr-form-field>` wraps; the checkboxes and\n // the search inputs in its own template are parts, not controls. Without this\n // shield each of them found the field's token and announced the one error as\n // its own — which is why `fieldAria` below reads the field with `skipSelf`.\n providers: [{ provide: WR_FORM_FIELD, useValue: null }],\n imports: [WrButton, WrCheckbox, WrInput],\n})\nexport class WrTransfer implements FormValueControl<readonly unknown[]> {\n /** Every row, in either pane. Membership of the right pane is `value`. */\n readonly items = input<readonly WrTransferItem[]>([]);\n\n /**\n * Values currently in the RIGHT pane. Two-way bindable; bound automatically\n * by `[formField]` / `[(ngModel)]`.\n */\n readonly value = model<readonly unknown[]>([]);\n\n /** Emitted on blur / commit so a bound field marks itself touched. */\n readonly touch = output<void>();\n\n /** Disable the whole control. @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Refuse changes to the value while both panes stay focusable and readable.\n * Bound automatically from the field's readonly state when used with\n * `[formField]`.\n *\n * The move buttons go inert and the row checkboxes go read-only; SEARCH keeps\n * working, because filtering a pane changes what is shown and not what is\n * chosen. No `aria-readonly` on the host: role `group` does not support the\n * state, so each row's checkbox mirrors its own instead.\n *\n * @default false\n */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /** The surrounding `<wr-form-field>`'s error state. @internal */\n protected readonly fieldAria = useFormFieldAria({ skipSelf: true });\n\n /** Show a filter box above each pane. @default false */\n readonly searchable = input(false, { transform: coerceBooleanProperty });\n\n /** Heading above the left pane. Falls back to `transfer.source`. */\n readonly sourceTitle = input<string | null>(null);\n\n /** Heading above the right pane. Falls back to `transfer.target`. */\n readonly targetTitle = input<string | null>(null);\n\n /** Placeholder in both filter boxes. Falls back to `transfer.search`. */\n readonly searchPlaceholder = input<string | null>(null);\n\n /** Shown in a pane with no rows. Falls back to `transfer.empty`. */\n readonly emptyText = input<string | null>(null);\n\n /**\n * Accessible name of each pane's select-all checkbox, composed with the pane\n * heading. Falls back to `transfer.selectAll`.\n */\n readonly selectAllLabel = input<string | null>(null);\n\n /** Accessible name of the move-right button. Falls back to `transfer.toTarget`. */\n readonly toTargetLabel = input<string | null>(null);\n\n /** Accessible name of the move-left button. Falls back to `transfer.toSource`. */\n readonly toSourceLabel = input<string | null>(null);\n\n protected readonly resolvedSourceTitle = useI18nText(this.sourceTitle, 'transfer.source', 'Available');\n protected readonly resolvedTargetTitle = useI18nText(this.targetTitle, 'transfer.target', 'Selected');\n protected readonly resolvedSearch = useI18nText(this.searchPlaceholder, 'transfer.search', 'Search');\n protected readonly resolvedEmpty = useI18nText(this.emptyText, 'transfer.empty', 'Nothing here');\n protected readonly resolvedSelectAll = useI18nText(this.selectAllLabel, 'transfer.selectAll', 'Select all');\n\n /**\n * The select-all checkbox's accessible name, composed IN THE CATALOG.\n *\n * It used to be a template literal — `` `${title} — ${selectAll}` `` — which\n * froze both the separator and the operand order into English. A translator\n * could change each half and not the sentence they make, which is the exact\n * defect the calendar and event-calendar catalogs were rewritten to remove.\n * The key ships as `{{pane}} — {{action}}` in every locale, so nothing reads\n * differently today and every locale can now reorder it without a code change.\n */\n protected readonly selectAllAria = useI18nFormatter('transfer.selectAllAria', '{{pane}} — {{action}}');\n protected readonly resolvedToTarget = useI18nText(this.toTargetLabel, 'transfer.toTarget', 'Move to selected');\n protected readonly resolvedToSource = useI18nText(this.toSourceLabel, 'transfer.toSource', 'Move to available');\n\n /** `{{checked}} / {{total}}` under each heading. @internal */\n protected readonly countLabel = useI18nFormatter('transfer.count', '{{checked}} / {{total}}');\n\n /** Pane order — the template iterates it so both panes share one block. */\n protected readonly panes = ['source', 'target'] as const;\n\n private readonly sourceQuery = signal('');\n private readonly targetQuery = signal('');\n\n /** Staged rows, per pane. Cleared as soon as a move commits them. */\n private readonly sourceChecked = signal<readonly unknown[]>([]);\n private readonly targetChecked = signal<readonly unknown[]>([]);\n\n // The value as a guaranteed array — a classic-forms binding can write null even\n // though the type is `readonly unknown[]`, so normalise every read. Same hazard\n // `wr-checkbox-group` guards; here an unguarded `.map` threw outright.\n private readonly selected = computed<readonly unknown[]>(() => {\n const v = this.value();\n return Array.isArray(v) ? v : [];\n });\n\n private readonly valueSet = computed(() => new Set(this.selected()));\n\n protected readonly source = computed<PaneState>(() =>\n this.pane(\n this.items().filter(item => !this.valueSet().has(item.value)),\n this.sourceQuery(),\n this.sourceChecked()\n )\n );\n\n protected readonly target = computed<PaneState>(() => {\n // Right-pane order follows `value`, not `items`: the order a user built is\n // the one they expect to read back.\n const rows = this.selected()\n .map(v => this.items().find(item => item.value === v))\n .filter((item): item is WrTransferItem => item !== undefined);\n\n if (isDevMode() && rows.length !== this.selected().length) this.warnAboutOrphans();\n\n return this.pane(rows, this.targetQuery(), this.targetChecked());\n });\n\n /**\n * Say something when `value` holds a value no `items` entry matches.\n *\n * Such a value is in NEITHER pane — the left one filters out anything already\n * selected and the right one can only render rows it found — and the header\n * counts the rows it shows, so nothing on screen contradicts anything. The\n * result is a value the user can neither see nor remove, and no sign that it\n * is there.\n *\n * A warning rather than an invented row, which matches how the library treats\n * the same question elsewhere: `wr-select` renders a value outside its option\n * list only under an explicit `freeText`, so an unmatched value is a mode a\n * consumer opts into, not a shape a component guesses at. It also stays quiet\n * about the common transient case — `items` arriving after `value` — because\n * the message names the values and a developer can see them settle.\n */\n private warnAboutOrphans(): void {\n const known = new Set(this.items().map(item => item.value));\n const orphans = this.selected().filter(v => !known.has(v));\n if (orphans.length === 0) return;\n\n // eslint-disable-next-line no-console -- dev-mode validation\n console.warn(\n `[NGWR] <wr-transfer>: ${orphans.length} value(s) in \\`value\\` match no \\`items\\` entry ` +\n `and are rendered in neither pane, so a user can neither see nor remove them: ` +\n `${orphans.map(v => JSON.stringify(v)).join(', ')}. ` +\n `If \\`items\\` loads after \\`value\\`, this settles on its own.`\n );\n }\n\n protected readonly canMoveRight = computed(\n () => !this.disabled() && !this.readonly() && this.source().checkedCount > 0\n );\n protected readonly canMoveLeft = computed(\n () => !this.disabled() && !this.readonly() && this.target().checkedCount > 0\n );\n\n protected readonly classes = computed(() => {\n const parts = ['wr-transfer'];\n if (this.disabled()) parts.push('wr-transfer--disabled');\n else if (this.readonly()) parts.push('wr-transfer--readonly');\n if (this.searchable()) parts.push('wr-transfer--searchable');\n return parts.join(' ');\n });\n\n protected isChecked(pane: 'source' | 'target', item: WrTransferItem): boolean {\n return (pane === 'source' ? this.sourceChecked() : this.targetChecked()).includes(item.value);\n }\n\n protected toggle(pane: 'source' | 'target', item: WrTransferItem, checked: boolean): void {\n if (item.disabled || this.readonly()) return;\n const box = pane === 'source' ? this.sourceChecked : this.targetChecked;\n const current = box();\n if (checked) {\n box.set([...current, item.value]);\n return;\n }\n // A `Set` compares with SameValueZero, which is what `WrTransferItem['value']`\n // is documented to use and what `isChecked`'s `includes` already does. A `!==`\n // filter cannot remove `NaN` from its own list, so such a row drew itself\n // unchecked while staying staged.\n const next = new Set(current);\n next.delete(item.value);\n box.set([...next]);\n }\n\n /** Header checkbox — stages or clears every enabled row the filter shows. */\n protected toggleAll(pane: 'source' | 'target', checked: boolean): void {\n if (this.readonly()) return;\n const state = pane === 'source' ? this.source() : this.target();\n const box = pane === 'source' ? this.sourceChecked : this.targetChecked;\n box.set(checked ? state.rows.filter(item => !item.disabled).map(item => item.value) : []);\n }\n\n protected onSearch(pane: 'source' | 'target', event: Event): void {\n const query = (event.target as HTMLInputElement | null)?.value ?? '';\n (pane === 'source' ? this.sourceQuery : this.targetQuery).set(query);\n }\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly injector = inject(Injector);\n\n /**\n * Keep focus in the widget after a move.\n *\n * A move empties the pane's staging set, which is exactly what disables the\n * button that was just pressed — so the element holding DOM focus went\n * `disabled` as a RESULT of its own activation and the browser dropped focus\n * to `<body>`. From there the next Tab restarts at the top of the document,\n * which for a keyboard user is being thrown out of a control they were in the\n * middle of using.\n *\n * The landing is the FIRST ROW OF THE PANE THE ROWS ARRIVED IN, and the\n * obvious alternative does not work: after a move BOTH buttons are disabled,\n * because each needs something ticked and the move cleared the tick on one\n * side and arrived unticked on the other. The rows that just moved are where\n * the user's attention is and where the next action lives, so that is where\n * focus goes.\n *\n * `afterNextRender`, not `queueMicrotask`: under zoneless CD the microtask\n * runs before the moved rows are in the DOM, so the focus call would land on\n * the pane as it was before the move.\n */\n private restoreFocusAfterMove(landing: 'source' | 'target'): void {\n afterNextRender(\n () => {\n const root = this.host.nativeElement;\n const pane =\n landing === 'target'\n ? root.querySelector<HTMLElement>('.wr-transfer__pane--target')\n : root.querySelector<HTMLElement>('.wr-transfer__pane:not(.wr-transfer__pane--target)');\n const row = pane?.querySelector<HTMLInputElement>('.wr-transfer__item input:not([disabled])');\n row?.focus();\n },\n { injector: this.injector }\n );\n }\n\n protected moveRight(): void {\n if (this.disabled() || this.readonly()) return;\n // `source().checked`, not the raw staging box: rows a filter change hid are\n // neither counted nor ticked any more, so moving them would transfer rows the\n // pane never showed as chosen — and, since staging outlives an external write\n // to `value`, could land the same value on the right twice.\n const moving = this.source().checked;\n if (moving.length === 0) return;\n this.value.set([...this.selected(), ...moving]);\n this.sourceChecked.set([]);\n this.touch.emit();\n this.restoreFocusAfterMove('target');\n }\n\n protected moveLeft(): void {\n if (this.disabled() || this.readonly()) return;\n const moving = new Set(this.target().checked);\n if (moving.size === 0) return;\n this.value.set(this.selected().filter(v => !moving.has(v)));\n this.targetChecked.set([]);\n this.touch.emit();\n this.restoreFocusAfterMove('source');\n }\n\n /** Filter, then derive the header state from what survived. */\n private pane(rows: readonly WrTransferItem[], query: string, checked: readonly unknown[]): PaneState {\n const q = query.trim().toLowerCase();\n const visible = q ? rows.filter(item => item.label.toLowerCase().includes(q)) : rows;\n const enabled = visible.filter(item => !item.disabled);\n const staged = new Set(checked);\n const shown = enabled.filter(item => staged.has(item.value)).map(item => item.value);\n return {\n rows: visible,\n checked: shown,\n checkedCount: shown.length,\n enabledCount: enabled.length,\n allChecked: enabled.length > 0 && shown.length === enabled.length,\n someChecked: shown.length > 0 && shown.length < enabled.length,\n };\n }\n}\n","<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid — an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"selectAllAria({ action: resolvedSelectAll(), pane: title })\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n size=\"sm\"\n type=\"search\"\n [placeholder]=\"resolvedSearch()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"`${title} — ${resolvedSearch()}`\"\n (input)=\"onSearch(pane, $event)\"\n />\n </div>\n }\n\n <ul class=\"wr-transfer__list\" [attr.aria-label]=\"title\">\n @for (item of state.rows; track item.value) {\n <li class=\"wr-transfer__item\" [class.wr-transfer__item--disabled]=\"item.disabled\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"isChecked(pane, item)\"\n [disabled]=\"disabled() || !!item.disabled\"\n [readonly]=\"readonly()\"\n (checkedChange)=\"toggle(pane, item, $event)\"\n (touch)=\"touch.emit()\"\n >\n {{ item.label }}\n </wr-checkbox>\n </li>\n } @empty {\n <li class=\"wr-transfer__empty\">{{ resolvedEmpty() }}</li>\n }\n </ul>\n </div>\n\n <!-- The buttons live between the panes, so they are emitted after the first. -->\n @if (pane === 'source') {\n <div class=\"wr-transfer__actions\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-target\"\n [disabled]=\"!canMoveRight()\"\n [attr.aria-label]=\"resolvedToTarget()\"\n (click)=\"moveRight()\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-source\"\n [disabled]=\"!canMoveLeft()\"\n [attr.aria-label]=\"resolvedToSource()\"\n (click)=\"moveLeft()\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n </div>\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -477,6 +477,30 @@ class WrTreeHarness extends ContentContainerComponentHarness {
477
477
  }
478
478
  await clear.click();
479
479
  }
480
+ /**
481
+ * Backspace on the closed overlay trigger — the keyboard route to the ×.
482
+ *
483
+ * In single mode it clears (only while `clearable` is on); in multi mode it
484
+ * drops the LAST chip, one per call, whatever `clearable` says. Both ×
485
+ * controls are `tabindex="-1"` spans inside the trigger, so a key can never
486
+ * reach them directly. In single mode this is the ONLY keyboard route back to
487
+ * no selection; in multi mode a keyboard user can also toggle a row off in the
488
+ * panel ({@link selectNode} with `additive`). {@link clear} / {@link removeChip}
489
+ * prove the pointer path and nothing more.
490
+ *
491
+ * The key is honoured only on the CLOSED trigger, so an open panel is closed
492
+ * first with Escape — the step a keyboard user takes too, since focus sits in
493
+ * the panel while it is up.
494
+ *
495
+ * Does nothing, rather than throwing, where the component does nothing —
496
+ * nothing selected, disabled or read-only — so a spec asserts the outcome on
497
+ * the value.
498
+ */
499
+ async clearByKeyboard() {
500
+ if (await this.isOpen())
501
+ await this.close();
502
+ await (await this.trigger()).sendKeys(TestKey.BACKSPACE);
503
+ }
480
504
  /**
481
505
  * The nodes currently rendered, in the order they are shown.
482
506
  *