ngwr 15.1.0 → 15.3.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 (290) hide show
  1. package/alert/styles/_index.scss +13 -5
  2. package/bits/aurora/styles/_rules.scss +11 -0
  3. package/bits/dot-grid/styles/_rules.scss +9 -1
  4. package/bits/iridescence/iridescence.scss +1 -0
  5. package/bits/iridescence/styles/_index.scss +8 -0
  6. package/bits/iridescence/styles/_rules.scss +72 -0
  7. package/bits/marquee/styles/_rules.scss +7 -3
  8. package/bits/ripple-grid/styles/_rules.scss +10 -2
  9. package/bits/spotlight-card/styles/_rules.scss +7 -4
  10. package/bits/star-border/styles/_rules.scss +5 -2
  11. package/bits/threads/styles/_rules.scss +9 -1
  12. package/bits/waves/styles/_rules.scss +8 -1
  13. package/charts/candlestick-chart/styles/_index.scss +2 -2
  14. package/fesm2022/ngwr-affix.mjs +3 -3
  15. package/fesm2022/ngwr-alert.mjs +5 -5
  16. package/fesm2022/ngwr-alert.mjs.map +1 -1
  17. package/fesm2022/ngwr-anchor.mjs +3 -3
  18. package/fesm2022/ngwr-avatar.mjs +3 -3
  19. package/fesm2022/ngwr-back-top.mjs +3 -3
  20. package/fesm2022/ngwr-badge.mjs +6 -6
  21. package/fesm2022/ngwr-bits-aurora.mjs +30 -38
  22. package/fesm2022/ngwr-bits-aurora.mjs.map +1 -1
  23. package/fesm2022/ngwr-bits-blur-text.mjs +3 -3
  24. package/fesm2022/ngwr-bits-border-glow-testing.mjs.map +1 -1
  25. package/fesm2022/ngwr-bits-border-glow.mjs +11 -5
  26. package/fesm2022/ngwr-bits-border-glow.mjs.map +1 -1
  27. package/fesm2022/ngwr-bits-circular-text.mjs +3 -3
  28. package/fesm2022/ngwr-bits-click-spark.mjs +3 -3
  29. package/fesm2022/ngwr-bits-confetti.mjs +3 -3
  30. package/fesm2022/ngwr-bits-decrypt-text.mjs +3 -3
  31. package/fesm2022/ngwr-bits-dot-grid.mjs +12 -9
  32. package/fesm2022/ngwr-bits-dot-grid.mjs.map +1 -1
  33. package/fesm2022/ngwr-bits-falling-text.mjs +3 -3
  34. package/fesm2022/ngwr-bits-fuzzy-text.mjs +3 -3
  35. package/fesm2022/ngwr-bits-glitch-text.mjs +3 -3
  36. package/fesm2022/ngwr-bits-gradient-text.mjs +3 -3
  37. package/fesm2022/ngwr-bits-iridescence-testing.mjs +31 -0
  38. package/fesm2022/ngwr-bits-iridescence-testing.mjs.map +1 -0
  39. package/fesm2022/ngwr-bits-iridescence.mjs +591 -0
  40. package/fesm2022/ngwr-bits-iridescence.mjs.map +1 -0
  41. package/fesm2022/ngwr-bits-marquee-testing.mjs +1 -1
  42. package/fesm2022/ngwr-bits-marquee-testing.mjs.map +1 -1
  43. package/fesm2022/ngwr-bits-marquee.mjs +7 -7
  44. package/fesm2022/ngwr-bits-marquee.mjs.map +1 -1
  45. package/fesm2022/ngwr-bits-ripple-grid.mjs +8 -11
  46. package/fesm2022/ngwr-bits-ripple-grid.mjs.map +1 -1
  47. package/fesm2022/ngwr-bits-rotating-text.mjs +3 -3
  48. package/fesm2022/ngwr-bits-shiny-text.mjs +6 -6
  49. package/fesm2022/ngwr-bits-splash-cursor.mjs +3 -3
  50. package/fesm2022/ngwr-bits-split-text.mjs +3 -3
  51. package/fesm2022/ngwr-bits-spotlight-card-testing.mjs +1 -1
  52. package/fesm2022/ngwr-bits-spotlight-card-testing.mjs.map +1 -1
  53. package/fesm2022/ngwr-bits-spotlight-card.mjs +10 -10
  54. package/fesm2022/ngwr-bits-spotlight-card.mjs.map +1 -1
  55. package/fesm2022/ngwr-bits-star-border-testing.mjs +1 -1
  56. package/fesm2022/ngwr-bits-star-border-testing.mjs.map +1 -1
  57. package/fesm2022/ngwr-bits-star-border.mjs +7 -7
  58. package/fesm2022/ngwr-bits-star-border.mjs.map +1 -1
  59. package/fesm2022/ngwr-bits-threads.mjs +17 -12
  60. package/fesm2022/ngwr-bits-threads.mjs.map +1 -1
  61. package/fesm2022/ngwr-bits-tilt-card.mjs +6 -6
  62. package/fesm2022/ngwr-bits-typewriter.mjs +3 -3
  63. package/fesm2022/ngwr-bits-waves.mjs +15 -11
  64. package/fesm2022/ngwr-bits-waves.mjs.map +1 -1
  65. package/fesm2022/ngwr-breadcrumbs.mjs +24 -8
  66. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  67. package/fesm2022/ngwr-burger.mjs +3 -3
  68. package/fesm2022/ngwr-button.mjs +6 -6
  69. package/fesm2022/ngwr-calendar.mjs +3 -3
  70. package/fesm2022/ngwr-card.mjs +9 -9
  71. package/fesm2022/ngwr-carousel.mjs +6 -6
  72. package/fesm2022/ngwr-cascader.mjs +3 -3
  73. package/fesm2022/ngwr-charts-bar-chart.mjs +3 -3
  74. package/fesm2022/ngwr-charts-boxplot-chart.mjs +3 -3
  75. package/fesm2022/ngwr-charts-bullet-chart.mjs +3 -3
  76. package/fesm2022/ngwr-charts-calendar-heatmap.mjs +3 -3
  77. package/fesm2022/ngwr-charts-candlestick-chart.mjs +7 -7
  78. package/fesm2022/ngwr-charts-candlestick-chart.mjs.map +1 -1
  79. package/fesm2022/ngwr-charts-chart-legend.mjs +6 -6
  80. package/fesm2022/ngwr-charts-core.mjs +3 -3
  81. package/fesm2022/ngwr-charts-donut-chart.mjs +3 -3
  82. package/fesm2022/ngwr-charts-funnel-chart.mjs +3 -3
  83. package/fesm2022/ngwr-charts-gauge.mjs +3 -3
  84. package/fesm2022/ngwr-charts-heatmap-chart.mjs +3 -3
  85. package/fesm2022/ngwr-charts-line-chart.mjs +3 -3
  86. package/fesm2022/ngwr-charts-meter-group.mjs +3 -3
  87. package/fesm2022/ngwr-charts-radar-chart.mjs +3 -3
  88. package/fesm2022/ngwr-charts-scatter-chart.mjs +3 -3
  89. package/fesm2022/ngwr-charts-sparkline.mjs +3 -3
  90. package/fesm2022/ngwr-charts-treemap-chart.mjs +3 -3
  91. package/fesm2022/ngwr-charts-waterfall-chart.mjs +3 -3
  92. package/fesm2022/ngwr-checkbox.mjs +6 -6
  93. package/fesm2022/ngwr-clipboard.mjs +3 -3
  94. package/fesm2022/ngwr-collapse.mjs +9 -9
  95. package/fesm2022/ngwr-color-picker.mjs +6 -6
  96. package/fesm2022/ngwr-command-palette.mjs +3 -3
  97. package/fesm2022/ngwr-compare.mjs +3 -3
  98. package/fesm2022/ngwr-context-menu.mjs +12 -12
  99. package/fesm2022/ngwr-cookie.mjs +3 -3
  100. package/fesm2022/ngwr-counter.mjs +6 -6
  101. package/fesm2022/ngwr-date-picker.mjs +21 -21
  102. package/fesm2022/ngwr-date.mjs +6 -6
  103. package/fesm2022/ngwr-density.mjs +6 -6
  104. package/fesm2022/ngwr-descriptions.mjs +6 -6
  105. package/fesm2022/ngwr-dialog.mjs +15 -15
  106. package/fesm2022/ngwr-directives.mjs +12 -12
  107. package/fesm2022/ngwr-divider.mjs +3 -3
  108. package/fesm2022/ngwr-drawer.mjs +18 -18
  109. package/fesm2022/ngwr-dropdown.mjs +9 -9
  110. package/fesm2022/ngwr-editor.mjs +3 -3
  111. package/fesm2022/ngwr-empty.mjs +3 -3
  112. package/fesm2022/ngwr-event-calendar.mjs +6 -6
  113. package/fesm2022/ngwr-file-upload.mjs +3 -3
  114. package/fesm2022/ngwr-form.mjs +9 -9
  115. package/fesm2022/ngwr-graph.mjs +6 -6
  116. package/fesm2022/ngwr-hotkey.mjs +29 -7
  117. package/fesm2022/ngwr-hotkey.mjs.map +1 -1
  118. package/fesm2022/ngwr-i18n-ar.mjs +2 -0
  119. package/fesm2022/ngwr-i18n-ar.mjs.map +1 -1
  120. package/fesm2022/ngwr-i18n-cs.mjs +2 -0
  121. package/fesm2022/ngwr-i18n-cs.mjs.map +1 -1
  122. package/fesm2022/ngwr-i18n-de.mjs +2 -0
  123. package/fesm2022/ngwr-i18n-de.mjs.map +1 -1
  124. package/fesm2022/ngwr-i18n-en.mjs +2 -0
  125. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  126. package/fesm2022/ngwr-i18n-es.mjs +2 -0
  127. package/fesm2022/ngwr-i18n-es.mjs.map +1 -1
  128. package/fesm2022/ngwr-i18n-fr.mjs +2 -0
  129. package/fesm2022/ngwr-i18n-fr.mjs.map +1 -1
  130. package/fesm2022/ngwr-i18n-he.mjs +2 -0
  131. package/fesm2022/ngwr-i18n-he.mjs.map +1 -1
  132. package/fesm2022/ngwr-i18n-hi.mjs +2 -0
  133. package/fesm2022/ngwr-i18n-hi.mjs.map +1 -1
  134. package/fesm2022/ngwr-i18n-id.mjs +2 -0
  135. package/fesm2022/ngwr-i18n-id.mjs.map +1 -1
  136. package/fesm2022/ngwr-i18n-it.mjs +2 -0
  137. package/fesm2022/ngwr-i18n-it.mjs.map +1 -1
  138. package/fesm2022/ngwr-i18n-ja.mjs +2 -0
  139. package/fesm2022/ngwr-i18n-ja.mjs.map +1 -1
  140. package/fesm2022/ngwr-i18n-ko.mjs +2 -0
  141. package/fesm2022/ngwr-i18n-ko.mjs.map +1 -1
  142. package/fesm2022/ngwr-i18n-nl.mjs +2 -0
  143. package/fesm2022/ngwr-i18n-nl.mjs.map +1 -1
  144. package/fesm2022/ngwr-i18n-pl.mjs +2 -0
  145. package/fesm2022/ngwr-i18n-pl.mjs.map +1 -1
  146. package/fesm2022/ngwr-i18n-pt.mjs +2 -0
  147. package/fesm2022/ngwr-i18n-pt.mjs.map +1 -1
  148. package/fesm2022/ngwr-i18n-ru.mjs +2 -0
  149. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  150. package/fesm2022/ngwr-i18n-sv.mjs +2 -0
  151. package/fesm2022/ngwr-i18n-sv.mjs.map +1 -1
  152. package/fesm2022/ngwr-i18n-tr.mjs +2 -0
  153. package/fesm2022/ngwr-i18n-tr.mjs.map +1 -1
  154. package/fesm2022/ngwr-i18n-uk.mjs +2 -0
  155. package/fesm2022/ngwr-i18n-uk.mjs.map +1 -1
  156. package/fesm2022/ngwr-i18n-vi.mjs +2 -0
  157. package/fesm2022/ngwr-i18n-vi.mjs.map +1 -1
  158. package/fesm2022/ngwr-i18n-zh-tw.mjs +2 -0
  159. package/fesm2022/ngwr-i18n-zh-tw.mjs.map +1 -1
  160. package/fesm2022/ngwr-i18n-zh.mjs +2 -0
  161. package/fesm2022/ngwr-i18n-zh.mjs.map +1 -1
  162. package/fesm2022/ngwr-i18n.mjs +9 -9
  163. package/fesm2022/ngwr-icon.mjs +6 -6
  164. package/fesm2022/ngwr-image-cropper.mjs +3 -3
  165. package/fesm2022/ngwr-input-number.mjs +3 -3
  166. package/fesm2022/ngwr-input-otp.mjs +3 -3
  167. package/fesm2022/ngwr-input.mjs +15 -15
  168. package/fesm2022/ngwr-keyboard.mjs +3 -3
  169. package/fesm2022/ngwr-knob.mjs +3 -3
  170. package/fesm2022/ngwr-layout.mjs +15 -15
  171. package/fesm2022/ngwr-lightbox.mjs +3 -3
  172. package/fesm2022/ngwr-list.mjs +6 -6
  173. package/fesm2022/ngwr-loading-bar.mjs +6 -6
  174. package/fesm2022/ngwr-mention.mjs +6 -6
  175. package/fesm2022/ngwr-meta.mjs +6 -6
  176. package/fesm2022/ngwr-overlay.mjs +6 -6
  177. package/fesm2022/ngwr-page-header.mjs +3 -3
  178. package/fesm2022/ngwr-pagination.mjs +6 -3
  179. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  180. package/fesm2022/ngwr-pipes.mjs +26 -23
  181. package/fesm2022/ngwr-pipes.mjs.map +1 -1
  182. package/fesm2022/ngwr-platform.mjs +6 -6
  183. package/fesm2022/ngwr-popconfirm.mjs +6 -6
  184. package/fesm2022/ngwr-popover.mjs +12 -12
  185. package/fesm2022/ngwr-progress.mjs +3 -3
  186. package/fesm2022/ngwr-pull-to-refresh.mjs +3 -3
  187. package/fesm2022/ngwr-qr.mjs +4 -3
  188. package/fesm2022/ngwr-qr.mjs.map +1 -1
  189. package/fesm2022/ngwr-radio.mjs +6 -6
  190. package/fesm2022/ngwr-rating.mjs +3 -3
  191. package/fesm2022/ngwr-result.mjs +12 -12
  192. package/fesm2022/ngwr-schema-form.mjs +6 -3
  193. package/fesm2022/ngwr-schema-form.mjs.map +1 -1
  194. package/fesm2022/ngwr-scroll.mjs +3 -3
  195. package/fesm2022/ngwr-segmented.mjs +3 -3
  196. package/fesm2022/ngwr-select-testing.mjs +16 -0
  197. package/fesm2022/ngwr-select-testing.mjs.map +1 -1
  198. package/fesm2022/ngwr-select.mjs +230 -50
  199. package/fesm2022/ngwr-select.mjs.map +1 -1
  200. package/fesm2022/ngwr-sidebar.mjs +3 -3
  201. package/fesm2022/ngwr-skeleton.mjs +3 -3
  202. package/fesm2022/ngwr-slider.mjs +3 -3
  203. package/fesm2022/ngwr-sortable-list.mjs +9 -9
  204. package/fesm2022/ngwr-speed-dial.mjs +3 -3
  205. package/fesm2022/ngwr-spinner.mjs +3 -3
  206. package/fesm2022/ngwr-splitter.mjs +3 -3
  207. package/fesm2022/ngwr-statistic.mjs +18 -14
  208. package/fesm2022/ngwr-statistic.mjs.map +1 -1
  209. package/fesm2022/ngwr-stepper.mjs +6 -6
  210. package/fesm2022/ngwr-storage.mjs +3 -3
  211. package/fesm2022/ngwr-switch.mjs +3 -3
  212. package/fesm2022/ngwr-table.mjs +18 -18
  213. package/fesm2022/ngwr-tabs-router.mjs +3 -3
  214. package/fesm2022/ngwr-tabs.mjs +6 -6
  215. package/fesm2022/ngwr-textarea.mjs +3 -3
  216. package/fesm2022/ngwr-theme.mjs +3 -3
  217. package/fesm2022/ngwr-timeline.mjs +6 -6
  218. package/fesm2022/ngwr-toast.mjs +9 -9
  219. package/fesm2022/ngwr-toolbar.mjs +3 -3
  220. package/fesm2022/ngwr-tour.mjs +6 -6
  221. package/fesm2022/ngwr-transfer.mjs +3 -3
  222. package/fesm2022/ngwr-tree.mjs +3 -3
  223. package/fesm2022/ngwr-typography.mjs +3 -3
  224. package/fesm2022/ngwr-utils.mjs +31 -1
  225. package/fesm2022/ngwr-utils.mjs.map +1 -1
  226. package/fesm2022/ngwr-version.mjs +1 -1
  227. package/fesm2022/ngwr-version.mjs.map +1 -1
  228. package/fesm2022/ngwr-virtual-scroll.mjs +3 -3
  229. package/fesm2022/ngwr-window.mjs +12 -12
  230. package/i18n/ar.json +2 -0
  231. package/i18n/cs.json +2 -0
  232. package/i18n/de.json +2 -0
  233. package/i18n/en.json +2 -0
  234. package/i18n/es.json +2 -0
  235. package/i18n/fr.json +2 -0
  236. package/i18n/he.json +2 -0
  237. package/i18n/hi.json +2 -0
  238. package/i18n/id.json +2 -0
  239. package/i18n/it.json +2 -0
  240. package/i18n/ja.json +2 -0
  241. package/i18n/ko.json +2 -0
  242. package/i18n/nl.json +2 -0
  243. package/i18n/pl.json +2 -0
  244. package/i18n/pt.json +2 -0
  245. package/i18n/ru.json +2 -0
  246. package/i18n/sv.json +2 -0
  247. package/i18n/tr.json +2 -0
  248. package/i18n/uk.json +2 -0
  249. package/i18n/vi.json +2 -0
  250. package/i18n/zh-TW.json +2 -0
  251. package/i18n/zh.json +2 -0
  252. package/llms-full.txt +15 -4
  253. package/mcp/server.js +15 -1
  254. package/mcp/tools.js +3 -3
  255. package/package.json +10 -1
  256. package/schematics/ng-add/index.js +2 -2
  257. package/schematics/use/symbol-map.json +2 -0
  258. package/select/styles/_index.scss +23 -1
  259. package/skills/ngwr/SKILL.md +1 -1
  260. package/skills/ngwr/references/catalog.md +3 -1
  261. package/types/ngwr-alert.d.ts +3 -1
  262. package/types/ngwr-bits-aurora.d.ts +11 -0
  263. package/types/ngwr-bits-border-glow-testing.d.ts +5 -6
  264. package/types/ngwr-bits-border-glow.d.ts +5 -1
  265. package/types/ngwr-bits-dot-grid.d.ts +7 -2
  266. package/types/ngwr-bits-iridescence-testing.d.ts +85 -0
  267. package/types/ngwr-bits-iridescence.d.ts +244 -0
  268. package/types/ngwr-bits-ripple-grid.d.ts +4 -7
  269. package/types/ngwr-bits-splash-cursor.d.ts +0 -1
  270. package/types/ngwr-bits-spotlight-card-testing.d.ts +3 -1
  271. package/types/ngwr-bits-star-border-testing.d.ts +7 -7
  272. package/types/ngwr-bits-threads.d.ts +11 -8
  273. package/types/ngwr-bits-waves.d.ts +6 -3
  274. package/types/ngwr-breadcrumbs.d.ts +15 -0
  275. package/types/ngwr-charts-core.d.ts +0 -10
  276. package/types/ngwr-color-picker.d.ts +0 -21
  277. package/types/ngwr-dropdown.d.ts +0 -52
  278. package/types/ngwr-editor.d.ts +2 -84
  279. package/types/ngwr-hotkey.d.ts +14 -2
  280. package/types/ngwr-i18n.d.ts +0 -15
  281. package/types/ngwr-mention.d.ts +0 -13
  282. package/types/ngwr-popconfirm.d.ts +0 -33
  283. package/types/ngwr-popover.d.ts +0 -51
  284. package/types/ngwr-select-testing.d.ts +9 -0
  285. package/types/ngwr-select.d.ts +133 -2
  286. package/types/ngwr-sidebar.d.ts +1 -1
  287. package/types/ngwr-tour.d.ts +0 -2
  288. package/types/ngwr-utils.d.ts +49 -7
  289. package/types/ngwr-version.d.ts +1 -1
  290. package/types/ngwr-window.d.ts +0 -56
