ngwr 15.5.0 → 15.6.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 (654) hide show
  1. package/README.md +3 -4
  2. package/affix/styles/_index.scss +2 -0
  3. package/alert/styles/_index.scss +18 -0
  4. package/anchor/styles/_index.scss +1 -0
  5. package/avatar/styles/_index.scss +44 -4
  6. package/back-top/styles/_index.scss +1 -0
  7. package/badge/styles/_index.scss +1 -0
  8. package/breadcrumbs/styles/_index.scss +1 -0
  9. package/burger/styles/_index.scss +1 -0
  10. package/button/styles/_index.scss +1 -0
  11. package/calendar/styles/_index.scss +1 -0
  12. package/card/styles/_index.scss +1 -0
  13. package/carousel/styles/_index.scss +1 -0
  14. package/cascader/styles/_index.scss +1 -0
  15. package/charts/bar-chart/styles/_index.scss +1 -0
  16. package/charts/boxplot-chart/styles/_index.scss +1 -0
  17. package/charts/bullet-chart/styles/_index.scss +1 -0
  18. package/charts/calendar-heatmap/styles/_index.scss +1 -0
  19. package/charts/candlestick-chart/styles/_index.scss +1 -0
  20. package/charts/chart-legend/styles/_index.scss +1 -0
  21. package/charts/donut-chart/styles/_index.scss +1 -0
  22. package/charts/funnel-chart/styles/_index.scss +1 -0
  23. package/charts/gauge/styles/_index.scss +1 -0
  24. package/charts/heatmap-chart/styles/_index.scss +1 -0
  25. package/charts/line-chart/styles/_index.scss +1 -0
  26. package/charts/meter-group/styles/_index.scss +1 -0
  27. package/charts/radar-chart/styles/_index.scss +1 -0
  28. package/charts/scatter-chart/styles/_index.scss +1 -0
  29. package/charts/sparkline/styles/_index.scss +2 -0
  30. package/charts/treemap-chart/styles/_index.scss +1 -0
  31. package/charts/waterfall-chart/styles/_index.scss +1 -0
  32. package/checkbox/styles/_index.scss +1 -0
  33. package/collapse/styles/_index.scss +1 -0
  34. package/color-picker/styles/_index.scss +1 -0
  35. package/command-palette/styles/_index.scss +1 -0
  36. package/comment/styles/_index.scss +1 -0
  37. package/compare/styles/_index.scss +1 -0
  38. package/context-menu/styles/_index.scss +1 -0
  39. package/counter/styles/_index.scss +1 -0
  40. package/date-picker/styles/_index.scss +1 -0
  41. package/density/styles/_index.scss +2 -0
  42. package/descriptions/styles/_index.scss +1 -0
  43. package/dialog/styles/_index.scss +1 -0
  44. package/divider/styles/_index.scss +1 -0
  45. package/drawer/styles/_index.scss +1 -0
  46. package/dropdown/styles/_index.scss +1 -0
  47. package/editor/styles/_index.scss +1 -0
  48. package/empty/styles/_index.scss +1 -0
  49. package/event-calendar/styles/_index.scss +1 -0
  50. package/fesm2022/ngwr-affix.mjs +2 -0
  51. package/fesm2022/ngwr-affix.mjs.map +1 -1
  52. package/fesm2022/ngwr-alert.mjs +7 -3
  53. package/fesm2022/ngwr-alert.mjs.map +1 -1
  54. package/fesm2022/ngwr-anchor.mjs +2 -0
  55. package/fesm2022/ngwr-anchor.mjs.map +1 -1
  56. package/fesm2022/ngwr-avatar.mjs +71 -6
  57. package/fesm2022/ngwr-avatar.mjs.map +1 -1
  58. package/fesm2022/ngwr-back-top.mjs +2 -0
  59. package/fesm2022/ngwr-back-top.mjs.map +1 -1
  60. package/fesm2022/ngwr-badge.mjs +9 -0
  61. package/fesm2022/ngwr-badge.mjs.map +1 -1
  62. package/fesm2022/ngwr-breadcrumbs.mjs +7 -2
  63. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  64. package/fesm2022/ngwr-burger.mjs +5 -0
  65. package/fesm2022/ngwr-burger.mjs.map +1 -1
  66. package/fesm2022/ngwr-button.mjs +6 -0
  67. package/fesm2022/ngwr-button.mjs.map +1 -1
  68. package/fesm2022/ngwr-calendar.mjs +2 -0
  69. package/fesm2022/ngwr-calendar.mjs.map +1 -1
  70. package/fesm2022/ngwr-card.mjs +11 -2
  71. package/fesm2022/ngwr-card.mjs.map +1 -1
  72. package/fesm2022/ngwr-carousel.mjs +6 -0
  73. package/fesm2022/ngwr-carousel.mjs.map +1 -1
  74. package/fesm2022/ngwr-cascader.mjs +3 -3
  75. package/fesm2022/ngwr-cascader.mjs.map +1 -1
  76. package/fesm2022/ngwr-charts-bar-chart.mjs +2 -1
  77. package/fesm2022/ngwr-charts-bar-chart.mjs.map +1 -1
  78. package/fesm2022/ngwr-charts-boxplot-chart.mjs +2 -0
  79. package/fesm2022/ngwr-charts-boxplot-chart.mjs.map +1 -1
  80. package/fesm2022/ngwr-charts-bullet-chart.mjs +2 -1
  81. package/fesm2022/ngwr-charts-bullet-chart.mjs.map +1 -1
  82. package/fesm2022/ngwr-charts-calendar-heatmap.mjs +2 -0
  83. package/fesm2022/ngwr-charts-calendar-heatmap.mjs.map +1 -1
  84. package/fesm2022/ngwr-charts-candlestick-chart.mjs +2 -1
  85. package/fesm2022/ngwr-charts-candlestick-chart.mjs.map +1 -1
  86. package/fesm2022/ngwr-charts-chart-legend.mjs +9 -0
  87. package/fesm2022/ngwr-charts-chart-legend.mjs.map +1 -1
  88. package/fesm2022/ngwr-charts-donut-chart.mjs +5 -0
  89. package/fesm2022/ngwr-charts-donut-chart.mjs.map +1 -1
  90. package/fesm2022/ngwr-charts-funnel-chart.mjs +5 -1
  91. package/fesm2022/ngwr-charts-funnel-chart.mjs.map +1 -1
  92. package/fesm2022/ngwr-charts-gauge.mjs +5 -1
  93. package/fesm2022/ngwr-charts-gauge.mjs.map +1 -1
  94. package/fesm2022/ngwr-charts-heatmap-chart.mjs +2 -1
  95. package/fesm2022/ngwr-charts-heatmap-chart.mjs.map +1 -1
  96. package/fesm2022/ngwr-charts-line-chart.mjs +2 -1
  97. package/fesm2022/ngwr-charts-line-chart.mjs.map +1 -1
  98. package/fesm2022/ngwr-charts-meter-group.mjs +5 -0
  99. package/fesm2022/ngwr-charts-meter-group.mjs.map +1 -1
  100. package/fesm2022/ngwr-charts-radar-chart.mjs +2 -1
  101. package/fesm2022/ngwr-charts-radar-chart.mjs.map +1 -1
  102. package/fesm2022/ngwr-charts-scatter-chart.mjs +5 -1
  103. package/fesm2022/ngwr-charts-scatter-chart.mjs.map +1 -1
  104. package/fesm2022/ngwr-charts-sparkline.mjs +5 -0
  105. package/fesm2022/ngwr-charts-sparkline.mjs.map +1 -1
  106. package/fesm2022/ngwr-charts-treemap-chart.mjs +2 -0
  107. package/fesm2022/ngwr-charts-treemap-chart.mjs.map +1 -1
  108. package/fesm2022/ngwr-charts-waterfall-chart.mjs +2 -0
  109. package/fesm2022/ngwr-charts-waterfall-chart.mjs.map +1 -1
  110. package/fesm2022/ngwr-checkbox.mjs +9 -1
  111. package/fesm2022/ngwr-checkbox.mjs.map +1 -1
  112. package/fesm2022/ngwr-collapse.mjs +11 -2
  113. package/fesm2022/ngwr-collapse.mjs.map +1 -1
  114. package/fesm2022/ngwr-color-picker.mjs +3 -1
  115. package/fesm2022/ngwr-color-picker.mjs.map +1 -1
  116. package/fesm2022/ngwr-command-palette.mjs +2 -1
  117. package/fesm2022/ngwr-command-palette.mjs.map +1 -1
  118. package/fesm2022/ngwr-comment.mjs +9 -1
  119. package/fesm2022/ngwr-comment.mjs.map +1 -1
  120. package/fesm2022/ngwr-compare.mjs +2 -1
  121. package/fesm2022/ngwr-compare.mjs.map +1 -1
  122. package/fesm2022/ngwr-context-menu.mjs +12 -2
  123. package/fesm2022/ngwr-context-menu.mjs.map +1 -1
  124. package/fesm2022/ngwr-counter.mjs +17 -23
  125. package/fesm2022/ngwr-counter.mjs.map +1 -1
  126. package/fesm2022/ngwr-date-picker.mjs +17 -2
  127. package/fesm2022/ngwr-date-picker.mjs.map +1 -1
  128. package/fesm2022/ngwr-density.mjs +2 -1
  129. package/fesm2022/ngwr-density.mjs.map +1 -1
  130. package/fesm2022/ngwr-descriptions.mjs +9 -0
  131. package/fesm2022/ngwr-descriptions.mjs.map +1 -1
  132. package/fesm2022/ngwr-dialog.mjs +23 -3
  133. package/fesm2022/ngwr-dialog.mjs.map +1 -1
  134. package/fesm2022/ngwr-directives.mjs +7 -4
  135. package/fesm2022/ngwr-directives.mjs.map +1 -1
  136. package/fesm2022/ngwr-divider.mjs +2 -0
  137. package/fesm2022/ngwr-divider.mjs.map +1 -1
  138. package/fesm2022/ngwr-drawer.mjs +20 -3
  139. package/fesm2022/ngwr-drawer.mjs.map +1 -1
  140. package/fesm2022/ngwr-dropdown.mjs +11 -2
  141. package/fesm2022/ngwr-dropdown.mjs.map +1 -1
  142. package/fesm2022/ngwr-editor.mjs +2 -1
  143. package/fesm2022/ngwr-editor.mjs.map +1 -1
  144. package/fesm2022/ngwr-empty.mjs +5 -0
  145. package/fesm2022/ngwr-empty.mjs.map +1 -1
  146. package/fesm2022/ngwr-event-calendar.mjs +5 -1
  147. package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
  148. package/fesm2022/ngwr-file-upload.mjs +2 -1
  149. package/fesm2022/ngwr-file-upload.mjs.map +1 -1
  150. package/fesm2022/ngwr-form.mjs +10 -0
  151. package/fesm2022/ngwr-form.mjs.map +1 -1
  152. package/fesm2022/ngwr-graph.mjs +2 -1
  153. package/fesm2022/ngwr-graph.mjs.map +1 -1
  154. package/fesm2022/ngwr-i18n-ar.mjs +4 -4
  155. package/fesm2022/ngwr-i18n-ar.mjs.map +1 -1
  156. package/fesm2022/ngwr-i18n-cs.mjs +4 -4
  157. package/fesm2022/ngwr-i18n-cs.mjs.map +1 -1
  158. package/fesm2022/ngwr-i18n-de.mjs +4 -4
  159. package/fesm2022/ngwr-i18n-de.mjs.map +1 -1
  160. package/fesm2022/ngwr-i18n-en.mjs +4 -4
  161. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  162. package/fesm2022/ngwr-i18n-es.mjs +4 -4
  163. package/fesm2022/ngwr-i18n-es.mjs.map +1 -1
  164. package/fesm2022/ngwr-i18n-fr.mjs +4 -4
  165. package/fesm2022/ngwr-i18n-fr.mjs.map +1 -1
  166. package/fesm2022/ngwr-i18n-he.mjs +4 -4
  167. package/fesm2022/ngwr-i18n-he.mjs.map +1 -1
  168. package/fesm2022/ngwr-i18n-hi.mjs +4 -4
  169. package/fesm2022/ngwr-i18n-hi.mjs.map +1 -1
  170. package/fesm2022/ngwr-i18n-id.mjs +4 -4
  171. package/fesm2022/ngwr-i18n-id.mjs.map +1 -1
  172. package/fesm2022/ngwr-i18n-it.mjs +4 -4
  173. package/fesm2022/ngwr-i18n-it.mjs.map +1 -1
  174. package/fesm2022/ngwr-i18n-ja.mjs +4 -4
  175. package/fesm2022/ngwr-i18n-ja.mjs.map +1 -1
  176. package/fesm2022/ngwr-i18n-ko.mjs +4 -4
  177. package/fesm2022/ngwr-i18n-ko.mjs.map +1 -1
  178. package/fesm2022/ngwr-i18n-nl.mjs +4 -4
  179. package/fesm2022/ngwr-i18n-nl.mjs.map +1 -1
  180. package/fesm2022/ngwr-i18n-pl.mjs +4 -4
  181. package/fesm2022/ngwr-i18n-pl.mjs.map +1 -1
  182. package/fesm2022/ngwr-i18n-pt.mjs +4 -4
  183. package/fesm2022/ngwr-i18n-pt.mjs.map +1 -1
  184. package/fesm2022/ngwr-i18n-ru.mjs +4 -4
  185. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  186. package/fesm2022/ngwr-i18n-sv.mjs +4 -4
  187. package/fesm2022/ngwr-i18n-sv.mjs.map +1 -1
  188. package/fesm2022/ngwr-i18n-tr.mjs +4 -4
  189. package/fesm2022/ngwr-i18n-tr.mjs.map +1 -1
  190. package/fesm2022/ngwr-i18n-uk.mjs +4 -4
  191. package/fesm2022/ngwr-i18n-uk.mjs.map +1 -1
  192. package/fesm2022/ngwr-i18n-vi.mjs +4 -4
  193. package/fesm2022/ngwr-i18n-vi.mjs.map +1 -1
  194. package/fesm2022/ngwr-i18n-zh-tw.mjs +4 -4
  195. package/fesm2022/ngwr-i18n-zh-tw.mjs.map +1 -1
  196. package/fesm2022/ngwr-i18n-zh.mjs +4 -4
  197. package/fesm2022/ngwr-i18n-zh.mjs.map +1 -1
  198. package/fesm2022/ngwr-icon.mjs +2 -0
  199. package/fesm2022/ngwr-icon.mjs.map +1 -1
  200. package/fesm2022/ngwr-image-cropper.mjs +2 -1
  201. package/fesm2022/ngwr-image-cropper.mjs.map +1 -1
  202. package/fesm2022/ngwr-input-number.mjs +2 -1
  203. package/fesm2022/ngwr-input-number.mjs.map +1 -1
  204. package/fesm2022/ngwr-input-otp.mjs +2 -1
  205. package/fesm2022/ngwr-input-otp.mjs.map +1 -1
  206. package/fesm2022/ngwr-input.mjs +20 -2
  207. package/fesm2022/ngwr-input.mjs.map +1 -1
  208. package/fesm2022/ngwr-keyboard.mjs +2 -1
  209. package/fesm2022/ngwr-keyboard.mjs.map +1 -1
  210. package/fesm2022/ngwr-knob.mjs +5 -1
  211. package/fesm2022/ngwr-knob.mjs.map +1 -1
  212. package/fesm2022/ngwr-layout.mjs +21 -3
  213. package/fesm2022/ngwr-layout.mjs.map +1 -1
  214. package/fesm2022/ngwr-lightbox.mjs +3 -3
  215. package/fesm2022/ngwr-lightbox.mjs.map +1 -1
  216. package/fesm2022/ngwr-list.mjs +7 -2
  217. package/fesm2022/ngwr-list.mjs.map +1 -1
  218. package/fesm2022/ngwr-load-state-testing.mjs +43 -0
  219. package/fesm2022/ngwr-load-state-testing.mjs.map +1 -0
  220. package/fesm2022/ngwr-load-state.mjs +495 -0
  221. package/fesm2022/ngwr-load-state.mjs.map +1 -0
  222. package/fesm2022/ngwr-mention.mjs +48 -20
  223. package/fesm2022/ngwr-mention.mjs.map +1 -1
  224. package/fesm2022/ngwr-page-header.mjs +49 -2
  225. package/fesm2022/ngwr-page-header.mjs.map +1 -1
  226. package/fesm2022/ngwr-pagination.mjs +3 -1
  227. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  228. package/fesm2022/ngwr-pipes.mjs +40 -1
  229. package/fesm2022/ngwr-pipes.mjs.map +1 -1
  230. package/fesm2022/ngwr-popconfirm.mjs +6 -1
  231. package/fesm2022/ngwr-popconfirm.mjs.map +1 -1
  232. package/fesm2022/ngwr-popover.mjs +10 -1
  233. package/fesm2022/ngwr-popover.mjs.map +1 -1
  234. package/fesm2022/ngwr-progress.mjs +5 -0
  235. package/fesm2022/ngwr-progress.mjs.map +1 -1
  236. package/fesm2022/ngwr-pull-to-refresh.mjs +2 -1
  237. package/fesm2022/ngwr-pull-to-refresh.mjs.map +1 -1
  238. package/fesm2022/ngwr-qr.mjs +2 -1
  239. package/fesm2022/ngwr-qr.mjs.map +1 -1
  240. package/fesm2022/ngwr-radio.mjs +6 -1
  241. package/fesm2022/ngwr-radio.mjs.map +1 -1
  242. package/fesm2022/ngwr-rating.mjs +5 -1
  243. package/fesm2022/ngwr-rating.mjs.map +1 -1
  244. package/fesm2022/ngwr-result.mjs +17 -0
  245. package/fesm2022/ngwr-result.mjs.map +1 -1
  246. package/fesm2022/ngwr-schema-form.mjs +7 -1
  247. package/fesm2022/ngwr-schema-form.mjs.map +1 -1
  248. package/fesm2022/ngwr-segmented.mjs +2 -0
  249. package/fesm2022/ngwr-segmented.mjs.map +1 -1
  250. package/fesm2022/ngwr-select.mjs +65 -17
  251. package/fesm2022/ngwr-select.mjs.map +1 -1
  252. package/fesm2022/ngwr-sidebar.mjs +2 -0
  253. package/fesm2022/ngwr-sidebar.mjs.map +1 -1
  254. package/fesm2022/ngwr-skeleton.mjs +30 -5
  255. package/fesm2022/ngwr-skeleton.mjs.map +1 -1
  256. package/fesm2022/ngwr-slider.mjs +26 -4
  257. package/fesm2022/ngwr-slider.mjs.map +1 -1
  258. package/fesm2022/ngwr-sortable-list.mjs +9 -3
  259. package/fesm2022/ngwr-sortable-list.mjs.map +1 -1
  260. package/fesm2022/ngwr-speed-dial.mjs +2 -0
  261. package/fesm2022/ngwr-speed-dial.mjs.map +1 -1
  262. package/fesm2022/ngwr-spinner.mjs +5 -0
  263. package/fesm2022/ngwr-spinner.mjs.map +1 -1
  264. package/fesm2022/ngwr-splitter.mjs +2 -1
  265. package/fesm2022/ngwr-splitter.mjs.map +1 -1
  266. package/fesm2022/ngwr-statistic.mjs +7 -1
  267. package/fesm2022/ngwr-statistic.mjs.map +1 -1
  268. package/fesm2022/ngwr-stepper.mjs +6 -0
  269. package/fesm2022/ngwr-stepper.mjs.map +1 -1
  270. package/fesm2022/ngwr-switch.mjs +5 -1
  271. package/fesm2022/ngwr-switch.mjs.map +1 -1
  272. package/fesm2022/ngwr-table.mjs +66 -8
  273. package/fesm2022/ngwr-table.mjs.map +1 -1
  274. package/fesm2022/ngwr-tabs.mjs +3 -1
  275. package/fesm2022/ngwr-tabs.mjs.map +1 -1
  276. package/fesm2022/ngwr-textarea.mjs +2 -0
  277. package/fesm2022/ngwr-textarea.mjs.map +1 -1
  278. package/fesm2022/ngwr-timeline.mjs +9 -0
  279. package/fesm2022/ngwr-timeline.mjs.map +1 -1
  280. package/fesm2022/ngwr-toast.mjs +6 -1
  281. package/fesm2022/ngwr-toast.mjs.map +1 -1
  282. package/fesm2022/ngwr-toolbar.mjs +5 -0
  283. package/fesm2022/ngwr-toolbar.mjs.map +1 -1
  284. package/fesm2022/ngwr-tour.mjs +6 -2
  285. package/fesm2022/ngwr-tour.mjs.map +1 -1
  286. package/fesm2022/ngwr-transfer.mjs +5 -0
  287. package/fesm2022/ngwr-transfer.mjs.map +1 -1
  288. package/fesm2022/ngwr-tree.mjs +2 -1
  289. package/fesm2022/ngwr-tree.mjs.map +1 -1
  290. package/fesm2022/ngwr-typography.mjs +5 -0
  291. package/fesm2022/ngwr-typography.mjs.map +1 -1
  292. package/fesm2022/ngwr-utils.mjs +65 -2
  293. package/fesm2022/ngwr-utils.mjs.map +1 -1
  294. package/fesm2022/ngwr-version.mjs +1 -1
  295. package/fesm2022/ngwr-version.mjs.map +1 -1
  296. package/fesm2022/ngwr-window.mjs +4 -1
  297. package/fesm2022/ngwr-window.mjs.map +1 -1
  298. package/file-upload/styles/_index.scss +1 -0
  299. package/form/styles/_index.scss +1 -0
  300. package/graph/styles/_index.scss +1 -0
  301. package/i18n/ar.json +4 -4
  302. package/i18n/cs.json +4 -4
  303. package/i18n/de.json +4 -4
  304. package/i18n/en.json +4 -4
  305. package/i18n/es.json +4 -4
  306. package/i18n/fr.json +4 -4
  307. package/i18n/he.json +4 -4
  308. package/i18n/hi.json +4 -4
  309. package/i18n/id.json +4 -4
  310. package/i18n/it.json +4 -4
  311. package/i18n/ja.json +4 -4
  312. package/i18n/ko.json +4 -4
  313. package/i18n/nl.json +4 -4
  314. package/i18n/pl.json +4 -4
  315. package/i18n/pt.json +4 -4
  316. package/i18n/ru.json +4 -4
  317. package/i18n/sv.json +4 -4
  318. package/i18n/tr.json +4 -4
  319. package/i18n/uk.json +4 -4
  320. package/i18n/vi.json +4 -4
  321. package/i18n/zh-TW.json +4 -4
  322. package/i18n/zh.json +4 -4
  323. package/icon/styles/_index.scss +1 -0
  324. package/image-cropper/styles/_index.scss +1 -0
  325. package/input/styles/_index.scss +1 -0
  326. package/input-number/styles/_index.scss +1 -0
  327. package/input-otp/styles/_index.scss +1 -0
  328. package/keyboard/styles/_index.scss +1 -0
  329. package/knob/styles/_index.scss +1 -0
  330. package/layout/styles/_index.scss +1 -0
  331. package/lightbox/styles/_index.scss +1 -0
  332. package/list/styles/_index.scss +1 -0
  333. package/llms-full.txt +16 -266
  334. package/llms.txt +16 -16
  335. package/load-state/styles/_index.scss +49 -0
  336. package/loading-bar/styles/_index.scss +1 -0
  337. package/mcp/catalog.js +3 -3
  338. package/mention/styles/_index.scss +1 -0
  339. package/package.json +10 -212
  340. package/page-header/styles/_index.scss +82 -0
  341. package/pagination/styles/_index.scss +1 -0
  342. package/popconfirm/styles/_index.scss +1 -0
  343. package/popover/styles/_index.scss +1 -0
  344. package/progress/styles/_index.scss +1 -0
  345. package/pull-to-refresh/styles/_index.scss +1 -0
  346. package/qr/styles/_index.scss +1 -0
  347. package/radio/styles/_index.scss +1 -0
  348. package/rating/styles/_index.scss +1 -0
  349. package/result/styles/_index.scss +1 -0
  350. package/schema-form/styles/_index.scss +1 -0
  351. package/schematics/component-style/index.js +1 -1
  352. package/schematics/component-style/schema.json +1 -1
  353. package/schematics/use/rule.js +0 -1
  354. package/schematics/use/symbol-map.json +6 -28
  355. package/schematics/utility/global-styles.js +3 -3
  356. package/segmented/styles/_index.scss +1 -0
  357. package/select/styles/_index.scss +1 -0
  358. package/sidebar/styles/_index.scss +1 -0
  359. package/skeleton/styles/_index.scss +65 -29
  360. package/skills/ngwr/SKILL.md +3 -4
  361. package/skills/ngwr/references/catalog.md +2 -47
  362. package/skills/ngwr/references/setup.md +1 -2
  363. package/slider/styles/_index.scss +1 -0
  364. package/sortable-list/styles/_index.scss +1 -0
  365. package/speed-dial/styles/_index.scss +1 -0
  366. package/spinner/styles/_index.scss +1 -0
  367. package/splitter/styles/_index.scss +1 -0
  368. package/statistic/styles/_index.scss +1 -0
  369. package/stepper/styles/_index.scss +1 -0
  370. package/styles/_animations.scss +0 -4
  371. package/switch/styles/_index.scss +1 -0
  372. package/table/styles/_index.scss +1 -0
  373. package/tabs/styles/_index.scss +1 -0
  374. package/textarea/styles/_index.scss +1 -0
  375. package/theme/styles/_focus.scss +0 -6
  376. package/theme/styles/_index.scss +6 -0
  377. package/theme/styles/_loaded.scss +29 -0
  378. package/theme/styles/_motion.scss +1 -0
  379. package/timeline/styles/_index.scss +1 -0
  380. package/toast/styles/_index.scss +1 -0
  381. package/toolbar/styles/_index.scss +1 -0
  382. package/tour/styles/_index.scss +1 -0
  383. package/transfer/styles/_index.scss +1 -0
  384. package/tree/styles/_index.scss +1 -0
  385. package/types/ngwr-alert.d.ts +10 -1
  386. package/types/ngwr-avatar.d.ts +49 -0
  387. package/types/ngwr-badge.d.ts +2 -0
  388. package/types/ngwr-breadcrumbs.d.ts +1 -0
  389. package/types/ngwr-burger.d.ts +1 -0
  390. package/types/ngwr-button.d.ts +1 -0
  391. package/types/ngwr-card.d.ts +2 -0
  392. package/types/ngwr-carousel.d.ts +3 -3
  393. package/types/ngwr-charts-chart-legend.d.ts +1 -0
  394. package/types/ngwr-charts-donut-chart.d.ts +1 -0
  395. package/types/ngwr-charts-funnel-chart.d.ts +1 -0
  396. package/types/ngwr-charts-gauge.d.ts +1 -0
  397. package/types/ngwr-charts-meter-group.d.ts +1 -0
  398. package/types/ngwr-charts-scatter-chart.d.ts +1 -0
  399. package/types/ngwr-charts-sparkline.d.ts +1 -0
  400. package/types/ngwr-checkbox.d.ts +2 -0
  401. package/types/ngwr-collapse.d.ts +2 -0
  402. package/types/ngwr-comment.d.ts +2 -0
  403. package/types/ngwr-compare.d.ts +1 -1
  404. package/types/ngwr-context-menu.d.ts +2 -0
  405. package/types/ngwr-counter.d.ts +12 -8
  406. package/types/ngwr-descriptions.d.ts +2 -0
  407. package/types/ngwr-dialog.d.ts +4 -0
  408. package/types/ngwr-directives.d.ts +7 -3
  409. package/types/ngwr-drawer.d.ts +4 -0
  410. package/types/ngwr-dropdown.d.ts +16 -3
  411. package/types/ngwr-empty.d.ts +1 -0
  412. package/types/ngwr-event-calendar.d.ts +1 -0
  413. package/types/ngwr-form.d.ts +2 -0
  414. package/types/ngwr-input.d.ts +4 -0
  415. package/types/ngwr-knob.d.ts +1 -0
  416. package/types/ngwr-layout.d.ts +4 -0
  417. package/types/ngwr-list.d.ts +1 -0
  418. package/types/ngwr-load-state-testing.d.ts +65 -0
  419. package/types/ngwr-load-state.d.ts +269 -0
  420. package/types/ngwr-mention.d.ts +27 -7
  421. package/types/ngwr-page-header.d.ts +60 -8
  422. package/types/ngwr-pipes.d.ts +19 -0
  423. package/types/ngwr-popconfirm.d.ts +1 -0
  424. package/types/ngwr-progress.d.ts +1 -0
  425. package/types/ngwr-radio.d.ts +1 -0
  426. package/types/ngwr-rating.d.ts +1 -0
  427. package/types/ngwr-result.d.ts +4 -0
  428. package/types/ngwr-schema-form.d.ts +1 -0
  429. package/types/ngwr-select.d.ts +56 -14
  430. package/types/ngwr-skeleton.d.ts +21 -1
  431. package/types/ngwr-slider.d.ts +21 -0
  432. package/types/ngwr-sortable-list.d.ts +2 -1
  433. package/types/ngwr-spinner.d.ts +1 -0
  434. package/types/ngwr-splitter.d.ts +1 -1
  435. package/types/ngwr-statistic.d.ts +1 -0
  436. package/types/ngwr-stepper.d.ts +2 -1
  437. package/types/ngwr-switch.d.ts +1 -0
  438. package/types/ngwr-table.d.ts +36 -1
  439. package/types/ngwr-timeline.d.ts +3 -1
  440. package/types/ngwr-toolbar.d.ts +1 -0
  441. package/types/ngwr-tour.d.ts +1 -0
  442. package/types/ngwr-transfer.d.ts +1 -0
  443. package/types/ngwr-typography.d.ts +1 -0
  444. package/types/ngwr-utils.d.ts +77 -0
  445. package/types/ngwr-version.d.ts +1 -1
  446. package/typography/styles/_index.scss +1 -0
  447. package/virtual-scroll/styles/_index.scss +1 -0
  448. package/window/styles/_index.scss +1 -0
  449. package/bits/aurora/aurora.scss +0 -1
  450. package/bits/aurora/styles/_index.scss +0 -8
  451. package/bits/aurora/styles/_rules.scss +0 -36
  452. package/bits/blur-text/blur-text.scss +0 -1
  453. package/bits/blur-text/styles/_index.scss +0 -27
  454. package/bits/border-glow/border-glow.scss +0 -4
  455. package/bits/border-glow/styles/_index.scss +0 -15
  456. package/bits/border-glow/styles/_rules.scss +0 -259
  457. package/bits/circular-text/circular-text.scss +0 -1
  458. package/bits/circular-text/styles/_index.scss +0 -15
  459. package/bits/circular-text/styles/_rules.scss +0 -60
  460. package/bits/click-spark/click-spark.scss +0 -5
  461. package/bits/click-spark/styles/_index.scss +0 -11
  462. package/bits/click-spark/styles/_rules.scss +0 -20
  463. package/bits/decrypt-text/decrypt-text.scss +0 -1
  464. package/bits/decrypt-text/styles/_index.scss +0 -15
  465. package/bits/decrypt-text/styles/_rules.scss +0 -40
  466. package/bits/dot-grid/dot-grid.scss +0 -1
  467. package/bits/dot-grid/styles/_index.scss +0 -8
  468. package/bits/dot-grid/styles/_rules.scss +0 -54
  469. package/bits/falling-text/falling-text.scss +0 -1
  470. package/bits/falling-text/styles/_index.scss +0 -15
  471. package/bits/falling-text/styles/_rules.scss +0 -44
  472. package/bits/glitch-text/glitch-text.scss +0 -1
  473. package/bits/glitch-text/styles/_index.scss +0 -15
  474. package/bits/glitch-text/styles/_rules.scss +0 -161
  475. package/bits/gradient-text/gradient-text.scss +0 -1
  476. package/bits/gradient-text/styles/_index.scss +0 -15
  477. package/bits/gradient-text/styles/_rules.scss +0 -118
  478. package/bits/iridescence/iridescence.scss +0 -1
  479. package/bits/iridescence/styles/_index.scss +0 -8
  480. package/bits/iridescence/styles/_rules.scss +0 -72
  481. package/bits/marquee/marquee.scss +0 -1
  482. package/bits/marquee/styles/_index.scss +0 -15
  483. package/bits/marquee/styles/_rules.scss +0 -203
  484. package/bits/ripple-grid/ripple-grid.scss +0 -1
  485. package/bits/ripple-grid/styles/_index.scss +0 -8
  486. package/bits/ripple-grid/styles/_rules.scss +0 -63
  487. package/bits/rotating-text/rotating-text.scss +0 -1
  488. package/bits/rotating-text/styles/_index.scss +0 -39
  489. package/bits/shiny-text/shiny-text.scss +0 -1
  490. package/bits/shiny-text/styles/_index.scss +0 -15
  491. package/bits/shiny-text/styles/_rules.scss +0 -121
  492. package/bits/splash-cursor/splash-cursor.scss +0 -1
  493. package/bits/splash-cursor/styles/_index.scss +0 -28
  494. package/bits/split-text/split-text.scss +0 -1
  495. package/bits/split-text/styles/_index.scss +0 -35
  496. package/bits/spotlight-card/spotlight-card.scss +0 -1
  497. package/bits/spotlight-card/styles/_index.scss +0 -15
  498. package/bits/spotlight-card/styles/_rules.scss +0 -52
  499. package/bits/star-border/star-border.scss +0 -1
  500. package/bits/star-border/styles/_index.scss +0 -15
  501. package/bits/star-border/styles/_rules.scss +0 -126
  502. package/bits/threads/styles/_index.scss +0 -8
  503. package/bits/threads/styles/_rules.scss +0 -76
  504. package/bits/threads/threads.scss +0 -1
  505. package/bits/tilt-card/styles/_index.scss +0 -15
  506. package/bits/tilt-card/styles/_rules.scss +0 -56
  507. package/bits/tilt-card/tilt-card.scss +0 -1
  508. package/bits/typewriter/styles/_index.scss +0 -15
  509. package/bits/typewriter/styles/_rules.scss +0 -45
  510. package/bits/typewriter/typewriter.scss +0 -1
  511. package/bits/waves/styles/_index.scss +0 -15
  512. package/bits/waves/styles/_rules.scss +0 -54
  513. package/bits/waves/waves.scss +0 -1
  514. package/fesm2022/ngwr-bits-aurora.mjs +0 -461
  515. package/fesm2022/ngwr-bits-aurora.mjs.map +0 -1
  516. package/fesm2022/ngwr-bits-blur-text-testing.mjs +0 -49
  517. package/fesm2022/ngwr-bits-blur-text-testing.mjs.map +0 -1
  518. package/fesm2022/ngwr-bits-blur-text.mjs +0 -337
  519. package/fesm2022/ngwr-bits-blur-text.mjs.map +0 -1
  520. package/fesm2022/ngwr-bits-border-glow-testing.mjs +0 -147
  521. package/fesm2022/ngwr-bits-border-glow-testing.mjs.map +0 -1
  522. package/fesm2022/ngwr-bits-border-glow.mjs +0 -561
  523. package/fesm2022/ngwr-bits-border-glow.mjs.map +0 -1
  524. package/fesm2022/ngwr-bits-circular-text-testing.mjs +0 -63
  525. package/fesm2022/ngwr-bits-circular-text-testing.mjs.map +0 -1
  526. package/fesm2022/ngwr-bits-circular-text.mjs +0 -228
  527. package/fesm2022/ngwr-bits-circular-text.mjs.map +0 -1
  528. package/fesm2022/ngwr-bits-click-spark.mjs +0 -304
  529. package/fesm2022/ngwr-bits-click-spark.mjs.map +0 -1
  530. package/fesm2022/ngwr-bits-confetti.mjs +0 -175
  531. package/fesm2022/ngwr-bits-confetti.mjs.map +0 -1
  532. package/fesm2022/ngwr-bits-decrypt-text-testing.mjs +0 -62
  533. package/fesm2022/ngwr-bits-decrypt-text-testing.mjs.map +0 -1
  534. package/fesm2022/ngwr-bits-decrypt-text.mjs +0 -525
  535. package/fesm2022/ngwr-bits-decrypt-text.mjs.map +0 -1
  536. package/fesm2022/ngwr-bits-dot-grid-testing.mjs +0 -40
  537. package/fesm2022/ngwr-bits-dot-grid-testing.mjs.map +0 -1
  538. package/fesm2022/ngwr-bits-dot-grid.mjs +0 -479
  539. package/fesm2022/ngwr-bits-dot-grid.mjs.map +0 -1
  540. package/fesm2022/ngwr-bits-falling-text-testing.mjs +0 -56
  541. package/fesm2022/ngwr-bits-falling-text-testing.mjs.map +0 -1
  542. package/fesm2022/ngwr-bits-falling-text.mjs +0 -418
  543. package/fesm2022/ngwr-bits-falling-text.mjs.map +0 -1
  544. package/fesm2022/ngwr-bits-fuzzy-text-testing.mjs +0 -38
  545. package/fesm2022/ngwr-bits-fuzzy-text-testing.mjs.map +0 -1
  546. package/fesm2022/ngwr-bits-fuzzy-text.mjs +0 -715
  547. package/fesm2022/ngwr-bits-fuzzy-text.mjs.map +0 -1
  548. package/fesm2022/ngwr-bits-glitch-text-testing.mjs +0 -63
  549. package/fesm2022/ngwr-bits-glitch-text-testing.mjs.map +0 -1
  550. package/fesm2022/ngwr-bits-glitch-text.mjs +0 -172
  551. package/fesm2022/ngwr-bits-glitch-text.mjs.map +0 -1
  552. package/fesm2022/ngwr-bits-gradient-text-testing.mjs +0 -89
  553. package/fesm2022/ngwr-bits-gradient-text-testing.mjs.map +0 -1
  554. package/fesm2022/ngwr-bits-gradient-text.mjs +0 -217
  555. package/fesm2022/ngwr-bits-gradient-text.mjs.map +0 -1
  556. package/fesm2022/ngwr-bits-iridescence-testing.mjs +0 -31
  557. package/fesm2022/ngwr-bits-iridescence-testing.mjs.map +0 -1
  558. package/fesm2022/ngwr-bits-iridescence.mjs +0 -591
  559. package/fesm2022/ngwr-bits-iridescence.mjs.map +0 -1
  560. package/fesm2022/ngwr-bits-marquee-testing.mjs +0 -126
  561. package/fesm2022/ngwr-bits-marquee-testing.mjs.map +0 -1
  562. package/fesm2022/ngwr-bits-marquee.mjs +0 -451
  563. package/fesm2022/ngwr-bits-marquee.mjs.map +0 -1
  564. package/fesm2022/ngwr-bits-ripple-grid-testing.mjs +0 -40
  565. package/fesm2022/ngwr-bits-ripple-grid-testing.mjs.map +0 -1
  566. package/fesm2022/ngwr-bits-ripple-grid.mjs +0 -806
  567. package/fesm2022/ngwr-bits-ripple-grid.mjs.map +0 -1
  568. package/fesm2022/ngwr-bits-rotating-text-testing.mjs +0 -50
  569. package/fesm2022/ngwr-bits-rotating-text-testing.mjs.map +0 -1
  570. package/fesm2022/ngwr-bits-rotating-text.mjs +0 -413
  571. package/fesm2022/ngwr-bits-rotating-text.mjs.map +0 -1
  572. package/fesm2022/ngwr-bits-shiny-text-testing.mjs +0 -92
  573. package/fesm2022/ngwr-bits-shiny-text-testing.mjs.map +0 -1
  574. package/fesm2022/ngwr-bits-shiny-text.mjs +0 -315
  575. package/fesm2022/ngwr-bits-shiny-text.mjs.map +0 -1
  576. package/fesm2022/ngwr-bits-splash-cursor-testing.mjs +0 -25
  577. package/fesm2022/ngwr-bits-splash-cursor-testing.mjs.map +0 -1
  578. package/fesm2022/ngwr-bits-splash-cursor.mjs +0 -1282
  579. package/fesm2022/ngwr-bits-splash-cursor.mjs.map +0 -1
  580. package/fesm2022/ngwr-bits-split-text-testing.mjs +0 -54
  581. package/fesm2022/ngwr-bits-split-text-testing.mjs.map +0 -1
  582. package/fesm2022/ngwr-bits-split-text.mjs +0 -378
  583. package/fesm2022/ngwr-bits-split-text.mjs.map +0 -1
  584. package/fesm2022/ngwr-bits-spotlight-card-testing.mjs +0 -113
  585. package/fesm2022/ngwr-bits-spotlight-card-testing.mjs.map +0 -1
  586. package/fesm2022/ngwr-bits-spotlight-card.mjs +0 -228
  587. package/fesm2022/ngwr-bits-spotlight-card.mjs.map +0 -1
  588. package/fesm2022/ngwr-bits-star-border-testing.mjs +0 -57
  589. package/fesm2022/ngwr-bits-star-border-testing.mjs.map +0 -1
  590. package/fesm2022/ngwr-bits-star-border.mjs +0 -162
  591. package/fesm2022/ngwr-bits-star-border.mjs.map +0 -1
  592. package/fesm2022/ngwr-bits-threads-testing.mjs +0 -41
  593. package/fesm2022/ngwr-bits-threads-testing.mjs.map +0 -1
  594. package/fesm2022/ngwr-bits-threads.mjs +0 -482
  595. package/fesm2022/ngwr-bits-threads.mjs.map +0 -1
  596. package/fesm2022/ngwr-bits-tilt-card-testing.mjs +0 -83
  597. package/fesm2022/ngwr-bits-tilt-card-testing.mjs.map +0 -1
  598. package/fesm2022/ngwr-bits-tilt-card.mjs +0 -260
  599. package/fesm2022/ngwr-bits-tilt-card.mjs.map +0 -1
  600. package/fesm2022/ngwr-bits-typewriter-testing.mjs +0 -61
  601. package/fesm2022/ngwr-bits-typewriter-testing.mjs.map +0 -1
  602. package/fesm2022/ngwr-bits-typewriter.mjs +0 -469
  603. package/fesm2022/ngwr-bits-typewriter.mjs.map +0 -1
  604. package/fesm2022/ngwr-bits-waves-testing.mjs +0 -37
  605. package/fesm2022/ngwr-bits-waves-testing.mjs.map +0 -1
  606. package/fesm2022/ngwr-bits-waves.mjs +0 -865
  607. package/fesm2022/ngwr-bits-waves.mjs.map +0 -1
  608. package/types/ngwr-bits-aurora.d.ts +0 -114
  609. package/types/ngwr-bits-blur-text-testing.d.ts +0 -162
  610. package/types/ngwr-bits-blur-text.d.ts +0 -135
  611. package/types/ngwr-bits-border-glow-testing.d.ts +0 -230
  612. package/types/ngwr-bits-border-glow.d.ts +0 -166
  613. package/types/ngwr-bits-circular-text-testing.d.ts +0 -178
  614. package/types/ngwr-bits-circular-text.d.ts +0 -70
  615. package/types/ngwr-bits-click-spark.d.ts +0 -101
  616. package/types/ngwr-bits-confetti.d.ts +0 -80
  617. package/types/ngwr-bits-decrypt-text-testing.d.ts +0 -204
  618. package/types/ngwr-bits-decrypt-text.d.ts +0 -135
  619. package/types/ngwr-bits-dot-grid-testing.d.ts +0 -92
  620. package/types/ngwr-bits-dot-grid.d.ts +0 -158
  621. package/types/ngwr-bits-falling-text-testing.d.ts +0 -170
  622. package/types/ngwr-bits-falling-text.d.ts +0 -95
  623. package/types/ngwr-bits-fuzzy-text-testing.d.ts +0 -112
  624. package/types/ngwr-bits-fuzzy-text.d.ts +0 -208
  625. package/types/ngwr-bits-glitch-text-testing.d.ts +0 -183
  626. package/types/ngwr-bits-glitch-text.d.ts +0 -60
  627. package/types/ngwr-bits-gradient-text-testing.d.ts +0 -168
  628. package/types/ngwr-bits-gradient-text.d.ts +0 -73
  629. package/types/ngwr-bits-iridescence-testing.d.ts +0 -85
  630. package/types/ngwr-bits-iridescence.d.ts +0 -244
  631. package/types/ngwr-bits-marquee-testing.d.ts +0 -266
  632. package/types/ngwr-bits-marquee.d.ts +0 -188
  633. package/types/ngwr-bits-ripple-grid-testing.d.ts +0 -96
  634. package/types/ngwr-bits-ripple-grid.d.ts +0 -299
  635. package/types/ngwr-bits-rotating-text-testing.d.ts +0 -173
  636. package/types/ngwr-bits-rotating-text.d.ts +0 -127
  637. package/types/ngwr-bits-shiny-text-testing.d.ts +0 -147
  638. package/types/ngwr-bits-shiny-text.d.ts +0 -131
  639. package/types/ngwr-bits-splash-cursor-testing.d.ts +0 -77
  640. package/types/ngwr-bits-splash-cursor.d.ts +0 -162
  641. package/types/ngwr-bits-split-text-testing.d.ts +0 -191
  642. package/types/ngwr-bits-split-text.d.ts +0 -163
  643. package/types/ngwr-bits-spotlight-card-testing.d.ts +0 -279
  644. package/types/ngwr-bits-spotlight-card.d.ts +0 -94
  645. package/types/ngwr-bits-star-border-testing.d.ts +0 -117
  646. package/types/ngwr-bits-star-border.d.ts +0 -62
  647. package/types/ngwr-bits-threads-testing.d.ts +0 -97
  648. package/types/ngwr-bits-threads.d.ts +0 -216
  649. package/types/ngwr-bits-tilt-card-testing.d.ts +0 -140
  650. package/types/ngwr-bits-tilt-card.d.ts +0 -94
  651. package/types/ngwr-bits-typewriter-testing.d.ts +0 -201
  652. package/types/ngwr-bits-typewriter.d.ts +0 -165
  653. package/types/ngwr-bits-waves-testing.d.ts +0 -103
  654. package/types/ngwr-bits-waves.d.ts +0 -142
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-affix.mjs","names":[],"sources":["../../../projects/lib/affix/affix.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 { coerceNumberProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n DestroyRef,\n Directive,\n ElementRef,\n Injector,\n PLATFORM_ID,\n afterNextRender,\n effect,\n inject,\n input,\n output,\n} from '@angular/core';\n\n/**\n * The element a `position: sticky` host actually pins against.\n *\n * Walks up looking for an ancestor that scrolls — `overflow` anything but\n * `visible` on an axis, and a box that can actually be scrolled. `null` means\n * the document scroller, which is both what the browser uses and what\n * `IntersectionObserver` takes for \"the viewport\".\n *\n * A `getComputedStyle` read per ancestor, once per observer construction —\n * cheap, and the only way to answer the question: CSS resolves it internally\n * and exposes nothing.\n */\nfunction scrollParent(el: HTMLElement): HTMLElement | null {\n for (let node = el.parentElement; node !== null; node = node.parentElement) {\n if (node === document.body || node === document.documentElement) break;\n const style = getComputedStyle(node);\n const scrolls = /auto|scroll|overlay|hidden/.test(`${style.overflowY} ${style.overflowX}`);\n if (scrolls && node.scrollHeight > node.clientHeight) return node;\n }\n return null;\n}\n\n/**\n * Whether a parent lays its children out as flex or grid items, where a flow\n * sentinel is not inert: a flex container puts a `gap` on each side of it, and\n * a grid container gives it a cell of its own, shifting every item after it. A\n * computed value, so `inline-flex` and `inline-grid` answer the same.\n */\nfunction laysOutItems(parent: HTMLElement): boolean {\n return /\\b(?:inline-)?(?:flex|grid)\\b/.test(getComputedStyle(parent).display);\n}\n\n/**\n * Every out-of-flow affix in one parent shares that parent's containing-block\n * claim, so only the last one to leave gives it back — sticky group headers in\n * one filtered list come and go in any order. `inline` is the parent's own\n * inline `position`, kept only when the fallback in `claimContainer` wrote one.\n */\nconst containers = new WeakMap<HTMLElement, { count: number; inline: string | null }>();\n\n/**\n * Makes `parent` the containing block of the absolutely positioned sentinels\n * inside it, and returns the release.\n *\n * An absolute box scrolls with a scroller's content only when its containing\n * block sits inside that scroller, and the nearest positioned ancestor may well\n * be outside it — so inside a nested scroller the sentinel would hold still while\n * the host scrolled away, and the state would never flip.\n *\n * The parent is TAGGED rather than styled: `ngwr/affix` gives\n * `.wr-affix__container` its `position: relative` under `:where()`, at zero\n * specificity, so any position the author gives the parent still wins, a media\n * query included. An inline `position` would beat all of them for the\n * directive's whole lifetime. Only when nothing positions the parent once it is\n * tagged — the stylesheet is not loaded — is `relative` written inline, and the\n * last release puts the inline value back.\n */\nfunction claimContainer(parent: HTMLElement): () => void {\n let claim = containers.get(parent);\n if (!claim) {\n parent.classList.add('wr-affix__container');\n const positioned = /relative|absolute|fixed|sticky/.test(getComputedStyle(parent).position);\n claim = { count: 0, inline: positioned ? null : parent.style.position };\n if (!positioned) parent.style.position = 'relative';\n containers.set(parent, claim);\n }\n claim.count++;\n\n const held = claim;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n if (--held.count > 0) return;\n containers.delete(parent);\n parent.classList.remove('wr-affix__container');\n if (held.inline !== null && parent.style.position === 'relative') parent.style.position = held.inline;\n };\n}\n\n/**\n * Takes the sentinel out of a flex or grid parent's layout and keeps it on the\n * host's natural top edge. Returns the teardown.\n *\n * Absolutely positioned and zero-sized, so neither a gap nor a track is made\n * for it — which leaves its place to be measured. Two choices follow:\n *\n * - **The parent becomes the containing block** — see `claimContainer`.\n * - **The host is measured with sticky switched off.** A stuck element's\n * `offsetTop` already includes the sticky shift, and the sentinel marks the\n * line the host leaves its natural place at. Static and sticky lay out\n * identically in flow, so nothing else moves, and both writes land before\n * the next paint.\n *\n * Re-measured whenever the parent, the host or anything laid out before the host\n * changes size — those are what move the host's top — and whenever the parent's\n * children change: an element inserted above the host moves it without resizing\n * a fixed-height scroll panel, and becomes one more thing to watch.\n */\nfunction pinOutOfFlow(host: HTMLElement, parent: HTMLElement, sentinel: HTMLElement): () => void {\n const release = claimContainer(parent);\n\n const measure = (): void => {\n const position = host.style.position;\n host.style.position = 'static';\n const top = host.offsetTop;\n const left = host.offsetLeft;\n host.style.position = position;\n sentinel.style.top = `${top}px`;\n // Physical on purpose: `offsetLeft` is physical too, so the pair lines up in either direction.\n sentinel.style.left = `${left}px`;\n };\n measure();\n\n const resize = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(() => measure());\n const watchLayout = (): void => {\n if (!resize) return;\n resize.disconnect();\n resize.observe(parent);\n for (let node: Element | null = host; node; node = node.previousElementSibling) {\n if (node !== sentinel) resize.observe(node);\n }\n };\n watchLayout();\n\n const children =\n typeof MutationObserver === 'undefined'\n ? null\n : new MutationObserver(() => {\n watchLayout();\n measure();\n });\n children?.observe(parent, { childList: true });\n\n return () => {\n resize?.disconnect();\n children?.disconnect();\n release();\n };\n}\n\n/**\n * Stick-on-scroll directive. Combines native CSS `position: sticky` with\n * an `IntersectionObserver`-driven `wr-affix--active` state class, so consumers\n * can style the element differently while it's pinned (e.g. add a\n * shadow, change the background, shrink the height).\n *\n * The host gets `position: sticky; top: ${offsetTop}px;` inline. A tiny\n * sentinel `<div>` is inserted just before the host — when the sentinel\n * scrolls out of view, the host is \"stuck\" and gets `wr-affix--active`\n * + an `(wrAffixChange)` emission.\n *\n * In block layout the sentinel is a zero-height flow element. In a flex or grid\n * parent (read once, after the first render) it is absolutely positioned and\n * zero-sized instead, so it adds no gap and takes no grid cell. It is placed on\n * the host's natural top edge and re-placed when the parent, the host or an\n * element before the host resizes, or when the parent's children change. That\n * needs the parent as its containing block: the parent is tagged\n * `wr-affix__container`, which `ngwr/affix` makes `position: relative` at zero\n * specificity, so a position the author gives it still wins. The tag comes off\n * when the last affix in that parent is destroyed.\n *\n * Works inside any scroll container without configuration: CSS sticky picks\n * the nearest scrollable ancestor, and the observer watching for the pinned\n * state is given that same element as its root — see `scrollParent`. Without\n * that second half the two measured different lines, and the state never\n * flipped inside a nested scroller.\n *\n * @example\n * ```html\n * <header wrAffix [wrAffixOffsetTop]=\"0\">\n * <!-- adds a shadow once stuck -->\n * </header>\n * ```\n *\n * ```scss\n * header.wr-affix.wr-affix--active {\n * box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n * }\n * ```\n */\n@Directive({\n selector: '[wrAffix]',\n host: {\n class: 'wr-affix',\n '[style.position]': '\"sticky\"',\n '[style.top.px]': 'offsetTop()',\n },\n})\nexport class WrAffix {\n /** Pixels from the top of the scroll container when stuck. @default 0 */\n readonly offsetTop = input(0, {\n alias: 'wrAffixOffsetTop',\n transform: (v: unknown): number => coerceNumberProperty(v, 0),\n });\n\n /**\n * Fires when the element pins or unpins — `true` once it has stuck, `false`\n * when it returns to the flow. Use it to swap a shadow or a compact layout.\n */\n // eslint-disable-next-line @angular-eslint/no-output-rename -- keep wr-prefixed binding to match `[wrAffix]`\n readonly affixChange = output<boolean>({ alias: 'wrAffixChange' });\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n /**\n * `afterNextRender`'s callback is not an injection context, and the observer\n * cannot exist before the sentinel it watches — so the effect that rebuilds it\n * is handed the injector explicitly.\n */\n private readonly injector = inject(Injector);\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => {\n const el = this.host.nativeElement;\n const parent = el.parentElement;\n if (!parent) return;\n\n // Sentinel: an empty zero-height block placed immediately before\n // the host. When the sentinel scrolls out of view, the host is\n // necessarily \"stuck\" at the configured offset.\n const sentinel = el.ownerDocument.createElement('div');\n sentinel.className = 'wr-affix__sentinel';\n sentinel.setAttribute('aria-hidden', 'true');\n // In a flex or grid parent a flow element is an item with a gap or a cell\n // of its own, so there it leaves the flow — see `pinOutOfFlow`.\n const outOfFlow = laysOutItems(parent);\n sentinel.style.cssText = outOfFlow\n ? 'position:absolute;width:0;height:0;margin:0;padding:0;pointer-events:none;'\n : 'height:0;width:100%;margin:0;padding:0;pointer-events:none;';\n parent.insertBefore(sentinel, el);\n const unpin = outOfFlow ? pinOutOfFlow(el, parent, sentinel) : null;\n\n // The observer reports STATE, not transitions: it delivers an entry for the\n // sentinel as soon as it starts watching it — before anything has scrolled —\n // and can repeat a state the host is already in (a resize, another crossing of\n // the same threshold). `affixChange` is documented as the transition, so it\n // only fires on one. The starting state is \"not affixed\", which is what the\n // element already renders as, so that first entry is silent.\n let affixed = false;\n\n let observer: IntersectionObserver | null = null;\n\n // `rootMargin` is fixed for an observer's lifetime, so a changed offset means\n // a NEW observer. Without this the two halves of the offset drifted apart:\n // `[style.top.px]` is a live binding, so a header that animates its offset\n // pinned at the new line while still flipping state at the old one.\n // Re-observing re-delivers the current state, which the transition guard\n // above absorbs when nothing has actually changed.\n effect(\n onCleanup => {\n const offset = this.offsetTop();\n\n // The observer's ROOT has to be the element the host sticks to, not\n // the viewport. `position: sticky` pins against the nearest\n // SCROLLING ancestor, and an observer with no root measures against\n // the viewport — so inside any nested scroller whose top sits below\n // the viewport's, `rootBounds.top` was 0 while the host pinned at\n // the container's own top. The direction guard below then computed\n // `false` at the one crossing it was ever handed, and because\n // `isIntersecting` stays false afterwards no further callback came:\n // `wr-affix--active` was never added and `(wrAffixChange)` never\n // emitted, for the whole scroll. The JSDoc promised the opposite —\n // \"works inside any scroll container without configuration\" — and\n // the docs page's own demo is exactly that shape.\n const root = scrollParent(el);\n\n observer = new IntersectionObserver(\n ([entry]) => {\n // NOT `!entry.isIntersecting` on its own. A sentinel is outside\n // the root in TWO directions, and only one of them means the\n // header has stuck: above the line because the page scrolled past\n // it, or below it because the host simply starts further down the\n // page than the first screen. The bare negation called both\n // \"affixed\", so any sticky element below the fold reported\n // `affixed = true` and painted `.wr-affix--active` on its very\n // first observer callback — before a pixel had scrolled — and\n // emitted a transition that never happened.\n //\n // `rootBounds.top` is the offset line (the negative `rootMargin`\n // above moves it), so a sentinel above it has genuinely gone by.\n const rootTop = entry.rootBounds?.top ?? offset;\n const next = !entry.isIntersecting && entry.boundingClientRect.top < rootTop;\n if (next === affixed) return;\n\n affixed = next;\n el.classList.toggle('wr-affix--active', next);\n this.affixChange.emit(next);\n },\n {\n // `null` is the document scroller, which is what the browser\n // uses for a host with no scrolling ancestor.\n root,\n // Trigger when the sentinel's top crosses the offset line. NEGATED\n // rather than prefixed with a minus: a negative offset is valid sticky\n // CSS (the host pins partly above the edge), and the prefix turned\n // `-10` into `--10px`, which the constructor refuses with a\n // SyntaxError — so the state never flipped at all. Negated, a\n // negative offset GROWS the root above its edge, which is where the\n // host pins.\n rootMargin: `${-offset}px 0px 0px 0px`,\n threshold: [0],\n }\n );\n observer.observe(sentinel);\n\n onCleanup(() => observer?.disconnect());\n },\n { injector: this.injector }\n );\n\n this.destroyRef.onDestroy(() => {\n observer?.disconnect();\n unpin?.();\n sentinel.remove();\n el.classList.remove('wr-affix--active');\n });\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-affix.mjs","names":[],"sources":["../../../projects/lib/affix/affix.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 { coerceNumberProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n DestroyRef,\n Directive,\n ElementRef,\n Injector,\n PLATFORM_ID,\n afterNextRender,\n effect,\n inject,\n input,\n output,\n} from '@angular/core';\n\nimport { wrCheckStyles } from 'ngwr/utils';\n\n/**\n * The element a `position: sticky` host actually pins against.\n *\n * Walks up looking for an ancestor that scrolls — `overflow` anything but\n * `visible` on an axis, and a box that can actually be scrolled. `null` means\n * the document scroller, which is both what the browser uses and what\n * `IntersectionObserver` takes for \"the viewport\".\n *\n * A `getComputedStyle` read per ancestor, once per observer construction —\n * cheap, and the only way to answer the question: CSS resolves it internally\n * and exposes nothing.\n */\nfunction scrollParent(el: HTMLElement): HTMLElement | null {\n for (let node = el.parentElement; node !== null; node = node.parentElement) {\n if (node === document.body || node === document.documentElement) break;\n const style = getComputedStyle(node);\n const scrolls = /auto|scroll|overlay|hidden/.test(`${style.overflowY} ${style.overflowX}`);\n if (scrolls && node.scrollHeight > node.clientHeight) return node;\n }\n return null;\n}\n\n/**\n * Whether a parent lays its children out as flex or grid items, where a flow\n * sentinel is not inert: a flex container puts a `gap` on each side of it, and\n * a grid container gives it a cell of its own, shifting every item after it. A\n * computed value, so `inline-flex` and `inline-grid` answer the same.\n */\nfunction laysOutItems(parent: HTMLElement): boolean {\n return /\\b(?:inline-)?(?:flex|grid)\\b/.test(getComputedStyle(parent).display);\n}\n\n/**\n * Every out-of-flow affix in one parent shares that parent's containing-block\n * claim, so only the last one to leave gives it back — sticky group headers in\n * one filtered list come and go in any order. `inline` is the parent's own\n * inline `position`, kept only when the fallback in `claimContainer` wrote one.\n */\nconst containers = new WeakMap<HTMLElement, { count: number; inline: string | null }>();\n\n/**\n * Makes `parent` the containing block of the absolutely positioned sentinels\n * inside it, and returns the release.\n *\n * An absolute box scrolls with a scroller's content only when its containing\n * block sits inside that scroller, and the nearest positioned ancestor may well\n * be outside it — so inside a nested scroller the sentinel would hold still while\n * the host scrolled away, and the state would never flip.\n *\n * The parent is TAGGED rather than styled: `ngwr/affix` gives\n * `.wr-affix__container` its `position: relative` under `:where()`, at zero\n * specificity, so any position the author gives the parent still wins, a media\n * query included. An inline `position` would beat all of them for the\n * directive's whole lifetime. Only when nothing positions the parent once it is\n * tagged — the stylesheet is not loaded — is `relative` written inline, and the\n * last release puts the inline value back.\n */\nfunction claimContainer(parent: HTMLElement): () => void {\n let claim = containers.get(parent);\n if (!claim) {\n parent.classList.add('wr-affix__container');\n const positioned = /relative|absolute|fixed|sticky/.test(getComputedStyle(parent).position);\n claim = { count: 0, inline: positioned ? null : parent.style.position };\n if (!positioned) parent.style.position = 'relative';\n containers.set(parent, claim);\n }\n claim.count++;\n\n const held = claim;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n if (--held.count > 0) return;\n containers.delete(parent);\n parent.classList.remove('wr-affix__container');\n if (held.inline !== null && parent.style.position === 'relative') parent.style.position = held.inline;\n };\n}\n\n/**\n * Takes the sentinel out of a flex or grid parent's layout and keeps it on the\n * host's natural top edge. Returns the teardown.\n *\n * Absolutely positioned and zero-sized, so neither a gap nor a track is made\n * for it — which leaves its place to be measured. Two choices follow:\n *\n * - **The parent becomes the containing block** — see `claimContainer`.\n * - **The host is measured with sticky switched off.** A stuck element's\n * `offsetTop` already includes the sticky shift, and the sentinel marks the\n * line the host leaves its natural place at. Static and sticky lay out\n * identically in flow, so nothing else moves, and both writes land before\n * the next paint.\n *\n * Re-measured whenever the parent, the host or anything laid out before the host\n * changes size — those are what move the host's top — and whenever the parent's\n * children change: an element inserted above the host moves it without resizing\n * a fixed-height scroll panel, and becomes one more thing to watch.\n */\nfunction pinOutOfFlow(host: HTMLElement, parent: HTMLElement, sentinel: HTMLElement): () => void {\n const release = claimContainer(parent);\n\n const measure = (): void => {\n const position = host.style.position;\n host.style.position = 'static';\n const top = host.offsetTop;\n const left = host.offsetLeft;\n host.style.position = position;\n sentinel.style.top = `${top}px`;\n // Physical on purpose: `offsetLeft` is physical too, so the pair lines up in either direction.\n sentinel.style.left = `${left}px`;\n };\n measure();\n\n const resize = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(() => measure());\n const watchLayout = (): void => {\n if (!resize) return;\n resize.disconnect();\n resize.observe(parent);\n for (let node: Element | null = host; node; node = node.previousElementSibling) {\n if (node !== sentinel) resize.observe(node);\n }\n };\n watchLayout();\n\n const children =\n typeof MutationObserver === 'undefined'\n ? null\n : new MutationObserver(() => {\n watchLayout();\n measure();\n });\n children?.observe(parent, { childList: true });\n\n return () => {\n resize?.disconnect();\n children?.disconnect();\n release();\n };\n}\n\n/**\n * Stick-on-scroll directive. Combines native CSS `position: sticky` with\n * an `IntersectionObserver`-driven `wr-affix--active` state class, so consumers\n * can style the element differently while it's pinned (e.g. add a\n * shadow, change the background, shrink the height).\n *\n * The host gets `position: sticky; top: ${offsetTop}px;` inline. A tiny\n * sentinel `<div>` is inserted just before the host — when the sentinel\n * scrolls out of view, the host is \"stuck\" and gets `wr-affix--active`\n * + an `(wrAffixChange)` emission.\n *\n * In block layout the sentinel is a zero-height flow element. In a flex or grid\n * parent (read once, after the first render) it is absolutely positioned and\n * zero-sized instead, so it adds no gap and takes no grid cell. It is placed on\n * the host's natural top edge and re-placed when the parent, the host or an\n * element before the host resizes, or when the parent's children change. That\n * needs the parent as its containing block: the parent is tagged\n * `wr-affix__container`, which `ngwr/affix` makes `position: relative` at zero\n * specificity, so a position the author gives it still wins. The tag comes off\n * when the last affix in that parent is destroyed.\n *\n * Works inside any scroll container without configuration: CSS sticky picks\n * the nearest scrollable ancestor, and the observer watching for the pinned\n * state is given that same element as its root — see `scrollParent`. Without\n * that second half the two measured different lines, and the state never\n * flipped inside a nested scroller.\n *\n * @example\n * ```html\n * <header wrAffix [wrAffixOffsetTop]=\"0\">\n * <!-- adds a shadow once stuck -->\n * </header>\n * ```\n *\n * ```scss\n * header.wr-affix.wr-affix--active {\n * box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n * }\n * ```\n */\n@Directive({\n selector: '[wrAffix]',\n host: {\n class: 'wr-affix',\n '[style.position]': '\"sticky\"',\n '[style.top.px]': 'offsetTop()',\n },\n})\nexport class WrAffix {\n /** Pixels from the top of the scroll container when stuck. @default 0 */\n readonly offsetTop = input(0, {\n alias: 'wrAffixOffsetTop',\n transform: (v: unknown): number => coerceNumberProperty(v, 0),\n });\n\n /**\n * Fires when the element pins or unpins — `true` once it has stuck, `false`\n * when it returns to the flow. Use it to swap a shadow or a compact layout.\n */\n // eslint-disable-next-line @angular-eslint/no-output-rename -- keep wr-prefixed binding to match `[wrAffix]`\n readonly affixChange = output<boolean>({ alias: 'wrAffixChange' });\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n /**\n * `afterNextRender`'s callback is not an injection context, and the observer\n * cannot exist before the sentinel it watches — so the effect that rebuilds it\n * is handed the injector explicitly.\n */\n private readonly injector = inject(Injector);\n\n constructor() {\n if (typeof ngDevMode === 'undefined' || ngDevMode) wrCheckStyles('affix');\n if (!this.isBrowser) return;\n\n afterNextRender(() => {\n const el = this.host.nativeElement;\n const parent = el.parentElement;\n if (!parent) return;\n\n // Sentinel: an empty zero-height block placed immediately before\n // the host. When the sentinel scrolls out of view, the host is\n // necessarily \"stuck\" at the configured offset.\n const sentinel = el.ownerDocument.createElement('div');\n sentinel.className = 'wr-affix__sentinel';\n sentinel.setAttribute('aria-hidden', 'true');\n // In a flex or grid parent a flow element is an item with a gap or a cell\n // of its own, so there it leaves the flow — see `pinOutOfFlow`.\n const outOfFlow = laysOutItems(parent);\n sentinel.style.cssText = outOfFlow\n ? 'position:absolute;width:0;height:0;margin:0;padding:0;pointer-events:none;'\n : 'height:0;width:100%;margin:0;padding:0;pointer-events:none;';\n parent.insertBefore(sentinel, el);\n const unpin = outOfFlow ? pinOutOfFlow(el, parent, sentinel) : null;\n\n // The observer reports STATE, not transitions: it delivers an entry for the\n // sentinel as soon as it starts watching it — before anything has scrolled —\n // and can repeat a state the host is already in (a resize, another crossing of\n // the same threshold). `affixChange` is documented as the transition, so it\n // only fires on one. The starting state is \"not affixed\", which is what the\n // element already renders as, so that first entry is silent.\n let affixed = false;\n\n let observer: IntersectionObserver | null = null;\n\n // `rootMargin` is fixed for an observer's lifetime, so a changed offset means\n // a NEW observer. Without this the two halves of the offset drifted apart:\n // `[style.top.px]` is a live binding, so a header that animates its offset\n // pinned at the new line while still flipping state at the old one.\n // Re-observing re-delivers the current state, which the transition guard\n // above absorbs when nothing has actually changed.\n effect(\n onCleanup => {\n const offset = this.offsetTop();\n\n // The observer's ROOT has to be the element the host sticks to, not\n // the viewport. `position: sticky` pins against the nearest\n // SCROLLING ancestor, and an observer with no root measures against\n // the viewport — so inside any nested scroller whose top sits below\n // the viewport's, `rootBounds.top` was 0 while the host pinned at\n // the container's own top. The direction guard below then computed\n // `false` at the one crossing it was ever handed, and because\n // `isIntersecting` stays false afterwards no further callback came:\n // `wr-affix--active` was never added and `(wrAffixChange)` never\n // emitted, for the whole scroll. The JSDoc promised the opposite —\n // \"works inside any scroll container without configuration\" — and\n // the docs page's own demo is exactly that shape.\n const root = scrollParent(el);\n\n observer = new IntersectionObserver(\n ([entry]) => {\n // NOT `!entry.isIntersecting` on its own. A sentinel is outside\n // the root in TWO directions, and only one of them means the\n // header has stuck: above the line because the page scrolled past\n // it, or below it because the host simply starts further down the\n // page than the first screen. The bare negation called both\n // \"affixed\", so any sticky element below the fold reported\n // `affixed = true` and painted `.wr-affix--active` on its very\n // first observer callback — before a pixel had scrolled — and\n // emitted a transition that never happened.\n //\n // `rootBounds.top` is the offset line (the negative `rootMargin`\n // above moves it), so a sentinel above it has genuinely gone by.\n const rootTop = entry.rootBounds?.top ?? offset;\n const next = !entry.isIntersecting && entry.boundingClientRect.top < rootTop;\n if (next === affixed) return;\n\n affixed = next;\n el.classList.toggle('wr-affix--active', next);\n this.affixChange.emit(next);\n },\n {\n // `null` is the document scroller, which is what the browser\n // uses for a host with no scrolling ancestor.\n root,\n // Trigger when the sentinel's top crosses the offset line. NEGATED\n // rather than prefixed with a minus: a negative offset is valid sticky\n // CSS (the host pins partly above the edge), and the prefix turned\n // `-10` into `--10px`, which the constructor refuses with a\n // SyntaxError — so the state never flipped at all. Negated, a\n // negative offset GROWS the root above its edge, which is where the\n // host pins.\n rootMargin: `${-offset}px 0px 0px 0px`,\n threshold: [0],\n }\n );\n observer.observe(sentinel);\n\n onCleanup(() => observer?.disconnect());\n },\n { injector: this.injector }\n );\n\n this.destroyRef.onDestroy(() => {\n observer?.disconnect();\n unpin?.();\n sentinel.remove();\n el.classList.remove('wr-affix--active');\n });\n });\n }\n}\n"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import { Component, ElementRef, Injector, ViewEncapsulation, afterNextRender, computed, inject, input, output, signal } from "@angular/core";
3
3
  import { coerceBooleanProperty } from "@angular/cdk/coercion";
4
+ import { getFocusableElements, wrCheckStyles } from "ngwr/utils";
4
5
  import { useI18nText } from "ngwr/i18n";
5
6
  import { WrIcon } from "ngwr/icon";
6
- import { getFocusableElements } from "ngwr/utils";
7
7
  /**
8
8
  * @license
9
9
  *
@@ -11,6 +11,9 @@ import { getFocusableElements } from "ngwr/utils";
11
11
  * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
12
12
  */
13
13
  var WrAlert = class WrAlert {
14
+ constructor() {
15
+ if (typeof ngDevMode === "undefined" || ngDevMode) wrCheckStyles("alert");
16
+ }
14
17
  closeLabel = input(null, ...ngDevMode ? [{ debugName: "closeLabel" }] : /* istanbul ignore next */ []);
15
18
  resolvedCloseLabel = useI18nText(this.closeLabel, "alert.close", "Close alert");
16
19
  title = input(null, ...ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []);
@@ -125,7 +128,7 @@ var WrAlert = class WrAlert {
125
128
  "attr.aria-live": "liveLevel()"
126
129
  } },
127
130
  ngImport: i0,
128
- template: "@if (!dismissed()) {\n @if (iconName(); as n) {\n <wr-icon class=\"wr-alert__icon\" [name]=\"n\" />\n } @else if (icon()) {\n <svg\n class=\"wr-alert__icon\"\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 @switch (type()) {\n @case ('success') {\n <path d=\"M21.801 10A10 10 0 1 1 17 3.335\" />\n <path d=\"m9 11 3 3L22 4\" />\n }\n @case ('warning') {\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n }\n <!-- An OCTAGON, and the shape is the point. `danger` used to be a\n circle holding a bar over a dot, which is the vertical mirror of\n the `i` the @default branch draws — at the rendered 18px the two\n differ only by where a 1.5px gap sits. The stop-sign silhouette\n reads at that size and survives greyscale, which a mirrored gap\n does not. -->\n @case ('danger') {\n <path\n d=\"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z\"\n />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n <!-- A bar, not the exclamation mark `danger` used to carry: `neutral`\n emitted that glyph byte for byte. The icon is the whole difference\n between these two, because what the component actually paints —\n `danger-ink` #a32a33 (L 0.0968) against `on-surface-muted` #5f6c7d\n (L 0.1464) on tints 1.036:1 apart — is a hue difference and little\n else. -->\n @case ('neutral') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M8 12h8\" />\n }\n @case ('offline') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m4.9 4.9 14.2 14.2\" />\n }\n @default {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n }\n }\n </svg>\n }\n\n <div class=\"wr-alert__body\">\n @if (title(); as t) {\n <p class=\"wr-alert__title\">{{ t }}</p>\n @if (message(); as m) {\n <p class=\"wr-alert__message\">{{ m }}</p>\n }\n } @else if (message(); as m) {\n <p class=\"wr-alert__title\">{{ m }}</p>\n }\n <div class=\"wr-alert__content\"><ng-content /></div>\n </div>\n\n @if (closable()) {\n <button type=\"button\" class=\"wr-alert__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"onClose()\">\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n}\n",
131
+ template: "@if (!dismissed()) {\n @if (iconName(); as n) {\n <wr-icon class=\"wr-alert__icon\" [name]=\"n\" />\n } @else if (icon()) {\n <svg\n class=\"wr-alert__icon\"\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 @switch (type()) {\n @case ('success') {\n <path d=\"M21.801 10A10 10 0 1 1 17 3.335\" />\n <path d=\"m9 11 3 3L22 4\" />\n }\n @case ('warning') {\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n }\n <!-- An OCTAGON, and the shape is the point. `danger` used to be a\n circle holding a bar over a dot, which is the vertical mirror of\n the `i` the @default branch draws — at the rendered 18px the two\n differ only by where a 1.5px gap sits. The stop-sign silhouette\n reads at that size and survives greyscale, which a mirrored gap\n does not. -->\n @case ('danger') {\n <path\n d=\"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z\"\n />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n <!-- A bar, not the exclamation mark `danger` used to carry: `neutral`\n emitted that glyph byte for byte. The icon is the whole difference\n between these two, because what the component actually paints —\n `danger-ink` #a32a33 (L 0.0968) against `on-surface-muted` #5f6c7d\n (L 0.1464) on tints 1.036:1 apart — is a hue difference and little\n else. -->\n @case ('neutral') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M8 12h8\" />\n }\n @case ('offline') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m4.9 4.9 14.2 14.2\" />\n }\n @default {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n }\n }\n </svg>\n }\n\n <div class=\"wr-alert__body\">\n @if (title(); as t) {\n <p class=\"wr-alert__title\">{{ t }}</p>\n @if (message(); as m) {\n <p class=\"wr-alert__message\">{{ m }}</p>\n }\n } @else if (message(); as m) {\n <p class=\"wr-alert__title\">{{ m }}</p>\n }\n <div class=\"wr-alert__content\"><ng-content /></div>\n <div class=\"wr-alert__actions\"><ng-content select=\"[wrAlertActions]\" /></div>\n </div>\n\n @if (closable()) {\n <button type=\"button\" class=\"wr-alert__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"onClose()\">\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n}\n",
129
132
  dependencies: [{
130
133
  kind: "component",
131
134
  type: WrIcon,
@@ -152,9 +155,10 @@ i0.ɵɵngDeclareClassMetadata({
152
155
  "[attr.role]": "liveRole()",
153
156
  "[attr.aria-live]": "liveLevel()"
154
157
  },
155
- template: "@if (!dismissed()) {\n @if (iconName(); as n) {\n <wr-icon class=\"wr-alert__icon\" [name]=\"n\" />\n } @else if (icon()) {\n <svg\n class=\"wr-alert__icon\"\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 @switch (type()) {\n @case ('success') {\n <path d=\"M21.801 10A10 10 0 1 1 17 3.335\" />\n <path d=\"m9 11 3 3L22 4\" />\n }\n @case ('warning') {\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n }\n <!-- An OCTAGON, and the shape is the point. `danger` used to be a\n circle holding a bar over a dot, which is the vertical mirror of\n the `i` the @default branch draws — at the rendered 18px the two\n differ only by where a 1.5px gap sits. The stop-sign silhouette\n reads at that size and survives greyscale, which a mirrored gap\n does not. -->\n @case ('danger') {\n <path\n d=\"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z\"\n />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n <!-- A bar, not the exclamation mark `danger` used to carry: `neutral`\n emitted that glyph byte for byte. The icon is the whole difference\n between these two, because what the component actually paints —\n `danger-ink` #a32a33 (L 0.0968) against `on-surface-muted` #5f6c7d\n (L 0.1464) on tints 1.036:1 apart — is a hue difference and little\n else. -->\n @case ('neutral') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M8 12h8\" />\n }\n @case ('offline') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m4.9 4.9 14.2 14.2\" />\n }\n @default {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n }\n }\n </svg>\n }\n\n <div class=\"wr-alert__body\">\n @if (title(); as t) {\n <p class=\"wr-alert__title\">{{ t }}</p>\n @if (message(); as m) {\n <p class=\"wr-alert__message\">{{ m }}</p>\n }\n } @else if (message(); as m) {\n <p class=\"wr-alert__title\">{{ m }}</p>\n }\n <div class=\"wr-alert__content\"><ng-content /></div>\n </div>\n\n @if (closable()) {\n <button type=\"button\" class=\"wr-alert__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"onClose()\">\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n}\n"
158
+ template: "@if (!dismissed()) {\n @if (iconName(); as n) {\n <wr-icon class=\"wr-alert__icon\" [name]=\"n\" />\n } @else if (icon()) {\n <svg\n class=\"wr-alert__icon\"\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 @switch (type()) {\n @case ('success') {\n <path d=\"M21.801 10A10 10 0 1 1 17 3.335\" />\n <path d=\"m9 11 3 3L22 4\" />\n }\n @case ('warning') {\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n }\n <!-- An OCTAGON, and the shape is the point. `danger` used to be a\n circle holding a bar over a dot, which is the vertical mirror of\n the `i` the @default branch draws — at the rendered 18px the two\n differ only by where a 1.5px gap sits. The stop-sign silhouette\n reads at that size and survives greyscale, which a mirrored gap\n does not. -->\n @case ('danger') {\n <path\n d=\"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z\"\n />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n <!-- A bar, not the exclamation mark `danger` used to carry: `neutral`\n emitted that glyph byte for byte. The icon is the whole difference\n between these two, because what the component actually paints —\n `danger-ink` #a32a33 (L 0.0968) against `on-surface-muted` #5f6c7d\n (L 0.1464) on tints 1.036:1 apart — is a hue difference and little\n else. -->\n @case ('neutral') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M8 12h8\" />\n }\n @case ('offline') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m4.9 4.9 14.2 14.2\" />\n }\n @default {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n }\n }\n </svg>\n }\n\n <div class=\"wr-alert__body\">\n @if (title(); as t) {\n <p class=\"wr-alert__title\">{{ t }}</p>\n @if (message(); as m) {\n <p class=\"wr-alert__message\">{{ m }}</p>\n }\n } @else if (message(); as m) {\n <p class=\"wr-alert__title\">{{ m }}</p>\n }\n <div class=\"wr-alert__content\"><ng-content /></div>\n <div class=\"wr-alert__actions\"><ng-content select=\"[wrAlertActions]\" /></div>\n </div>\n\n @if (closable()) {\n <button type=\"button\" class=\"wr-alert__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"onClose()\">\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n}\n"
156
159
  }]
157
160
  }],
161
+ ctorParameters: () => [],
158
162
  propDecorators: {
159
163
  closeLabel: [{
160
164
  type: i0.Input,
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-alert.mjs","names":[],"sources":["../../../projects/lib/alert/alert.ts","../../../projects/lib/alert/alert.html"],"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 output,\n signal,\n} from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrIcon, type WrIconName } from 'ngwr/icon';\nimport { getFocusableElements } from 'ngwr/utils';\n\nimport type { WrAlertType } from './types';\n\n/**\n * Inline status banner. Use for feedback messages — saved/failed/notice etc.\n * Content placed inside renders below the message, in `.wr-alert__content`.\n *\n * @example\n * ```html\n * <wr-alert title=\"Saved\" message=\"Your changes are live.\" type=\"success\" />\n * <wr-alert title=\"Failed\" type=\"danger\" closable (closed)=\"onClose()\" />\n * <wr-alert title=\"Sync failed\" type=\"danger\">Request <code>8f2c41</code> timed out.</wr-alert>\n * ```\n *\n * @see https://ngwr.dev/reference/components/alert\n */\n@Component({\n selector: 'wr-alert',\n templateUrl: './alert.html',\n imports: [WrIcon],\n encapsulation: ViewEncapsulation.None,\n host: {\n // `title` is an input, and a static attribute that feeds an input is ALSO\n // written onto the host — where the browser shows it as a native tooltip\n // over the whole component, and where it feeds the accessible name of any\n // element that has a role. `<wr-alert title=\"Heads up\">` is the documented\n // spelling, so every page writing it shipped both. A bound `[title]` never\n // did this, which is why the two forms behaved differently for no reason a\n // reader could see.\n '[attr.title]': 'null',\n '[class]': 'classes()',\n '[attr.role]': 'liveRole()',\n '[attr.aria-live]': 'liveLevel()',\n },\n})\nexport class WrAlert {\n /** Accessible name. Falls back to `alert.close`, then `'Close alert'`. */\n readonly closeLabel = input<string | null>(null);\n\n protected readonly resolvedCloseLabel = useI18nText(this.closeLabel, 'alert.close', 'Close alert');\n\n /**\n * Optional headline shown at the top of the alert.\n *\n * @default null\n */\n readonly title = input<string | null>(null);\n\n /**\n * Visual variant.\n *\n * @default 'info'\n */\n readonly type = input<WrAlertType>('info');\n\n /**\n * Override the default per-type icon with any ngwr icon name.\n *\n * @default null\n */\n readonly iconName = input<WrIconName | null>(null);\n\n /**\n * Optional secondary message rendered below the title.\n *\n * @default null\n */\n readonly message = input<string | null>(null);\n\n /**\n * When `true`, renders a leading status icon matching the `type`.\n * Pass `false` to hide. Ignored when `iconName` is set.\n *\n * @default true\n */\n readonly icon = input(true, { transform: coerceBooleanProperty });\n\n /**\n * When `true`, renders a close button.\n *\n * @default false\n */\n readonly closable = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Emitted when the user dismisses the alert via the close button. If the close\n * button had focus, focus moves on to the next focusable element after the alert\n * (or the one before it, when nothing follows) — unless a `(closed)` handler has\n * placed it somewhere itself.\n */\n readonly closed = output<void>();\n\n protected readonly dismissed = signal(false);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-alert', `wr-alert--${this.type()}`];\n if (this.dismissed()) parts.push('wr-alert--dismissed');\n return parts.join(' ');\n });\n\n /** Escalate role/aria-live for danger/warning alerts. */\n protected readonly liveRole = computed(() =>\n this.dismissed() ? null : this.type() === 'danger' ? 'alert' : 'status'\n );\n protected readonly liveLevel = computed(() =>\n this.dismissed() ? null : this.type() === 'danger' || this.type() === 'warning' ? 'assertive' : 'polite'\n );\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly injector = inject(Injector);\n\n protected onClose(): void {\n const host = this.host.nativeElement;\n const hadFocus = host.contains(host.ownerDocument.activeElement);\n this.dismissed.set(true);\n this.closed.emit();\n if (hadFocus) afterNextRender(() => this.handOnFocus(), { injector: this.injector });\n }\n\n /**\n * The close button is removed with the rest of the content, and a focused element\n * that leaves the DOM drops the caret on `<body>`: a keyboard user's next Tab, and\n * a screen reader's reading position, started over from the top of the page. Run\n * after the render that removed it, so a `(closed)` handler that moved focus on\n * purpose is seen and left alone.\n */\n private handOnFocus(): void {\n const host = this.host.nativeElement;\n const doc = host.ownerDocument;\n const active = doc.activeElement;\n if (active && active !== doc.body) return;\n // Tab stops only: a roving composite parks its other items at -1. The host has\n // nothing focusable left inside it, so every stop is either before or after it.\n const stops = getFocusableElements(doc.body).filter(el => el.tabIndex >= 0);\n // eslint-disable-next-line no-bitwise -- compareDocumentPosition answers with a bitmask\n const after = stops.findIndex(el => (host.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0);\n (after >= 0 ? stops[after] : stops.at(-1))?.focus();\n }\n}\n","@if (!dismissed()) {\n @if (iconName(); as n) {\n <wr-icon class=\"wr-alert__icon\" [name]=\"n\" />\n } @else if (icon()) {\n <svg\n class=\"wr-alert__icon\"\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 @switch (type()) {\n @case ('success') {\n <path d=\"M21.801 10A10 10 0 1 1 17 3.335\" />\n <path d=\"m9 11 3 3L22 4\" />\n }\n @case ('warning') {\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n }\n <!-- An OCTAGON, and the shape is the point. `danger` used to be a\n circle holding a bar over a dot, which is the vertical mirror of\n the `i` the @default branch draws — at the rendered 18px the two\n differ only by where a 1.5px gap sits. The stop-sign silhouette\n reads at that size and survives greyscale, which a mirrored gap\n does not. -->\n @case ('danger') {\n <path\n d=\"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z\"\n />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n <!-- A bar, not the exclamation mark `danger` used to carry: `neutral`\n emitted that glyph byte for byte. The icon is the whole difference\n between these two, because what the component actually paints —\n `danger-ink` #a32a33 (L 0.0968) against `on-surface-muted` #5f6c7d\n (L 0.1464) on tints 1.036:1 apart — is a hue difference and little\n else. -->\n @case ('neutral') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M8 12h8\" />\n }\n @case ('offline') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m4.9 4.9 14.2 14.2\" />\n }\n @default {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n }\n }\n </svg>\n }\n\n <div class=\"wr-alert__body\">\n @if (title(); as t) {\n <p class=\"wr-alert__title\">{{ t }}</p>\n @if (message(); as m) {\n <p class=\"wr-alert__message\">{{ m }}</p>\n }\n } @else if (message(); as m) {\n <p class=\"wr-alert__title\">{{ m }}</p>\n }\n <div class=\"wr-alert__content\"><ng-content /></div>\n </div>\n\n @if (closable()) {\n <button type=\"button\" class=\"wr-alert__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"onClose()\">\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n}\n"],"mappings":";;;;;;;;;;;;AA2DA,IAAa,UAAb,MAAa,QAAO;CAET,aAAa,MAAqB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAE7B,qBAAqB,YAAY,KAAK,YAAY,eAAe,aAAa;CAOxF,QAAQ,MAAqB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAOlC,OAAO,MAAmB,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAOjC,WAAW,MAAyB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAOzC,UAAU,MAAqB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAQpC,OAAO,MAAM,MAAI;EAAA,GAAA,YAAA,EAAA,WAAA,OAAA,+BAAA,CAAA;EAAI,WAAW;CAAqB,CAAA;CAOrD,WAAW,MAAM,OAAK;EAAA,GAAA,YAAA,EAAA,WAAA,WAAA,+BAAA,CAAA;EAAI,WAAW;CAAqB,CAAA;CAQ1D,SAAS,OAAM;CAEL,YAAY,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAEzB,UAAU,eAAc;EACzC,MAAM,QAAQ,CAAC,YAAY,aAAa,KAAK,KAAI,GAAI;EACrD,IAAI,KAAK,UAAS,GAAI,MAAM,KAAK,qBAAqB;EACtD,OAAO,MAAM,KAAK,GAAG;CACvB,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAGiB,WAAW,eAC5B,KAAK,UAAS,IAAK,OAAO,KAAK,KAAI,MAAO,WAAW,UAAU,UAChE,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CACkB,YAAY,eAC7B,KAAK,UAAS,IAAK,OAAO,KAAK,KAAI,MAAO,YAAY,KAAK,KAAI,MAAO,YAAY,cAAc,UACjG,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAEgB,OAAO,OAAgC,UAAU;CACjD,WAAW,OAAO,QAAQ;CAEjC,UAAO;EACf,MAAM,OAAO,KAAK,KAAK;EACvB,MAAM,WAAW,KAAK,SAAS,KAAK,cAAc,aAAa;EAC/D,KAAK,UAAU,IAAI,IAAI;EACvB,KAAK,OAAO,KAAI;EAChB,IAAI,UAAU,sBAAsB,KAAK,YAAW,GAAI,EAAE,UAAU,KAAK,SAAQ,CAAE;CACrF;CASQ,cAAW;EACjB,MAAM,OAAO,KAAK,KAAK;EACvB,MAAM,MAAM,KAAK;EACjB,MAAM,SAAS,IAAI;EACnB,IAAI,UAAU,WAAW,IAAI,MAAM;EAGnC,MAAM,QAAQ,qBAAqB,IAAI,IAAI,CAAC,CAAC,QAAO,OAAM,GAAG,YAAY,CAAC;EAE1E,MAAM,QAAQ,MAAM,WAAU,QAAO,KAAK,wBAAwB,EAAE,IAAI,KAAK,iCAAiC,CAAC;EAC/G,CAAC,SAAS,IAAI,MAAM,SAAS,MAAM,GAAG,EAAE,EAAA,EAAI,MAAK;CACnD;CAtGW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAO,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAP,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAO,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA,EAAA,QAAA,SAAA;EAAA,MAAA,EAAA,YAAA;GAAA,cAAA;GAAA,SAAA;GAAA,aAAA;GAAA,kBAAA;EAAA,EAAA;EAAA,UAAA;EAAA,UC3DpB;EA4FA,cAAA,CAAA;GAAA,MAAA;GAAA,MDjDY;GAAM,UAAA;GAAA,QAAA,CAAA,MAAA;EAAA,CAAA;EAAA,eAAA,GAAA,kBAAA;CAAA,CAAA;;AAgBL,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAO,YAAA,CAAA;EAnBnB,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAU,SAEX,CAAC,MAAM;GAAC,eACF,kBAAkB;GAAI,MAC/B;IAQJ,gBAAgB;IAChB,WAAW;IACX,eAAe;IACf,oBAAoB;GACrB;GAAA,UAAA;EAAA,CAAA"}
1
+ {"version":3,"file":"ngwr-alert.mjs","names":[],"sources":["../../../projects/lib/alert/alert.ts","../../../projects/lib/alert/alert.html"],"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 output,\n signal,\n} from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrIcon, type WrIconName } from 'ngwr/icon';\nimport { getFocusableElements, wrCheckStyles } from 'ngwr/utils';\n\nimport type { WrAlertType } from './types';\n\n/**\n * Inline status banner. Use for feedback messages — saved/failed/notice etc.\n * Content placed inside renders below the message, in `.wr-alert__content`.\n *\n * Slots:\n *\n * - **Default** — text under the message.\n * - **`[wrAlertActions]`** — buttons under the message, in a row that wraps.\n *\n * @example\n * ```html\n * <wr-alert title=\"Saved\" message=\"Your changes are live.\" type=\"success\" />\n * <wr-alert title=\"Failed\" type=\"danger\" closable (closed)=\"onClose()\" />\n * <wr-alert title=\"Sync failed\" type=\"danger\">Request <code>8f2c41</code> timed out.</wr-alert>\n * <wr-alert title=\"Update available\" type=\"info\">\n * <wr-btn wrAlertActions size=\"sm\" (click)=\"update()\">Install</wr-btn>\n * </wr-alert>\n * ```\n *\n * @see https://ngwr.dev/reference/components/alert\n */\n@Component({\n selector: 'wr-alert',\n templateUrl: './alert.html',\n imports: [WrIcon],\n encapsulation: ViewEncapsulation.None,\n host: {\n // `title` is an input, and a static attribute that feeds an input is ALSO\n // written onto the host — where the browser shows it as a native tooltip\n // over the whole component, and where it feeds the accessible name of any\n // element that has a role. `<wr-alert title=\"Heads up\">` is the documented\n // spelling, so every page writing it shipped both. A bound `[title]` never\n // did this, which is why the two forms behaved differently for no reason a\n // reader could see.\n '[attr.title]': 'null',\n '[class]': 'classes()',\n '[attr.role]': 'liveRole()',\n '[attr.aria-live]': 'liveLevel()',\n },\n})\nexport class WrAlert {\n constructor() {\n if (typeof ngDevMode === 'undefined' || ngDevMode) wrCheckStyles('alert');\n }\n\n /** Accessible name. Falls back to `alert.close`, then `'Close alert'`. */\n readonly closeLabel = input<string | null>(null);\n\n protected readonly resolvedCloseLabel = useI18nText(this.closeLabel, 'alert.close', 'Close alert');\n\n /**\n * Optional headline shown at the top of the alert.\n *\n * @default null\n */\n readonly title = input<string | null>(null);\n\n /**\n * Visual variant.\n *\n * @default 'info'\n */\n readonly type = input<WrAlertType>('info');\n\n /**\n * Override the default per-type icon with any ngwr icon name.\n *\n * @default null\n */\n readonly iconName = input<WrIconName | null>(null);\n\n /**\n * Optional secondary message rendered below the title.\n *\n * @default null\n */\n readonly message = input<string | null>(null);\n\n /**\n * When `true`, renders a leading status icon matching the `type`.\n * Pass `false` to hide. Ignored when `iconName` is set.\n *\n * @default true\n */\n readonly icon = input(true, { transform: coerceBooleanProperty });\n\n /**\n * When `true`, renders a close button.\n *\n * @default false\n */\n readonly closable = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Emitted when the user dismisses the alert via the close button. If the close\n * button had focus, focus moves on to the next focusable element after the alert\n * (or the one before it, when nothing follows) — unless a `(closed)` handler has\n * placed it somewhere itself.\n */\n readonly closed = output<void>();\n\n protected readonly dismissed = signal(false);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-alert', `wr-alert--${this.type()}`];\n if (this.dismissed()) parts.push('wr-alert--dismissed');\n return parts.join(' ');\n });\n\n /** Escalate role/aria-live for danger/warning alerts. */\n protected readonly liveRole = computed(() =>\n this.dismissed() ? null : this.type() === 'danger' ? 'alert' : 'status'\n );\n protected readonly liveLevel = computed(() =>\n this.dismissed() ? null : this.type() === 'danger' || this.type() === 'warning' ? 'assertive' : 'polite'\n );\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly injector = inject(Injector);\n\n protected onClose(): void {\n const host = this.host.nativeElement;\n const hadFocus = host.contains(host.ownerDocument.activeElement);\n this.dismissed.set(true);\n this.closed.emit();\n if (hadFocus) afterNextRender(() => this.handOnFocus(), { injector: this.injector });\n }\n\n /**\n * The close button is removed with the rest of the content, and a focused element\n * that leaves the DOM drops the caret on `<body>`: a keyboard user's next Tab, and\n * a screen reader's reading position, started over from the top of the page. Run\n * after the render that removed it, so a `(closed)` handler that moved focus on\n * purpose is seen and left alone.\n */\n private handOnFocus(): void {\n const host = this.host.nativeElement;\n const doc = host.ownerDocument;\n const active = doc.activeElement;\n if (active && active !== doc.body) return;\n // Tab stops only: a roving composite parks its other items at -1. The host has\n // nothing focusable left inside it, so every stop is either before or after it.\n const stops = getFocusableElements(doc.body).filter(el => el.tabIndex >= 0);\n // eslint-disable-next-line no-bitwise -- compareDocumentPosition answers with a bitmask\n const after = stops.findIndex(el => (host.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0);\n (after >= 0 ? stops[after] : stops.at(-1))?.focus();\n }\n}\n","@if (!dismissed()) {\n @if (iconName(); as n) {\n <wr-icon class=\"wr-alert__icon\" [name]=\"n\" />\n } @else if (icon()) {\n <svg\n class=\"wr-alert__icon\"\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 @switch (type()) {\n @case ('success') {\n <path d=\"M21.801 10A10 10 0 1 1 17 3.335\" />\n <path d=\"m9 11 3 3L22 4\" />\n }\n @case ('warning') {\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n }\n <!-- An OCTAGON, and the shape is the point. `danger` used to be a\n circle holding a bar over a dot, which is the vertical mirror of\n the `i` the @default branch draws — at the rendered 18px the two\n differ only by where a 1.5px gap sits. The stop-sign silhouette\n reads at that size and survives greyscale, which a mirrored gap\n does not. -->\n @case ('danger') {\n <path\n d=\"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z\"\n />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n <!-- A bar, not the exclamation mark `danger` used to carry: `neutral`\n emitted that glyph byte for byte. The icon is the whole difference\n between these two, because what the component actually paints —\n `danger-ink` #a32a33 (L 0.0968) against `on-surface-muted` #5f6c7d\n (L 0.1464) on tints 1.036:1 apart — is a hue difference and little\n else. -->\n @case ('neutral') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M8 12h8\" />\n }\n @case ('offline') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m4.9 4.9 14.2 14.2\" />\n }\n @default {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n }\n }\n </svg>\n }\n\n <div class=\"wr-alert__body\">\n @if (title(); as t) {\n <p class=\"wr-alert__title\">{{ t }}</p>\n @if (message(); as m) {\n <p class=\"wr-alert__message\">{{ m }}</p>\n }\n } @else if (message(); as m) {\n <p class=\"wr-alert__title\">{{ m }}</p>\n }\n <div class=\"wr-alert__content\"><ng-content /></div>\n <div class=\"wr-alert__actions\"><ng-content select=\"[wrAlertActions]\" /></div>\n </div>\n\n @if (closable()) {\n <button type=\"button\" class=\"wr-alert__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"onClose()\">\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import { Component, DestroyRef, NgZone, ViewEncapsulation, afterNextRender, computed, inject, input, signal } from "@angular/core";
3
3
  import { coerceNumberProperty } from "@angular/cdk/coercion";
4
+ import { wrCheckStyles } from "ngwr/utils";
4
5
  import { useI18nText } from "ngwr/i18n";
5
6
  import { WrPlatform } from "ngwr/platform";
6
7
  import { WrScroll } from "ngwr/scroll";
@@ -38,6 +39,7 @@ var WrAnchor = class WrAnchor {
38
39
  pinned = null;
39
40
  lastY = 0;
40
41
  constructor() {
42
+ if (typeof ngDevMode === "undefined" || ngDevMode) wrCheckStyles("anchor");
41
43
  if (!this.platform.isBrowser) return;
42
44
  const handler = () => {
43
45
  const y = window.scrollY;
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-anchor.mjs","names":[],"sources":["../../../projects/lib/anchor/anchor.ts","../../../projects/lib/anchor/anchor.html"],"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 { coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n DestroyRef,\n NgZone,\n ViewEncapsulation,\n afterNextRender,\n computed,\n inject,\n input,\n signal,\n} from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrPlatform } from 'ngwr/platform';\nimport { WrScroll } from 'ngwr/scroll';\n\nimport type { WrAnchorLink } from './types';\n\n/**\n * Scroll-spy in-page navigation. Renders a list of links to elements on\n * the page; clicking a link smooth-scrolls (via {@link WrScroll}),\n * and the closest visible heading auto-highlights as the user scrolls.\n *\n * @example\n * ```html\n * <wr-anchor\n * [links]=\"[\n * { id: 'intro', label: 'Introduction' },\n * { id: 'usage', label: 'Usage', children: [\n * { id: 'basic', label: 'Basic' },\n * { id: 'advanced', label: 'Advanced' },\n * ]}\n * ]\"\n * [offset]=\"80\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/anchor\n */\n@Component({\n selector: 'wr-anchor',\n templateUrl: './anchor.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-anchor', role: 'navigation', '[attr.aria-label]': 'resolvedAriaLabel()' },\n})\nexport class WrAnchor {\n /**\n * The entries, in DOCUMENT ORDER — the scroll spy walks them top to bottom\n * and stops at the first target below the cursor line, so a list in any other\n * order highlights the wrong entry.\n */\n readonly links = input<readonly WrAnchorLink[]>([]);\n\n /**\n * Accessible name for the navigation landmark. Falls back to `anchor.label`,\n * then `'Table of contents'` — it used to be that English string, hard-coded\n * on the host, in a landmark a screen reader reads out on every page.\n */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'anchor.label', 'Table of contents');\n\n /** Pixel offset subtracted on scroll — for sticky headers. @default 0 */\n readonly offset = input(0, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 0)) });\n\n /** Highlight an item when its target's top is within this many px of the offset line. @default 80 */\n readonly hitArea = input(80, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 80)) });\n\n protected readonly activeId = signal<string | null>(null);\n\n /**\n * Flat list of the ids the template RENDERS — top level plus one level of\n * children, which is the nesting `WrAnchorLink` documents. It used to recurse\n * all the way down, so a third level was spied on and drawn nowhere: scrolling\n * through a grandchild made it active, no rendered link matched the id, and\n * the highlight vanished for that whole region rather than resting on its\n * parent.\n */\n protected readonly flatIds = computed<readonly string[]>(() => {\n const out: string[] = [];\n for (const item of this.links()) {\n out.push(item.id);\n for (const child of item.children ?? []) out.push(child.id);\n }\n return out;\n });\n\n private readonly scroll = inject(WrScroll);\n private readonly platform = inject(WrPlatform);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n\n /**\n * The entry a click asked for, and the scroll position that click sends the\n * page to. A target near the end of the page can never reach the cursor line —\n * the page runs out of scroll first — so the spy took the highlight straight\n * back from the entry just clicked and handed it to the one above. While the\n * page is still travelling toward that position, or parked on it, the clicked\n * entry stays lit; the first scroll AWAY from it is the reader's own, and the\n * spy takes over again.\n */\n private pinned: { readonly id: string; readonly y: number } | null = null;\n private lastY = 0;\n\n constructor() {\n if (!this.platform.isBrowser) return;\n const handler = (): void => {\n const y = window.scrollY;\n const pin = this.pinned;\n if (pin) {\n const distance = Math.abs(pin.y - y);\n if (distance <= 1 || distance < Math.abs(pin.y - this.lastY)) {\n this.lastY = y;\n if (pin.id !== this.activeId()) this.zone.run(() => this.activeId.set(pin.id));\n return;\n }\n this.pinned = null;\n }\n this.lastY = y;\n const cursor = this.offset() + this.hitArea();\n let active: string | null = null;\n for (const id of this.flatIds()) {\n const el = document.getElementById(id);\n if (!el) continue;\n const top = el.getBoundingClientRect().top;\n if (top <= cursor) active = id;\n else break;\n }\n if (active !== this.activeId()) this.zone.run(() => this.activeId.set(active));\n };\n this.zone.runOutsideAngular(() => window.addEventListener('scroll', handler, { passive: true }));\n this.destroyRef.onDestroy(() => window.removeEventListener('scroll', handler));\n // The first pass has to wait for a render: the targets are elsewhere on the\n // page and are usually rendered AFTER this list, so a constructor-time pass\n // found nothing by `getElementById` and left the page with no highlight at\n // all until the reader happened to scroll.\n afterNextRender(() => handler());\n }\n\n protected onClick(id: string, event: MouseEvent): void {\n event.preventDefault();\n const target = document.getElementById(id);\n if (target) {\n // Where `WrScroll` sends the page, clamped the way the browser clamps it.\n const root = document.documentElement;\n const max = Math.max(0, root.scrollHeight - root.clientHeight);\n const y = target.getBoundingClientRect().top + window.scrollY - this.offset();\n this.pinned = { id, y: Math.min(max, Math.max(0, y)) };\n this.lastY = window.scrollY;\n }\n this.scroll.to(`#${id}`, { offset: this.offset() });\n this.activeId.set(id);\n }\n}\n\nexport type { WrAnchorLink } from './types';\n","<ul class=\"wr-anchor__list\">\n @for (link of links(); track link.id) {\n <li class=\"wr-anchor__item\">\n <a\n class=\"wr-anchor__link\"\n [class.wr-anchor__link--active]=\"activeId() === link.id\"\n [attr.aria-current]=\"activeId() === link.id ? 'location' : null\"\n [href]=\"`#${link.id}`\"\n (click)=\"onClick(link.id, $event)\"\n >\n {{ link.label }}\n </a>\n @if (link.children?.length) {\n <ul class=\"wr-anchor__list wr-anchor__list--nested\">\n @for (child of link.children; track child.id) {\n <li class=\"wr-anchor__item\">\n <a\n class=\"wr-anchor__link\"\n [class.wr-anchor__link--active]=\"activeId() === child.id\"\n [attr.aria-current]=\"activeId() === child.id ? 'location' : null\"\n [href]=\"`#${child.id}`\"\n (click)=\"onClick(child.id, $event)\"\n >\n {{ child.label }}\n </a>\n </li>\n }\n </ul>\n }\n </li>\n }\n</ul>\n"],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-anchor.mjs","names":[],"sources":["../../../projects/lib/anchor/anchor.ts","../../../projects/lib/anchor/anchor.html"],"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 { coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n DestroyRef,\n NgZone,\n ViewEncapsulation,\n afterNextRender,\n computed,\n inject,\n input,\n signal,\n} from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrPlatform } from 'ngwr/platform';\nimport { WrScroll } from 'ngwr/scroll';\nimport { wrCheckStyles } from 'ngwr/utils';\n\nimport type { WrAnchorLink } from './types';\n\n/**\n * Scroll-spy in-page navigation. Renders a list of links to elements on\n * the page; clicking a link smooth-scrolls (via {@link WrScroll}),\n * and the closest visible heading auto-highlights as the user scrolls.\n *\n * @example\n * ```html\n * <wr-anchor\n * [links]=\"[\n * { id: 'intro', label: 'Introduction' },\n * { id: 'usage', label: 'Usage', children: [\n * { id: 'basic', label: 'Basic' },\n * { id: 'advanced', label: 'Advanced' },\n * ]}\n * ]\"\n * [offset]=\"80\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/anchor\n */\n@Component({\n selector: 'wr-anchor',\n templateUrl: './anchor.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-anchor', role: 'navigation', '[attr.aria-label]': 'resolvedAriaLabel()' },\n})\nexport class WrAnchor {\n /**\n * The entries, in DOCUMENT ORDER — the scroll spy walks them top to bottom\n * and stops at the first target below the cursor line, so a list in any other\n * order highlights the wrong entry.\n */\n readonly links = input<readonly WrAnchorLink[]>([]);\n\n /**\n * Accessible name for the navigation landmark. Falls back to `anchor.label`,\n * then `'Table of contents'` — it used to be that English string, hard-coded\n * on the host, in a landmark a screen reader reads out on every page.\n */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'anchor.label', 'Table of contents');\n\n /** Pixel offset subtracted on scroll — for sticky headers. @default 0 */\n readonly offset = input(0, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 0)) });\n\n /** Highlight an item when its target's top is within this many px of the offset line. @default 80 */\n readonly hitArea = input(80, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 80)) });\n\n protected readonly activeId = signal<string | null>(null);\n\n /**\n * Flat list of the ids the template RENDERS — top level plus one level of\n * children, which is the nesting `WrAnchorLink` documents. It used to recurse\n * all the way down, so a third level was spied on and drawn nowhere: scrolling\n * through a grandchild made it active, no rendered link matched the id, and\n * the highlight vanished for that whole region rather than resting on its\n * parent.\n */\n protected readonly flatIds = computed<readonly string[]>(() => {\n const out: string[] = [];\n for (const item of this.links()) {\n out.push(item.id);\n for (const child of item.children ?? []) out.push(child.id);\n }\n return out;\n });\n\n private readonly scroll = inject(WrScroll);\n private readonly platform = inject(WrPlatform);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n\n /**\n * The entry a click asked for, and the scroll position that click sends the\n * page to. A target near the end of the page can never reach the cursor line —\n * the page runs out of scroll first — so the spy took the highlight straight\n * back from the entry just clicked and handed it to the one above. While the\n * page is still travelling toward that position, or parked on it, the clicked\n * entry stays lit; the first scroll AWAY from it is the reader's own, and the\n * spy takes over again.\n */\n private pinned: { readonly id: string; readonly y: number } | null = null;\n private lastY = 0;\n\n constructor() {\n if (typeof ngDevMode === 'undefined' || ngDevMode) wrCheckStyles('anchor');\n if (!this.platform.isBrowser) return;\n const handler = (): void => {\n const y = window.scrollY;\n const pin = this.pinned;\n if (pin) {\n const distance = Math.abs(pin.y - y);\n if (distance <= 1 || distance < Math.abs(pin.y - this.lastY)) {\n this.lastY = y;\n if (pin.id !== this.activeId()) this.zone.run(() => this.activeId.set(pin.id));\n return;\n }\n this.pinned = null;\n }\n this.lastY = y;\n const cursor = this.offset() + this.hitArea();\n let active: string | null = null;\n for (const id of this.flatIds()) {\n const el = document.getElementById(id);\n if (!el) continue;\n const top = el.getBoundingClientRect().top;\n if (top <= cursor) active = id;\n else break;\n }\n if (active !== this.activeId()) this.zone.run(() => this.activeId.set(active));\n };\n this.zone.runOutsideAngular(() => window.addEventListener('scroll', handler, { passive: true }));\n this.destroyRef.onDestroy(() => window.removeEventListener('scroll', handler));\n // The first pass has to wait for a render: the targets are elsewhere on the\n // page and are usually rendered AFTER this list, so a constructor-time pass\n // found nothing by `getElementById` and left the page with no highlight at\n // all until the reader happened to scroll.\n afterNextRender(() => handler());\n }\n\n protected onClick(id: string, event: MouseEvent): void {\n event.preventDefault();\n const target = document.getElementById(id);\n if (target) {\n // Where `WrScroll` sends the page, clamped the way the browser clamps it.\n const root = document.documentElement;\n const max = Math.max(0, root.scrollHeight - root.clientHeight);\n const y = target.getBoundingClientRect().top + window.scrollY - this.offset();\n this.pinned = { id, y: Math.min(max, Math.max(0, y)) };\n this.lastY = window.scrollY;\n }\n this.scroll.to(`#${id}`, { offset: this.offset() });\n this.activeId.set(id);\n }\n}\n\nexport type { WrAnchorLink } from './types';\n","<ul class=\"wr-anchor__list\">\n @for (link of links(); track link.id) {\n <li class=\"wr-anchor__item\">\n <a\n class=\"wr-anchor__link\"\n [class.wr-anchor__link--active]=\"activeId() === link.id\"\n [attr.aria-current]=\"activeId() === link.id ? 'location' : null\"\n [href]=\"`#${link.id}`\"\n (click)=\"onClick(link.id, $event)\"\n >\n {{ link.label }}\n </a>\n @if (link.children?.length) {\n <ul class=\"wr-anchor__list wr-anchor__list--nested\">\n @for (child of link.children; track child.id) {\n <li class=\"wr-anchor__item\">\n <a\n class=\"wr-anchor__link\"\n [class.wr-anchor__link--active]=\"activeId() === child.id\"\n [attr.aria-current]=\"activeId() === child.id ? 'location' : null\"\n [href]=\"`#${child.id}`\"\n (click)=\"onClick(child.id, $event)\"\n >\n {{ child.label }}\n </a>\n </li>\n }\n </ul>\n }\n </li>\n }\n</ul>\n"],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import * as i0 from "@angular/core";
2
- import { Component, ViewEncapsulation, afterNextRender, computed, effect, input, signal, untracked, viewChild } from "@angular/core";
2
+ import { Component, LOCALE_ID, ViewEncapsulation, afterNextRender, computed, effect, inject, input, signal, untracked, viewChild } from "@angular/core";
3
+ import { coerceBooleanProperty } from "@angular/cdk/coercion";
4
+ import { resolveCssSize, wrCheckStyles, wrInitials } from "ngwr/utils";
3
5
  import { useI18nText } from "ngwr/i18n";
4
- import { resolveCssSize } from "ngwr/utils";
5
6
  import { WrSpinner } from "ngwr/spinner";
6
7
  /**
7
8
  * @license
@@ -10,11 +11,41 @@ import { WrSpinner } from "ngwr/spinner";
10
11
  * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
11
12
  */
12
13
  const DEFAULT_SIZE = "6rem";
14
+ const TINTS = [
15
+ "primary",
16
+ "success",
17
+ "warning",
18
+ "danger",
19
+ "info"
20
+ ];
21
+ function hash(text) {
22
+ let h = 2166136261;
23
+ for (let i = 0; i < text.length; i++) {
24
+ h ^= text.charCodeAt(i);
25
+ h = Math.imul(h, 16777619);
26
+ }
27
+ return h >>> 0;
28
+ }
13
29
  var WrAvatar = class WrAvatar {
14
30
  url = input(null, ...ngDevMode ? [{ debugName: "url" }] : /* istanbul ignore next */ []);
15
31
  alt = input(null, ...ngDevMode ? [{ debugName: "alt" }] : /* istanbul ignore next */ []);
32
+ name = input(null, ...ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []);
33
+ tint = input(false, {
34
+ ...ngDevMode ? { debugName: "tint" } : /* istanbul ignore next */ {},
35
+ transform: coerceBooleanProperty
36
+ });
16
37
  decorative = computed(() => this.alt() === "", ...ngDevMode ? [{ debugName: "decorative" }] : /* istanbul ignore next */ []);
17
- namedAlt = useI18nText(this.alt, "avatar.alt", "Avatar");
38
+ locale = inject(LOCALE_ID);
39
+ namedAlt = useI18nText(computed(() => {
40
+ const name = this.name()?.trim();
41
+ if (this.alt() === null && !name) return null;
42
+ return this.alt() ?? name;
43
+ }), "avatar.alt", "Avatar");
44
+ initials = computed(() => wrInitials(this.name(), this.locale), ...ngDevMode ? [{ debugName: "initials" }] : /* istanbul ignore next */ []);
45
+ tintName = computed(() => {
46
+ if (!this.tint() || !this.initials()) return null;
47
+ return TINTS[hash(this.name().trim()) % TINTS.length];
48
+ }, ...ngDevMode ? [{ debugName: "tintName" }] : /* istanbul ignore next */ []);
18
49
  resolvedAlt = computed(() => this.decorative() ? "" : this.namedAlt(), ...ngDevMode ? [{ debugName: "resolvedAlt" }] : /* istanbul ignore next */ []);
19
50
  shape = input("rounded", ...ngDevMode ? [{ debugName: "shape" }] : /* istanbul ignore next */ []);
20
51
  size = input(DEFAULT_SIZE, ...ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []);
@@ -22,6 +53,7 @@ var WrAvatar = class WrAvatar {
22
53
  failed = signal(false, ...ngDevMode ? [{ debugName: "failed" }] : /* istanbul ignore next */ []);
23
54
  img = viewChild("img", ...ngDevMode ? [{ debugName: "img" }] : /* istanbul ignore next */ []);
24
55
  constructor() {
56
+ if (typeof ngDevMode === "undefined" || ngDevMode) wrCheckStyles("avatar");
25
57
  effect(() => {
26
58
  this.url();
27
59
  untracked(() => {
@@ -46,7 +78,8 @@ var WrAvatar = class WrAvatar {
46
78
  if (result.pxValue == null || result.pxValue <= 0) return resolveCssSize(DEFAULT_SIZE, { defaultValue: DEFAULT_SIZE });
47
79
  return result;
48
80
  }, ...ngDevMode ? [{ debugName: "resolved" }] : /* istanbul ignore next */ []);
49
- fallbackHidden = computed(() => !!this.url() && !this.failed() && (this.decorative() || !!this.alt()?.trim()), ...ngDevMode ? [{ debugName: "fallbackHidden" }] : /* istanbul ignore next */ []);
81
+ fallbackHidden = computed(() => !!this.url() && !this.failed() && (this.decorative() || !!this.alt()?.trim() || !!this.name()?.trim()), ...ngDevMode ? [{ debugName: "fallbackHidden" }] : /* istanbul ignore next */ []);
82
+ initialsHidden = computed(() => this.fallbackHidden() || this.decorative(), ...ngDevMode ? [{ debugName: "initialsHidden" }] : /* istanbul ignore next */ []);
50
83
  cssSize = computed(() => this.resolved().cssValue, ...ngDevMode ? [{ debugName: "cssSize" }] : /* istanbul ignore next */ []);
51
84
  pxSize = computed(() => this.resolved().pxValue, ...ngDevMode ? [{ debugName: "pxSize" }] : /* istanbul ignore next */ []);
52
85
  classes = computed(() => {
@@ -54,6 +87,8 @@ var WrAvatar = class WrAvatar {
54
87
  const shape = this.shape();
55
88
  if (shape !== "rounded") parts.push(`wr-avatar--${shape}`);
56
89
  if (this.loaded()) parts.push("wr-avatar--loaded");
90
+ const tint = this.tintName();
91
+ if (tint) parts.push(`wr-avatar--tint-${tint}`);
57
92
  return parts.join(" ");
58
93
  }, ...ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []);
59
94
  onImageLoad() {
@@ -91,6 +126,20 @@ var WrAvatar = class WrAvatar {
91
126
  isRequired: false,
92
127
  transformFunction: null
93
128
  },
129
+ name: {
130
+ classPropertyName: "name",
131
+ publicName: "name",
132
+ isSignal: true,
133
+ isRequired: false,
134
+ transformFunction: null
135
+ },
136
+ tint: {
137
+ classPropertyName: "tint",
138
+ publicName: "tint",
139
+ isSignal: true,
140
+ isRequired: false,
141
+ transformFunction: null
142
+ },
94
143
  shape: {
95
144
  classPropertyName: "shape",
96
145
  publicName: "shape",
@@ -120,7 +169,7 @@ var WrAvatar = class WrAvatar {
120
169
  isSignal: true
121
170
  }],
122
171
  ngImport: i0,
123
- template: "<span class=\"wr-avatar__content\" [attr.aria-hidden]=\"fallbackHidden() ? 'true' : null\"><ng-content /></span>\n\n@if (url(); as src) {\n @if (!failed()) {\n <img\n #img\n class=\"wr-avatar__img\"\n [src]=\"src\"\n [alt]=\"resolvedAlt()\"\n [attr.width]=\"pxSize()\"\n [attr.height]=\"pxSize()\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError()\"\n decoding=\"async\"\n />\n\n @if (!loaded()) {\n <wr-spinner class=\"wr-avatar__spin\" />\n }\n } @else if (resolvedAlt()) {\n <!--\n The broken <img> is removed so the fallback can show, and its `alt` went with\n it: with no initials projected, the avatar was left with no name at all. With\n initials, the stylesheet hides this and they name the avatar, as before.\n -->\n <span class=\"wr-avatar__alt\" role=\"img\" [attr.aria-label]=\"resolvedAlt()\"></span>\n }\n}\n",
172
+ template: "<span class=\"wr-avatar__content\" [attr.aria-hidden]=\"fallbackHidden() ? 'true' : null\"><ng-content /></span>\n\n@if (initials(); as letters) {\n <!--\n The `name` initials. After the projected content, which the stylesheet lets\n win when there is any; named by the person rather than by the letters.\n -->\n <span\n class=\"wr-avatar__initials\"\n role=\"img\"\n [attr.aria-label]=\"resolvedAlt()\"\n [attr.aria-hidden]=\"initialsHidden() ? 'true' : null\"\n >{{ letters }}</span\n >\n}\n\n@if (url(); as src) {\n @if (!failed()) {\n <img\n #img\n class=\"wr-avatar__img\"\n [src]=\"src\"\n [alt]=\"resolvedAlt()\"\n [attr.width]=\"pxSize()\"\n [attr.height]=\"pxSize()\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError()\"\n decoding=\"async\"\n />\n\n @if (!loaded()) {\n <wr-spinner class=\"wr-avatar__spin\" />\n }\n } @else if (resolvedAlt()) {\n <!--\n The broken <img> is removed so the fallback can show, and its `alt` went with\n it: with no initials projected, the avatar was left with no name at all. With\n initials, the stylesheet hides this and they name the avatar, as before.\n -->\n <span class=\"wr-avatar__alt\" role=\"img\" [attr.aria-label]=\"resolvedAlt()\"></span>\n }\n}\n",
124
173
  dependencies: [{
125
174
  kind: "component",
126
175
  type: WrSpinner,
@@ -147,7 +196,7 @@ i0.ɵɵngDeclareClassMetadata({
147
196
  "[style.--wr-avatar-size]": "cssSize()"
148
197
  },
149
198
  imports: [WrSpinner],
150
- template: "<span class=\"wr-avatar__content\" [attr.aria-hidden]=\"fallbackHidden() ? 'true' : null\"><ng-content /></span>\n\n@if (url(); as src) {\n @if (!failed()) {\n <img\n #img\n class=\"wr-avatar__img\"\n [src]=\"src\"\n [alt]=\"resolvedAlt()\"\n [attr.width]=\"pxSize()\"\n [attr.height]=\"pxSize()\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError()\"\n decoding=\"async\"\n />\n\n @if (!loaded()) {\n <wr-spinner class=\"wr-avatar__spin\" />\n }\n } @else if (resolvedAlt()) {\n <!--\n The broken <img> is removed so the fallback can show, and its `alt` went with\n it: with no initials projected, the avatar was left with no name at all. With\n initials, the stylesheet hides this and they name the avatar, as before.\n -->\n <span class=\"wr-avatar__alt\" role=\"img\" [attr.aria-label]=\"resolvedAlt()\"></span>\n }\n}\n"
199
+ template: "<span class=\"wr-avatar__content\" [attr.aria-hidden]=\"fallbackHidden() ? 'true' : null\"><ng-content /></span>\n\n@if (initials(); as letters) {\n <!--\n The `name` initials. After the projected content, which the stylesheet lets\n win when there is any; named by the person rather than by the letters.\n -->\n <span\n class=\"wr-avatar__initials\"\n role=\"img\"\n [attr.aria-label]=\"resolvedAlt()\"\n [attr.aria-hidden]=\"initialsHidden() ? 'true' : null\"\n >{{ letters }}</span\n >\n}\n\n@if (url(); as src) {\n @if (!failed()) {\n <img\n #img\n class=\"wr-avatar__img\"\n [src]=\"src\"\n [alt]=\"resolvedAlt()\"\n [attr.width]=\"pxSize()\"\n [attr.height]=\"pxSize()\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError()\"\n decoding=\"async\"\n />\n\n @if (!loaded()) {\n <wr-spinner class=\"wr-avatar__spin\" />\n }\n } @else if (resolvedAlt()) {\n <!--\n The broken <img> is removed so the fallback can show, and its `alt` went with\n it: with no initials projected, the avatar was left with no name at all. With\n initials, the stylesheet hides this and they name the avatar, as before.\n -->\n <span class=\"wr-avatar__alt\" role=\"img\" [attr.aria-label]=\"resolvedAlt()\"></span>\n }\n}\n"
151
200
  }]
152
201
  }],
153
202
  ctorParameters: () => [],
@@ -168,6 +217,22 @@ i0.ɵɵngDeclareClassMetadata({
168
217
  required: false
169
218
  }]
170
219
  }],
220
+ name: [{
221
+ type: i0.Input,
222
+ args: [{
223
+ isSignal: true,
224
+ alias: "name",
225
+ required: false
226
+ }]
227
+ }],
228
+ tint: [{
229
+ type: i0.Input,
230
+ args: [{
231
+ isSignal: true,
232
+ alias: "tint",
233
+ required: false
234
+ }]
235
+ }],
171
236
  shape: [{
172
237
  type: i0.Input,
173
238
  args: [{
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-avatar.mjs","names":[],"sources":["../../../projects/lib/avatar/avatar.ts","../../../projects/lib/avatar/avatar.html"],"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 {\n Component,\n type ElementRef,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n input,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrSpinner } from 'ngwr/spinner';\nimport { resolveCssSize, type ResolvedCssSize } from 'ngwr/utils';\n\nimport type { WrAvatarShape, WrAvatarSize } from './types';\n\nconst DEFAULT_SIZE: WrAvatarSize = '6rem';\n\n/**\n * Image avatar with configurable corner treatment.\n *\n * Projected content (typically initials) is the FALLBACK: it shows with no\n * `url`, while the image loads and after it fails, and leaves the paint once the\n * image has loaded. It is projected into a `.wr-avatar__content` wrapper, so a\n * `wr-avatar > …` child selector no longer reaches it. While it is there, no\n * spinner is drawn over it; with nothing projected, the spinner marks the\n * loading state instead. See `alt` for what a screen reader hears.\n *\n * Projected initials take the text size around the avatar, capped at\n * `--wr-avatar-initials-scale` (`0.375`) of the box, so they shrink only where\n * they would not fit: a 16px avatar in a 14px chip draws 6px letters, while a\n * `6rem` one keeps the surrounding size up to 36px and is capped only past it.\n * Anything projected that is sized in `em` follows the cap too — a `<wr-icon>`\n * at its default `1.2em` included — so pin a `px` or `rem` size on the projected\n * element, or override the hook, to keep it. A `font-size` rule on the avatar\n * itself still wins, unless it sits in a cascade layer (Tailwind v4's utilities\n * do), which ngwr's unlayered stylesheet beats whatever the selector.\n *\n * The initials are inert: `pointer-events: none` hands a click on them to whatever\n * holds the avatar, and `user-select: none` keeps them out of a text selection.\n *\n * @example\n * ```html\n * <wr-avatar url=\"/me.png\" alt=\"Roman\" size=\"3rem\" shape=\"circle\" />\n * <wr-avatar [url]=\"photo\" alt=\"Roman Khegay\" [size]=\"48\">RK</wr-avatar>\n * ```\n *\n * @see https://ngwr.dev/reference/components/avatar\n */\n@Component({\n selector: 'wr-avatar',\n templateUrl: './avatar.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n '[style.width]': 'cssSize()',\n '[style.height]': 'cssSize()',\n // What the stylesheet sizes projected initials against. Always px or rem: a\n // value `resolveCssSize` has no pixels for falls back to the default box.\n '[style.--wr-avatar-size]': 'cssSize()',\n },\n imports: [WrSpinner],\n})\nexport class WrAvatar {\n /**\n * Image URL. Projected content (e.g. initials) shows while it is unset,\n * loading or broken, and gives way once the image has loaded.\n *\n * @default null\n */\n readonly url = input<string | null>(null);\n\n /**\n * Alt text for the image. Falls back to `avatar.alt`, then `'Avatar'` — pass\n * the person's name where you have it, since that is what a reader wants.\n *\n * With an `alt`, projected initials are hidden from assistive technology while\n * an image is in play, so the name is announced once. Without one, the image\n * carries only the generic name, so the initials stay announced beside it —\n * visually hidden once it has loaded, never dropped.\n *\n * `alt=\"\"` marks the avatar decorative, as it does on an `<img>`: the image is\n * silent and so are the initials while an image is in play — for an avatar beside\n * a name that is already printed. After a failed load with nothing projected, the\n * `alt` is still announced, so a broken image never leaves the avatar nameless.\n *\n * @default null\n */\n readonly alt = input<string | null>(null);\n\n /** `alt=\"\"`, given on purpose — see `alt`. */\n private readonly decorative = computed(() => this.alt() === '');\n\n private readonly namedAlt = useI18nText(this.alt, 'avatar.alt', 'Avatar');\n\n /** The image's text alternative: empty when decorative, else the catalog's fallback for an unset one. */\n protected readonly resolvedAlt = computed(() => (this.decorative() ? '' : this.namedAlt()));\n\n /**\n * Corner treatment. `rounded` (default) is a soft rounded square,\n * `circle` is the classic profile avatar, `squircle` is the iOS look.\n *\n * @default 'rounded'\n */\n readonly shape = input<WrAvatarShape>('rounded');\n\n /**\n * Box size. See {@link WrAvatarSize} for accepted values. The resolved value is\n * published on the host as `--wr-avatar-size`.\n *\n * @default '6rem'\n */\n readonly size = input<WrAvatarSize>(DEFAULT_SIZE);\n\n protected readonly loaded = signal(false);\n\n /**\n * Whether the image failed. Without this the spinner spun forever on a broken\n * URL — the one state where an avatar is guaranteed to have projected initials\n * worth showing instead.\n */\n protected readonly failed = signal(false);\n\n private readonly img = viewChild<ElementRef<HTMLImageElement>>('img');\n\n constructor() {\n // A new URL is a new attempt: after a failure the fallback would otherwise\n // stay put for an image that loads perfectly well.\n effect(() => {\n this.url();\n untracked(() => {\n this.loaded.set(false);\n this.failed.set(false);\n });\n });\n\n afterNextRender(() => this.adoptSettledImage());\n }\n\n /**\n * A prerendered `<img>` starts loading when the browser parses the HTML, and\n * `(load)` / `(error)` are only attached at hydration — so on a slow bundle the\n * image settles first, both events go nowhere, and the avatar sits on its\n * fallback over a photo it already has (or names a broken image it never\n * learns about). Only the FIRST render can race: every later `<img>` is\n * created on the client with its listeners in place before `src` is set.\n */\n private adoptSettledImage(): void {\n const img = this.img()?.nativeElement;\n if (!img?.complete || this.loaded() || this.failed()) return;\n\n if (img.naturalWidth > 0) {\n this.loaded.set(true);\n return;\n }\n\n // Zero wide is a broken image, or an SVG with no intrinsic size in an engine\n // that reports it as 0 — only the element can tell. Setting `src` to the\n // value it already has makes it fire `load` or `error` again, to listeners\n // that exist now.\n const src = img.getAttribute('src');\n if (src !== null) img.setAttribute('src', src);\n }\n\n protected readonly resolved = computed<ResolvedCssSize>(() => {\n const result = resolveCssSize(this.size(), { defaultValue: DEFAULT_SIZE });\n if (result.pxValue == null || result.pxValue <= 0) {\n return resolveCssSize(DEFAULT_SIZE, { defaultValue: DEFAULT_SIZE });\n }\n return result;\n });\n\n /**\n * Whether the projected fallback is `aria-hidden`: an `<img>` is in the DOM\n * (loading or loaded) AND the consumer named it, or marked it decorative with\n * `alt=\"\"`. Then the image's `alt` owns the name, announced once and unchanged\n * at the moment the photo arrives. Without a real `alt` the image is only\n * \"Avatar\", and hiding the initials would drop the one thing that says who\n * this is.\n *\n * The stylesheet keys on the same attribute once `--loaded`: a hidden fallback\n * leaves the paint through `visibility`, a named-by-initials one is clipped and\n * stays in the accessibility tree. While loading it is painted either way.\n */\n protected readonly fallbackHidden = computed(\n () => !!this.url() && !this.failed() && (this.decorative() || !!this.alt()?.trim())\n );\n\n protected readonly cssSize = computed(() => this.resolved().cssValue);\n protected readonly pxSize = computed(() => this.resolved().pxValue);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-avatar'];\n const shape = this.shape();\n if (shape !== 'rounded') parts.push(`wr-avatar--${shape}`);\n if (this.loaded()) parts.push('wr-avatar--loaded');\n return parts.join(' ');\n });\n\n protected onImageLoad(): void {\n this.loaded.set(true);\n }\n\n protected onImageError(): void {\n this.failed.set(true);\n }\n}\n","<span class=\"wr-avatar__content\" [attr.aria-hidden]=\"fallbackHidden() ? 'true' : null\"><ng-content /></span>\n\n@if (url(); as src) {\n @if (!failed()) {\n <img\n #img\n class=\"wr-avatar__img\"\n [src]=\"src\"\n [alt]=\"resolvedAlt()\"\n [attr.width]=\"pxSize()\"\n [attr.height]=\"pxSize()\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError()\"\n decoding=\"async\"\n />\n\n @if (!loaded()) {\n <wr-spinner class=\"wr-avatar__spin\" />\n }\n } @else if (resolvedAlt()) {\n <!--\n The broken <img> is removed so the fallback can show, and its `alt` went with\n it: with no initials projected, the avatar was left with no name at all. With\n initials, the stylesheet hides this and they name the avatar, as before.\n -->\n <span class=\"wr-avatar__alt\" role=\"img\" [attr.aria-label]=\"resolvedAlt()\"></span>\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-avatar.mjs","names":[],"sources":["../../../projects/lib/avatar/avatar.ts","../../../projects/lib/avatar/avatar.html"],"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 type ElementRef,\n LOCALE_ID,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrSpinner } from 'ngwr/spinner';\nimport { resolveCssSize, type ResolvedCssSize, wrCheckStyles, wrInitials } from 'ngwr/utils';\n\nimport type { WrAvatarShape, WrAvatarSize } from './types';\n\nconst DEFAULT_SIZE: WrAvatarSize = '6rem';\n\n/**\n * The tints `tint` picks from, each a `-soft` fill under its `-ink` text. Index\n * order is part of the contract: the same name lands on the same tint on every\n * render, server or client, and a reorder would re-colour every avatar.\n */\nconst TINTS = ['primary', 'success', 'warning', 'danger', 'info'] as const;\n\n/** FNV-1a over the UTF-16 units: small, dependency-free and the same everywhere. */\nfunction hash(text: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < text.length; i++) {\n // eslint-disable-next-line no-bitwise -- the hash is defined on bits\n h ^= text.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n // eslint-disable-next-line no-bitwise -- to an unsigned 32-bit integer\n return h >>> 0;\n}\n\n/**\n * Image avatar with configurable corner treatment.\n *\n * Projected content (typically initials) is the FALLBACK: it shows with no\n * `url`, while the image loads and after it fails, and leaves the paint once the\n * image has loaded. It is projected into a `.wr-avatar__content` wrapper, so a\n * `wr-avatar > …` child selector no longer reaches it. While it is there, no\n * spinner is drawn over it; with nothing projected, the spinner marks the\n * loading state instead. See `alt` for what a screen reader hears.\n *\n * `name` is the shorter route to the same avatar: it names the image, draws the\n * person's initials (`wrInitials` from `ngwr/utils`) as the fallback, and with\n * `tint` colours the box from a hash of the name. The fallback order is\n * projected content, then the `name` initials, then the spinner: the initials\n * are drawn only when nothing is projected.\n *\n * Projected initials take the text size around the avatar, capped at\n * `--wr-avatar-initials-scale` (`0.375`) of the box, so they shrink only where\n * they would not fit: a 16px avatar in a 14px chip draws 6px letters, while a\n * `6rem` one keeps the surrounding size up to 36px and is capped only past it.\n * Anything projected that is sized in `em` follows the cap too — a `<wr-icon>`\n * at its default `1.2em` included — so pin a `px` or `rem` size on the projected\n * element, or override the hook, to keep it. A `font-size` rule on the avatar\n * itself still wins, unless it sits in a cascade layer (Tailwind v4's utilities\n * do), which ngwr's unlayered stylesheet beats whatever the selector.\n *\n * The initials are inert: `pointer-events: none` hands a click on them to whatever\n * holds the avatar, and `user-select: none` keeps them out of a text selection.\n *\n * @example\n * ```html\n * <wr-avatar url=\"/me.png\" alt=\"Roman\" size=\"3rem\" shape=\"circle\" />\n * <wr-avatar [url]=\"photo\" alt=\"Roman Khegay\" [size]=\"48\">RK</wr-avatar>\n * <wr-avatar [url]=\"photo\" name=\"Roman Khegay\" [size]=\"48\" tint />\n * ```\n *\n * @see https://ngwr.dev/reference/components/avatar\n */\n@Component({\n selector: 'wr-avatar',\n templateUrl: './avatar.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n '[style.width]': 'cssSize()',\n '[style.height]': 'cssSize()',\n // What the stylesheet sizes projected initials against. Always px or rem: a\n // value `resolveCssSize` has no pixels for falls back to the default box.\n '[style.--wr-avatar-size]': 'cssSize()',\n },\n imports: [WrSpinner],\n})\nexport class WrAvatar {\n /**\n * Image URL. Projected content (e.g. initials) shows while it is unset,\n * loading or broken, and gives way once the image has loaded.\n *\n * @default null\n */\n readonly url = input<string | null>(null);\n\n /**\n * Alt text for the image. Falls back to `avatar.alt`, then `'Avatar'` — pass\n * the person's name where you have it, since that is what a reader wants.\n *\n * With an `alt`, projected initials are hidden from assistive technology while\n * an image is in play, so the name is announced once. Without one, the image\n * carries only the generic name, so the initials stay announced beside it —\n * visually hidden once it has loaded, never dropped.\n *\n * `alt=\"\"` marks the avatar decorative, as it does on an `<img>`: the image is\n * silent and so are the initials while an image is in play — for an avatar beside\n * a name that is already printed. After a failed load with nothing projected, the\n * `alt` is still announced, so a broken image never leaves the avatar nameless.\n *\n * @default null\n */\n readonly alt = input<string | null>(null);\n\n /**\n * The person's name. It is the image's alt text when `alt` is not given\n * (`alt` wins when both are, `alt=\"\"` included), and its initials are the\n * fallback when nothing is projected: shown with no `url`, while the image\n * loads and after it fails, hidden once the image has loaded. Announced as the\n * name rather than as two letters, and once only beside an image. A name with\n * no initial (`'...'`) draws nothing and leaves the spinner to the image.\n *\n * @default null\n */\n readonly name = input<string | null>(null);\n\n /**\n * Colour the initials avatar from a stable hash of `name`: the same name is\n * the same tint on every render, server and client alike, and a new name is a\n * new tint. Five tints, each an intent's `-soft` fill under its `-ink` text,\n * so the initials reach 4.5:1 in both schemes. Nothing without `name`, and\n * nothing while projected content stands in for the initials.\n *\n * @default false\n */\n readonly tint = input(false, { transform: coerceBooleanProperty });\n\n /** `alt=\"\"`, given on purpose — see `alt`. */\n private readonly decorative = computed(() => this.alt() === '');\n\n private readonly locale = inject(LOCALE_ID);\n\n private readonly namedAlt = useI18nText(\n computed(() => {\n const name = this.name()?.trim();\n // A blank name is no name: the catalog fallback, as with no name at all.\n if (this.alt() === null && !name) return null;\n return this.alt() ?? name;\n }),\n 'avatar.alt',\n 'Avatar'\n );\n\n /** The initials `name` yields, `''` when it yields none. */\n protected readonly initials = computed(() => wrInitials(this.name(), this.locale));\n\n /** The tint modifier's intent, or `null` with `tint` off or no initials to colour. */\n private readonly tintName = computed(() => {\n if (!this.tint() || !this.initials()) return null;\n return TINTS[hash(this.name()!.trim()) % TINTS.length];\n });\n\n /** The image's text alternative: empty when decorative, else the catalog's fallback for an unset one. */\n protected readonly resolvedAlt = computed(() => (this.decorative() ? '' : this.namedAlt()));\n\n /**\n * Corner treatment. `rounded` (default) is a soft rounded square,\n * `circle` is the classic profile avatar, `squircle` is the iOS look.\n *\n * @default 'rounded'\n */\n readonly shape = input<WrAvatarShape>('rounded');\n\n /**\n * Box size. See {@link WrAvatarSize} for accepted values. The resolved value is\n * published on the host as `--wr-avatar-size`.\n *\n * @default '6rem'\n */\n readonly size = input<WrAvatarSize>(DEFAULT_SIZE);\n\n protected readonly loaded = signal(false);\n\n /**\n * Whether the image failed. Without this the spinner spun forever on a broken\n * URL — the one state where an avatar is guaranteed to have projected initials\n * worth showing instead.\n */\n protected readonly failed = signal(false);\n\n private readonly img = viewChild<ElementRef<HTMLImageElement>>('img');\n\n constructor() {\n if (typeof ngDevMode === 'undefined' || ngDevMode) wrCheckStyles('avatar');\n // A new URL is a new attempt: after a failure the fallback would otherwise\n // stay put for an image that loads perfectly well.\n effect(() => {\n this.url();\n untracked(() => {\n this.loaded.set(false);\n this.failed.set(false);\n });\n });\n\n afterNextRender(() => this.adoptSettledImage());\n }\n\n /**\n * A prerendered `<img>` starts loading when the browser parses the HTML, and\n * `(load)` / `(error)` are only attached at hydration — so on a slow bundle the\n * image settles first, both events go nowhere, and the avatar sits on its\n * fallback over a photo it already has (or names a broken image it never\n * learns about). Only the FIRST render can race: every later `<img>` is\n * created on the client with its listeners in place before `src` is set.\n */\n private adoptSettledImage(): void {\n const img = this.img()?.nativeElement;\n if (!img?.complete || this.loaded() || this.failed()) return;\n\n if (img.naturalWidth > 0) {\n this.loaded.set(true);\n return;\n }\n\n // Zero wide is a broken image, or an SVG with no intrinsic size in an engine\n // that reports it as 0 — only the element can tell. Setting `src` to the\n // value it already has makes it fire `load` or `error` again, to listeners\n // that exist now.\n const src = img.getAttribute('src');\n if (src !== null) img.setAttribute('src', src);\n }\n\n protected readonly resolved = computed<ResolvedCssSize>(() => {\n const result = resolveCssSize(this.size(), { defaultValue: DEFAULT_SIZE });\n if (result.pxValue == null || result.pxValue <= 0) {\n return resolveCssSize(DEFAULT_SIZE, { defaultValue: DEFAULT_SIZE });\n }\n return result;\n });\n\n /**\n * Whether the projected fallback is `aria-hidden`: an `<img>` is in the DOM\n * (loading or loaded) AND the consumer named it, or marked it decorative with\n * `alt=\"\"`. Then the image's `alt` owns the name, announced once and unchanged\n * at the moment the photo arrives. Without a real `alt` the image is only\n * \"Avatar\", and hiding the initials would drop the one thing that says who\n * this is.\n *\n * The stylesheet keys on the same attribute once `--loaded`: a hidden fallback\n * leaves the paint through `visibility`, a named-by-initials one is clipped and\n * stays in the accessibility tree. While loading it is painted either way.\n */\n protected readonly fallbackHidden = computed(\n () => !!this.url() && !this.failed() && (this.decorative() || !!this.alt()?.trim() || !!this.name()?.trim())\n );\n\n /**\n * The `name` initials follow the projected fallback, and go silent for a\n * decorative avatar too: with `alt=\"\"` there is a name printed beside it, and\n * two letters read on top of it would say it twice.\n */\n protected readonly initialsHidden = computed(() => this.fallbackHidden() || this.decorative());\n\n protected readonly cssSize = computed(() => this.resolved().cssValue);\n protected readonly pxSize = computed(() => this.resolved().pxValue);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-avatar'];\n const shape = this.shape();\n if (shape !== 'rounded') parts.push(`wr-avatar--${shape}`);\n if (this.loaded()) parts.push('wr-avatar--loaded');\n const tint = this.tintName();\n if (tint) parts.push(`wr-avatar--tint-${tint}`);\n return parts.join(' ');\n });\n\n protected onImageLoad(): void {\n this.loaded.set(true);\n }\n\n protected onImageError(): void {\n this.failed.set(true);\n }\n}\n","<span class=\"wr-avatar__content\" [attr.aria-hidden]=\"fallbackHidden() ? 'true' : null\"><ng-content /></span>\n\n@if (initials(); as letters) {\n <!--\n The `name` initials. After the projected content, which the stylesheet lets\n win when there is any; named by the person rather than by the letters.\n -->\n <span\n class=\"wr-avatar__initials\"\n role=\"img\"\n [attr.aria-label]=\"resolvedAlt()\"\n [attr.aria-hidden]=\"initialsHidden() ? 'true' : null\"\n >{{ letters }}</span\n >\n}\n\n@if (url(); as src) {\n @if (!failed()) {\n <img\n #img\n class=\"wr-avatar__img\"\n [src]=\"src\"\n [alt]=\"resolvedAlt()\"\n [attr.width]=\"pxSize()\"\n [attr.height]=\"pxSize()\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError()\"\n decoding=\"async\"\n />\n\n @if (!loaded()) {\n <wr-spinner class=\"wr-avatar__spin\" />\n }\n } @else if (resolvedAlt()) {\n <!--\n The broken <img> is removed so the fallback can show, and its `alt` went with\n it: with no initials projected, the avatar was left with no name at all. With\n initials, the stylesheet hides this and they name the avatar, as before.\n -->\n <span class=\"wr-avatar__alt\" role=\"img\" [attr.aria-label]=\"resolvedAlt()\"></span>\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import { Component, DestroyRef, NgZone, ViewEncapsulation, computed, inject, input, signal } from "@angular/core";
3
3
  import { coerceNumberProperty } from "@angular/cdk/coercion";
4
+ import { wrCheckStyles } from "ngwr/utils";
4
5
  import { useI18nText } from "ngwr/i18n";
5
6
  import { WrPlatform } from "ngwr/platform";
6
7
  import { WrScroll } from "ngwr/scroll";
@@ -32,6 +33,7 @@ var WrBackTop = class WrBackTop {
32
33
  return parts.join(" ");
33
34
  }, ...ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []);
34
35
  constructor() {
36
+ if (typeof ngDevMode === "undefined" || ngDevMode) wrCheckStyles("back-top");
35
37
  if (!this.platform.isBrowser) return;
36
38
  const handler = () => {
37
39
  const should = window.scrollY > this.visibilityThreshold();
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-back-top.mjs","names":[],"sources":["../../../projects/lib/back-top/back-top.ts","../../../projects/lib/back-top/back-top.html"],"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 { coerceNumberProperty } from '@angular/cdk/coercion';\nimport { Component, DestroyRef, NgZone, ViewEncapsulation, computed, inject, input, signal } from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrPlatform } from 'ngwr/platform';\nimport { WrScroll } from 'ngwr/scroll';\n\n/**\n * Pinned floating button that scrolls the page back to the top. Appears\n * once the user has scrolled past `visibilityThreshold` pixels.\n *\n * @example\n * ```html\n * <wr-back-top />\n *\n * <wr-back-top visibilityThreshold=\"600\" [offset]=\"80\">\n * <svg>…custom icon…</svg>\n * </wr-back-top>\n * ```\n *\n * @see https://ngwr.dev/reference/components/back-top\n */\n@Component({\n selector: 'wr-back-top',\n templateUrl: './back-top.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n '[attr.aria-hidden]': '!visible()',\n // `aria-hidden` alone left a 44px button that focus still landed on: invisible\n // (opacity 0) but in the tab order, and unannounceable once it got there.\n // `inert` is what actually takes it out of both.\n '[attr.inert]': \"visible() ? null : ''\",\n },\n})\nexport class WrBackTop {\n /** Show the button once `window.scrollY` exceeds this many pixels. @default 400 */\n readonly visibilityThreshold = input(400, {\n transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 400)),\n });\n\n /** Pixel offset subtracted from the scroll target — for sticky headers. @default 0 */\n readonly offset = input(0, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 0)) });\n\n /** Accessible label for the button. Falls back to `backTop.label`, then `'Back to top'`. */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'backTop.label', 'Back to top');\n\n protected readonly visible = signal(false);\n\n private readonly scroll = inject(WrScroll);\n private readonly platform = inject(WrPlatform);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-back-top'];\n if (this.visible()) parts.push('wr-back-top--visible');\n return parts.join(' ');\n });\n\n constructor() {\n if (!this.platform.isBrowser) return;\n const handler = (): void => {\n const should = window.scrollY > this.visibilityThreshold();\n if (should !== this.visible()) this.zone.run(() => this.visible.set(should));\n };\n this.zone.runOutsideAngular(() => window.addEventListener('scroll', handler, { passive: true }));\n // Initial check (page might already be scrolled), and it is SYNCHRONOUS on\n // purpose. `window.scrollY` forces a style + layout flush, which is what\n // makes constructing N of these quadratic — 1000 instances cost 90ms and\n // 5000 cost 1640ms, of which ~95% is the flush: replacing `scrollY` with a\n // plain property drops 5000 to 83ms with all 5000 scroll listeners still\n // registered, so the per-instance listener is not the cost it looks like.\n // The bulk figure is nobody's bill — a page has one back-to-top button, and\n // one instance measures 0.03ms over an equivalent component. Deferring to\n // `afterNextRender` would land the signal a change-detection pass later, so\n // a page restored part way down would fade the button in from `opacity: 0`\n // instead of painting it in place. That first paint is the contract.\n handler();\n this.destroyRef.onDestroy(() => window.removeEventListener('scroll', handler));\n }\n\n protected onClick(): void {\n this.scroll.toTop({ offset: this.offset() });\n }\n}\n","<button type=\"button\" class=\"wr-back-top__button\" [attr.aria-label]=\"resolvedAriaLabel()\" (click)=\"onClick()\">\n <!-- The arrow is FALLBACK content: rendered next to a projected icon rather\n than instead of it, the documented custom-icon example put two glyphs in\n one 44px circle. -->\n <ng-content>\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" aria-hidden=\"true\">\n <path\n d=\"M9 4 L9 14 M4 9 L9 4 L14 9\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </ng-content>\n</button>\n"],"mappings":";;;;;;;;;;;;AA0CA,IAAa,YAAb,MAAa,UAAS;CAEX,sBAAsB,MAAM,KAAG;EAAA,GAAA,YAAA,EAAA,WAAA,sBAAA,+BAAA,CAAA;EACtC,YAAY,MAAuB,KAAK,IAAI,GAAG,qBAAqB,GAAG,GAAG,CAAC;CAAC,CAAA;CAIrE,SAAS,MAAM,GAAC;EAAA,GAAA,YAAA,EAAA,WAAA,SAAA,+BAAA,CAAA;EAAI,YAAY,MAAuB,KAAK,IAAI,GAAG,qBAAqB,GAAG,CAAC,CAAC;CAAC,CAAA;CAG9F,YAAY,MAAqB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAE5B,oBAAoB,YAAY,KAAK,WAAW,iBAAiB,aAAa;CAE9E,UAAU,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAEzB,SAAS,OAAO,QAAQ;CACxB,WAAW,OAAO,UAAU;CAC5B,OAAO,OAAO,MAAM;CACpB,aAAa,OAAO,UAAU;CAE5B,UAAU,eAAc;EACzC,MAAM,QAAQ,CAAC,aAAa;EAC5B,IAAI,KAAK,QAAO,GAAI,MAAM,KAAK,sBAAsB;EACrD,OAAO,MAAM,KAAK,GAAG;CACvB,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAEF,cAAA;EACE,IAAI,CAAC,KAAK,SAAS,WAAW;EAC9B,MAAM,gBAAqB;GACzB,MAAM,SAAS,OAAO,UAAU,KAAK,oBAAmB;GACxD,IAAI,WAAW,KAAK,QAAO,GAAI,KAAK,KAAK,UAAU,KAAK,QAAQ,IAAI,MAAM,CAAC;EAC7E;EACA,KAAK,KAAK,wBAAwB,OAAO,iBAAiB,UAAU,SAAS,EAAE,SAAS,KAAI,CAAE,CAAC;EAY/F,QAAO;EACP,KAAK,WAAW,gBAAgB,OAAO,oBAAoB,UAAU,OAAO,CAAC;CAC/E;CAEU,UAAO;EACf,KAAK,OAAO,MAAM,EAAE,QAAQ,KAAK,OAAM,EAAE,CAAE;CAC7C;CAnDW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAS,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAT,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAS,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,qBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,MAAA,EAAA,YAAA;GAAA,SAAA;GAAA,oBAAA;GAAA,cAAA;EAAA,EAAA;EAAA,UAAA;EAAA,UC1CtB;EAiBA,eAAA,GAAA,kBAAA;CAAA,CAAA;;ADyBa,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAS,YAAA,CAAA;EAbrB,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAa,eAER,kBAAkB;GAAI,MAC/B;IACJ,WAAW;IACX,sBAAsB;IAItB,gBAAgB;GACjB;GAAA,UAAA;EAAA,CAAA"}
1
+ {"version":3,"file":"ngwr-back-top.mjs","names":[],"sources":["../../../projects/lib/back-top/back-top.ts","../../../projects/lib/back-top/back-top.html"],"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 { coerceNumberProperty } from '@angular/cdk/coercion';\nimport { Component, DestroyRef, NgZone, ViewEncapsulation, computed, inject, input, signal } from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrPlatform } from 'ngwr/platform';\nimport { WrScroll } from 'ngwr/scroll';\nimport { wrCheckStyles } from 'ngwr/utils';\n\n/**\n * Pinned floating button that scrolls the page back to the top. Appears\n * once the user has scrolled past `visibilityThreshold` pixels.\n *\n * @example\n * ```html\n * <wr-back-top />\n *\n * <wr-back-top visibilityThreshold=\"600\" [offset]=\"80\">\n * <svg>…custom icon…</svg>\n * </wr-back-top>\n * ```\n *\n * @see https://ngwr.dev/reference/components/back-top\n */\n@Component({\n selector: 'wr-back-top',\n templateUrl: './back-top.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n '[attr.aria-hidden]': '!visible()',\n // `aria-hidden` alone left a 44px button that focus still landed on: invisible\n // (opacity 0) but in the tab order, and unannounceable once it got there.\n // `inert` is what actually takes it out of both.\n '[attr.inert]': \"visible() ? null : ''\",\n },\n})\nexport class WrBackTop {\n /** Show the button once `window.scrollY` exceeds this many pixels. @default 400 */\n readonly visibilityThreshold = input(400, {\n transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 400)),\n });\n\n /** Pixel offset subtracted from the scroll target — for sticky headers. @default 0 */\n readonly offset = input(0, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 0)) });\n\n /** Accessible label for the button. Falls back to `backTop.label`, then `'Back to top'`. */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'backTop.label', 'Back to top');\n\n protected readonly visible = signal(false);\n\n private readonly scroll = inject(WrScroll);\n private readonly platform = inject(WrPlatform);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-back-top'];\n if (this.visible()) parts.push('wr-back-top--visible');\n return parts.join(' ');\n });\n\n constructor() {\n if (typeof ngDevMode === 'undefined' || ngDevMode) wrCheckStyles('back-top');\n if (!this.platform.isBrowser) return;\n const handler = (): void => {\n const should = window.scrollY > this.visibilityThreshold();\n if (should !== this.visible()) this.zone.run(() => this.visible.set(should));\n };\n this.zone.runOutsideAngular(() => window.addEventListener('scroll', handler, { passive: true }));\n // Initial check (page might already be scrolled), and it is SYNCHRONOUS on\n // purpose. `window.scrollY` forces a style + layout flush, which is what\n // makes constructing N of these quadratic — 1000 instances cost 90ms and\n // 5000 cost 1640ms, of which ~95% is the flush: replacing `scrollY` with a\n // plain property drops 5000 to 83ms with all 5000 scroll listeners still\n // registered, so the per-instance listener is not the cost it looks like.\n // The bulk figure is nobody's bill — a page has one back-to-top button, and\n // one instance measures 0.03ms over an equivalent component. Deferring to\n // `afterNextRender` would land the signal a change-detection pass later, so\n // a page restored part way down would fade the button in from `opacity: 0`\n // instead of painting it in place. That first paint is the contract.\n handler();\n this.destroyRef.onDestroy(() => window.removeEventListener('scroll', handler));\n }\n\n protected onClick(): void {\n this.scroll.toTop({ offset: this.offset() });\n }\n}\n","<button type=\"button\" class=\"wr-back-top__button\" [attr.aria-label]=\"resolvedAriaLabel()\" (click)=\"onClick()\">\n <!-- The arrow is FALLBACK content: rendered next to a projected icon rather\n than instead of it, the documented custom-icon example put two glyphs in\n one 44px circle. -->\n <ng-content>\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" aria-hidden=\"true\">\n <path\n d=\"M9 4 L9 14 M4 9 L9 4 L14 9\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </ng-content>\n</button>\n"],"mappings":";;;;;;;;;;;;;AA2CA,IAAa,YAAb,MAAa,UAAS;CAEX,sBAAsB,MAAM,KAAG;EAAA,GAAA,YAAA,EAAA,WAAA,sBAAA,+BAAA,CAAA;EACtC,YAAY,MAAuB,KAAK,IAAI,GAAG,qBAAqB,GAAG,GAAG,CAAC;CAAC,CAAA;CAIrE,SAAS,MAAM,GAAC;EAAA,GAAA,YAAA,EAAA,WAAA,SAAA,+BAAA,CAAA;EAAI,YAAY,MAAuB,KAAK,IAAI,GAAG,qBAAqB,GAAG,CAAC,CAAC;CAAC,CAAA;CAG9F,YAAY,MAAqB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAE5B,oBAAoB,YAAY,KAAK,WAAW,iBAAiB,aAAa;CAE9E,UAAU,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAEzB,SAAS,OAAO,QAAQ;CACxB,WAAW,OAAO,UAAU;CAC5B,OAAO,OAAO,MAAM;CACpB,aAAa,OAAO,UAAU;CAE5B,UAAU,eAAc;EACzC,MAAM,QAAQ,CAAC,aAAa;EAC5B,IAAI,KAAK,QAAO,GAAI,MAAM,KAAK,sBAAsB;EACrD,OAAO,MAAM,KAAK,GAAG;CACvB,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAEF,cAAA;EACE,IAAI,OAAO,cAAc,eAAe,WAAW,cAAc,UAAU;EAC3E,IAAI,CAAC,KAAK,SAAS,WAAW;EAC9B,MAAM,gBAAqB;GACzB,MAAM,SAAS,OAAO,UAAU,KAAK,oBAAmB;GACxD,IAAI,WAAW,KAAK,QAAO,GAAI,KAAK,KAAK,UAAU,KAAK,QAAQ,IAAI,MAAM,CAAC;EAC7E;EACA,KAAK,KAAK,wBAAwB,OAAO,iBAAiB,UAAU,SAAS,EAAE,SAAS,KAAI,CAAE,CAAC;EAY/F,QAAO;EACP,KAAK,WAAW,gBAAgB,OAAO,oBAAoB,UAAU,OAAO,CAAC;CAC/E;CAEU,UAAO;EACf,KAAK,OAAO,MAAM,EAAE,QAAQ,KAAK,OAAM,EAAE,CAAE;CAC7C;CApDW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAS,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAT,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAS,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,qBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,MAAA,EAAA,YAAA;GAAA,SAAA;GAAA,oBAAA;GAAA,cAAA;EAAA,EAAA;EAAA,UAAA;EAAA,UC3CtB;EAiBA,eAAA,GAAA,kBAAA;CAAA,CAAA;;AD0Ba,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAS,YAAA,CAAA;EAbrB,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAa,eAER,kBAAkB;GAAI,MAC/B;IACJ,WAAW;IACX,sBAAsB;IAItB,gBAAgB;GACjB;GAAA,UAAA;EAAA,CAAA"}