ngwr 14.3.0 → 14.4.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 (330) hide show
  1. package/README.md +1 -1
  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 +6 -6
  24. package/fesm2022/ngwr-burger.mjs +3 -3
  25. package/fesm2022/ngwr-button.mjs +6 -6
  26. package/fesm2022/ngwr-calendar-heatmap-testing.mjs +12 -9
  27. package/fesm2022/ngwr-calendar-heatmap-testing.mjs.map +1 -1
  28. package/fesm2022/ngwr-calendar-heatmap.mjs +35 -7
  29. package/fesm2022/ngwr-calendar-heatmap.mjs.map +1 -1
  30. package/fesm2022/ngwr-calendar.mjs +32 -4
  31. package/fesm2022/ngwr-calendar.mjs.map +1 -1
  32. package/fesm2022/ngwr-card.mjs +9 -9
  33. package/fesm2022/ngwr-carousel.mjs +6 -6
  34. package/fesm2022/ngwr-cascader-testing.mjs +87 -17
  35. package/fesm2022/ngwr-cascader-testing.mjs.map +1 -1
  36. package/fesm2022/ngwr-cascader.mjs +63 -4
  37. package/fesm2022/ngwr-cascader.mjs.map +1 -1
  38. package/fesm2022/ngwr-checkbox-testing.mjs +13 -2
  39. package/fesm2022/ngwr-checkbox-testing.mjs.map +1 -1
  40. package/fesm2022/ngwr-checkbox.mjs +42 -7
  41. package/fesm2022/ngwr-checkbox.mjs.map +1 -1
  42. package/fesm2022/ngwr-circular-text.mjs +3 -3
  43. package/fesm2022/ngwr-click-spark.mjs +3 -3
  44. package/fesm2022/ngwr-clipboard.mjs +3 -3
  45. package/fesm2022/ngwr-collapse.mjs +9 -9
  46. package/fesm2022/ngwr-color-picker.mjs +6 -6
  47. package/fesm2022/ngwr-command-palette.mjs +16 -3
  48. package/fesm2022/ngwr-command-palette.mjs.map +1 -1
  49. package/fesm2022/ngwr-compare.mjs +3 -3
  50. package/fesm2022/ngwr-confetti.mjs +3 -3
  51. package/fesm2022/ngwr-context-menu.mjs +12 -12
  52. package/fesm2022/ngwr-cookie.mjs +3 -3
  53. package/fesm2022/ngwr-counter.mjs +6 -6
  54. package/fesm2022/ngwr-date-adapters-fns.mjs +3 -3
  55. package/fesm2022/ngwr-date-adapters-luxon.mjs +3 -3
  56. package/fesm2022/ngwr-date-picker.mjs +81 -26
  57. package/fesm2022/ngwr-date-picker.mjs.map +1 -1
  58. package/fesm2022/ngwr-date.mjs +3 -3
  59. package/fesm2022/ngwr-decrypt-text.mjs +3 -3
  60. package/fesm2022/ngwr-density.mjs +6 -6
  61. package/fesm2022/ngwr-descriptions.mjs +6 -6
  62. package/fesm2022/ngwr-dialog.mjs +15 -15
  63. package/fesm2022/ngwr-directives.mjs +12 -12
  64. package/fesm2022/ngwr-divider.mjs +3 -3
  65. package/fesm2022/ngwr-donut-chart-testing.mjs +18 -2
  66. package/fesm2022/ngwr-donut-chart-testing.mjs.map +1 -1
  67. package/fesm2022/ngwr-donut-chart.mjs +4 -4
  68. package/fesm2022/ngwr-donut-chart.mjs.map +1 -1
  69. package/fesm2022/ngwr-drag-drop.mjs +6 -6
  70. package/fesm2022/ngwr-drawer.mjs +18 -18
  71. package/fesm2022/ngwr-dropdown.mjs +32 -14
  72. package/fesm2022/ngwr-dropdown.mjs.map +1 -1
  73. package/fesm2022/ngwr-empty.mjs +3 -3
  74. package/fesm2022/ngwr-event-calendar.mjs +6 -6
  75. package/fesm2022/ngwr-falling-text.mjs +3 -3
  76. package/fesm2022/ngwr-file-upload.mjs +18 -5
  77. package/fesm2022/ngwr-file-upload.mjs.map +1 -1
  78. package/fesm2022/ngwr-form.mjs +66 -14
  79. package/fesm2022/ngwr-form.mjs.map +1 -1
  80. package/fesm2022/ngwr-fuzzy-text.mjs +3 -3
  81. package/fesm2022/ngwr-gauge-testing.mjs +16 -2
  82. package/fesm2022/ngwr-gauge-testing.mjs.map +1 -1
  83. package/fesm2022/ngwr-gauge.mjs +3 -3
  84. package/fesm2022/ngwr-glitch-text.mjs +3 -3
  85. package/fesm2022/ngwr-gradient-text.mjs +3 -3
  86. package/fesm2022/ngwr-hotkey.mjs +6 -6
  87. package/fesm2022/ngwr-i18n-ar.mjs +2 -0
  88. package/fesm2022/ngwr-i18n-ar.mjs.map +1 -1
  89. package/fesm2022/ngwr-i18n-cs.mjs +2 -0
  90. package/fesm2022/ngwr-i18n-cs.mjs.map +1 -1
  91. package/fesm2022/ngwr-i18n-de.mjs +2 -0
  92. package/fesm2022/ngwr-i18n-de.mjs.map +1 -1
  93. package/fesm2022/ngwr-i18n-en.mjs +14 -2
  94. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  95. package/fesm2022/ngwr-i18n-es.mjs +2 -0
  96. package/fesm2022/ngwr-i18n-es.mjs.map +1 -1
  97. package/fesm2022/ngwr-i18n-fr.mjs +2 -0
  98. package/fesm2022/ngwr-i18n-fr.mjs.map +1 -1
  99. package/fesm2022/ngwr-i18n-he.mjs +2 -0
  100. package/fesm2022/ngwr-i18n-he.mjs.map +1 -1
  101. package/fesm2022/ngwr-i18n-hi.mjs +2 -0
  102. package/fesm2022/ngwr-i18n-hi.mjs.map +1 -1
  103. package/fesm2022/ngwr-i18n-id.mjs +2 -0
  104. package/fesm2022/ngwr-i18n-id.mjs.map +1 -1
  105. package/fesm2022/ngwr-i18n-it.mjs +2 -0
  106. package/fesm2022/ngwr-i18n-it.mjs.map +1 -1
  107. package/fesm2022/ngwr-i18n-ja.mjs +2 -0
  108. package/fesm2022/ngwr-i18n-ja.mjs.map +1 -1
  109. package/fesm2022/ngwr-i18n-ko.mjs +2 -0
  110. package/fesm2022/ngwr-i18n-ko.mjs.map +1 -1
  111. package/fesm2022/ngwr-i18n-nl.mjs +2 -0
  112. package/fesm2022/ngwr-i18n-nl.mjs.map +1 -1
  113. package/fesm2022/ngwr-i18n-pl.mjs +2 -0
  114. package/fesm2022/ngwr-i18n-pl.mjs.map +1 -1
  115. package/fesm2022/ngwr-i18n-pt.mjs +2 -0
  116. package/fesm2022/ngwr-i18n-pt.mjs.map +1 -1
  117. package/fesm2022/ngwr-i18n-ru.mjs +2 -0
  118. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  119. package/fesm2022/ngwr-i18n-sv.mjs +2 -0
  120. package/fesm2022/ngwr-i18n-sv.mjs.map +1 -1
  121. package/fesm2022/ngwr-i18n-tr.mjs +2 -0
  122. package/fesm2022/ngwr-i18n-tr.mjs.map +1 -1
  123. package/fesm2022/ngwr-i18n-uk.mjs +2 -0
  124. package/fesm2022/ngwr-i18n-uk.mjs.map +1 -1
  125. package/fesm2022/ngwr-i18n-vi.mjs +2 -0
  126. package/fesm2022/ngwr-i18n-vi.mjs.map +1 -1
  127. package/fesm2022/ngwr-i18n-zh-TW.mjs +2 -0
  128. package/fesm2022/ngwr-i18n-zh-TW.mjs.map +1 -1
  129. package/fesm2022/ngwr-i18n-zh.mjs +2 -0
  130. package/fesm2022/ngwr-i18n-zh.mjs.map +1 -1
  131. package/fesm2022/ngwr-i18n.mjs +9 -9
  132. package/fesm2022/ngwr-icon.mjs +6 -6
  133. package/fesm2022/ngwr-image-cropper.mjs +3 -3
  134. package/fesm2022/ngwr-input-number.mjs +15 -6
  135. package/fesm2022/ngwr-input-number.mjs.map +1 -1
  136. package/fesm2022/ngwr-input-otp.mjs +38 -6
  137. package/fesm2022/ngwr-input-otp.mjs.map +1 -1
  138. package/fesm2022/ngwr-input.mjs +15 -15
  139. package/fesm2022/ngwr-keyboard.mjs +3 -3
  140. package/fesm2022/ngwr-knob-testing.mjs +11 -3
  141. package/fesm2022/ngwr-knob-testing.mjs.map +1 -1
  142. package/fesm2022/ngwr-knob.mjs +19 -4
  143. package/fesm2022/ngwr-knob.mjs.map +1 -1
  144. package/fesm2022/ngwr-layout.mjs +15 -15
  145. package/fesm2022/ngwr-lightbox.mjs +3 -3
  146. package/fesm2022/ngwr-line-chart-testing.mjs +18 -2
  147. package/fesm2022/ngwr-line-chart-testing.mjs.map +1 -1
  148. package/fesm2022/ngwr-line-chart.mjs +22 -4
  149. package/fesm2022/ngwr-line-chart.mjs.map +1 -1
  150. package/fesm2022/ngwr-list.mjs +6 -6
  151. package/fesm2022/ngwr-loading-bar.mjs +6 -6
  152. package/fesm2022/ngwr-markdown.mjs +6 -6
  153. package/fesm2022/ngwr-marquee.mjs +3 -3
  154. package/fesm2022/ngwr-media.mjs +3 -3
  155. package/fesm2022/ngwr-mention.mjs +6 -6
  156. package/fesm2022/ngwr-meta.mjs +6 -6
  157. package/fesm2022/ngwr-meter-group.mjs +3 -3
  158. package/fesm2022/ngwr-overlay.mjs +6 -6
  159. package/fesm2022/ngwr-page-header.mjs +3 -3
  160. package/fesm2022/ngwr-pagination.mjs +33 -6
  161. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  162. package/fesm2022/ngwr-pipes.mjs +21 -21
  163. package/fesm2022/ngwr-platform.mjs +9 -9
  164. package/fesm2022/ngwr-popconfirm.mjs +6 -6
  165. package/fesm2022/ngwr-popover.mjs +29 -11
  166. package/fesm2022/ngwr-popover.mjs.map +1 -1
  167. package/fesm2022/ngwr-progress.mjs +3 -3
  168. package/fesm2022/ngwr-pull-to-refresh.mjs +3 -3
  169. package/fesm2022/ngwr-qr.mjs +3 -3
  170. package/fesm2022/ngwr-radio.mjs +6 -6
  171. package/fesm2022/ngwr-rating-testing.mjs +12 -3
  172. package/fesm2022/ngwr-rating-testing.mjs.map +1 -1
  173. package/fesm2022/ngwr-rating.mjs +19 -4
  174. package/fesm2022/ngwr-rating.mjs.map +1 -1
  175. package/fesm2022/ngwr-result.mjs +13 -13
  176. package/fesm2022/ngwr-result.mjs.map +1 -1
  177. package/fesm2022/ngwr-rotating-text.mjs +3 -3
  178. package/fesm2022/ngwr-schema-form.mjs +40 -4
  179. package/fesm2022/ngwr-schema-form.mjs.map +1 -1
  180. package/fesm2022/ngwr-scroll.mjs +3 -3
  181. package/fesm2022/ngwr-segmented.mjs +3 -3
  182. package/fesm2022/ngwr-select.mjs +9 -9
  183. package/fesm2022/ngwr-shiny-text.mjs +6 -6
  184. package/fesm2022/ngwr-sidebar.mjs +3 -3
  185. package/fesm2022/ngwr-skeleton.mjs +3 -3
  186. package/fesm2022/ngwr-slider.mjs +18 -5
  187. package/fesm2022/ngwr-slider.mjs.map +1 -1
  188. package/fesm2022/ngwr-sparkline.mjs +3 -3
  189. package/fesm2022/ngwr-speed-dial.mjs +3 -3
  190. package/fesm2022/ngwr-spinner.mjs +3 -3
  191. package/fesm2022/ngwr-splash-cursor.mjs +3 -3
  192. package/fesm2022/ngwr-split-text.mjs +3 -3
  193. package/fesm2022/ngwr-splitter.mjs +3 -3
  194. package/fesm2022/ngwr-spotlight-card.mjs +6 -6
  195. package/fesm2022/ngwr-squircle.mjs +28 -9
  196. package/fesm2022/ngwr-squircle.mjs.map +1 -1
  197. package/fesm2022/ngwr-star-border.mjs +3 -3
  198. package/fesm2022/ngwr-statistic.mjs +20 -13
  199. package/fesm2022/ngwr-statistic.mjs.map +1 -1
  200. package/fesm2022/ngwr-stepper.mjs +6 -6
  201. package/fesm2022/ngwr-storage.mjs +3 -3
  202. package/fesm2022/ngwr-switch.mjs +3 -3
  203. package/fesm2022/ngwr-table.mjs +18 -18
  204. package/fesm2022/ngwr-tabs-router.mjs +3 -3
  205. package/fesm2022/ngwr-tabs.mjs +6 -6
  206. package/fesm2022/ngwr-textarea.mjs +3 -3
  207. package/fesm2022/ngwr-theme.mjs +3 -3
  208. package/fesm2022/ngwr-tilt-card.mjs +6 -6
  209. package/fesm2022/ngwr-timeline.mjs +6 -6
  210. package/fesm2022/ngwr-toast.mjs +10 -10
  211. package/fesm2022/ngwr-toast.mjs.map +1 -1
  212. package/fesm2022/ngwr-toolbar.mjs +3 -3
  213. package/fesm2022/ngwr-tour.mjs +6 -6
  214. package/fesm2022/ngwr-transfer.mjs +88 -11
  215. package/fesm2022/ngwr-transfer.mjs.map +1 -1
  216. package/fesm2022/ngwr-tree-testing.mjs +24 -0
  217. package/fesm2022/ngwr-tree-testing.mjs.map +1 -1
  218. package/fesm2022/ngwr-tree.mjs +72 -7
  219. package/fesm2022/ngwr-tree.mjs.map +1 -1
  220. package/fesm2022/ngwr-typewriter.mjs +3 -3
  221. package/fesm2022/ngwr-typography.mjs +3 -3
  222. package/fesm2022/ngwr-version.mjs +1 -1
  223. package/fesm2022/ngwr-version.mjs.map +1 -1
  224. package/fesm2022/ngwr-virtual-scroll.mjs +3 -3
  225. package/fesm2022/ngwr-waves.mjs +3 -3
  226. package/fesm2022/ngwr-window.mjs +12 -12
  227. package/form/styles/_index.scss +63 -0
  228. package/i18n/ar.json +4 -2
  229. package/i18n/cs.json +4 -2
  230. package/i18n/de.json +4 -2
  231. package/i18n/en.json +6 -4
  232. package/i18n/es.json +4 -2
  233. package/i18n/fr.json +4 -2
  234. package/i18n/he.json +4 -2
  235. package/i18n/hi.json +4 -2
  236. package/i18n/id.json +4 -2
  237. package/i18n/it.json +4 -2
  238. package/i18n/ja.json +4 -2
  239. package/i18n/ko.json +4 -2
  240. package/i18n/nl.json +4 -2
  241. package/i18n/pl.json +4 -2
  242. package/i18n/pt.json +4 -2
  243. package/i18n/ru.json +4 -2
  244. package/i18n/sv.json +4 -2
  245. package/i18n/tr.json +4 -2
  246. package/i18n/uk.json +4 -2
  247. package/i18n/vi.json +4 -2
  248. package/i18n/zh-TW.json +4 -2
  249. package/i18n/zh.json +4 -2
  250. package/input-otp/styles/_index.scss +5 -1
  251. package/line-chart/styles/_index.scss +5 -3
  252. package/llms-full.txt +1 -1
  253. package/markdown/styles/_index.scss +4 -1
  254. package/mention/styles/_index.scss +22 -1
  255. package/package.json +1 -1
  256. package/radio/styles/_index.scss +3 -0
  257. package/result/styles/_index.scss +12 -0
  258. package/schematics/migrations/v14/index.js +6 -3
  259. package/schematics/migrations.json +1 -1
  260. package/schematics/ng-add/index.js +34 -5
  261. package/segmented/styles/_index.scss +4 -1
  262. package/select/styles/_index.scss +20 -1
  263. package/statistic/styles/_index.scss +4 -0
  264. package/stepper/styles/_index.scss +36 -0
  265. package/switch/styles/_index.scss +3 -0
  266. package/table/styles/_index.scss +12 -0
  267. package/tabs/styles/_index.scss +4 -1
  268. package/textarea/styles/_index.scss +6 -1
  269. package/theme/styles/_colors.scss +11 -0
  270. package/theme/styles/_contrast.scss +136 -0
  271. package/theme/styles/_index.scss +1 -0
  272. package/theme/styles/_mixins.scss +15 -8
  273. package/timeline/styles/_index.scss +28 -1
  274. package/toast/styles/_index.scss +11 -0
  275. package/tree/styles/_index.scss +27 -1
  276. package/types/ngwr-badge.d.ts +1 -1
  277. package/types/ngwr-bar-chart-testing.d.ts +11 -1
  278. package/types/ngwr-button.d.ts +1 -1
  279. package/types/ngwr-calendar-heatmap-testing.d.ts +10 -5
  280. package/types/ngwr-calendar-heatmap.d.ts +22 -0
  281. package/types/ngwr-calendar.d.ts +21 -0
  282. package/types/ngwr-cascader-testing.d.ts +74 -17
  283. package/types/ngwr-cascader.d.ts +28 -0
  284. package/types/ngwr-checkbox-testing.d.ts +12 -1
  285. package/types/ngwr-checkbox.d.ts +10 -0
  286. package/types/ngwr-clipboard.d.ts +1 -1
  287. package/types/ngwr-confetti.d.ts +1 -1
  288. package/types/ngwr-cookie.d.ts +1 -1
  289. package/types/ngwr-date-adapters-fns.d.ts +1 -1
  290. package/types/ngwr-date-adapters-luxon.d.ts +1 -1
  291. package/types/ngwr-date.d.ts +9 -3
  292. package/types/ngwr-density.d.ts +1 -1
  293. package/types/ngwr-dialog.d.ts +1 -1
  294. package/types/ngwr-donut-chart-testing.d.ts +11 -1
  295. package/types/ngwr-drawer.d.ts +1 -1
  296. package/types/ngwr-dropdown.d.ts +16 -0
  297. package/types/ngwr-file-upload.d.ts +11 -0
  298. package/types/ngwr-form.d.ts +25 -0
  299. package/types/ngwr-gauge-testing.d.ts +9 -1
  300. package/types/ngwr-hotkey.d.ts +1 -1
  301. package/types/ngwr-i18n.d.ts +1 -1
  302. package/types/ngwr-input-number.d.ts +12 -2
  303. package/types/ngwr-input-otp.d.ts +12 -0
  304. package/types/ngwr-knob-testing.d.ts +11 -3
  305. package/types/ngwr-knob.d.ts +13 -0
  306. package/types/ngwr-line-chart-testing.d.ts +11 -1
  307. package/types/ngwr-line-chart.d.ts +18 -0
  308. package/types/ngwr-loading-bar.d.ts +1 -1
  309. package/types/ngwr-markdown.d.ts +1 -1
  310. package/types/ngwr-media.d.ts +1 -1
  311. package/types/ngwr-meta.d.ts +1 -1
  312. package/types/ngwr-overlay.d.ts +2 -2
  313. package/types/ngwr-pagination.d.ts +16 -0
  314. package/types/ngwr-platform.d.ts +3 -3
  315. package/types/ngwr-popover.d.ts +16 -0
  316. package/types/ngwr-rating-testing.d.ts +12 -3
  317. package/types/ngwr-rating.d.ts +13 -0
  318. package/types/ngwr-scroll.d.ts +1 -1
  319. package/types/ngwr-squircle.d.ts +22 -3
  320. package/types/ngwr-statistic.d.ts +0 -1
  321. package/types/ngwr-storage.d.ts +1 -1
  322. package/types/ngwr-theme.d.ts +1 -1
  323. package/types/ngwr-toast.d.ts +1 -1
  324. package/types/ngwr-tour.d.ts +1 -1
  325. package/types/ngwr-transfer.d.ts +52 -0
  326. package/types/ngwr-tree-testing.d.ts +20 -0
  327. package/types/ngwr-tree.d.ts +41 -0
  328. package/types/ngwr-version.d.ts +1 -1
  329. package/types/ngwr-window.d.ts +1 -1
  330. package/typography/styles/_index.scss +26 -4
@@ -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
  *