@@ -1,7 +1,7 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import { Component, DestroyRef, ElementRef, NgZone, PLATFORM_ID, ViewEncapsulation, afterNextRender, effect, inject, input, signal, viewChild } from "@angular/core";
3
3
  import { isPlatformBrowser } from "@angular/common";
4
- import { numAttr, resolveCssColor, watchColorScheme } from "ngwr/utils";
4
+ import { numAttr, resolveCssColorChannels, watchColorScheme } from "ngwr/utils";
5
5
  import { WrPlatform } from "ngwr/platform";
6
6
  /**
7
7
  * @license
@@ -133,24 +133,6 @@ const STOP_HOOKS = [
133
133
  "var(--wr-aurora-stop-2)",
134
134
  "var(--wr-aurora-stop-3)"
135
135
  ];
136
- function toRgb01(color) {
137
- const value = color.trim().toLowerCase();
138
- const fn = /^(rgba?|color)\(\s*(srgb\s+)?([^)]*)\)$/.exec(value);
139
- if (fn && (fn[1] !== "color" || fn[2] !== void 0)) {
140
- const scale = fn[1] === "color" ? 1 : 255;
141
- const channels = fn[3].split(/[\s,/]+/).slice(0, 3).map((part) => part === "none" ? 0 : Number.parseFloat(part) / scale);
142
- if (channels.length !== 3 || !channels.every(Number.isFinite)) return null;
143
- return channels.map((channel) => Math.min(1, Math.max(0, channel)));
144
- }
145
- const hex = /^#([\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})$/.exec(value)?.[1];
146
- if (hex === void 0) return null;
147
- const full = hex.length <= 4 ? [...hex].map((digit) => digit + digit).join("") : hex;
148
- return [
149
- 0,
150
- 2,
151
- 4
152
- ].map((at) => Number.parseInt(full.slice(at, at + 2), 16) / 255);
153
- }
154
136
  var WrAurora = class WrAurora {
155
137
  colorStops = input(null, ...ngDevMode ? [{ debugName: "colorStops" }] : /* istanbul ignore next */ []);
156
138
  amplitude = input(1, {
@@ -174,8 +156,10 @@ var WrAurora = class WrAurora {
174
156
  gl = null;
175
157
  uniforms = {};
176
158
  rafId = 0;
159
+ time = 0;
177
160
  booted = signal(false, ...ngDevMode ? [{ debugName: "booted" }] : /* istanbul ignore next */ []);
178
161
  onScreen = signal(true, ...ngDevMode ? [{ debugName: "onScreen" }] : /* istanbul ignore next */ []);
162
+ hasBox = signal(false, ...ngDevMode ? [{ debugName: "hasBox" }] : /* istanbul ignore next */ []);
179
163
  constructor() {
180
164
  if (!this.isBrowser) return;
181
165
  afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));
@@ -193,7 +177,7 @@ var WrAurora = class WrAurora {
193
177
  this.draw(0);
194
178
  });
195
179
  effect((onCleanup) => {
196
- if (!this.booted() || !this.onScreen() || this.platform.prefersReducedMotion()) return;
180
+ if (!this.booted() || !this.onScreen() || !this.hasBox() || this.platform.prefersReducedMotion()) return;
197
181
  const loop = (t) => {
198
182
  this.rafId = requestAnimationFrame(loop);
199
183
  this.pushUniforms();
@@ -247,22 +231,31 @@ var WrAurora = class WrAurora {
247
231
  const position = gl.getAttribLocation(program, "position");
248
232
  gl.enableVertexAttribArray(position);
249
233
  gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);
250
- const resize = () => {
251
- const rect = this.host.nativeElement.getBoundingClientRect();
252
- canvas.width = Math.max(1, Math.round(rect.width));
253
- canvas.height = Math.max(1, Math.round(rect.height));
234
+ const hostEl = this.host.nativeElement;
235
+ const fit = () => {
236
+ const width = hostEl.clientWidth;
237
+ const height = hostEl.clientHeight;
238
+ this.hasBox.set(width > 0 && height > 0);
239
+ canvas.width = Math.max(1, width);
240
+ canvas.height = Math.max(1, height);
254
241
  gl.viewport(0, 0, canvas.width, canvas.height);
255
242
  gl.uniform2f(this.uniforms["uResolution"], canvas.width, canvas.height);
256
- if (this.platform.prefersReducedMotion()) this.draw(0);
243
+ };
244
+ const resize = () => {
245
+ fit();
246
+ this.draw(this.platform.prefersReducedMotion() ? 0 : this.time);
257
247
  };
258
248
  window.addEventListener("resize", resize);
259
249
  const ro = new ResizeObserver(resize);
260
- ro.observe(this.host.nativeElement);
261
- resize();
250
+ ro.observe(hostEl);
251
+ fit();
262
252
  this.pushUniforms();
263
253
  this.draw(0);
264
- const io = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver((entries) => this.onScreen.set(entries[entries.length - 1].isIntersecting));
265
- io?.observe(this.host.nativeElement);
254
+ const io = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver((entries) => {
255
+ const { isIntersecting, intersectionRatio } = entries[entries.length - 1];
256
+ this.onScreen.set(isIntersecting && intersectionRatio > 0);
257
+ }, { threshold: [0, 1e-9] });
258
+ io?.observe(canvas);
266
259
  this.destroyRef.onDestroy(() => {
267
260
  window.removeEventListener("resize", resize);
268
261
  ro.disconnect();
@@ -283,20 +276,19 @@ var WrAurora = class WrAurora {
283
276
  const explicit = this.colorStops();
284
277
  const fallback = DEFAULT_STOPS[index];
285
278
  const expression = explicit === null ? STOP_HOOKS[index] : explicit[Math.min(index, explicit.length - 1)] ?? fallback;
286
- const canvas = this.canvasRef().nativeElement;
287
- const resolved = resolveCssColor(canvas, expression, fallback);
288
- return toRgb01(resolved) ?? toRgb01(resolveCssColor(canvas, `color-mix(in srgb, ${resolved} 100%, transparent)`, fallback)) ?? toRgb01(fallback);
279
+ return resolveCssColorChannels(this.canvasRef().nativeElement, expression, fallback);
289
280
  }
290
281
  draw(time) {
291
282
  const gl = this.gl;
292
- if (!gl) return;
283
+ if (!gl || !this.hasBox()) return;
284
+ this.time = time;
293
285
  gl.uniform1f(this.uniforms["uTime"], time);
294
286
  gl.clear(gl.COLOR_BUFFER_BIT);
295
287
  gl.drawArrays(gl.TRIANGLES, 0, 3);
296
288
  }
297
289
  static ɵfac = i0.ɵɵngDeclareFactory({
298
290
  minVersion: "12.0.0",
299
- version: "22.2.3",
291
+ version: "22.2.4",
300
292
  ngImport: i0,
301
293
  type: WrAurora,
302
294
  deps: [],
@@ -304,7 +296,7 @@ var WrAurora = class WrAurora {
304
296
  });
305
297
  static ɵcmp = i0.ɵɵngDeclareComponent({
306
298
  minVersion: "17.2.0",
307
- version: "22.2.3",
299
+ version: "22.2.4",
308
300
  type: WrAurora,
309
301
  isStandalone: true,
310
302
  selector: "wr-aurora",
@@ -349,13 +341,13 @@ var WrAurora = class WrAurora {
349
341
  ngImport: i0,
350
342
  template: "<canvas #canvas class=\"wr-aurora__canvas\" aria-hidden=\"true\"></canvas>",
351
343
  isInline: true,
352
- styles: [".wr-aurora{--wr-aurora-stop-1: light-dark(#6366f1, #5227ff);--wr-aurora-stop-2: light-dark(#10b981, #7cff67);--wr-aurora-stop-3: light-dark(#6366f1, #5227ff);position:absolute;inset:0;display:block;overflow:hidden;pointer-events:none;z-index:0}.wr-aurora__canvas{display:block;width:100%;height:100%}\n"],
344
+ styles: [".wr-aurora{--wr-aurora-stop-1: light-dark(#6366f1, #5227ff);--wr-aurora-stop-2: light-dark(#10b981, #7cff67);--wr-aurora-stop-3: light-dark(#6366f1, #5227ff);position:absolute;inset:0;display:block;overflow:hidden;pointer-events:none;z-index:0}.wr-aurora__canvas{display:block;width:100%;height:100%}@media(forced-colors:active){.wr-aurora__canvas{display:none;forced-color-adjust:none}}\n"],
353
345
  encapsulation: i0.ViewEncapsulation.None
354
346
  });
355
347
  };
356
348
  i0.ɵɵngDeclareClassMetadata({
357
349
  minVersion: "12.0.0",
358
- version: "22.2.3",
350
+ version: "22.2.4",
359
351
  ngImport: i0,
360
352
  type: WrAurora,
361
353
  decorators: [{
@@ -365,7 +357,7 @@ i0.ɵɵngDeclareClassMetadata({
365
357
  template: "<canvas #canvas class=\"wr-aurora__canvas\" aria-hidden=\"true\"></canvas>",
366
358
  encapsulation: ViewEncapsulation.None,
367
359
  host: { class: "wr-aurora" },
368
- styles: [".wr-aurora{--wr-aurora-stop-1: light-dark(#6366f1, #5227ff);--wr-aurora-stop-2: light-dark(#10b981, #7cff67);--wr-aurora-stop-3: light-dark(#6366f1, #5227ff);position:absolute;inset:0;display:block;overflow:hidden;pointer-events:none;z-index:0}.wr-aurora__canvas{display:block;width:100%;height:100%}\n"]
360
+ styles: [".wr-aurora{--wr-aurora-stop-1: light-dark(#6366f1, #5227ff);--wr-aurora-stop-2: light-dark(#10b981, #7cff67);--wr-aurora-stop-3: light-dark(#6366f1, #5227ff);position:absolute;inset:0;display:block;overflow:hidden;pointer-events:none;z-index:0}.wr-aurora__canvas{display:block;width:100%;height:100%}@media(forced-colors:active){.wr-aurora__canvas{display:none;forced-color-adjust:none}}\n"]
369
361
  }]
370
362
  }],
371
363
  ctorParameters: () => [],
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-bits-aurora.mjs","names":[],"sources":["../../../projects/lib/bits/aurora/aurora.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 * Angular adaptation of the Aurora background by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/backgrounds/aurora\n *\n * The reactbits version renders through the OGL library; this port talks\n * to WebGL2 directly — same vertex/fragment shaders, zero dependencies.\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n NgZone,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n effect,\n inject,\n input,\n signal,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\nconst VERT = `#version 300 es\nin vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAG = `#version 300 es\nprecision highp float;\n\nuniform float uTime;\nuniform float uAmplitude;\nuniform vec3 uColorStops[3];\nuniform vec2 uResolution;\nuniform float uBlend;\n\nout vec4 fragColor;\n\nvec3 permute(vec3 x) {\n return mod(((x * 34.0) + 1.0) * x, 289.0);\n}\n\nfloat snoise(vec2 v){\n const vec4 C = vec4(\n 0.211324865405187, 0.366025403784439,\n -0.577350269189626, 0.024390243902439\n );\n vec2 i = floor(v + dot(v, C.yy));\n vec2 x0 = v - i + dot(i, C.xx);\n vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n vec4 x12 = x0.xyxy + C.xxzz;\n x12.xy -= i1;\n i = mod(i, 289.0);\n\n vec3 p = permute(\n permute(i.y + vec3(0.0, i1.y, 1.0))\n + i.x + vec3(0.0, i1.x, 1.0)\n );\n\n vec3 m = max(\n 0.5 - vec3(\n dot(x0, x0),\n dot(x12.xy, x12.xy),\n dot(x12.zw, x12.zw)\n ),\n 0.0\n );\n m = m * m;\n m = m * m;\n\n vec3 x = 2.0 * fract(p * C.www) - 1.0;\n vec3 h = abs(x) - 0.5;\n vec3 ox = floor(x + 0.5);\n vec3 a0 = x - ox;\n m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);\n\n vec3 g;\n g.x = a0.x * x0.x + h.x * x0.y;\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n return 130.0 * dot(m, g);\n}\n\nstruct ColorStop {\n vec3 color;\n float position;\n};\n\n#define COLOR_RAMP(colors, factor, finalColor) { \\\\\n int index = 0; \\\\\n for (int i = 0; i < 2; i++) { \\\\\n ColorStop currentColor = colors[i]; \\\\\n bool isInBetween = currentColor.position <= factor; \\\\\n index = int(mix(float(index), float(i), float(isInBetween))); \\\\\n } \\\\\n ColorStop currentColor = colors[index]; \\\\\n ColorStop nextColor = colors[index + 1]; \\\\\n float range = nextColor.position - currentColor.position; \\\\\n float lerpFactor = (factor - currentColor.position) / range; \\\\\n finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \\\\\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / uResolution;\n\n ColorStop colors[3];\n colors[0] = ColorStop(uColorStops[0], 0.0);\n colors[1] = ColorStop(uColorStops[1], 0.5);\n colors[2] = ColorStop(uColorStops[2], 1.0);\n\n vec3 rampColor;\n COLOR_RAMP(colors, uv.x, rampColor);\n\n float height = snoise(vec2(uv.x * 2.0 + uTime * 0.1, uTime * 0.25)) * 0.5 * uAmplitude;\n height = exp(height);\n height = (uv.y * 2.0 - height + 0.2);\n float intensity = 0.6 * height;\n\n float midPoint = 0.20;\n float auroraAlpha = smoothstep(midPoint - uBlend * 0.5, midPoint + uBlend * 0.5, intensity);\n\n // Clamp the brightness floor: the upstream shader multiplies colour by\n // raw intensity, which fades the ribbon to BLACK while still opaque —\n // invisible on dark pages, a dirty gray halo on light ones. Keeping the\n // hue saturated and letting alpha alone do the fading works on both.\n vec3 auroraColor = rampColor * max(intensity, 0.85);\n\n fragColor = vec4(auroraColor * auroraAlpha, auroraAlpha);\n}\n`;\n\n/** What a stop falls back to when it cannot be read: the reactbits palette. */\nconst DEFAULT_STOPS = ['#5227ff', '#7cff67', '#5227ff'] as const;\n\n/**\n * The theme's stop hooks, written out in full: `gen:css-vars` finds a read by\n * its literal name, and a name built from a template string is invisible to it.\n */\nconst STOP_HOOKS = ['var(--wr-aurora-stop-1)', 'var(--wr-aurora-stop-2)', 'var(--wr-aurora-stop-3)'] as const;\n\n/**\n * A resolved colour as the shader's 0–1 channels, or `null` for a form it does\n * not read. Alpha is dropped: the uniform is a `vec3`, and the shader fades the\n * ribbon through its own alpha.\n *\n * A computed colour comes back in one of two shapes — `rgb()` / `rgba()` with\n * 0–255 channels for a legacy colour, `color(srgb r g b / a)` with 0–1 channels\n * for a `color-mix()` — and neither is the hex this used to parse, which turned\n * a `light-dark()` hook into `NaN` uniforms. Hex stays for the literal\n * fallbacks in `DEFAULT_STOPS`.\n */\nfunction toRgb01(color: string): [number, number, number] | null {\n const value = color.trim().toLowerCase();\n const fn = /^(rgba?|color)\\(\\s*(srgb\\s+)?([^)]*)\\)$/.exec(value);\n if (fn && (fn[1] !== 'color' || fn[2] !== undefined)) {\n const scale = fn[1] === 'color' ? 1 : 255;\n const channels = fn[3]\n .split(/[\\s,/]+/)\n .slice(0, 3)\n .map(part => (part === 'none' ? 0 : Number.parseFloat(part) / scale));\n if (channels.length !== 3 || !channels.every(Number.isFinite)) return null;\n return channels.map(channel => Math.min(1, Math.max(0, channel))) as [number, number, number];\n }\n const hex = /^#([\\da-f]{3,4}|[\\da-f]{6}|[\\da-f]{8})$/.exec(value)?.[1];\n if (hex === undefined) return null;\n const full = hex.length <= 4 ? [...hex].map(digit => digit + digit).join('') : hex;\n return [0, 2, 4].map(at => Number.parseInt(full.slice(at, at + 2), 16) / 255) as [number, number, number];\n}\n\n/**\n * Aurora background — flowing northern-lights ribbons rendered with a\n * WebGL2 simplex-noise shader, ramped across three colour stops. Fills\n * its nearest positioned ancestor; renders nothing without WebGL2.\n *\n * Under `prefers-reduced-motion` a single static frame is drawn, and drawn\n * again in the new colours when the page's colour scheme switches.\n *\n * @example\n * ```html\n * <div style=\"position: relative; min-height: 24rem; overflow: hidden\">\n * <wr-aurora />\n * <h1 style=\"position: relative\">Welcome aboard</h1>\n * </div>\n * ```\n *\n * @see https://ngwr.dev/bits/aurora\n */\n@Component({\n selector: 'wr-aurora',\n template: '<canvas #canvas class=\"wr-aurora__canvas\" aria-hidden=\"true\"></canvas>',\n styleUrl: './aurora.scss',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-aurora' },\n})\nexport class WrAurora {\n /**\n * Exactly three colour stops, ramped left → right — any CSS colour each, a\n * `var()` token or a `light-dark()` pair included. Alpha is ignored; the\n * ribbon fades through its own. When unset, the theme decides: deep\n * violet/emerald on light, the neon reactbits palette on dark.\n */\n readonly colorStops = input<readonly string[] | null>(null);\n\n /** Wave height multiplier. @default 1 */\n readonly amplitude = input(1, { transform: numAttr(1) });\n\n /** Softness of the aurora's lower edge, 0..1. @default 0.5 */\n readonly blend = input(0.5, { transform: numAttr(0.5) });\n\n /** Time multiplier. @default 1 */\n readonly speed = input(1, { transform: numAttr(1) });\n\n private readonly canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private gl: WebGL2RenderingContext | null = null;\n private uniforms: Record<string, WebGLUniformLocation | null> = {};\n private rafId = 0;\n\n /** Flips once `boot()` has a context and a program — the loop waits for it. */\n private readonly booted = signal(false);\n /** Whether the host is in the viewport; stays `true` without an observer. */\n private readonly onScreen = signal(true);\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));\n\n // Push input changes into uniforms without restarting the loop. Under\n // reduced motion this also repaints the static frame.\n effect(() => {\n this.colorStops();\n this.amplitude();\n this.blend();\n if (!this.gl) return;\n this.pushUniforms();\n if (this.platform.prefersReducedMotion()) this.draw(0);\n });\n\n // A still frame keeps the colours it was drawn with, so a scheme switch\n // under reduced motion re-resolves the stops and paints it again. The loop\n // needs no help: it re-resolves on every frame it draws.\n watchColorScheme(() => {\n if (!this.gl || !this.platform.prefersReducedMotion()) return;\n this.pushUniforms();\n this.draw(0);\n });\n\n // The frame loop runs only while there is something to see: never under\n // `prefers-reduced-motion`, read live (the effect above paints the static\n // frame when it switches on), and not while the host is off screen — a\n // background redrawn out of sight still costs a frame every frame.\n effect(onCleanup => {\n if (!this.booted() || !this.onScreen() || this.platform.prefersReducedMotion()) return;\n const loop = (t: number): void => {\n this.rafId = requestAnimationFrame(loop);\n this.pushUniforms();\n this.draw(t * 0.01 * this.speed() * 0.1);\n };\n this.zone.runOutsideAngular(() => (this.rafId = requestAnimationFrame(loop)));\n onCleanup(() => cancelAnimationFrame(this.rafId));\n });\n }\n\n private boot(): void {\n const canvas = this.canvasRef().nativeElement;\n const gl = canvas.getContext('webgl2', { alpha: true, premultipliedAlpha: true, antialias: true });\n if (!gl) return;\n this.gl = gl;\n\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n\n const compile = (type: number, source: string): WebGLShader => {\n const shader = gl.createShader(type)!;\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n // eslint-disable-next-line no-console -- GPU shader failures are otherwise invisible\n console.error('wr-aurora shader error:', gl.getShaderInfoLog(shader));\n }\n return shader;\n };\n\n const program = gl.createProgram();\n gl.attachShader(program, compile(gl.VERTEX_SHADER, VERT));\n gl.attachShader(program, compile(gl.FRAGMENT_SHADER, FRAG));\n gl.linkProgram(program);\n gl.useProgram(program);\n\n for (const name of ['uTime', 'uAmplitude', 'uColorStops', 'uResolution', 'uBlend']) {\n this.uniforms[name] = gl.getUniformLocation(program, name);\n }\n\n // One oversized triangle covers the viewport — no index buffer needed.\n const buffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);\n const position = gl.getAttribLocation(program, 'position');\n gl.enableVertexAttribArray(position);\n gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);\n\n const resize = (): void => {\n const rect = this.host.nativeElement.getBoundingClientRect();\n canvas.width = Math.max(1, Math.round(rect.width));\n canvas.height = Math.max(1, Math.round(rect.height));\n gl.viewport(0, 0, canvas.width, canvas.height);\n gl.uniform2f(this.uniforms['uResolution'], canvas.width, canvas.height);\n if (this.platform.prefersReducedMotion()) this.draw(0);\n };\n\n window.addEventListener('resize', resize);\n const ro = new ResizeObserver(resize);\n ro.observe(this.host.nativeElement);\n\n resize();\n this.pushUniforms();\n\n // Static frame — the ribbons hold their t=0 shape. Painted under reduced\n // motion, and as the first frame either way; the loop takes over from here.\n this.draw(0);\n\n // Without an observer (jsdom) the host simply counts as visible.\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(entries => this.onScreen.set(entries[entries.length - 1].isIntersecting));\n io?.observe(this.host.nativeElement);\n\n this.destroyRef.onDestroy(() => {\n window.removeEventListener('resize', resize);\n ro.disconnect();\n io?.disconnect();\n });\n this.booted.set(true);\n }\n\n private pushUniforms(): void {\n const gl = this.gl;\n if (!gl) return;\n const flat = new Float32Array(9);\n for (let i = 0; i < 3; i++) flat.set(this.resolveStop(i), i * 3);\n gl.uniform3fv(this.uniforms['uColorStops'], flat);\n gl.uniform1f(this.uniforms['uAmplitude'], this.amplitude());\n gl.uniform1f(this.uniforms['uBlend'], this.blend());\n }\n\n /**\n * Stop `index` as shader channels. Explicit input wins; otherwise the theme's\n * `--wr-aurora-stop-*` hook, which holds a `light-dark()` pair no uniform can\n * take, so either is resolved through the cascade first. Read every frame, so\n * a colour-scheme change reaches the ribbon on the next one.\n */\n private resolveStop(index: number): [number, number, number] {\n const explicit = this.colorStops();\n const fallback = DEFAULT_STOPS[index];\n const expression =\n explicit === null ? STOP_HOOKS[index] : (explicit[Math.min(index, explicit.length - 1)] ?? fallback);\n const canvas = this.canvasRef().nativeElement;\n const resolved = resolveCssColor(canvas, expression, fallback);\n // A colour authored in another space — `oklch()`, `lab()`, `display-p3` —\n // computes to itself; mixing it into sRGB is what brings it to a form the\n // parser reads.\n return (\n toRgb01(resolved) ??\n toRgb01(resolveCssColor(canvas, `color-mix(in srgb, ${resolved} 100%, transparent)`, fallback)) ??\n toRgb01(fallback)!\n );\n }\n\n private draw(time: number): void {\n const gl = this.gl;\n if (!gl) return;\n gl.uniform1f(this.uniforms['uTime'], time);\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-bits-aurora.mjs","names":[],"sources":["../../../projects/lib/bits/aurora/aurora.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 * Angular adaptation of the Aurora background by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/backgrounds/aurora\n *\n * The reactbits version renders through the OGL library; this port talks\n * to WebGL2 directly — same vertex/fragment shaders, zero dependencies.\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n NgZone,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n effect,\n inject,\n input,\n signal,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr, resolveCssColorChannels, watchColorScheme } from 'ngwr/utils';\n\nconst VERT = `#version 300 es\nin vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAG = `#version 300 es\nprecision highp float;\n\nuniform float uTime;\nuniform float uAmplitude;\nuniform vec3 uColorStops[3];\nuniform vec2 uResolution;\nuniform float uBlend;\n\nout vec4 fragColor;\n\nvec3 permute(vec3 x) {\n return mod(((x * 34.0) + 1.0) * x, 289.0);\n}\n\nfloat snoise(vec2 v){\n const vec4 C = vec4(\n 0.211324865405187, 0.366025403784439,\n -0.577350269189626, 0.024390243902439\n );\n vec2 i = floor(v + dot(v, C.yy));\n vec2 x0 = v - i + dot(i, C.xx);\n vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n vec4 x12 = x0.xyxy + C.xxzz;\n x12.xy -= i1;\n i = mod(i, 289.0);\n\n vec3 p = permute(\n permute(i.y + vec3(0.0, i1.y, 1.0))\n + i.x + vec3(0.0, i1.x, 1.0)\n );\n\n vec3 m = max(\n 0.5 - vec3(\n dot(x0, x0),\n dot(x12.xy, x12.xy),\n dot(x12.zw, x12.zw)\n ),\n 0.0\n );\n m = m * m;\n m = m * m;\n\n vec3 x = 2.0 * fract(p * C.www) - 1.0;\n vec3 h = abs(x) - 0.5;\n vec3 ox = floor(x + 0.5);\n vec3 a0 = x - ox;\n m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);\n\n vec3 g;\n g.x = a0.x * x0.x + h.x * x0.y;\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n return 130.0 * dot(m, g);\n}\n\nstruct ColorStop {\n vec3 color;\n float position;\n};\n\n#define COLOR_RAMP(colors, factor, finalColor) { \\\\\n int index = 0; \\\\\n for (int i = 0; i < 2; i++) { \\\\\n ColorStop currentColor = colors[i]; \\\\\n bool isInBetween = currentColor.position <= factor; \\\\\n index = int(mix(float(index), float(i), float(isInBetween))); \\\\\n } \\\\\n ColorStop currentColor = colors[index]; \\\\\n ColorStop nextColor = colors[index + 1]; \\\\\n float range = nextColor.position - currentColor.position; \\\\\n float lerpFactor = (factor - currentColor.position) / range; \\\\\n finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \\\\\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / uResolution;\n\n ColorStop colors[3];\n colors[0] = ColorStop(uColorStops[0], 0.0);\n colors[1] = ColorStop(uColorStops[1], 0.5);\n colors[2] = ColorStop(uColorStops[2], 1.0);\n\n vec3 rampColor;\n COLOR_RAMP(colors, uv.x, rampColor);\n\n float height = snoise(vec2(uv.x * 2.0 + uTime * 0.1, uTime * 0.25)) * 0.5 * uAmplitude;\n height = exp(height);\n height = (uv.y * 2.0 - height + 0.2);\n float intensity = 0.6 * height;\n\n float midPoint = 0.20;\n float auroraAlpha = smoothstep(midPoint - uBlend * 0.5, midPoint + uBlend * 0.5, intensity);\n\n // Clamp the brightness floor: the upstream shader multiplies colour by\n // raw intensity, which fades the ribbon to BLACK while still opaque —\n // invisible on dark pages, a dirty gray halo on light ones. Keeping the\n // hue saturated and letting alpha alone do the fading works on both.\n vec3 auroraColor = rampColor * max(intensity, 0.85);\n\n fragColor = vec4(auroraColor * auroraAlpha, auroraAlpha);\n}\n`;\n\n/** What a stop falls back to when it cannot be read: the reactbits palette. */\nconst DEFAULT_STOPS = ['#5227ff', '#7cff67', '#5227ff'] as const;\n\n/**\n * The theme's stop hooks, written out in full: `gen:css-vars` finds a read by\n * its literal name, and a name built from a template string is invisible to it.\n */\nconst STOP_HOOKS = ['var(--wr-aurora-stop-1)', 'var(--wr-aurora-stop-2)', 'var(--wr-aurora-stop-3)'] as const;\n\n/**\n * Aurora background — flowing northern-lights ribbons rendered with a\n * WebGL2 simplex-noise shader, ramped across three colour stops. Fills\n * its nearest positioned ancestor; renders nothing without WebGL2.\n *\n * Under `prefers-reduced-motion` a single static frame is drawn, and drawn\n * again in the new colours when the page's colour scheme switches.\n *\n * Under forced colours the canvas is hidden and its loop stops with it, leaving the\n * box on the palette's own ground, which is what forced text and marks are chosen\n * against. The loop also stops off screen and with no box to draw on.\n *\n * @example\n * ```html\n * <div style=\"position: relative; min-height: 24rem; overflow: hidden\">\n * <wr-aurora />\n * <h1 style=\"position: relative\">Welcome aboard</h1>\n * </div>\n * ```\n *\n * @see https://ngwr.dev/bits/aurora\n */\n@Component({\n selector: 'wr-aurora',\n template: '<canvas #canvas class=\"wr-aurora__canvas\" aria-hidden=\"true\"></canvas>',\n styleUrl: './aurora.scss',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-aurora' },\n})\nexport class WrAurora {\n /**\n * Exactly three colour stops, ramped left → right — any CSS colour each, a\n * `var()` token or a `light-dark()` pair included. Alpha is ignored; the\n * ribbon fades through its own. When unset, the theme decides: deep\n * violet/emerald on light, the neon reactbits palette on dark.\n */\n readonly colorStops = input<readonly string[] | null>(null);\n\n /** Wave height multiplier. @default 1 */\n readonly amplitude = input(1, { transform: numAttr(1) });\n\n /** Softness of the aurora's lower edge, 0..1. @default 0.5 */\n readonly blend = input(0.5, { transform: numAttr(0.5) });\n\n /** Time multiplier. @default 1 */\n readonly speed = input(1, { transform: numAttr(1) });\n\n private readonly canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private gl: WebGL2RenderingContext | null = null;\n private uniforms: Record<string, WebGLUniformLocation | null> = {};\n private rafId = 0;\n /** The shader time last drawn, so a resize can repaint the frame it just cleared. */\n private time = 0;\n\n /** Flips once `boot()` has a context and a program — the loop waits for it. */\n private readonly booted = signal(false);\n /** Whether the host is in the viewport; stays `true` without an observer. */\n private readonly onScreen = signal(true);\n /**\n * Whether the box has an area. The intersection observer calls a zero-area canvas\n * intersecting and sends no entry when it gains a size, so this reads the box instead.\n */\n private readonly hasBox = signal(false);\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));\n\n // Push input changes into uniforms without restarting the loop. Under\n // reduced motion this also repaints the static frame.\n effect(() => {\n this.colorStops();\n this.amplitude();\n this.blend();\n if (!this.gl) return;\n this.pushUniforms();\n if (this.platform.prefersReducedMotion()) this.draw(0);\n });\n\n // A still frame keeps the colours it was drawn with, so a scheme switch\n // under reduced motion re-resolves the stops and paints it again. The loop\n // needs no help: it re-resolves on every frame it draws.\n watchColorScheme(() => {\n if (!this.gl || !this.platform.prefersReducedMotion()) return;\n this.pushUniforms();\n this.draw(0);\n });\n\n // The frame loop runs only while there is something to see: never under\n // `prefers-reduced-motion`, read live (the effect above paints the static\n // frame when it switches on), and not while the host is off screen or has no\n // box — a background redrawn out of sight still costs a frame every frame.\n effect(onCleanup => {\n if (!this.booted() || !this.onScreen() || !this.hasBox() || this.platform.prefersReducedMotion()) return;\n const loop = (t: number): void => {\n this.rafId = requestAnimationFrame(loop);\n this.pushUniforms();\n this.draw(t * 0.01 * this.speed() * 0.1);\n };\n this.zone.runOutsideAngular(() => (this.rafId = requestAnimationFrame(loop)));\n onCleanup(() => cancelAnimationFrame(this.rafId));\n });\n }\n\n private boot(): void {\n const canvas = this.canvasRef().nativeElement;\n const gl = canvas.getContext('webgl2', { alpha: true, premultipliedAlpha: true, antialias: true });\n if (!gl) return;\n this.gl = gl;\n\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n\n const compile = (type: number, source: string): WebGLShader => {\n const shader = gl.createShader(type)!;\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n // eslint-disable-next-line no-console -- GPU shader failures are otherwise invisible\n console.error('wr-aurora shader error:', gl.getShaderInfoLog(shader));\n }\n return shader;\n };\n\n const program = gl.createProgram();\n gl.attachShader(program, compile(gl.VERTEX_SHADER, VERT));\n gl.attachShader(program, compile(gl.FRAGMENT_SHADER, FRAG));\n gl.linkProgram(program);\n gl.useProgram(program);\n\n for (const name of ['uTime', 'uAmplitude', 'uColorStops', 'uResolution', 'uBlend']) {\n this.uniforms[name] = gl.getUniformLocation(program, name);\n }\n\n // One oversized triangle covers the viewport — no index buffer needed.\n const buffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);\n const position = gl.getAttribLocation(program, 'position');\n gl.enableVertexAttribArray(position);\n gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);\n\n const hostEl = this.host.nativeElement;\n const fit = (): void => {\n // The layout box and not `getBoundingClientRect()`, which is the box after\n // transforms: no observer hears of a transform, so a host booted under a\n // `scale(0)` would count as having no box for good.\n const width = hostEl.clientWidth;\n const height = hostEl.clientHeight;\n this.hasBox.set(width > 0 && height > 0);\n canvas.width = Math.max(1, width);\n canvas.height = Math.max(1, height);\n gl.viewport(0, 0, canvas.width, canvas.height);\n gl.uniform2f(this.uniforms['uResolution'], canvas.width, canvas.height);\n };\n // Sizing the bitmap clears it, and observer entries arrive after the frame's own\n // callbacks and before its paint, so a repaint left to the loop would leave this\n // frame empty.\n const resize = (): void => {\n fit();\n this.draw(this.platform.prefersReducedMotion() ? 0 : this.time);\n };\n\n window.addEventListener('resize', resize);\n const ro = new ResizeObserver(resize);\n ro.observe(hostEl);\n\n fit();\n this.pushUniforms();\n\n // Static frame — the ribbons hold their t=0 shape. Painted under reduced\n // motion, and as the first frame either way; the loop takes over from here.\n this.draw(0);\n\n // Without an observer (jsdom) the host simply counts as visible. The canvas\n // rather than the host, which fills the same box, so a canvas hidden under forced\n // colours stops the loop too. A box flush against a zero-height clip intersects with\n // nothing showing, and only a second threshold reports the clip moving. It sits below\n // any real ratio, or a clip closing on a transition steps from under it to 0 without\n // crossing anything.\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(\n entries => {\n const { isIntersecting, intersectionRatio } = entries[entries.length - 1];\n this.onScreen.set(isIntersecting && intersectionRatio > 0);\n },\n { threshold: [0, 1e-9] }\n );\n io?.observe(canvas);\n\n this.destroyRef.onDestroy(() => {\n window.removeEventListener('resize', resize);\n ro.disconnect();\n io?.disconnect();\n });\n this.booted.set(true);\n }\n\n private pushUniforms(): void {\n const gl = this.gl;\n if (!gl) return;\n const flat = new Float32Array(9);\n for (let i = 0; i < 3; i++) flat.set(this.resolveStop(i), i * 3);\n gl.uniform3fv(this.uniforms['uColorStops'], flat);\n gl.uniform1f(this.uniforms['uAmplitude'], this.amplitude());\n gl.uniform1f(this.uniforms['uBlend'], this.blend());\n }\n\n /**\n * Stop `index` as shader channels. Explicit input wins; otherwise the theme's\n * `--wr-aurora-stop-*` hook, which holds a `light-dark()` pair no uniform can\n * take, so either is resolved through the cascade first. Read every frame, so\n * a colour-scheme change reaches the ribbon on the next one.\n */\n private resolveStop(index: number): [number, number, number] {\n const explicit = this.colorStops();\n const fallback = DEFAULT_STOPS[index];\n const expression =\n explicit === null ? STOP_HOOKS[index] : (explicit[Math.min(index, explicit.length - 1)] ?? fallback);\n return resolveCssColorChannels(this.canvasRef().nativeElement, expression, fallback);\n }\n\n private draw(time: number): void {\n const gl = this.gl;\n if (!gl || !this.hasBox()) return;\n this.time = time;\n gl.uniform1f(this.uniforms['uTime'], time);\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n }\n}\n"],"mappings":"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"}
@@ -161,7 +161,7 @@ var WrBlurText = class WrBlurText {
161
161
  }
162
162
  static ɵfac = i0.ɵɵngDeclareFactory({
163
163
  minVersion: "12.0.0",
164
- version: "22.2.3",
164
+ version: "22.2.4",
165
165
  ngImport: i0,
166
166
  type: WrBlurText,
167
167
  deps: [],
@@ -169,7 +169,7 @@ var WrBlurText = class WrBlurText {
169
169
  });
170
170
  static ɵcmp = i0.ɵɵngDeclareComponent({
171
171
  minVersion: "17.0.0",
172
- version: "22.2.3",
172
+ version: "22.2.4",
173
173
  type: WrBlurText,
174
174
  isStandalone: true,
175
175
  selector: "wr-blur-text",
@@ -244,7 +244,7 @@ var WrBlurText = class WrBlurText {
244
244
  };
245
245
  i0.ɵɵngDeclareClassMetadata({
246
246
  minVersion: "12.0.0",
247
- version: "22.2.3",
247
+ version: "22.2.4",
248
248
  ngImport: i0,
249
249
  type: WrBlurText,
250
250
  decorators: [{
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-bits-border-glow-testing.mjs","names":[],"sources":["../../../projects/lib/bits/border-glow/testing/wr-border-glow-harness.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrBorderGlowHarnessFilters } from './types';\n\n/** The seven halo steps, in the order the component writes them: 100%, 60%, 50%, 40%, 30%, 20%, 10%. */\nconst GLOW_VAR_SUFFIXES = ['', '-60', '-50', '-40', '-30', '-20', '-10'] as const;\n\n/** The seven mesh-gradient slots, spelled out rather than numbered — as the component names them. */\nconst GRADIENT_VAR_SLOTS = ['one', 'two', 'three', 'four', 'five', 'six', 'seven'] as const;\n\n/** `hsl(40deg 80% 80% / 65%)` — the alpha, which is the only part `glowIntensity` moves. */\nconst GLOW_ALPHA = /\\/\\s*(-?[\\d.]+)%\\s*\\)\\s*$/;\n\n/** `color-mix(in srgb, var(--wr-color-primary) 65%, transparent)` — the same alpha, for a CSS colour. */\nconst GLOW_MIX_ALPHA = /^color-mix\\(in srgb,\\s*.+\\s+(-?[\\d.]+)%\\s*,\\s*transparent\\s*\\)$/;\n\n/** `radial-gradient(at 80% 55%, #c084fc 0px, transparent 50%)` — the colour, not the position. */\nconst RADIAL_GRADIENT_COLOR = /^radial-gradient\\(\\s*at\\s+[^,]+,\\s*(.+?)\\s+0px\\s*,/;\n\n/** `linear-gradient(#c084fc 0 100%)` — the flat base slice. */\nconst LINEAR_GRADIENT_COLOR = /^linear-gradient\\(\\s*(.+?)\\s+0\\s+100%\\s*\\)$/;\n\n/**\n * Split an inline `style` attribute into its declarations.\n *\n * Read from the attribute rather than through `getCssValue()` on purpose. jsdom echoes\n * an inline custom property back through `getComputedStyle`, so the computed read passes\n * here and would answer something else entirely in a browser — where the stylesheet's\n * own per-theme default resolves and \"the author left this to the theme\" quietly becomes\n * a colour. The attribute holds what the component itself wrote, and nothing else.\n */\nfunction parseInlineDeclarations(style: string): Map<string, string> {\n const declarations = new Map<string, string>();\n for (const declaration of style.split(';')) {\n const colon = declaration.indexOf(':');\n if (colon === -1) continue;\n const name = declaration.slice(0, colon).trim();\n if (name !== '') declarations.set(name, declaration.slice(colon + 1).trim());\n }\n return declarations;\n}\n\n/**\n * Test harness for `<wr-border-glow>` — a card whose border lights up under the cursor.\n *\n * **Nearly all of this component is a bag of custom properties written onto the host,\n * and that is what the harness reads.** The paint itself — the conic mask, the stacked\n * `box-shadow` halo, the soft-light bloom — lives in the stylesheet, so a unit test can\n * never see it. What it CAN see is the arithmetic that feeds it, and that is where the\n * breakages are: the seven-step alpha ramp {@link getGlowAlphaRamp} derives from\n * `glowIntensity`, the `[0,1,2,0,1,2,1]` palette mapping in {@link getGradientColors}\n * with its clamp for short palettes, and the pointer maths behind\n * {@link getPointerAngle} / {@link getEdgeProximity}.\n *\n * **Every custom-property getter reads the inline declaration** — see\n * {@link parseInlineDeclarations} for why a computed read would be a different, and\n * wrong, question.\n *\n * **A colour getter answering `null` means the component has never written that\n * variable**, which is the contract when the input is unset: the stylesheet's per-theme\n * default takes over. Note that it does not un-write them either — setting `[glowColor]`\n * and then putting it back to `null` leaves the last value on the host — so `null` is\n * \"never set\", not \"not set now\".\n *\n * There is deliberately no `getGlowOpacity()`, `isGlowVisible()` or `isHovered()`: the\n * visible strength is a `calc()` in the stylesheet gated behind `:not(:hover)`, and\n * jsdom loads no stylesheet and matches no `:hover`, so any number would be invented.\n * There is no measured halo extent either — `.wr-border-glow__edge` is 0×0 in a test,\n * and {@link getGlowRadius} reads what the component asked for instead. And there is no\n * `getSweepProgress()` / `waitForSweep()`: the mount sweep is a four-second chain of\n * `requestAnimationFrame` tweens, so awaiting it either hangs the spec or samples one\n * frame and reports a value that was never settled. {@link isSweeping} answers the one\n * durable fact — that the sweep started. Nor is there a `getLoopAngle()` or a wait for\n * the loop: `[loop]` writes a new angle on every frame for as long as it is on, so the\n * angle never settles and any sample is one frame of an endless animation.\n * {@link isLooping} answers that it is on.\n *\n * @example\n * ```ts\n * const card = await loader.getHarness(WrBorderGlowHarness.with({ text: /Hover me/ }));\n *\n * expect(await card.getBorderRadius()).toBe(28);\n * expect(await card.getPointerAngle()).toBeNull(); // no pointer, sweep or loop has written one yet\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrBorderGlowHarness extends ComponentHarness {\n static hostSelector = 'wr-border-glow';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrBorderGlowHarnessFilters = {}): HarnessPredicate<WrBorderGlowHarness> {\n return new HarnessPredicate(WrBorderGlowHarness, options)\n .addOption('text', options.text, (harness, text) =>\n HarnessPredicate.stringMatches(harness.getContentText(), text)\n )\n .addOption('sweeping', options.sweeping, async (harness, sweeping) => (await harness.isSweeping()) === sweeping)\n .addOption('looping', options.looping, async (harness, looping) => (await harness.isLooping()) === looping);\n }\n\n private readonly edge = this.locatorFor('.wr-border-glow__edge');\n\n /** The corner radius the stylesheet is told to round to, in pixels. */\n async getBorderRadius(): Promise<number> {\n return this.readNumberVar('--wr-border-glow-radius', 'getBorderRadius');\n }\n\n /** How far past the card edge the halo is told to reach, in pixels. */\n async getGlowRadius(): Promise<number> {\n return this.readNumberVar('--wr-border-glow-padding', 'getGlowRadius');\n }\n\n /**\n * The width of the lit cone, as a share of the perimeter.\n *\n * Unitless by contract — the stylesheet multiplies it — so a `px` creeping in here is\n * a real regression that leaves the mask with a length where it wants a number.\n */\n async getConeSpread(): Promise<number> {\n return this.readNumberVar('--wr-border-glow-cone-spread', 'getConeSpread');\n }\n\n /** How sharply the halo fades as the cursor leaves an edge; lower is a wider falloff. */\n async getEdgeSensitivity(): Promise<number> {\n return this.readNumberVar('--wr-border-glow-edge-sensitivity', 'getEdgeSensitivity');\n }\n\n /** Strength of the interior soft-light bloom, again unitless. */\n async getFillOpacity(): Promise<number> {\n return this.readNumberVar('--wr-border-glow-fill-opacity', 'getFillOpacity');\n }\n\n /**\n * The card fill the author asked for, or `null` when they left it to the theme.\n *\n * The distinction is the whole point: an unset `[backgroundColor]` means the surface\n * token decides, light or dark, and a card that starts declaring its own fill has\n * stopped following the theme.\n */\n async getBackgroundColor(): Promise<string | null> {\n return this.readVar('--wr-border-glow-bg');\n }\n\n /**\n * The halo colour at full strength, or `null` when the theme decides.\n *\n * Comes back as written, in one of two forms, so this also pins the assembly: the\n * `hsl(H deg S% L% / A%)` built from bare `'H S L'` parts, or\n * `color-mix(in srgb, <colour> 100%, transparent)` for any other value. Be aware the\n * second form is a containment check and not a colour parser — `'banana'` is written\n * just as faithfully as `'#3567ff'`, and the browser then drops every halo declaration\n * that reads it — so a plausible-looking value here does not mean it paints. A value\n * that could escape the `color-mix()` is never written, and reads `null`.\n */\n async getGlowColor(): Promise<string | null> {\n return this.readVar('--wr-border-glow-color');\n }\n\n /**\n * The seven halo alphas in percent, brightest first, or `null` when no glow colour\n * was ever set.\n *\n * This is the whole of `[glowIntensity]`: the steps are `[100, 60, 50, 40, 30, 20,\n * 10]` multiplied by the intensity and clamped at 100, so `0.5` gives\n * `[50, 30, 25, 20, 15, 10, 5]` and anything above 1 saturates from the top down. The\n * clamp is one-sided — a negative intensity produces negative percentages, which a\n * browser drops as invalid declarations and the halo vanishes — so the numbers are\n * returned as written rather than corrected, which is how a spec can see it.\n *\n * Read from either form {@link getGlowColor} describes: the `/ A%` of an `hsl()`, or\n * the colour's share in a `color-mix()`. All seven steps must be in the form the first\n * one is — they are written together from one value, so a mix of the two is a ramp\n * that broke half way.\n */\n async getGlowAlphaRamp(): Promise<number[] | null> {\n const declarations = await this.declarations();\n const first = declarations.get('--wr-border-glow-color');\n if (first === undefined) return null;\n const form = first.startsWith('color-mix(') ? GLOW_MIX_ALPHA : GLOW_ALPHA;\n\n return GLOW_VAR_SUFFIXES.map(suffix => {\n const name = `--wr-border-glow-color${suffix}`;\n const raw = declarations.get(name);\n const alpha = raw === undefined ? null : form.exec(raw);\n if (!alpha) {\n throw new Error(\n `WrBorderGlowHarness.getGlowAlphaRamp(): \\`${name}\\` reads \"${raw ?? '(absent)'}\". The seven halo steps ` +\n 'are written together from one glow colour, so a step that is missing, carries no alpha, or is not ' +\n `the same \\`${form === GLOW_ALPHA ? 'hsl(… / N%)' : 'color-mix(in srgb, … N%, transparent)'}\\` as the ` +\n 'first means the ramp stopped being built — the halo would fade in bands the stylesheet cannot use.'\n );\n }\n return Number.parseFloat(alpha[1]);\n });\n }\n\n /**\n * The seven mesh-gradient colours, in slot order, or `null` when no palette was set.\n *\n * The slots do not map one-to-one onto the palette: they take colours\n * `[0, 1, 2, 0, 1, 2, 1]`, each index clamped to the last entry the palette actually\n * has. So a two-colour palette repeats the second colour rather than reading past the\n * end — which is the case worth a spec, since `undefined` in a `radial-gradient()`\n * kills the whole declaration silently.\n */\n async getGradientColors(): Promise<string[] | null> {\n const declarations = await this.declarations();\n if (!declarations.has('--wr-border-glow-gradient-one')) return null;\n\n return GRADIENT_VAR_SLOTS.map(slot => {\n const name = `--wr-border-glow-gradient-${slot}`;\n const raw = declarations.get(name);\n const colour = raw === undefined ? null : RADIAL_GRADIENT_COLOR.exec(raw);\n if (!colour) {\n throw new Error(\n `WrBorderGlowHarness.getGradientColors(): \\`${name}\\` reads \"${raw ?? '(absent)'}\", which is not the ` +\n '`radial-gradient(at X% Y%, <colour> 0px, …)` the component builds. All seven slots are written from ' +\n 'one palette, so one of them being different means the mapping broke, not the palette.'\n );\n }\n return colour[1];\n });\n }\n\n /**\n * The flat colour behind the mesh, or `null` when no palette was set.\n *\n * Always the palette's FIRST entry. Worth its own method because the base slice is the\n * one that shows where the gradients are transparent, and it drifting to the last\n * colour changes the card's whole cast without touching a gradient.\n */\n async getBaseGradientColor(): Promise<string | null> {\n const raw = await this.readVar('--wr-border-glow-gradient-base');\n if (raw === null) return null;\n\n const colour = LINEAR_GRADIENT_COLOR.exec(raw);\n if (!colour) {\n throw new Error(\n `WrBorderGlowHarness.getBaseGradientColor(): \\`--wr-border-glow-gradient-base\\` reads \"${raw}\", which is ` +\n 'not the `linear-gradient(<colour> 0 100%)` the component builds from the palette.'\n );\n }\n return colour[1];\n }\n\n /**\n * Where the lit cone points, in degrees: `0` is straight up and the angle grows\n * clockwise. Three things write it — the pointer, from where it sits; the mount sweep;\n * and `[loop]`, on every frame, or once at a resting 45 under reduced motion — so a\n * looping card answers with no pointer at all. `null` means nothing has written it yet.\n *\n * `null` is a state of its own rather than a stand-in for zero — the stylesheet's own\n * default applies until the first write, and a pointer at dead centre legitimately\n * reports `0`, so a harness that folded the two together could not tell an untouched\n * card from one being pointed at from the middle.\n */\n async getPointerAngle(): Promise<number | null> {\n const raw = await this.readVar('--wr-border-glow-angle');\n return raw === null ? null : Number.parseFloat(raw);\n }\n\n /**\n * How lit the edge is told to be: `0` at dead centre, `100` at the edge. The pointer\n * writes it from where it sits, the mount sweep as it lights and fades, and `[loop]` holds it at\n * 100 for as long as it is on, re-lighting it when a pointer leaves. `null` means\n * nothing has written it yet.\n *\n * For a pointer on a diagonal the NEARER axis wins rather than the two averaging, so a\n * cursor level with the bottom edge reads 100 wherever it is along it.\n */\n async getEdgeProximity(): Promise<number | null> {\n const raw = await this.readVar('--wr-border-glow-edge-proximity');\n return raw === null ? null : Number.parseFloat(raw);\n }\n\n /**\n * Point at the card, in client coordinates.\n *\n * **Refuses on a card with no box, which in a unit test is every card.** The component\n * measures the pointer against its own centre, and with a 0×0 rect that maths\n * degenerates: the centre is the top-left corner, and `1 / Math.min(0, 0)` pins the\n * proximity at 100 for every position on the page. Both would come back as confident\n * numbers describing nothing. Give the host a box first, exactly as the component's own\n * spec does:\n *\n * ```ts\n * const card = fixture.nativeElement.querySelector('wr-border-glow');\n * card.getBoundingClientRect = () => ({ left: 0, top: 0, width: 200, height: 100 }) as DOMRect;\n * ```\n *\n * A `pointermove` is dispatched on the host itself, so it needs no hit test and cannot\n * miss — the coordinates only ever reach the component's own arithmetic.\n */\n async movePointerTo(clientX: number, clientY: number): Promise<void> {\n const host = await this.host();\n const { width, height } = await host.getDimensions();\n if (!width || !height) {\n throw new Error(\n `WrBorderGlowHarness.movePointerTo(${clientX}, ${clientY}): this card measures ${width}×${height}, so a ` +\n 'pointer position over it means nothing — the component reads the cursor against the card centre, and ' +\n 'with no box the centre is the top-left corner and the proximity saturates for every coordinate. Stub ' +\n '`getBoundingClientRect` on the host first.'\n );\n }\n await host.dispatchEvent('pointermove', { clientX, clientY, pointerId: 1, isPrimary: true });\n }\n\n /**\n * Whether the one-shot mount sweep is running.\n *\n * The class is added synchronously when the sweep starts and removed when it ends\n * about four seconds later, or when the component is torn down — so a spec asserts\n * that it STARTED, and leaves the angle alone while it does. This is also the\n * reduced-motion assertion: `[animated]=\"true\"` for someone who asked for less motion\n * must leave this `false`, and no angle written at all.\n */\n async isSweeping(): Promise<boolean> {\n return (await this.host()).hasClass('wr-border-glow--sweeping');\n }\n\n /**\n * Whether the endless loop holds the card.\n *\n * The class is added when `[loop]` turns on, under reduced motion too — there the edge\n * stays lit and still, so the glow layers must stay visible — and removed when it turns\n * off. Like {@link isSweeping}, it answers that the loop is ON, not where the cone is:\n * the angle moves on every `requestAnimationFrame` and never settles, which is why the\n * class JSDoc lists no wait for it.\n */\n async isLooping(): Promise<boolean> {\n return (await this.host()).hasClass('wr-border-glow--looping');\n }\n\n /** Whether the halo layer is in the markup — the element the whole outer glow paints on. */\n async hasHaloLayer(): Promise<boolean> {\n return (await this.locatorForOptional('.wr-border-glow__edge')()) !== null;\n }\n\n /**\n * Whether the decoration stays out of the accessibility tree.\n *\n * The halo layer is the component's only decorative NODE — every other layer is a\n * pseudo-element and so was never in the tree to begin with. It carries no text and no\n * name, so losing its `aria-hidden` puts an anonymous element between the reader and\n * the card's content.\n */\n async isDecorationHiddenFromAssistiveTech(): Promise<boolean> {\n return (await (await this.edge()).getAttribute('aria-hidden')) === 'true';\n }\n\n /**\n * The projected content's text, trimmed.\n *\n * Read from `.wr-border-glow__inner` rather than the host, which is the assertion:\n * content that lands as a bare host child instead of inside the wrapper renders\n * underneath the interior bloom, and reading the host would report the same string\n * either way.\n */\n async getContentText(): Promise<string> {\n return (await this.locatorFor('.wr-border-glow__inner')()).text();\n }\n\n /** The host's inline declarations — the component's whole written output. */\n private async declarations(): Promise<Map<string, string>> {\n return parseInlineDeclarations((await (await this.host()).getAttribute('style')) ?? '');\n }\n\n /** One inline custom property, or `null` when the component has not written it. */\n private async readVar(name: string): Promise<string | null> {\n return (await this.declarations()).get(name) ?? null;\n }\n\n /** One inline custom property that is always written, as a number. */\n private async readNumberVar(name: string, method: string): Promise<number> {\n const raw = await this.readVar(name);\n const value = raw === null ? Number.NaN : Number.parseFloat(raw);\n if (Number.isNaN(value)) {\n throw new Error(\n `WrBorderGlowHarness.${method}(): \\`${name}\\` reads \"${raw ?? '(absent)'}\" on the host. The component ` +\n 'writes this one on every input change whatever the inputs say, so a missing or unreadable value means ' +\n 'the variable bag stopped reaching the element at all.'\n );\n }\n return value;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-bits-border-glow-testing.mjs","names":[],"sources":["../../../projects/lib/bits/border-glow/testing/wr-border-glow-harness.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrBorderGlowHarnessFilters } from './types';\n\n/** The seven halo steps, in the order the component writes them: 100%, 60%, 50%, 40%, 30%, 20%, 10%. */\nconst GLOW_VAR_SUFFIXES = ['', '-60', '-50', '-40', '-30', '-20', '-10'] as const;\n\n/** The seven mesh-gradient slots, spelled out rather than numbered — as the component names them. */\nconst GRADIENT_VAR_SLOTS = ['one', 'two', 'three', 'four', 'five', 'six', 'seven'] as const;\n\n/** `hsl(40deg 80% 80% / 65%)` — the alpha, which is the only part `glowIntensity` moves. */\nconst GLOW_ALPHA = /\\/\\s*(-?[\\d.]+)%\\s*\\)\\s*$/;\n\n/** `color-mix(in srgb, var(--wr-color-primary) 65%, transparent)` — the same alpha, for a CSS colour. */\nconst GLOW_MIX_ALPHA = /^color-mix\\(in srgb,\\s*.+\\s+(-?[\\d.]+)%\\s*,\\s*transparent\\s*\\)$/;\n\n/** `radial-gradient(at 80% 55%, #c084fc 0px, transparent 50%)` — the colour, not the position. */\nconst RADIAL_GRADIENT_COLOR = /^radial-gradient\\(\\s*at\\s+[^,]+,\\s*(.+?)\\s+0px\\s*,/;\n\n/** `linear-gradient(#c084fc 0 100%)` — the flat base slice. */\nconst LINEAR_GRADIENT_COLOR = /^linear-gradient\\(\\s*(.+?)\\s+0\\s+100%\\s*\\)$/;\n\n/**\n * Split an inline `style` attribute into its declarations.\n *\n * Read from the attribute rather than through `getCssValue()` on purpose. jsdom echoes\n * an inline custom property back through `getComputedStyle`, so the computed read passes\n * here and would answer something else entirely in a browser — where the stylesheet's\n * own per-theme default resolves and \"the author left this to the theme\" quietly becomes\n * a colour. The attribute holds what the component itself wrote, and nothing else.\n */\nfunction parseInlineDeclarations(style: string): Map<string, string> {\n const declarations = new Map<string, string>();\n for (const declaration of style.split(';')) {\n const colon = declaration.indexOf(':');\n if (colon === -1) continue;\n const name = declaration.slice(0, colon).trim();\n if (name !== '') declarations.set(name, declaration.slice(colon + 1).trim());\n }\n return declarations;\n}\n\n/**\n * Test harness for `<wr-border-glow>` — a card whose border lights up under the cursor.\n *\n * **Nearly all of this component is a bag of custom properties written onto the host,\n * and that is what the harness reads.** The paint itself — the conic mask, the stacked\n * `box-shadow` halo, the soft-light bloom — lives in the stylesheet, so a unit test can\n * never see it. What it CAN see is the arithmetic that feeds it, and that is where the\n * breakages are: the seven-step alpha ramp {@link getGlowAlphaRamp} derives from\n * `glowIntensity`, the `[0,1,2,0,1,2,1]` palette mapping in {@link getGradientColors}\n * with its clamp for short palettes, and the pointer maths behind\n * {@link getPointerAngle} / {@link getEdgeProximity}.\n *\n * **Every custom-property getter reads the inline declaration** — see\n * {@link parseInlineDeclarations} for why a computed read would be a different, and\n * wrong, question.\n *\n * **A colour getter answering `null` means that variable is not on the host now**, which\n * is the contract when the input is unset or its value was refused: the stylesheet's\n * per-theme default takes over. An input set and then put back to `null` comes off the\n * host, so `null` says nothing about whether it was ever set.\n *\n * There is deliberately no `getGlowOpacity()`, `isGlowVisible()` or `isHovered()`: the\n * visible strength is a `calc()` in the stylesheet gated behind `:not(:hover)`, and\n * jsdom loads no stylesheet and matches no `:hover`, so any number would be invented.\n * There is no measured halo extent either — `.wr-border-glow__edge` is 0×0 in a test,\n * and {@link getGlowRadius} reads what the component asked for instead. And there is no\n * `getSweepProgress()` / `waitForSweep()`: the mount sweep is a four-second chain of\n * `requestAnimationFrame` tweens, so awaiting it either hangs the spec or samples one\n * frame and reports a value that was never settled. {@link isSweeping} answers the one\n * durable fact — that the sweep started. Nor is there a `getLoopAngle()` or a wait for\n * the loop: `[loop]` writes a new angle on every frame for as long as it is on, so the\n * angle never settles and any sample is one frame of an endless animation.\n * {@link isLooping} answers that it is on.\n *\n * @example\n * ```ts\n * const card = await loader.getHarness(WrBorderGlowHarness.with({ text: /Hover me/ }));\n *\n * expect(await card.getBorderRadius()).toBe(28);\n * expect(await card.getPointerAngle()).toBeNull(); // no pointer, sweep or loop has written one yet\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrBorderGlowHarness extends ComponentHarness {\n static hostSelector = 'wr-border-glow';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrBorderGlowHarnessFilters = {}): HarnessPredicate<WrBorderGlowHarness> {\n return new HarnessPredicate(WrBorderGlowHarness, options)\n .addOption('text', options.text, (harness, text) =>\n HarnessPredicate.stringMatches(harness.getContentText(), text)\n )\n .addOption('sweeping', options.sweeping, async (harness, sweeping) => (await harness.isSweeping()) === sweeping)\n .addOption('looping', options.looping, async (harness, looping) => (await harness.isLooping()) === looping);\n }\n\n private readonly edge = this.locatorFor('.wr-border-glow__edge');\n\n /** The corner radius the stylesheet is told to round to, in pixels. */\n async getBorderRadius(): Promise<number> {\n return this.readNumberVar('--wr-border-glow-radius', 'getBorderRadius');\n }\n\n /** How far past the card edge the halo is told to reach, in pixels. */\n async getGlowRadius(): Promise<number> {\n return this.readNumberVar('--wr-border-glow-padding', 'getGlowRadius');\n }\n\n /**\n * The width of the lit cone, as a share of the perimeter.\n *\n * Unitless by contract — the stylesheet multiplies it — so a `px` creeping in here is\n * a real regression that leaves the mask with a length where it wants a number.\n */\n async getConeSpread(): Promise<number> {\n return this.readNumberVar('--wr-border-glow-cone-spread', 'getConeSpread');\n }\n\n /** How sharply the halo fades as the cursor leaves an edge; lower is a wider falloff. */\n async getEdgeSensitivity(): Promise<number> {\n return this.readNumberVar('--wr-border-glow-edge-sensitivity', 'getEdgeSensitivity');\n }\n\n /** Strength of the interior soft-light bloom, again unitless. */\n async getFillOpacity(): Promise<number> {\n return this.readNumberVar('--wr-border-glow-fill-opacity', 'getFillOpacity');\n }\n\n /**\n * The card fill the author asked for, or `null` when they left it to the theme.\n *\n * The distinction is the whole point: an unset `[backgroundColor]` means the surface\n * token decides, light or dark, and a card that starts declaring its own fill has\n * stopped following the theme.\n */\n async getBackgroundColor(): Promise<string | null> {\n return this.readVar('--wr-border-glow-bg');\n }\n\n /**\n * The halo colour at full strength, or `null` when the theme decides.\n *\n * Comes back as written, in one of two forms, so this also pins the assembly: the\n * `hsl(H deg S% L% / A%)` built from bare `'H S L'` parts, or\n * `color-mix(in srgb, <colour> 100%, transparent)` for any other value. Be aware the\n * second form is a containment check and not a colour parser — `'banana'` is written\n * just as faithfully as `'#3567ff'`, and the browser then drops every halo declaration\n * that reads it — so a plausible-looking value here does not mean it paints. A value\n * that could escape the `color-mix()` is never written, and reads `null`.\n */\n async getGlowColor(): Promise<string | null> {\n return this.readVar('--wr-border-glow-color');\n }\n\n /**\n * The seven halo alphas in percent, brightest first, or `null` when no glow colour\n * is set.\n *\n * This is the whole of `[glowIntensity]`: the steps are `[100, 60, 50, 40, 30, 20,\n * 10]` multiplied by the intensity and clamped at 100, so `0.5` gives\n * `[50, 30, 25, 20, 15, 10, 5]` and anything above 1 saturates from the top down. The\n * clamp is one-sided — a negative intensity produces negative percentages, which a\n * browser drops as invalid declarations and the halo vanishes — so the numbers are\n * returned as written rather than corrected, which is how a spec can see it.\n *\n * Read from either form {@link getGlowColor} describes: the `/ A%` of an `hsl()`, or\n * the colour's share in a `color-mix()`. All seven steps must be in the form the first\n * one is — they are written together from one value, so a mix of the two is a ramp\n * that broke half way.\n */\n async getGlowAlphaRamp(): Promise<number[] | null> {\n const declarations = await this.declarations();\n const first = declarations.get('--wr-border-glow-color');\n if (first === undefined) return null;\n const form = first.startsWith('color-mix(') ? GLOW_MIX_ALPHA : GLOW_ALPHA;\n\n return GLOW_VAR_SUFFIXES.map(suffix => {\n const name = `--wr-border-glow-color${suffix}`;\n const raw = declarations.get(name);\n const alpha = raw === undefined ? null : form.exec(raw);\n if (!alpha) {\n throw new Error(\n `WrBorderGlowHarness.getGlowAlphaRamp(): \\`${name}\\` reads \"${raw ?? '(absent)'}\". The seven halo steps ` +\n 'are written together from one glow colour, so a step that is missing, carries no alpha, or is not ' +\n `the same \\`${form === GLOW_ALPHA ? 'hsl(… / N%)' : 'color-mix(in srgb, … N%, transparent)'}\\` as the ` +\n 'first means the ramp stopped being built — the halo would fade in bands the stylesheet cannot use.'\n );\n }\n return Number.parseFloat(alpha[1]);\n });\n }\n\n /**\n * The seven mesh-gradient colours, in slot order, or `null` when no palette was set.\n *\n * The slots do not map one-to-one onto the palette: they take colours\n * `[0, 1, 2, 0, 1, 2, 1]`, each index clamped to the last entry the palette actually\n * has. So a two-colour palette repeats the second colour rather than reading past the\n * end — which is the case worth a spec, since `undefined` in a `radial-gradient()`\n * kills the whole declaration silently.\n */\n async getGradientColors(): Promise<string[] | null> {\n const declarations = await this.declarations();\n if (!declarations.has('--wr-border-glow-gradient-one')) return null;\n\n return GRADIENT_VAR_SLOTS.map(slot => {\n const name = `--wr-border-glow-gradient-${slot}`;\n const raw = declarations.get(name);\n const colour = raw === undefined ? null : RADIAL_GRADIENT_COLOR.exec(raw);\n if (!colour) {\n throw new Error(\n `WrBorderGlowHarness.getGradientColors(): \\`${name}\\` reads \"${raw ?? '(absent)'}\", which is not the ` +\n '`radial-gradient(at X% Y%, <colour> 0px, …)` the component builds. All seven slots are written from ' +\n 'one palette, so one of them being different means the mapping broke, not the palette.'\n );\n }\n return colour[1];\n });\n }\n\n /**\n * The flat colour behind the mesh, or `null` when no palette was set.\n *\n * Always the palette's FIRST entry. Worth its own method because the base slice is the\n * one that shows where the gradients are transparent, and it drifting to the last\n * colour changes the card's whole cast without touching a gradient.\n */\n async getBaseGradientColor(): Promise<string | null> {\n const raw = await this.readVar('--wr-border-glow-gradient-base');\n if (raw === null) return null;\n\n const colour = LINEAR_GRADIENT_COLOR.exec(raw);\n if (!colour) {\n throw new Error(\n `WrBorderGlowHarness.getBaseGradientColor(): \\`--wr-border-glow-gradient-base\\` reads \"${raw}\", which is ` +\n 'not the `linear-gradient(<colour> 0 100%)` the component builds from the palette.'\n );\n }\n return colour[1];\n }\n\n /**\n * Where the lit cone points, in degrees: `0` is straight up and the angle grows\n * clockwise. Three things write it — the pointer, from where it sits; the mount sweep;\n * and `[loop]`, on every frame, or once at a resting 45 under reduced motion — so a\n * looping card answers with no pointer at all. `null` means nothing has written it yet.\n *\n * `null` is a state of its own rather than a stand-in for zero — the stylesheet's own\n * default applies until the first write, and a pointer at dead centre legitimately\n * reports `0`, so a harness that folded the two together could not tell an untouched\n * card from one being pointed at from the middle.\n */\n async getPointerAngle(): Promise<number | null> {\n const raw = await this.readVar('--wr-border-glow-angle');\n return raw === null ? null : Number.parseFloat(raw);\n }\n\n /**\n * How lit the edge is told to be: `0` at dead centre, `100` at the edge. The pointer\n * writes it from where it sits, the mount sweep as it lights and fades, and `[loop]` holds it at\n * 100 for as long as it is on, re-lighting it when a pointer leaves. `null` means\n * nothing has written it yet.\n *\n * For a pointer on a diagonal the NEARER axis wins rather than the two averaging, so a\n * cursor level with the bottom edge reads 100 wherever it is along it.\n */\n async getEdgeProximity(): Promise<number | null> {\n const raw = await this.readVar('--wr-border-glow-edge-proximity');\n return raw === null ? null : Number.parseFloat(raw);\n }\n\n /**\n * Point at the card, in client coordinates.\n *\n * **Refuses on a card with no box, which in a unit test is every card.** The component\n * measures the pointer against its own centre, and with a 0×0 rect that maths\n * degenerates: the centre is the top-left corner, and `1 / Math.min(0, 0)` pins the\n * proximity at 100 for every position on the page. Both would come back as confident\n * numbers describing nothing. Give the host a box first, exactly as the component's own\n * spec does:\n *\n * ```ts\n * const card = fixture.nativeElement.querySelector('wr-border-glow');\n * card.getBoundingClientRect = () => ({ left: 0, top: 0, width: 200, height: 100 }) as DOMRect;\n * ```\n *\n * A `pointermove` is dispatched on the host itself, so it needs no hit test and cannot\n * miss — the coordinates only ever reach the component's own arithmetic.\n */\n async movePointerTo(clientX: number, clientY: number): Promise<void> {\n const host = await this.host();\n const { width, height } = await host.getDimensions();\n if (!width || !height) {\n throw new Error(\n `WrBorderGlowHarness.movePointerTo(${clientX}, ${clientY}): this card measures ${width}×${height}, so a ` +\n 'pointer position over it means nothing — the component reads the cursor against the card centre, and ' +\n 'with no box the centre is the top-left corner and the proximity saturates for every coordinate. Stub ' +\n '`getBoundingClientRect` on the host first.'\n );\n }\n await host.dispatchEvent('pointermove', { clientX, clientY, pointerId: 1, isPrimary: true });\n }\n\n /**\n * Whether the one-shot mount sweep is running.\n *\n * The class is added synchronously when the sweep starts and removed when it ends\n * about four seconds later, or when the component is torn down — so a spec asserts\n * that it STARTED, and leaves the angle alone while it does. This is also the\n * reduced-motion assertion: `[animated]=\"true\"` for someone who asked for less motion\n * must leave this `false`, and no angle written at all.\n */\n async isSweeping(): Promise<boolean> {\n return (await this.host()).hasClass('wr-border-glow--sweeping');\n }\n\n /**\n * Whether the endless loop holds the card.\n *\n * The class is added when `[loop]` turns on, under reduced motion too — there the edge\n * stays lit and still, so the glow layers must stay visible — and removed when it turns\n * off. Like {@link isSweeping}, it answers that the loop is ON, not where the cone is:\n * the angle moves on every `requestAnimationFrame` and never settles, which is why the\n * class JSDoc lists no wait for it.\n */\n async isLooping(): Promise<boolean> {\n return (await this.host()).hasClass('wr-border-glow--looping');\n }\n\n /** Whether the halo layer is in the markup — the element the whole outer glow paints on. */\n async hasHaloLayer(): Promise<boolean> {\n return (await this.locatorForOptional('.wr-border-glow__edge')()) !== null;\n }\n\n /**\n * Whether the decoration stays out of the accessibility tree.\n *\n * The halo layer is the component's only decorative NODE — every other layer is a\n * pseudo-element and so was never in the tree to begin with. It carries no text and no\n * name, so losing its `aria-hidden` puts an anonymous element between the reader and\n * the card's content.\n */\n async isDecorationHiddenFromAssistiveTech(): Promise<boolean> {\n return (await (await this.edge()).getAttribute('aria-hidden')) === 'true';\n }\n\n /**\n * The projected content's text, trimmed.\n *\n * Read from `.wr-border-glow__inner` rather than the host, which is the assertion:\n * content that lands as a bare host child instead of inside the wrapper renders\n * underneath the interior bloom, and reading the host would report the same string\n * either way.\n */\n async getContentText(): Promise<string> {\n return (await this.locatorFor('.wr-border-glow__inner')()).text();\n }\n\n /** The host's inline declarations — the component's whole written output. */\n private async declarations(): Promise<Map<string, string>> {\n return parseInlineDeclarations((await (await this.host()).getAttribute('style')) ?? '');\n }\n\n /** One inline custom property, or `null` when the component has not written it. */\n private async readVar(name: string): Promise<string | null> {\n return (await this.declarations()).get(name) ?? null;\n }\n\n /** One inline custom property that is always written, as a number. */\n private async readNumberVar(name: string, method: string): Promise<number> {\n const raw = await this.readVar(name);\n const value = raw === null ? Number.NaN : Number.parseFloat(raw);\n if (Number.isNaN(value)) {\n throw new Error(\n `WrBorderGlowHarness.${method}(): \\`${name}\\` reads \"${raw ?? '(absent)'}\" on the host. The component ` +\n 'writes this one on every input change whatever the inputs say, so a missing or unreadable value means ' +\n 'the variable bag stopped reaching the element at all.'\n );\n }\n return value;\n }\n}\n"],"mappings":";;;;;;;AAYA,MAAM,oBAAoB;CAAC;CAAI;CAAO;CAAO;CAAO;CAAO;CAAO;AAAK;AAGvE,MAAM,qBAAqB;CAAC;CAAO;CAAO;CAAS;CAAQ;CAAQ;CAAO;AAAO;AAGjF,MAAM,aAAa;AAGnB,MAAM,iBAAiB;AAGvB,MAAM,wBAAwB;AAG9B,MAAM,wBAAwB;AAW9B,SAAS,wBAAwB,OAAa;CAC5C,MAAM,+BAAe,IAAI,IAAG;CAC5B,KAAK,MAAM,eAAe,MAAM,MAAM,GAAG,GAAG;EAC1C,MAAM,QAAQ,YAAY,QAAQ,GAAG;EACrC,IAAI,UAAU,IAAI;EAClB,MAAM,OAAO,YAAY,MAAM,GAAG,KAAK,CAAC,CAAC,KAAI;EAC7C,IAAI,SAAS,IAAI,aAAa,IAAI,MAAM,YAAY,MAAM,QAAQ,CAAC,CAAC,CAAC,KAAI,CAAE;CAC7E;CACA,OAAO;AACT;AA8CA,IAAa,sBAAb,MAAa,4BAA4B,iBAAgB;CACvD,OAAO,eAAe;CAGtB,OAAO,KAAK,UAAsC,CAAA,GAAE;EAClD,OAAO,IAAI,iBAAiB,qBAAqB,OAAO,CAAC,CACtD,UAAU,QAAQ,QAAQ,OAAO,SAAS,SACzC,iBAAiB,cAAc,QAAQ,eAAc,GAAI,IAAI,CAAC,CAC/D,CACA,UAAU,YAAY,QAAQ,UAAU,OAAO,SAAS,aAAc,MAAM,QAAQ,WAAU,MAAQ,QAAQ,CAAC,CAC/G,UAAU,WAAW,QAAQ,SAAS,OAAO,SAAS,YAAa,MAAM,QAAQ,UAAS,MAAQ,OAAO;CAC9G;CAEiB,OAAO,KAAK,WAAW,uBAAuB;CAG/D,MAAM,kBAAe;EACnB,OAAO,KAAK,cAAc,2BAA2B,iBAAiB;CACxE;CAGA,MAAM,gBAAa;EACjB,OAAO,KAAK,cAAc,4BAA4B,eAAe;CACvE;CAQA,MAAM,gBAAa;EACjB,OAAO,KAAK,cAAc,gCAAgC,eAAe;CAC3E;CAGA,MAAM,qBAAkB;EACtB,OAAO,KAAK,cAAc,qCAAqC,oBAAoB;CACrF;CAGA,MAAM,iBAAc;EAClB,OAAO,KAAK,cAAc,iCAAiC,gBAAgB;CAC7E;CASA,MAAM,qBAAkB;EACtB,OAAO,KAAK,QAAQ,qBAAqB;CAC3C;CAaA,MAAM,eAAY;EAChB,OAAO,KAAK,QAAQ,wBAAwB;CAC9C;CAkBA,MAAM,mBAAgB;EACpB,MAAM,eAAe,MAAM,KAAK,aAAY;EAC5C,MAAM,QAAQ,aAAa,IAAI,wBAAwB;EACvD,IAAI,UAAU,QAAW,OAAO;EAChC,MAAM,OAAO,MAAM,WAAW,YAAY,IAAI,iBAAiB;EAE/D,OAAO,kBAAkB,KAAI,WAAS;GACpC,MAAM,OAAO,yBAAyB;GACtC,MAAM,MAAM,aAAa,IAAI,IAAI;GACjC,MAAM,QAAQ,QAAQ,SAAY,OAAO,KAAK,KAAK,GAAG;GACtD,IAAI,CAAC,OACH,MAAM,IAAI,MACR,6CAA6C,KAAI,YAAa,OAAO,WAAU,uIAE/D,SAAS,aAAa,gBAAgB,wCAAuC,6GACS;GAG1G,OAAO,OAAO,WAAW,MAAM,EAAE;EACnC,CAAC;CACH;CAWA,MAAM,oBAAiB;EACrB,MAAM,eAAe,MAAM,KAAK,aAAY;EAC5C,IAAI,CAAC,aAAa,IAAI,+BAA+B,GAAG,OAAO;EAE/D,OAAO,mBAAmB,KAAI,SAAO;GACnC,MAAM,OAAO,6BAA6B;GAC1C,MAAM,MAAM,aAAa,IAAI,IAAI;GACjC,MAAM,SAAS,QAAQ,SAAY,OAAO,sBAAsB,KAAK,GAAG;GACxE,IAAI,CAAC,QACH,MAAM,IAAI,MACR,8CAA8C,KAAI,YAAa,OAAO,WAAU,gNAES;GAG7F,OAAO,OAAO;EAChB,CAAC;CACH;CASA,MAAM,uBAAoB;EACxB,MAAM,MAAM,MAAM,KAAK,QAAQ,gCAAgC;EAC/D,IAAI,QAAQ,MAAM,OAAO;EAEzB,MAAM,SAAS,sBAAsB,KAAK,GAAG;EAC7C,IAAI,CAAC,QACH,MAAM,IAAI,MACR,yFAAyF,IAAG,gGACP;EAGzF,OAAO,OAAO;CAChB;CAaA,MAAM,kBAAe;EACnB,MAAM,MAAM,MAAM,KAAK,QAAQ,wBAAwB;EACvD,OAAO,QAAQ,OAAO,OAAO,OAAO,WAAW,GAAG;CACpD;CAWA,MAAM,mBAAgB;EACpB,MAAM,MAAM,MAAM,KAAK,QAAQ,iCAAiC;EAChE,OAAO,QAAQ,OAAO,OAAO,OAAO,WAAW,GAAG;CACpD;CAoBA,MAAM,cAAc,SAAiB,SAAe;EAClD,MAAM,OAAO,MAAM,KAAK,KAAI;EAC5B,MAAM,EAAE,OAAO,WAAW,MAAM,KAAK,cAAa;EAClD,IAAI,CAAC,SAAS,CAAC,QACb,MAAM,IAAI,MACR,qCAAqC,QAAO,IAAK,QAAO,wBAAyB,MAAK,GAAI,OAAM,8PAGlD;EAGlD,MAAM,KAAK,cAAc,eAAe;GAAE;GAAS;GAAS,WAAW;GAAG,WAAW;EAAI,CAAE;CAC7F;CAWA,MAAM,aAAU;EACd,QAAQ,MAAM,KAAK,KAAI,EAAA,CAAI,SAAS,0BAA0B;CAChE;CAWA,MAAM,YAAS;EACb,QAAQ,MAAM,KAAK,KAAI,EAAA,CAAI,SAAS,yBAAyB;CAC/D;CAGA,MAAM,eAAY;EAChB,OAAQ,MAAM,KAAK,mBAAmB,uBAAuB,CAAC,CAAA,MAAQ;CACxE;CAUA,MAAM,sCAAmC;EACvC,OAAQ,OAAO,MAAM,KAAK,KAAI,EAAA,CAAI,aAAa,aAAa,MAAO;CACrE;CAUA,MAAM,iBAAc;EAClB,QAAQ,MAAM,KAAK,WAAW,wBAAwB,CAAC,CAAA,EAAA,CAAI,KAAI;CACjE;CAGQ,MAAM,eAAY;EACxB,OAAO,wBAAyB,OAAO,MAAM,KAAK,KAAI,EAAA,CAAI,aAAa,OAAO,KAAM,EAAE;CACxF;CAGQ,MAAM,QAAQ,MAAY;EAChC,QAAQ,MAAM,KAAK,aAAY,EAAA,CAAI,IAAI,IAAI,KAAK;CAClD;CAGQ,MAAM,cAAc,MAAc,QAAc;EACtD,MAAM,MAAM,MAAM,KAAK,QAAQ,IAAI;EACnC,MAAM,QAAQ,QAAQ,OAAO,MAAa,OAAO,WAAW,GAAG;EAC/D,IAAI,OAAO,MAAM,KAAK,GACpB,MAAM,IAAI,MACR,uBAAuB,OAAM,QAAS,KAAI,YAAa,OAAO,WAAU,yLAEf;EAG7D,OAAO;CACT"}
@@ -174,10 +174,16 @@ var WrBorderGlow = class WrBorderGlow {
174
174
  return vars;
175
175
  }, ...ngDevMode ? [{ debugName: "cssVars" }] : /* istanbul ignore next */ []);
176
176
  constructor() {
177
+ let written = {};
177
178
  effect(() => {
178
- const host = this.el.nativeElement;
179
+ const { style } = this.el.nativeElement;
179
180
  const vars = this.cssVars();
180
- for (const [k, v] of Object.entries(vars)) host.style.setProperty(k, v);
181
+ for (const [k, held] of Object.entries(written)) if (!Object.hasOwn(vars, k) && style.getPropertyValue(k) === held) style.removeProperty(k);
182
+ written = {};
183
+ for (const [k, v] of Object.entries(vars)) {
184
+ style.setProperty(k, v);
185
+ written[k] = style.getPropertyValue(k);
186
+ }
181
187
  });
182
188
  if (!this.isBrowser) return;
183
189
  effect((onCleanup) => {
@@ -315,7 +321,7 @@ var WrBorderGlow = class WrBorderGlow {
315
321
  }
316
322
  static ɵfac = i0.ɵɵngDeclareFactory({
317
323
  minVersion: "12.0.0",
318
- version: "22.2.3",
324
+ version: "22.2.4",
319
325
  ngImport: i0,
320
326
  type: WrBorderGlow,
321
327
  deps: [],
@@ -323,7 +329,7 @@ var WrBorderGlow = class WrBorderGlow {
323
329
  });
324
330
  static ɵcmp = i0.ɵɵngDeclareComponent({
325
331
  minVersion: "17.1.0",
326
- version: "22.2.3",
332
+ version: "22.2.4",
327
333
  type: WrBorderGlow,
328
334
  isStandalone: true,
329
335
  selector: "wr-border-glow",
@@ -429,7 +435,7 @@ var WrBorderGlow = class WrBorderGlow {
429
435
  };
430
436
  i0.ɵɵngDeclareClassMetadata({
431
437
  minVersion: "12.0.0",
432
- version: "22.2.3",
438
+ version: "22.2.4",
433
439
  ngImport: i0,
434
440
  type: WrBorderGlow,
435
441
  decorators: [{
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-bits-border-glow.mjs","names":[],"sources":["../../../projects/lib/bits/border-glow/border-glow.ts","../../../projects/lib/bits/border-glow/border-glow.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 * Port of the reactbits.dev BorderGlow effect — a cursor-tracked cone of\n * mesh-gradient light that follows the pointer along the card's perimeter,\n * with an outer halo, an optional auto-sweep on mount and an optional endless\n * loop.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport { Component, ElementRef, PLATFORM_ID, ViewEncapsulation, computed, effect, inject, input } from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { isSafeCssValue, numAttr } from 'ngwr/utils';\n\nconst RADIANS_TO_DEG = 180 / Math.PI;\n\n/**\n * Where a looping cone rests under reduced motion, and where it starts when\n * nothing has written an angle yet — the stylesheet's own default.\n */\nconst REST_ANGLE = 45;\n\n/**\n * The most frame time one loop step may count, in ms. A hidden tab, a long task\n * or a debugger stops frames without telling the loop, and the first frame back\n * would otherwise jump the cone by the whole time away.\n */\nconst MAX_LOOP_STEP_MS = 100;\n\n/** `'40 80 80'`, `'268 100% 76%'`, `'40deg 80% 80%'` — the bare HSL parts `glowColor` has always taken. */\nconst HSL_PARTS = /^\\s*([\\d.]+)(?:deg)?\\s+([\\d.]+)%?\\s+([\\d.]+)%?\\s*$/;\n\nconst GLOW_OPACITY_STEPS = [100, 60, 50, 40, 30, 20, 10] as const;\nconst GLOW_VAR_SUFFIXES = ['', '-60', '-50', '-40', '-30', '-20', '-10'] as const;\n\nconst GRADIENT_POSITIONS = ['80% 55%', '69% 34%', '8% 6%', '41% 38%', '86% 85%', '82% 18%', '51% 4%'] as const;\nconst GRADIENT_VAR_NAMES = [\n '--wr-border-glow-gradient-one',\n '--wr-border-glow-gradient-two',\n '--wr-border-glow-gradient-three',\n '--wr-border-glow-gradient-four',\n '--wr-border-glow-gradient-five',\n '--wr-border-glow-gradient-six',\n '--wr-border-glow-gradient-seven',\n] as const;\nconst GRADIENT_COLOR_INDEX = [0, 1, 2, 0, 1, 2, 1] as const;\n\nconst DEFAULT_COLORS: readonly string[] = ['#c084fc', '#f472b6', '#38bdf8'];\n\nfunction buildGlowVars(glowColor: string, intensity: number): Record<string, string> | null {\n const parts = HSL_PARTS.exec(glowColor);\n // Anything else is a CSS colour, interpolated into a `color-mix()` — so it is\n // held to the containment check `colors` and `backgroundColor` pass, and an\n // unsafe value leaves the halo to the theme.\n if (!parts && !isSafeCssValue(glowColor)) return null;\n const base = parts\n ? `${Number.parseFloat(parts[1])}deg ${Number.parseFloat(parts[2])}% ${Number.parseFloat(parts[3])}%`\n : glowColor.trim();\n const out: Record<string, string> = {};\n for (let i = 0; i < GLOW_OPACITY_STEPS.length; i++) {\n const alpha = Math.min(GLOW_OPACITY_STEPS[i] * intensity, 100);\n out[`--wr-border-glow-color${GLOW_VAR_SUFFIXES[i]}`] = parts\n ? `hsl(${base} / ${alpha}%)`\n : `color-mix(in srgb, ${base} ${alpha}%, transparent)`;\n }\n return out;\n}\n\nfunction buildGradientVars(colors: readonly string[]): Record<string, string> {\n const out: Record<string, string> = {};\n // Each entry is INTERPOLATED into the `radial-gradient()` below, so a value\n // that balances the parens around it escapes into a sibling layer and can add\n // a `url()` the browser fetches. One unsafe entry discards the palette — the\n // same all-or-nothing rule `wr-gradient-text` follows, for the same reason.\n const palette = colors.length > 0 && colors.every(color => isSafeCssValue(color)) ? colors : DEFAULT_COLORS;\n for (let i = 0; i < GRADIENT_VAR_NAMES.length; i++) {\n const colour = palette[Math.min(GRADIENT_COLOR_INDEX[i], palette.length - 1)];\n out[GRADIENT_VAR_NAMES[i]] = `radial-gradient(at ${GRADIENT_POSITIONS[i]}, ${colour} 0px, transparent 50%)`;\n }\n out['--wr-border-glow-gradient-base'] = `linear-gradient(${palette[0]} 0 100%)`;\n return out;\n}\n\ninterface AnimateOptions {\n readonly start?: number;\n readonly end?: number;\n readonly duration?: number;\n readonly delay?: number;\n readonly ease?: (t: number) => number;\n readonly onUpdate: (v: number) => void;\n readonly onEnd?: () => void;\n}\n\n/** Tiny rAF-driven tweener. Returns a cancel function so callers can clean up on destroy. */\nfunction animateValue(opts: AnimateOptions): () => void {\n const { start = 0, end = 100, duration = 1000, delay = 0, ease = (t: number): number => t, onUpdate, onEnd } = opts;\n let raf = 0;\n let cancelled = false;\n\n const startAt = performance.now() + delay;\n const tick = (): void => {\n if (cancelled) return;\n const elapsed = performance.now() - startAt;\n const t = Math.min(Math.max(elapsed / duration, 0), 1);\n onUpdate(start + (end - start) * ease(t));\n if (t < 1) raf = requestAnimationFrame(tick);\n else if (onEnd) onEnd();\n };\n\n const timer = setTimeout(() => {\n raf = requestAnimationFrame(tick);\n }, delay);\n\n return () => {\n cancelled = true;\n clearTimeout(timer);\n cancelAnimationFrame(raf);\n };\n}\n\n/**\n * Cursor-tracked glowing border card. A cone of light follows the pointer\n * along the card's perimeter, brightening as the cursor approaches an edge.\n *\n * - Outer halo via stacked `box-shadow` on an inner `.edge-light` element.\n * - Inner bloom near the edges, painted with a normal blend by default and\n * overridable through `--wr-border-glow-fill-blend` (subtle; control the\n * strength via `[fillOpacity]`).\n * - Optional `[animated]` mode performs a one-shot sweep on mount, useful\n * for drawing attention to a freshly-rendered card.\n * - Optional `[loop]` mode circles the perimeter for ever, edge fully lit —\n * a frame for a call to action rather than a reaction to the cursor.\n *\n * @example\n * ```html\n * <wr-border-glow [animated]=\"true\" [colors]=\"['#c084fc', '#f472b6', '#38bdf8']\">\n * <h3>Hover me</h3>\n * <p>The border lights up where the cursor is.</p>\n * </wr-border-glow>\n * ```\n *\n * @see https://www.reactbits.dev/components/border-glow (visual reference)\n */\n@Component({\n selector: 'wr-border-glow',\n templateUrl: './border-glow.html',\n styleUrl: './border-glow.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-border-glow',\n '(pointerenter)': 'onPointerEnter()',\n '(pointermove)': 'onPointerMove($event)',\n '(pointerleave)': 'onPointerLeave()',\n },\n})\nexport class WrBorderGlow {\n /** Card background fill. When unset, uses the theme surface token. */\n readonly backgroundColor = input<string | null>(null);\n\n /** Corner radius in pixels. @default 28 */\n readonly borderRadius = input(28, { transform: numAttr(28) });\n\n /** Halo extent in pixels — how far the outer glow reaches past the card edge. @default 40 */\n readonly glowRadius = input(40, { transform: numAttr(40) });\n\n /**\n * Halo opacity multiplier (1 = full strength).\n *\n * Applies only alongside an explicit `glowColor`: the halo variables are\n * built from the two together, so on the default palette this input is read\n * and never reaches a declaration.\n *\n * @default 1\n */\n readonly glowIntensity = input(1, { transform: numAttr(1) });\n\n /** Width of the lit cone as a percentage of the perimeter. @default 25 */\n readonly coneSpread = input(25, { transform: numAttr(25) });\n\n /** How sharply the halo fades as the cursor leaves the edge. Lower = wider falloff. @default 30 */\n readonly edgeSensitivity = input(30, { transform: numAttr(30) });\n\n /** Strength of the interior bloom near edges (blend: `--wr-border-glow-fill-blend`). @default 0.5 */\n readonly fillOpacity = input(0.5, { transform: numAttr(0.5) });\n\n /**\n * Halo colour: any CSS colour (`'#3567ff'`, `'var(--wr-color-primary)'`), or\n * the bare `'H S L'` parts (no commas) it has always taken. A value that\n * could escape the `color-mix()` it is composed into is ignored. When unset,\n * the theme decides: a deep amber on light surfaces, pale amber on dark.\n * Note `glowIntensity` only applies alongside an explicit `glowColor`.\n */\n readonly glowColor = input<string | null>(null);\n\n /**\n * Mesh-gradient palette for the colored border slice. When unset, the\n * theme decides: deep tones on light, pastels on dark.\n */\n readonly colors = input<readonly string[] | null>(null);\n\n /** Play a one-shot perimeter sweep on mount, then fade out. @default false */\n readonly animated = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Circle the perimeter for ever at a constant pace, edge fully lit. The\n * pointer still drives while it is over the card, and the loop picks up from\n * where it left. Replaces the `animated` sweep; under reduced motion the edge\n * stays lit and still.\n *\n * @default false\n */\n readonly loop = input(false, { transform: coerceBooleanProperty });\n\n /** Seconds per lap while `loop` is on. @default 6 */\n readonly speed = input(6, { transform: numAttr(6) });\n\n private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n /** Whether a pointer is over the card — the loop holds still while it drives. */\n private pointerInside = false;\n\n /** Inline CSS variable bag — recomputed when colour / radius / sensitivity inputs change. */\n protected readonly cssVars = computed<Record<string, string>>(() => {\n const vars: Record<string, string> = {\n '--wr-border-glow-radius': `${this.borderRadius()}px`,\n '--wr-border-glow-padding': `${this.glowRadius()}px`,\n '--wr-border-glow-cone-spread': String(this.coneSpread()),\n '--wr-border-glow-edge-sensitivity': String(this.edgeSensitivity()),\n '--wr-border-glow-fill-opacity': String(this.fillOpacity()),\n };\n const colors = this.colors();\n if (colors !== null) Object.assign(vars, buildGradientVars(colors));\n // Colour vars are written only when set — otherwise the stylesheet's\n // per-theme defaults (light vs dark) take over.\n // Composed, not passed through: `_rules.scss` reads this var inside a\n // `linear-gradient()` and again in a `background` shorthand, where a `url()`\n // is valid and fetches. An audit bound `url(\"…\")` here and recorded the\n // request. Validated like its `colors` sibling on this same component —\n // leaving one of the two unchecked is two rules inside one component.\n const bg = this.backgroundColor();\n if (bg !== null && isSafeCssValue(bg)) vars['--wr-border-glow-bg'] = bg;\n const glow = this.glowColor();\n const glowVars = glow === null ? null : buildGlowVars(glow, this.glowIntensity());\n if (glowVars !== null) Object.assign(vars, glowVars);\n return vars;\n });\n\n constructor() {\n // Apply the variable bag to the host on every input change. Bind in an\n // effect rather than `host: { '[style.--foo]': '...' }` so a single\n // `setProperty` loop handles the lot.\n effect(() => {\n const host = this.el.nativeElement;\n const vars = this.cssVars();\n for (const [k, v] of Object.entries(vars)) host.style.setProperty(k, v);\n });\n\n if (!this.isBrowser) return;\n\n // Auto-sweep on mount — fires once when `animated` is true, unless `loop`\n // replaces it.\n effect(onCleanup => {\n if (!this.animated() || this.loop()) return;\n onCleanup(this.runAutoSweep());\n });\n\n // Endless loop. Reduced motion is read here, so a change to the preference\n // swaps the rotation for a still, lit edge and back.\n effect(onCleanup => {\n if (!this.loop()) return;\n onCleanup(this.runLoop(this.platform.prefersReducedMotion()));\n });\n }\n\n protected onPointerEnter(): void {\n this.pointerInside = true;\n }\n\n /** Hands the cone back to the loop, re-lit, from wherever the pointer left it. */\n protected onPointerLeave(): void {\n this.pointerInside = false;\n if (this.loop()) this.el.nativeElement.style.setProperty('--wr-border-glow-edge-proximity', '100');\n }\n\n /** Pointer-driven update of `--wr-border-glow-angle` + `--wr-border-glow-edge-proximity`. */\n protected onPointerMove(event: PointerEvent): void {\n const host = this.el.nativeElement;\n const rect = host.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const cx = rect.width / 2;\n const cy = rect.height / 2;\n\n const dx = x - cx;\n const dy = y - cy;\n\n // `edge-proximity`: 0 at centre, 1 at the perimeter (whichever axis hits first).\n let edge = 0;\n if (dx !== 0 || dy !== 0) {\n const kx = dx === 0 ? Infinity : cx / Math.abs(dx);\n const ky = dy === 0 ? Infinity : cy / Math.abs(dy);\n edge = Math.min(Math.max(1 / Math.min(kx, ky), 0), 1);\n }\n\n // `cursor-angle`: 0deg = top, increases clockwise.\n let angle = 0;\n if (dx !== 0 || dy !== 0) {\n angle = Math.atan2(dy, dx) * RADIANS_TO_DEG + 90;\n if (angle < 0) angle += 360;\n }\n\n host.style.setProperty('--wr-border-glow-edge-proximity', (edge * 100).toFixed(3));\n host.style.setProperty('--wr-border-glow-angle', `${angle.toFixed(3)}deg`);\n }\n\n /** One-shot perimeter sweep: in → around → out, mirroring the reactbits behaviour. */\n private runAutoSweep(): () => void {\n // Reduced motion: skip the decorative mount sweep. The pointer-driven\n // glow stays — it maps 1:1 to the cursor with no autonomous motion.\n if (this.platform.prefersReducedMotion()) return () => undefined;\n const host = this.el.nativeElement;\n const ANGLE_START = 110;\n const ANGLE_END = 465;\n host.classList.add('wr-border-glow--sweeping');\n host.style.setProperty('--wr-border-glow-angle', `${ANGLE_START}deg`);\n\n const easeOutCubic = (t: number): number => 1 - (1 - t) ** 3;\n const easeInCubic = (t: number): number => t * t * t;\n const lerpAngle = (v: number): string => `${(ANGLE_END - ANGLE_START) * (v / 100) + ANGLE_START}deg`;\n\n // 1. Ramp edge-proximity up to 100 quickly.\n const cancelA = animateValue({\n duration: 500,\n onUpdate: v => host.style.setProperty('--wr-border-glow-edge-proximity', `${v}`),\n });\n // 2. Sweep angle first half (ease-in).\n const cancelB = animateValue({\n duration: 1500,\n end: 50,\n ease: easeInCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-angle', lerpAngle(v)),\n });\n // 3. Sweep angle second half (ease-out).\n const cancelC = animateValue({\n delay: 1500,\n duration: 2250,\n start: 50,\n end: 100,\n ease: easeOutCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-angle', lerpAngle(v)),\n });\n // 4. Fade proximity back to 0.\n const cancelD = animateValue({\n delay: 2500,\n duration: 1500,\n start: 100,\n end: 0,\n ease: easeInCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-edge-proximity', `${v}`),\n onEnd: () => host.classList.remove('wr-border-glow--sweeping'),\n });\n\n // Returned rather than registered on `destroyRef`: this method runs from an\n // effect, so registering there stacked one more teardown per re-run and none\n // of them fired until the component died. The caller ties it to the effect's\n // own cleanup instead.\n return () => {\n cancelA();\n cancelB();\n cancelC();\n cancelD();\n host.classList.remove('wr-border-glow--sweeping');\n };\n }\n\n /** Endless perimeter loop at a constant pace. Returns its canceller, like `runAutoSweep`. */\n private runLoop(reducedMotion: boolean): () => void {\n const host = this.el.nativeElement;\n host.classList.add('wr-border-glow--looping');\n host.style.setProperty('--wr-border-glow-edge-proximity', '100');\n\n // Reduced motion: no rotation at all. The edge stays lit and still, so a\n // card that uses it as a frame keeps one.\n if (reducedMotion) {\n host.style.setProperty('--wr-border-glow-angle', `${REST_ANGLE}deg`);\n return () => host.classList.remove('wr-border-glow--looping');\n }\n\n let raf = 0;\n let angle = REST_ANGLE;\n // `null` until there is a frame to measure from: the first frame, and the\n // first after the pointer leaves or the card scrolls back into view, pick up\n // the painted angle instead of jumping by the time that passed.\n let last: number | null = null;\n\n const tick = (now: number): void => {\n raf = requestAnimationFrame(tick);\n if (this.pointerInside) {\n last = null;\n return;\n }\n if (last === null) {\n angle = this.paintedAngle();\n } else {\n // By elapsed time rather than per frame, so the pace is the same at\n // any refresh rate. `speed` is in seconds, the timestamps in ms.\n const lap = this.speed() * 1000;\n const step = Math.min(now - last, MAX_LOOP_STEP_MS);\n if (lap > 0) angle = (angle + (360 * step) / lap) % 360;\n }\n last = now;\n host.style.setProperty('--wr-border-glow-angle', `${angle.toFixed(3)}deg`);\n };\n const start = (): void => {\n if (raf === 0) raf = requestAnimationFrame(tick);\n };\n const pause = (): void => {\n cancelAnimationFrame(raf);\n raf = 0;\n last = null;\n };\n\n // Off screen the loop stops asking for frames. Without an observer (jsdom)\n // it simply runs.\n let observer: IntersectionObserver | null = null;\n if (typeof IntersectionObserver === 'undefined') {\n start();\n } else {\n observer = new IntersectionObserver(entries => (entries[entries.length - 1].isIntersecting ? start() : pause()));\n observer.observe(host);\n }\n\n return () => {\n observer?.disconnect();\n pause();\n host.classList.remove('wr-border-glow--looping');\n };\n }\n\n /** The angle the host paints now — the pointer's, a sweep's or the loop's own. */\n private paintedAngle(): number {\n const angle = Number.parseFloat(this.el.nativeElement.style.getPropertyValue('--wr-border-glow-angle'));\n return Number.isNaN(angle) ? REST_ANGLE : angle;\n }\n}\n","<span class=\"wr-border-glow__edge\" aria-hidden=\"true\"></span>\n<div class=\"wr-border-glow__inner\">\n <ng-content />\n</div>\n"],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-bits-border-glow.mjs","names":[],"sources":["../../../projects/lib/bits/border-glow/border-glow.ts","../../../projects/lib/bits/border-glow/border-glow.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 * Port of the reactbits.dev BorderGlow effect — a cursor-tracked cone of\n * mesh-gradient light that follows the pointer along the card's perimeter,\n * with an outer halo, an optional auto-sweep on mount and an optional endless\n * loop.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport { Component, ElementRef, PLATFORM_ID, ViewEncapsulation, computed, effect, inject, input } from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { isSafeCssValue, numAttr } from 'ngwr/utils';\n\nconst RADIANS_TO_DEG = 180 / Math.PI;\n\n/**\n * Where a looping cone rests under reduced motion, and where it starts when\n * nothing has written an angle yet — the stylesheet's own default.\n */\nconst REST_ANGLE = 45;\n\n/**\n * The most frame time one loop step may count, in ms. A hidden tab, a long task\n * or a debugger stops frames without telling the loop, and the first frame back\n * would otherwise jump the cone by the whole time away.\n */\nconst MAX_LOOP_STEP_MS = 100;\n\n/** `'40 80 80'`, `'268 100% 76%'`, `'40deg 80% 80%'` — the bare HSL parts `glowColor` has always taken. */\nconst HSL_PARTS = /^\\s*([\\d.]+)(?:deg)?\\s+([\\d.]+)%?\\s+([\\d.]+)%?\\s*$/;\n\nconst GLOW_OPACITY_STEPS = [100, 60, 50, 40, 30, 20, 10] as const;\nconst GLOW_VAR_SUFFIXES = ['', '-60', '-50', '-40', '-30', '-20', '-10'] as const;\n\nconst GRADIENT_POSITIONS = ['80% 55%', '69% 34%', '8% 6%', '41% 38%', '86% 85%', '82% 18%', '51% 4%'] as const;\nconst GRADIENT_VAR_NAMES = [\n '--wr-border-glow-gradient-one',\n '--wr-border-glow-gradient-two',\n '--wr-border-glow-gradient-three',\n '--wr-border-glow-gradient-four',\n '--wr-border-glow-gradient-five',\n '--wr-border-glow-gradient-six',\n '--wr-border-glow-gradient-seven',\n] as const;\nconst GRADIENT_COLOR_INDEX = [0, 1, 2, 0, 1, 2, 1] as const;\n\nconst DEFAULT_COLORS: readonly string[] = ['#c084fc', '#f472b6', '#38bdf8'];\n\nfunction buildGlowVars(glowColor: string, intensity: number): Record<string, string> | null {\n const parts = HSL_PARTS.exec(glowColor);\n // Anything else is a CSS colour, interpolated into a `color-mix()` — so it is\n // held to the containment check `colors` and `backgroundColor` pass, and an\n // unsafe value leaves the halo to the theme.\n if (!parts && !isSafeCssValue(glowColor)) return null;\n const base = parts\n ? `${Number.parseFloat(parts[1])}deg ${Number.parseFloat(parts[2])}% ${Number.parseFloat(parts[3])}%`\n : glowColor.trim();\n const out: Record<string, string> = {};\n for (let i = 0; i < GLOW_OPACITY_STEPS.length; i++) {\n const alpha = Math.min(GLOW_OPACITY_STEPS[i] * intensity, 100);\n out[`--wr-border-glow-color${GLOW_VAR_SUFFIXES[i]}`] = parts\n ? `hsl(${base} / ${alpha}%)`\n : `color-mix(in srgb, ${base} ${alpha}%, transparent)`;\n }\n return out;\n}\n\nfunction buildGradientVars(colors: readonly string[]): Record<string, string> {\n const out: Record<string, string> = {};\n // Each entry is INTERPOLATED into the `radial-gradient()` below, so a value\n // that balances the parens around it escapes into a sibling layer and can add\n // a `url()` the browser fetches. One unsafe entry discards the palette — the\n // same all-or-nothing rule `wr-gradient-text` follows, for the same reason.\n const palette = colors.length > 0 && colors.every(color => isSafeCssValue(color)) ? colors : DEFAULT_COLORS;\n for (let i = 0; i < GRADIENT_VAR_NAMES.length; i++) {\n const colour = palette[Math.min(GRADIENT_COLOR_INDEX[i], palette.length - 1)];\n out[GRADIENT_VAR_NAMES[i]] = `radial-gradient(at ${GRADIENT_POSITIONS[i]}, ${colour} 0px, transparent 50%)`;\n }\n out['--wr-border-glow-gradient-base'] = `linear-gradient(${palette[0]} 0 100%)`;\n return out;\n}\n\ninterface AnimateOptions {\n readonly start?: number;\n readonly end?: number;\n readonly duration?: number;\n readonly delay?: number;\n readonly ease?: (t: number) => number;\n readonly onUpdate: (v: number) => void;\n readonly onEnd?: () => void;\n}\n\n/** Tiny rAF-driven tweener. Returns a cancel function so callers can clean up on destroy. */\nfunction animateValue(opts: AnimateOptions): () => void {\n const { start = 0, end = 100, duration = 1000, delay = 0, ease = (t: number): number => t, onUpdate, onEnd } = opts;\n let raf = 0;\n let cancelled = false;\n\n const startAt = performance.now() + delay;\n const tick = (): void => {\n if (cancelled) return;\n const elapsed = performance.now() - startAt;\n const t = Math.min(Math.max(elapsed / duration, 0), 1);\n onUpdate(start + (end - start) * ease(t));\n if (t < 1) raf = requestAnimationFrame(tick);\n else if (onEnd) onEnd();\n };\n\n const timer = setTimeout(() => {\n raf = requestAnimationFrame(tick);\n }, delay);\n\n return () => {\n cancelled = true;\n clearTimeout(timer);\n cancelAnimationFrame(raf);\n };\n}\n\n/**\n * Cursor-tracked glowing border card. A cone of light follows the pointer\n * along the card's perimeter, brightening as the cursor approaches an edge.\n *\n * - Outer halo via stacked `box-shadow` on an inner `.edge-light` element.\n * - Inner bloom near the edges, painted with a normal blend by default and\n * overridable through `--wr-border-glow-fill-blend` (subtle; control the\n * strength via `[fillOpacity]`).\n * - Optional `[animated]` mode performs a one-shot sweep on mount, useful\n * for drawing attention to a freshly-rendered card.\n * - Optional `[loop]` mode circles the perimeter for ever, edge fully lit —\n * a frame for a call to action rather than a reaction to the cursor.\n *\n * @example\n * ```html\n * <wr-border-glow [animated]=\"true\" [colors]=\"['#c084fc', '#f472b6', '#38bdf8']\">\n * <h3>Hover me</h3>\n * <p>The border lights up where the cursor is.</p>\n * </wr-border-glow>\n * ```\n *\n * @see https://www.reactbits.dev/components/border-glow (visual reference)\n */\n@Component({\n selector: 'wr-border-glow',\n templateUrl: './border-glow.html',\n styleUrl: './border-glow.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-border-glow',\n '(pointerenter)': 'onPointerEnter()',\n '(pointermove)': 'onPointerMove($event)',\n '(pointerleave)': 'onPointerLeave()',\n },\n})\nexport class WrBorderGlow {\n /**\n * Card background fill. When unset, uses the theme surface token. A hook set inline on\n * the same element is replaced by this input and does not come back when it is unset,\n * so set it from a stylesheet rule. The same goes for `glowColor` and `colors`.\n */\n readonly backgroundColor = input<string | null>(null);\n\n /** Corner radius in pixels. @default 28 */\n readonly borderRadius = input(28, { transform: numAttr(28) });\n\n /** Halo extent in pixels — how far the outer glow reaches past the card edge. @default 40 */\n readonly glowRadius = input(40, { transform: numAttr(40) });\n\n /**\n * Halo opacity multiplier (1 = full strength).\n *\n * Applies only alongside an explicit `glowColor`: the halo variables are\n * built from the two together, so on the default palette this input is read\n * and never reaches a declaration.\n *\n * @default 1\n */\n readonly glowIntensity = input(1, { transform: numAttr(1) });\n\n /** Width of the lit cone as a percentage of the perimeter. @default 25 */\n readonly coneSpread = input(25, { transform: numAttr(25) });\n\n /** How sharply the halo fades as the cursor leaves the edge. Lower = wider falloff. @default 30 */\n readonly edgeSensitivity = input(30, { transform: numAttr(30) });\n\n /** Strength of the interior bloom near edges (blend: `--wr-border-glow-fill-blend`). @default 0.5 */\n readonly fillOpacity = input(0.5, { transform: numAttr(0.5) });\n\n /**\n * Halo colour: any CSS colour (`'#3567ff'`, `'var(--wr-color-primary)'`), or\n * the bare `'H S L'` parts (no commas) it has always taken. A value that\n * could escape the `color-mix()` it is composed into is ignored. When unset,\n * the theme decides: a deep amber on light surfaces, pale amber on dark.\n * Note `glowIntensity` only applies alongside an explicit `glowColor`.\n */\n readonly glowColor = input<string | null>(null);\n\n /**\n * Mesh-gradient palette for the colored border slice. When unset, the\n * theme decides: deep tones on light, pastels on dark.\n */\n readonly colors = input<readonly string[] | null>(null);\n\n /** Play a one-shot perimeter sweep on mount, then fade out. @default false */\n readonly animated = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Circle the perimeter for ever at a constant pace, edge fully lit. The\n * pointer still drives while it is over the card, and the loop picks up from\n * where it left. Replaces the `animated` sweep; under reduced motion the edge\n * stays lit and still.\n *\n * @default false\n */\n readonly loop = input(false, { transform: coerceBooleanProperty });\n\n /** Seconds per lap while `loop` is on. @default 6 */\n readonly speed = input(6, { transform: numAttr(6) });\n\n private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n /** Whether a pointer is over the card — the loop holds still while it drives. */\n private pointerInside = false;\n\n /** Inline CSS variable bag — recomputed when colour / radius / sensitivity inputs change. */\n protected readonly cssVars = computed<Record<string, string>>(() => {\n const vars: Record<string, string> = {\n '--wr-border-glow-radius': `${this.borderRadius()}px`,\n '--wr-border-glow-padding': `${this.glowRadius()}px`,\n '--wr-border-glow-cone-spread': String(this.coneSpread()),\n '--wr-border-glow-edge-sensitivity': String(this.edgeSensitivity()),\n '--wr-border-glow-fill-opacity': String(this.fillOpacity()),\n };\n const colors = this.colors();\n if (colors !== null) Object.assign(vars, buildGradientVars(colors));\n // Colour vars are written only when set — otherwise the stylesheet's\n // per-theme defaults (light vs dark) take over.\n // Composed, not passed through: `_rules.scss` reads this var inside a\n // `linear-gradient()` and again in a `background` shorthand, where a `url()`\n // is valid and fetches. An audit bound `url(\"…\")` here and recorded the\n // request. Validated like its `colors` sibling on this same component —\n // leaving one of the two unchecked is two rules inside one component.\n const bg = this.backgroundColor();\n if (bg !== null && isSafeCssValue(bg)) vars['--wr-border-glow-bg'] = bg;\n const glow = this.glowColor();\n const glowVars = glow === null ? null : buildGlowVars(glow, this.glowIntensity());\n if (glowVars !== null) Object.assign(vars, glowVars);\n return vars;\n });\n\n constructor() {\n // Apply the variable bag to the host on every input change. Bind in an\n // effect rather than `host: { '[style.--foo]': '...' }` so a single\n // `setProperty` loop handles the lot. What left the bag comes off the host, or an\n // input set and then unset stays over the stylesheet's default for good, but only\n // while the host still holds what this wrote: anything else there is someone else's.\n // Read back rather than kept as written, because the browser trims a custom property.\n let written: Record<string, string> = {};\n effect(() => {\n const { style } = this.el.nativeElement;\n const vars = this.cssVars();\n for (const [k, held] of Object.entries(written)) {\n if (!Object.hasOwn(vars, k) && style.getPropertyValue(k) === held) style.removeProperty(k);\n }\n written = {};\n for (const [k, v] of Object.entries(vars)) {\n style.setProperty(k, v);\n written[k] = style.getPropertyValue(k);\n }\n });\n\n if (!this.isBrowser) return;\n\n // Auto-sweep on mount — fires once when `animated` is true, unless `loop`\n // replaces it.\n effect(onCleanup => {\n if (!this.animated() || this.loop()) return;\n onCleanup(this.runAutoSweep());\n });\n\n // Endless loop. Reduced motion is read here, so a change to the preference\n // swaps the rotation for a still, lit edge and back.\n effect(onCleanup => {\n if (!this.loop()) return;\n onCleanup(this.runLoop(this.platform.prefersReducedMotion()));\n });\n }\n\n protected onPointerEnter(): void {\n this.pointerInside = true;\n }\n\n /** Hands the cone back to the loop, re-lit, from wherever the pointer left it. */\n protected onPointerLeave(): void {\n this.pointerInside = false;\n if (this.loop()) this.el.nativeElement.style.setProperty('--wr-border-glow-edge-proximity', '100');\n }\n\n /** Pointer-driven update of `--wr-border-glow-angle` + `--wr-border-glow-edge-proximity`. */\n protected onPointerMove(event: PointerEvent): void {\n const host = this.el.nativeElement;\n const rect = host.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const cx = rect.width / 2;\n const cy = rect.height / 2;\n\n const dx = x - cx;\n const dy = y - cy;\n\n // `edge-proximity`: 0 at centre, 1 at the perimeter (whichever axis hits first).\n let edge = 0;\n if (dx !== 0 || dy !== 0) {\n const kx = dx === 0 ? Infinity : cx / Math.abs(dx);\n const ky = dy === 0 ? Infinity : cy / Math.abs(dy);\n edge = Math.min(Math.max(1 / Math.min(kx, ky), 0), 1);\n }\n\n // `cursor-angle`: 0deg = top, increases clockwise.\n let angle = 0;\n if (dx !== 0 || dy !== 0) {\n angle = Math.atan2(dy, dx) * RADIANS_TO_DEG + 90;\n if (angle < 0) angle += 360;\n }\n\n host.style.setProperty('--wr-border-glow-edge-proximity', (edge * 100).toFixed(3));\n host.style.setProperty('--wr-border-glow-angle', `${angle.toFixed(3)}deg`);\n }\n\n /** One-shot perimeter sweep: in → around → out, mirroring the reactbits behaviour. */\n private runAutoSweep(): () => void {\n // Reduced motion: skip the decorative mount sweep. The pointer-driven\n // glow stays — it maps 1:1 to the cursor with no autonomous motion.\n if (this.platform.prefersReducedMotion()) return () => undefined;\n const host = this.el.nativeElement;\n const ANGLE_START = 110;\n const ANGLE_END = 465;\n host.classList.add('wr-border-glow--sweeping');\n host.style.setProperty('--wr-border-glow-angle', `${ANGLE_START}deg`);\n\n const easeOutCubic = (t: number): number => 1 - (1 - t) ** 3;\n const easeInCubic = (t: number): number => t * t * t;\n const lerpAngle = (v: number): string => `${(ANGLE_END - ANGLE_START) * (v / 100) + ANGLE_START}deg`;\n\n // 1. Ramp edge-proximity up to 100 quickly.\n const cancelA = animateValue({\n duration: 500,\n onUpdate: v => host.style.setProperty('--wr-border-glow-edge-proximity', `${v}`),\n });\n // 2. Sweep angle first half (ease-in).\n const cancelB = animateValue({\n duration: 1500,\n end: 50,\n ease: easeInCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-angle', lerpAngle(v)),\n });\n // 3. Sweep angle second half (ease-out).\n const cancelC = animateValue({\n delay: 1500,\n duration: 2250,\n start: 50,\n end: 100,\n ease: easeOutCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-angle', lerpAngle(v)),\n });\n // 4. Fade proximity back to 0.\n const cancelD = animateValue({\n delay: 2500,\n duration: 1500,\n start: 100,\n end: 0,\n ease: easeInCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-edge-proximity', `${v}`),\n onEnd: () => host.classList.remove('wr-border-glow--sweeping'),\n });\n\n // Returned rather than registered on `destroyRef`: this method runs from an\n // effect, so registering there stacked one more teardown per re-run and none\n // of them fired until the component died. The caller ties it to the effect's\n // own cleanup instead.\n return () => {\n cancelA();\n cancelB();\n cancelC();\n cancelD();\n host.classList.remove('wr-border-glow--sweeping');\n };\n }\n\n /** Endless perimeter loop at a constant pace. Returns its canceller, like `runAutoSweep`. */\n private runLoop(reducedMotion: boolean): () => void {\n const host = this.el.nativeElement;\n host.classList.add('wr-border-glow--looping');\n host.style.setProperty('--wr-border-glow-edge-proximity', '100');\n\n // Reduced motion: no rotation at all. The edge stays lit and still, so a\n // card that uses it as a frame keeps one.\n if (reducedMotion) {\n host.style.setProperty('--wr-border-glow-angle', `${REST_ANGLE}deg`);\n return () => host.classList.remove('wr-border-glow--looping');\n }\n\n let raf = 0;\n let angle = REST_ANGLE;\n // `null` until there is a frame to measure from: the first frame, and the\n // first after the pointer leaves or the card scrolls back into view, pick up\n // the painted angle instead of jumping by the time that passed.\n let last: number | null = null;\n\n const tick = (now: number): void => {\n raf = requestAnimationFrame(tick);\n if (this.pointerInside) {\n last = null;\n return;\n }\n if (last === null) {\n angle = this.paintedAngle();\n } else {\n // By elapsed time rather than per frame, so the pace is the same at\n // any refresh rate. `speed` is in seconds, the timestamps in ms.\n const lap = this.speed() * 1000;\n const step = Math.min(now - last, MAX_LOOP_STEP_MS);\n if (lap > 0) angle = (angle + (360 * step) / lap) % 360;\n }\n last = now;\n host.style.setProperty('--wr-border-glow-angle', `${angle.toFixed(3)}deg`);\n };\n const start = (): void => {\n if (raf === 0) raf = requestAnimationFrame(tick);\n };\n const pause = (): void => {\n cancelAnimationFrame(raf);\n raf = 0;\n last = null;\n };\n\n // Off screen the loop stops asking for frames. Without an observer (jsdom)\n // it simply runs.\n let observer: IntersectionObserver | null = null;\n if (typeof IntersectionObserver === 'undefined') {\n start();\n } else {\n observer = new IntersectionObserver(entries => (entries[entries.length - 1].isIntersecting ? start() : pause()));\n observer.observe(host);\n }\n\n return () => {\n observer?.disconnect();\n pause();\n host.classList.remove('wr-border-glow--looping');\n };\n }\n\n /** The angle the host paints now — the pointer's, a sweep's or the loop's own. */\n private paintedAngle(): number {\n const angle = Number.parseFloat(this.el.nativeElement.style.getPropertyValue('--wr-border-glow-angle'));\n return Number.isNaN(angle) ? REST_ANGLE : angle;\n }\n}\n","<span class=\"wr-border-glow__edge\" aria-hidden=\"true\"></span>\n<div class=\"wr-border-glow__inner\">\n <ng-content />\n</div>\n"],"mappings":"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"}
@@ -121,7 +121,7 @@ var WrCircularText = class WrCircularText {
121
121
  }
122
122
  static ɵfac = i0.ɵɵngDeclareFactory({
123
123
  minVersion: "12.0.0",
124
- version: "22.2.3",
124
+ version: "22.2.4",
125
125
  ngImport: i0,
126
126
  type: WrCircularText,
127
127
  deps: [],
@@ -129,7 +129,7 @@ var WrCircularText = class WrCircularText {
129
129
  });
130
130
  static ɵcmp = i0.ɵɵngDeclareComponent({
131
131
  minVersion: "17.0.0",
132
- version: "22.2.3",
132
+ version: "22.2.4",
133
133
  type: WrCircularText,
134
134
  isStandalone: true,
135
135
  selector: "wr-circular-text",
@@ -175,7 +175,7 @@ var WrCircularText = class WrCircularText {
175
175
  };
176
176
  i0.ɵɵngDeclareClassMetadata({
177
177
  minVersion: "12.0.0",
178
- version: "22.2.3",
178
+ version: "22.2.4",
179
179
  ngImport: i0,
180
180
  type: WrCircularText,
181
181
  decorators: [{