ngwr 11.0.0 → 11.2.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 (237) hide show
  1. package/action-sheet/styles/_index.scss +4 -1
  2. package/anchor/styles/_index.scss +2 -1
  3. package/calendar/styles/_index.scss +11 -2
  4. package/cascader/styles/_index.scss +3 -1
  5. package/circular-text/styles/_index.scss +11 -0
  6. package/command-palette/styles/_index.scss +14 -1
  7. package/context-menu/styles/_index.scss +2 -1
  8. package/falling-text/styles/_index.scss +2 -1
  9. package/fesm2022/ngwr-action-sheet-testing.mjs +313 -0
  10. package/fesm2022/ngwr-action-sheet-testing.mjs.map +1 -0
  11. package/fesm2022/ngwr-bar-chart-testing.mjs +89 -0
  12. package/fesm2022/ngwr-bar-chart-testing.mjs.map +1 -0
  13. package/fesm2022/ngwr-blur-text-testing.mjs +203 -0
  14. package/fesm2022/ngwr-blur-text-testing.mjs.map +1 -0
  15. package/fesm2022/ngwr-blur-text.mjs +15 -1
  16. package/fesm2022/ngwr-blur-text.mjs.map +1 -1
  17. package/fesm2022/ngwr-border-glow-testing.mjs +325 -0
  18. package/fesm2022/ngwr-border-glow-testing.mjs.map +1 -0
  19. package/fesm2022/ngwr-border-glow.mjs +2 -2
  20. package/fesm2022/ngwr-breadcrumbs.mjs +2 -2
  21. package/fesm2022/ngwr-calendar-heatmap-testing.mjs +95 -0
  22. package/fesm2022/ngwr-calendar-heatmap-testing.mjs.map +1 -0
  23. package/fesm2022/ngwr-calendar-testing.mjs +368 -0
  24. package/fesm2022/ngwr-calendar-testing.mjs.map +1 -0
  25. package/fesm2022/ngwr-calendar.mjs +43 -3
  26. package/fesm2022/ngwr-calendar.mjs.map +1 -1
  27. package/fesm2022/ngwr-card.mjs +2 -2
  28. package/fesm2022/ngwr-cascader.mjs +14 -2
  29. package/fesm2022/ngwr-cascader.mjs.map +1 -1
  30. package/fesm2022/ngwr-checkbox.mjs +8 -0
  31. package/fesm2022/ngwr-checkbox.mjs.map +1 -1
  32. package/fesm2022/ngwr-circular-text-testing.mjs +230 -0
  33. package/fesm2022/ngwr-circular-text-testing.mjs.map +1 -0
  34. package/fesm2022/ngwr-circular-text.mjs +2 -2
  35. package/fesm2022/ngwr-circular-text.mjs.map +1 -1
  36. package/fesm2022/ngwr-color-picker-testing.mjs +432 -0
  37. package/fesm2022/ngwr-color-picker-testing.mjs.map +1 -0
  38. package/fesm2022/ngwr-color-picker.mjs +29 -3
  39. package/fesm2022/ngwr-color-picker.mjs.map +1 -1
  40. package/fesm2022/ngwr-context-menu.mjs +25 -5
  41. package/fesm2022/ngwr-context-menu.mjs.map +1 -1
  42. package/fesm2022/ngwr-date-picker-testing.mjs +10 -79
  43. package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -1
  44. package/fesm2022/ngwr-decrypt-text-testing.mjs +261 -0
  45. package/fesm2022/ngwr-decrypt-text-testing.mjs.map +1 -0
  46. package/fesm2022/ngwr-decrypt-text.mjs +6 -1
  47. package/fesm2022/ngwr-decrypt-text.mjs.map +1 -1
  48. package/fesm2022/ngwr-donut-chart-testing.mjs +93 -0
  49. package/fesm2022/ngwr-donut-chart-testing.mjs.map +1 -0
  50. package/fesm2022/ngwr-drag-drop.mjs +2 -2
  51. package/fesm2022/ngwr-drawer.mjs +36 -7
  52. package/fesm2022/ngwr-drawer.mjs.map +1 -1
  53. package/fesm2022/ngwr-dropdown.mjs +32 -2
  54. package/fesm2022/ngwr-dropdown.mjs.map +1 -1
  55. package/fesm2022/ngwr-event-calendar-testing.mjs +446 -0
  56. package/fesm2022/ngwr-event-calendar-testing.mjs.map +1 -0
  57. package/fesm2022/ngwr-event-calendar.mjs +43 -18
  58. package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
  59. package/fesm2022/ngwr-falling-text-testing.mjs +218 -0
  60. package/fesm2022/ngwr-falling-text-testing.mjs.map +1 -0
  61. package/fesm2022/ngwr-falling-text.mjs +2 -2
  62. package/fesm2022/ngwr-falling-text.mjs.map +1 -1
  63. package/fesm2022/ngwr-fuzzy-text-testing.mjs +134 -0
  64. package/fesm2022/ngwr-fuzzy-text-testing.mjs.map +1 -0
  65. package/fesm2022/ngwr-gauge-testing.mjs +86 -0
  66. package/fesm2022/ngwr-gauge-testing.mjs.map +1 -0
  67. package/fesm2022/ngwr-glitch-text-testing.mjs +181 -0
  68. package/fesm2022/ngwr-glitch-text-testing.mjs.map +1 -0
  69. package/fesm2022/ngwr-glitch-text.mjs +2 -2
  70. package/fesm2022/ngwr-gradient-text-testing.mjs +223 -0
  71. package/fesm2022/ngwr-gradient-text-testing.mjs.map +1 -0
  72. package/fesm2022/ngwr-gradient-text.mjs +2 -2
  73. package/fesm2022/ngwr-i18n-en.mjs +14 -0
  74. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  75. package/fesm2022/ngwr-i18n-ru.mjs +14 -0
  76. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  77. package/fesm2022/ngwr-image-cropper-testing.mjs +156 -0
  78. package/fesm2022/ngwr-image-cropper-testing.mjs.map +1 -0
  79. package/fesm2022/ngwr-image-cropper.mjs +10 -3
  80. package/fesm2022/ngwr-image-cropper.mjs.map +1 -1
  81. package/fesm2022/ngwr-input-number.mjs +73 -6
  82. package/fesm2022/ngwr-input-number.mjs.map +1 -1
  83. package/fesm2022/ngwr-knob-testing.mjs +220 -0
  84. package/fesm2022/ngwr-knob-testing.mjs.map +1 -0
  85. package/fesm2022/ngwr-lightbox-testing.mjs +223 -0
  86. package/fesm2022/ngwr-lightbox-testing.mjs.map +1 -0
  87. package/fesm2022/ngwr-lightbox.mjs +9 -2
  88. package/fesm2022/ngwr-lightbox.mjs.map +1 -1
  89. package/fesm2022/ngwr-line-chart-testing.mjs +119 -0
  90. package/fesm2022/ngwr-line-chart-testing.mjs.map +1 -0
  91. package/fesm2022/ngwr-list.mjs +2 -2
  92. package/fesm2022/ngwr-loading-bar.mjs +2 -2
  93. package/fesm2022/ngwr-marquee-testing.mjs +336 -0
  94. package/fesm2022/ngwr-marquee-testing.mjs.map +1 -0
  95. package/fesm2022/ngwr-marquee.mjs +2 -2
  96. package/fesm2022/ngwr-meter-group-testing.mjs +80 -0
  97. package/fesm2022/ngwr-meter-group-testing.mjs.map +1 -0
  98. package/fesm2022/ngwr-overlay.mjs +28 -1
  99. package/fesm2022/ngwr-overlay.mjs.map +1 -1
  100. package/fesm2022/ngwr-popover.mjs +14 -0
  101. package/fesm2022/ngwr-popover.mjs.map +1 -1
  102. package/fesm2022/ngwr-rotating-text-testing.mjs +220 -0
  103. package/fesm2022/ngwr-rotating-text-testing.mjs.map +1 -0
  104. package/fesm2022/ngwr-rotating-text.mjs +2 -2
  105. package/fesm2022/ngwr-rotating-text.mjs.map +1 -1
  106. package/fesm2022/ngwr-select.mjs +70 -11
  107. package/fesm2022/ngwr-select.mjs.map +1 -1
  108. package/fesm2022/ngwr-shiny-text-testing.mjs +237 -0
  109. package/fesm2022/ngwr-shiny-text-testing.mjs.map +1 -0
  110. package/fesm2022/ngwr-shiny-text.mjs +2 -2
  111. package/fesm2022/ngwr-slider.mjs +44 -6
  112. package/fesm2022/ngwr-slider.mjs.map +1 -1
  113. package/fesm2022/ngwr-sparkline-testing.mjs +76 -0
  114. package/fesm2022/ngwr-sparkline-testing.mjs.map +1 -0
  115. package/fesm2022/ngwr-speed-dial-testing.mjs +279 -0
  116. package/fesm2022/ngwr-speed-dial-testing.mjs.map +1 -0
  117. package/fesm2022/ngwr-splash-cursor-testing.mjs +89 -0
  118. package/fesm2022/ngwr-splash-cursor-testing.mjs.map +1 -0
  119. package/fesm2022/ngwr-split-text-testing.mjs +224 -0
  120. package/fesm2022/ngwr-split-text-testing.mjs.map +1 -0
  121. package/fesm2022/ngwr-split-text.mjs +15 -1
  122. package/fesm2022/ngwr-split-text.mjs.map +1 -1
  123. package/fesm2022/ngwr-splitter-testing.mjs +245 -0
  124. package/fesm2022/ngwr-splitter-testing.mjs.map +1 -0
  125. package/fesm2022/ngwr-spotlight-card-testing.mjs +368 -0
  126. package/fesm2022/ngwr-spotlight-card-testing.mjs.map +1 -0
  127. package/fesm2022/ngwr-spotlight-card.mjs +2 -2
  128. package/fesm2022/ngwr-star-border-testing.mjs +172 -0
  129. package/fesm2022/ngwr-star-border-testing.mjs.map +1 -0
  130. package/fesm2022/ngwr-star-border.mjs +2 -2
  131. package/fesm2022/ngwr-table.mjs +13 -3
  132. package/fesm2022/ngwr-table.mjs.map +1 -1
  133. package/fesm2022/ngwr-tabs.mjs +15 -1
  134. package/fesm2022/ngwr-tabs.mjs.map +1 -1
  135. package/fesm2022/ngwr-tilt-card-testing.mjs +190 -0
  136. package/fesm2022/ngwr-tilt-card-testing.mjs.map +1 -0
  137. package/fesm2022/ngwr-tilt-card.mjs +21 -3
  138. package/fesm2022/ngwr-tilt-card.mjs.map +1 -1
  139. package/fesm2022/ngwr-toast.mjs +36 -7
  140. package/fesm2022/ngwr-toast.mjs.map +1 -1
  141. package/fesm2022/ngwr-tour-testing.mjs +173 -0
  142. package/fesm2022/ngwr-tour-testing.mjs.map +1 -0
  143. package/fesm2022/ngwr-tour.mjs +2 -2
  144. package/fesm2022/ngwr-tour.mjs.map +1 -1
  145. package/fesm2022/ngwr-tree.mjs +10 -3
  146. package/fesm2022/ngwr-tree.mjs.map +1 -1
  147. package/fesm2022/ngwr-typewriter-testing.mjs +228 -0
  148. package/fesm2022/ngwr-typewriter-testing.mjs.map +1 -0
  149. package/fesm2022/ngwr-typewriter.mjs +2 -2
  150. package/fesm2022/ngwr-version.mjs +1 -1
  151. package/fesm2022/ngwr-version.mjs.map +1 -1
  152. package/fesm2022/ngwr-virtual-scroll.mjs +2 -2
  153. package/fesm2022/ngwr-waves-testing.mjs +128 -0
  154. package/fesm2022/ngwr-waves-testing.mjs.map +1 -0
  155. package/fesm2022/ngwr-waves.mjs +2 -2
  156. package/fesm2022/ngwr-window-testing.mjs +361 -0
  157. package/fesm2022/ngwr-window-testing.mjs.map +1 -0
  158. package/fesm2022/ngwr-window.mjs +84 -10
  159. package/fesm2022/ngwr-window.mjs.map +1 -1
  160. package/file-upload/styles/_index.scss +7 -2
  161. package/i18n/en.json +16 -2
  162. package/i18n/ru.json +16 -2
  163. package/llms-full.txt +147 -0
  164. package/llms.txt +27 -8
  165. package/mcp/providers.js +29 -0
  166. package/mcp/tools.js +1 -29
  167. package/package.json +145 -1
  168. package/result/styles/_index.scss +3 -1
  169. package/segmented/styles/_index.scss +5 -1
  170. package/select/styles/_index.scss +3 -1
  171. package/skills/ngwr/SKILL.md +115 -0
  172. package/skills/ngwr/references/catalog.md +212 -0
  173. package/skills/ngwr/references/setup.md +73 -0
  174. package/stepper/styles/_index.scss +18 -2
  175. package/table/styles/_index.scss +3 -1
  176. package/theme/styles/_colors.scss +15 -2
  177. package/theme/styles/_dark.scss +24 -12
  178. package/tree/styles/_index.scss +4 -2
  179. package/types/ngwr-action-sheet-testing.d.ts +260 -0
  180. package/types/ngwr-bar-chart-testing.d.ts +77 -0
  181. package/types/ngwr-blur-text-testing.d.ts +180 -0
  182. package/types/ngwr-blur-text.d.ts +12 -0
  183. package/types/ngwr-border-glow-testing.d.ts +219 -0
  184. package/types/ngwr-calendar-heatmap-testing.d.ts +79 -0
  185. package/types/ngwr-calendar-testing.d.ts +277 -0
  186. package/types/ngwr-calendar.d.ts +10 -0
  187. package/types/ngwr-checkbox.d.ts +13 -0
  188. package/types/ngwr-circular-text-testing.d.ts +196 -0
  189. package/types/ngwr-color-picker-testing.d.ts +291 -0
  190. package/types/ngwr-color-picker.d.ts +13 -1
  191. package/types/ngwr-context-menu.d.ts +15 -1
  192. package/types/ngwr-date-picker-testing.d.ts +6 -66
  193. package/types/ngwr-decrypt-text-testing.d.ts +222 -0
  194. package/types/ngwr-donut-chart-testing.d.ts +79 -0
  195. package/types/ngwr-drawer.d.ts +12 -0
  196. package/types/ngwr-dropdown.d.ts +9 -0
  197. package/types/ngwr-event-calendar-testing.d.ts +301 -0
  198. package/types/ngwr-event-calendar.d.ts +9 -6
  199. package/types/ngwr-falling-text-testing.d.ts +186 -0
  200. package/types/ngwr-fuzzy-text-testing.d.ts +130 -0
  201. package/types/ngwr-gauge-testing.d.ts +71 -0
  202. package/types/ngwr-glitch-text-testing.d.ts +172 -0
  203. package/types/ngwr-gradient-text-testing.d.ts +173 -0
  204. package/types/ngwr-image-cropper-testing.d.ts +134 -0
  205. package/types/ngwr-image-cropper.d.ts +1 -0
  206. package/types/ngwr-input-number.d.ts +33 -1
  207. package/types/ngwr-knob-testing.d.ts +150 -0
  208. package/types/ngwr-lightbox-testing.d.ts +154 -0
  209. package/types/ngwr-lightbox.d.ts +6 -0
  210. package/types/ngwr-line-chart-testing.d.ts +93 -0
  211. package/types/ngwr-marquee-testing.d.ts +280 -0
  212. package/types/ngwr-meter-group-testing.d.ts +67 -0
  213. package/types/ngwr-overlay.d.ts +19 -1
  214. package/types/ngwr-rotating-text-testing.d.ts +191 -0
  215. package/types/ngwr-select.d.ts +32 -0
  216. package/types/ngwr-shiny-text-testing.d.ts +165 -0
  217. package/types/ngwr-slider.d.ts +15 -0
  218. package/types/ngwr-sparkline-testing.d.ts +69 -0
  219. package/types/ngwr-speed-dial-testing.d.ts +195 -0
  220. package/types/ngwr-splash-cursor-testing.d.ts +95 -0
  221. package/types/ngwr-split-text-testing.d.ts +209 -0
  222. package/types/ngwr-split-text.d.ts +12 -0
  223. package/types/ngwr-splitter-testing.d.ts +174 -0
  224. package/types/ngwr-spotlight-card-testing.d.ts +303 -0
  225. package/types/ngwr-star-border-testing.d.ts +135 -0
  226. package/types/ngwr-table.d.ts +10 -1
  227. package/types/ngwr-tilt-card-testing.d.ts +158 -0
  228. package/types/ngwr-tilt-card.d.ts +2 -0
  229. package/types/ngwr-toast.d.ts +14 -5
  230. package/types/ngwr-tour-testing.d.ts +143 -0
  231. package/types/ngwr-tree.d.ts +7 -0
  232. package/types/ngwr-typewriter-testing.d.ts +201 -0
  233. package/types/ngwr-version.d.ts +1 -1
  234. package/types/ngwr-waves-testing.d.ts +121 -0
  235. package/types/ngwr-window-testing.d.ts +263 -0
  236. package/types/ngwr-window.d.ts +27 -10
  237. package/window/styles/_index.scss +44 -16
@@ -0,0 +1,203 @@
1
+ import { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';
2
+
3
+ /**
4
+ * @license
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
8
+ */
9
+ /** The animated pieces — one per word or per character, whitespace excluded. */
10
+ const PIECE = '.wr-blur-text__piece';
11
+ /** The whitespace runs between them, rendered but never staggered. */
12
+ const SPACE = '.wr-blur-text__space';
13
+ /** The readable copy of the string, carried once and hidden with CSS. */
14
+ const SR_ONLY = '.wr-blur-text__sr-only';
15
+ /**
16
+ * An inline `opacity` declaration — the one property the reveal writes on every path.
17
+ *
18
+ * It is staged before the tween starts and re-committed when it finishes, so its
19
+ * presence is the durable evidence that the component touched a piece at all. Matched
20
+ * off the `style` ATTRIBUTE rather than through `getCssValue`, which would answer from
21
+ * a computed style and report `1` for a piece nobody ever wrote to.
22
+ */
23
+ const INLINE_OPACITY = /(?:^|;)\s*opacity\s*:/;
24
+ /** Exactly what the element holds — untrimmed, unlike `TestElement.text()`. */
25
+ async function rawText(element) {
26
+ return (await element.getProperty('textContent')) ?? '';
27
+ }
28
+ /**
29
+ * Test harness for `<wr-blur-text>`.
30
+ *
31
+ * **Most of what this component does cannot be read from a test, and the methods that
32
+ * are missing are missing on purpose.** The reveal is a Web Animations tween per piece
33
+ * — blur, vertical offset and opacity, staggered — started by an `IntersectionObserver`.
34
+ * A unit test has no layout, no compositor and no WAAPI, so there is no
35
+ * `getOpacity()`, no `getBlurRadius()` and no `getOffset()`: each would report the
36
+ * staged from-state forever in jsdom, and in a real browser it would report the inline
37
+ * value while the compositor paints a different one. There is no `isAnimating()` and no
38
+ * `whenAnimationComplete()` either — nothing in the DOM says a tween is running, and
39
+ * completion is the `animationComplete` OUTPUT, which the host binds. Nor is there an
40
+ * `enterViewport()` / `scrollIntoView()`: the trigger is an observer, which a
41
+ * `TestElement` cannot reach, and a method that pretended to scroll would do nothing in
42
+ * jsdom and something untimed in a browser. The inputs that shape the tween —
43
+ * `direction`, `delay`, `stepDuration`, `easing` — never reach the DOM at all; they live
44
+ * in a keyframe array and an effect timing, and neither is a document.
45
+ *
46
+ * **What is real is the split and the accessible layer, and they are two questions.**
47
+ * The string is carried once in a visually-hidden span, and every animated piece is
48
+ * `aria-hidden` so a screen reader does not spell the text out letter by letter — so
49
+ * {@link getAccessibleText} is what is heard and {@link getPieces} is what is drawn, and
50
+ * a spec that only asserted the second would pass on a component that announces
51
+ * `W. e. l. c. o. m. e.` {@link getRenderedText} closes the loop: the split has to be
52
+ * lossless, and comparing it against the accessible copy is what catches a dropped space
53
+ * or a halved surrogate pair. (Whether the hidden copy is really off-screen is CSS —
54
+ * `clip-path` and a 1px box — and a test running no stylesheets cannot tell a correctly
55
+ * hidden span from one printing the string twice, so nothing here claims to.)
56
+ *
57
+ * **{@link hasStagedMotion} is the one thing about the tween worth pinning**, and it
58
+ * reports that the pieces were taken over rather than that anything is moving. Under
59
+ * reduced motion the component returns before touching them, which is the promise: a
60
+ * reader who asked for less motion must never be left with text staged at `opacity: 0`
61
+ * by an animation that then never runs.
62
+ *
63
+ * Driving the reveal is the spec's job, since jsdom supplies neither half of it:
64
+ *
65
+ * ```ts
66
+ * // Before TestBed.createComponent — keep the callbacks so the spec can fire them.
67
+ * vi.stubGlobal('IntersectionObserver', StubObserver);
68
+ * // Otherwise `el.animate(...)` throws mid-stagger and leaves the DOM half-staged.
69
+ * Reflect.set(Element.prototype, 'animate', () => ({ cancel: () => undefined, onfinish: null }));
70
+ * ```
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * const text = await loader.getHarness(WrBlurTextHarness.with({ text: 'Welcome to ngwr' }));
75
+ *
76
+ * expect(await text.getPieces()).toEqual(['Welcome', 'to', 'ngwr']);
77
+ * expect(await text.getRenderedText()).toBe(await text.getAccessibleText());
78
+ * expect(await text.isTextHidden()).toBe(true);
79
+ * ```
80
+ *
81
+ * @see https://ngwr.dev/guides/testing
82
+ */
83
+ class WrBlurTextHarness extends ComponentHarness {
84
+ static hostSelector = 'wr-blur-text';
85
+ /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */
86
+ static with(options = {}) {
87
+ return new HarnessPredicate(WrBlurTextHarness, options)
88
+ .addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getAccessibleText(), text))
89
+ .addOption('pieceCount', options.pieceCount, async (harness, count) => (await harness.getPieceCount()) === count);
90
+ }
91
+ pieces = this.locatorForAll(PIECE);
92
+ spaces = this.locatorForAll(SPACE);
93
+ /**
94
+ * The whole string, as the one readable copy holds it.
95
+ *
96
+ * The only thing a screen reader gets, since every drawn piece is out of the tree —
97
+ * lose this span and the component becomes a nameless pile of hidden spans with no
98
+ * other symptom anywhere in the DOM.
99
+ *
100
+ * Untrimmed, deliberately. Leading and trailing whitespace is content the split has to
101
+ * carry through, and {@link getRenderedText} is meant to compare equal to this; a
102
+ * trimmed read on one side of that comparison would quietly forgive the case it exists
103
+ * to catch.
104
+ */
105
+ async getAccessibleText() {
106
+ return rawText(await this.locatorFor(SR_ONLY)());
107
+ }
108
+ /**
109
+ * The animated pieces, in document order — one per word or one per character.
110
+ *
111
+ * This is the split the stagger runs over: `['Hi', 'there']` for `animateBy="words"`
112
+ * against `['H', 'i', 't', 'h', 'e', 'r', 'e']` for `"chars"`, and `['a', '🚀']` for a
113
+ * surrogate pair the code-point split kept whole rather than halved into two broken
114
+ * glyphs. Whitespace is not here — it is rendered separately and never staggered, so
115
+ * see {@link getSpaceCount}.
116
+ */
117
+ async getPieces() {
118
+ return Promise.all((await this.pieces()).map(rawText));
119
+ }
120
+ /**
121
+ * How many pieces the stagger runs over.
122
+ *
123
+ * `delay` × this is the whole length of the effect, and it is the only number about
124
+ * the animation that exists in the document.
125
+ */
126
+ async getPieceCount() {
127
+ return (await this.pieces()).length;
128
+ }
129
+ /**
130
+ * How many whitespace runs sit between the pieces.
131
+ *
132
+ * Worth a method of its own because whitespace is deliberately NOT a staggered piece:
133
+ * a regression that reclassified spaces as pieces would leave {@link getRenderedText}
134
+ * identical, change the stagger count and the rhythm of the reveal, and show up
135
+ * nowhere else.
136
+ */
137
+ async getSpaceCount() {
138
+ return (await this.spaces()).length;
139
+ }
140
+ /**
141
+ * Everything the component drew, pieces and spaces together, in document order.
142
+ *
143
+ * The assertion this exists for is `getRenderedText() === getAccessibleText()`: the
144
+ * split must be lossless. A swallowed space, a re-split that dropped a character, or a
145
+ * surrogate pair cut in half all leave the accessible copy untouched and are invisible
146
+ * from anywhere else.
147
+ */
148
+ async getRenderedText() {
149
+ const parts = await this.locatorForAll(`${PIECE}, ${SPACE}`)();
150
+ return (await Promise.all(parts.map(rawText))).join('');
151
+ }
152
+ /**
153
+ * Whether every drawn piece is out of the accessibility tree.
154
+ *
155
+ * The defect this component exists to avoid is a screen reader reading
156
+ * `W. e. l. c. o. m. e.`, and one piece missing `aria-hidden` is enough to bring it
157
+ * back — so this is all-or-nothing, and across both element kinds: a stray readable
158
+ * space is heard as a pause in the middle of a word.
159
+ *
160
+ * Throws when the split produced nothing at all. An empty run of elements satisfies
161
+ * "every one of them is hidden" without anything being hidden, which is the answer a
162
+ * component that stopped rendering its pieces would also give.
163
+ */
164
+ async isTextHidden() {
165
+ const parts = await this.locatorForAll(`${PIECE}, ${SPACE}`)();
166
+ if (parts.length === 0) {
167
+ throw new Error('WrBlurTextHarness.isTextHidden(): this <wr-blur-text> drew no pieces, so there is nothing to be ' +
168
+ 'hidden from a screen reader and "true" would be a vacuous answer — the same one a component that ' +
169
+ 'stopped splitting its text would give. Check `text` is set, or ask getPieceCount().');
170
+ }
171
+ const hidden = await Promise.all(parts.map(part => part.getAttribute('aria-hidden')));
172
+ return hidden.every(value => value === 'true');
173
+ }
174
+ /**
175
+ * Whether the reveal has taken the pieces over — NOT whether anything is animating.
176
+ *
177
+ * Read off the inline `style` attribute the component writes itself, so it stays
178
+ * `true` after the reveal finishes: the tween commits its end state back to the same
179
+ * inline properties on `onfinish`. Anyone reaching for `isAnimating()` wants a frame,
180
+ * and there are no frames here.
181
+ *
182
+ * `false` is the answer worth asserting. It means no piece was written to at all,
183
+ * which is both the pre-trigger state and the reduced-motion promise — the component
184
+ * returns before staging anything, so a reader who asked for less motion cannot be
185
+ * left staring at text parked at `opacity: 0` by an animation that never runs.
186
+ *
187
+ * ANY piece counts, not all of them, and that is the conservative reading rather than
188
+ * a loose one: a stagger that stages the first piece and then throws leaves exactly one
189
+ * piece invisible, which is a real failure and one jsdom reproduces whenever
190
+ * `Element.prototype.animate` is missing.
191
+ */
192
+ async hasStagedMotion() {
193
+ const styles = await Promise.all((await this.pieces()).map(piece => piece.getAttribute('style')));
194
+ return styles.some(style => INLINE_OPACITY.test(style ?? ''));
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Generated bundle index. Do not edit.
200
+ */
201
+
202
+ export { WrBlurTextHarness };
203
+ //# sourceMappingURL=ngwr-blur-text-testing.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngwr-blur-text-testing.mjs","sources":["../../../projects/lib/blur-text/testing/wr-blur-text-harness.ts","../../../projects/lib/blur-text/testing/ngwr-blur-text-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate, type TestElement } from '@angular/cdk/testing';\n\nimport type { WrBlurTextHarnessFilters } from './interfaces';\n\n/** The animated pieces — one per word or per character, whitespace excluded. */\nconst PIECE = '.wr-blur-text__piece';\n\n/** The whitespace runs between them, rendered but never staggered. */\nconst SPACE = '.wr-blur-text__space';\n\n/** The readable copy of the string, carried once and hidden with CSS. */\nconst SR_ONLY = '.wr-blur-text__sr-only';\n\n/**\n * An inline `opacity` declaration — the one property the reveal writes on every path.\n *\n * It is staged before the tween starts and re-committed when it finishes, so its\n * presence is the durable evidence that the component touched a piece at all. Matched\n * off the `style` ATTRIBUTE rather than through `getCssValue`, which would answer from\n * a computed style and report `1` for a piece nobody ever wrote to.\n */\nconst INLINE_OPACITY = /(?:^|;)\\s*opacity\\s*:/;\n\n/** Exactly what the element holds — untrimmed, unlike `TestElement.text()`. */\nasync function rawText(element: TestElement): Promise<string> {\n return (await element.getProperty<string | null>('textContent')) ?? '';\n}\n\n/**\n * Test harness for `<wr-blur-text>`.\n *\n * **Most of what this component does cannot be read from a test, and the methods that\n * are missing are missing on purpose.** The reveal is a Web Animations tween per piece\n * — blur, vertical offset and opacity, staggered — started by an `IntersectionObserver`.\n * A unit test has no layout, no compositor and no WAAPI, so there is no\n * `getOpacity()`, no `getBlurRadius()` and no `getOffset()`: each would report the\n * staged from-state forever in jsdom, and in a real browser it would report the inline\n * value while the compositor paints a different one. There is no `isAnimating()` and no\n * `whenAnimationComplete()` either — nothing in the DOM says a tween is running, and\n * completion is the `animationComplete` OUTPUT, which the host binds. Nor is there an\n * `enterViewport()` / `scrollIntoView()`: the trigger is an observer, which a\n * `TestElement` cannot reach, and a method that pretended to scroll would do nothing in\n * jsdom and something untimed in a browser. The inputs that shape the tween —\n * `direction`, `delay`, `stepDuration`, `easing` — never reach the DOM at all; they live\n * in a keyframe array and an effect timing, and neither is a document.\n *\n * **What is real is the split and the accessible layer, and they are two questions.**\n * The string is carried once in a visually-hidden span, and every animated piece is\n * `aria-hidden` so a screen reader does not spell the text out letter by letter — so\n * {@link getAccessibleText} is what is heard and {@link getPieces} is what is drawn, and\n * a spec that only asserted the second would pass on a component that announces\n * `W. e. l. c. o. m. e.` {@link getRenderedText} closes the loop: the split has to be\n * lossless, and comparing it against the accessible copy is what catches a dropped space\n * or a halved surrogate pair. (Whether the hidden copy is really off-screen is CSS —\n * `clip-path` and a 1px box — and a test running no stylesheets cannot tell a correctly\n * hidden span from one printing the string twice, so nothing here claims to.)\n *\n * **{@link hasStagedMotion} is the one thing about the tween worth pinning**, and it\n * reports that the pieces were taken over rather than that anything is moving. Under\n * reduced motion the component returns before touching them, which is the promise: a\n * reader who asked for less motion must never be left with text staged at `opacity: 0`\n * by an animation that then never runs.\n *\n * Driving the reveal is the spec's job, since jsdom supplies neither half of it:\n *\n * ```ts\n * // Before TestBed.createComponent — keep the callbacks so the spec can fire them.\n * vi.stubGlobal('IntersectionObserver', StubObserver);\n * // Otherwise `el.animate(...)` throws mid-stagger and leaves the DOM half-staged.\n * Reflect.set(Element.prototype, 'animate', () => ({ cancel: () => undefined, onfinish: null }));\n * ```\n *\n * @example\n * ```ts\n * const text = await loader.getHarness(WrBlurTextHarness.with({ text: 'Welcome to ngwr' }));\n *\n * expect(await text.getPieces()).toEqual(['Welcome', 'to', 'ngwr']);\n * expect(await text.getRenderedText()).toBe(await text.getAccessibleText());\n * expect(await text.isTextHidden()).toBe(true);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrBlurTextHarness extends ComponentHarness {\n static hostSelector = 'wr-blur-text';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrBlurTextHarnessFilters = {}): HarnessPredicate<WrBlurTextHarness> {\n return new HarnessPredicate(WrBlurTextHarness, options)\n .addOption('text', options.text, (harness, text) =>\n HarnessPredicate.stringMatches(harness.getAccessibleText(), text)\n )\n .addOption('pieceCount', options.pieceCount, async (harness, count) => (await harness.getPieceCount()) === count);\n }\n\n private readonly pieces = this.locatorForAll(PIECE);\n private readonly spaces = this.locatorForAll(SPACE);\n\n /**\n * The whole string, as the one readable copy holds it.\n *\n * The only thing a screen reader gets, since every drawn piece is out of the tree —\n * lose this span and the component becomes a nameless pile of hidden spans with no\n * other symptom anywhere in the DOM.\n *\n * Untrimmed, deliberately. Leading and trailing whitespace is content the split has to\n * carry through, and {@link getRenderedText} is meant to compare equal to this; a\n * trimmed read on one side of that comparison would quietly forgive the case it exists\n * to catch.\n */\n async getAccessibleText(): Promise<string> {\n return rawText(await this.locatorFor(SR_ONLY)());\n }\n\n /**\n * The animated pieces, in document order — one per word or one per character.\n *\n * This is the split the stagger runs over: `['Hi', 'there']` for `animateBy=\"words\"`\n * against `['H', 'i', 't', 'h', 'e', 'r', 'e']` for `\"chars\"`, and `['a', '🚀']` for a\n * surrogate pair the code-point split kept whole rather than halved into two broken\n * glyphs. Whitespace is not here — it is rendered separately and never staggered, so\n * see {@link getSpaceCount}.\n */\n async getPieces(): Promise<string[]> {\n return Promise.all((await this.pieces()).map(rawText));\n }\n\n /**\n * How many pieces the stagger runs over.\n *\n * `delay` × this is the whole length of the effect, and it is the only number about\n * the animation that exists in the document.\n */\n async getPieceCount(): Promise<number> {\n return (await this.pieces()).length;\n }\n\n /**\n * How many whitespace runs sit between the pieces.\n *\n * Worth a method of its own because whitespace is deliberately NOT a staggered piece:\n * a regression that reclassified spaces as pieces would leave {@link getRenderedText}\n * identical, change the stagger count and the rhythm of the reveal, and show up\n * nowhere else.\n */\n async getSpaceCount(): Promise<number> {\n return (await this.spaces()).length;\n }\n\n /**\n * Everything the component drew, pieces and spaces together, in document order.\n *\n * The assertion this exists for is `getRenderedText() === getAccessibleText()`: the\n * split must be lossless. A swallowed space, a re-split that dropped a character, or a\n * surrogate pair cut in half all leave the accessible copy untouched and are invisible\n * from anywhere else.\n */\n async getRenderedText(): Promise<string> {\n const parts = await this.locatorForAll(`${PIECE}, ${SPACE}`)();\n return (await Promise.all(parts.map(rawText))).join('');\n }\n\n /**\n * Whether every drawn piece is out of the accessibility tree.\n *\n * The defect this component exists to avoid is a screen reader reading\n * `W. e. l. c. o. m. e.`, and one piece missing `aria-hidden` is enough to bring it\n * back — so this is all-or-nothing, and across both element kinds: a stray readable\n * space is heard as a pause in the middle of a word.\n *\n * Throws when the split produced nothing at all. An empty run of elements satisfies\n * \"every one of them is hidden\" without anything being hidden, which is the answer a\n * component that stopped rendering its pieces would also give.\n */\n async isTextHidden(): Promise<boolean> {\n const parts = await this.locatorForAll(`${PIECE}, ${SPACE}`)();\n\n if (parts.length === 0) {\n throw new Error(\n 'WrBlurTextHarness.isTextHidden(): this <wr-blur-text> drew no pieces, so there is nothing to be ' +\n 'hidden from a screen reader and \"true\" would be a vacuous answer — the same one a component that ' +\n 'stopped splitting its text would give. Check `text` is set, or ask getPieceCount().'\n );\n }\n\n const hidden = await Promise.all(parts.map(part => part.getAttribute('aria-hidden')));\n return hidden.every(value => value === 'true');\n }\n\n /**\n * Whether the reveal has taken the pieces over — NOT whether anything is animating.\n *\n * Read off the inline `style` attribute the component writes itself, so it stays\n * `true` after the reveal finishes: the tween commits its end state back to the same\n * inline properties on `onfinish`. Anyone reaching for `isAnimating()` wants a frame,\n * and there are no frames here.\n *\n * `false` is the answer worth asserting. It means no piece was written to at all,\n * which is both the pre-trigger state and the reduced-motion promise — the component\n * returns before staging anything, so a reader who asked for less motion cannot be\n * left staring at text parked at `opacity: 0` by an animation that never runs.\n *\n * ANY piece counts, not all of them, and that is the conservative reading rather than\n * a loose one: a stagger that stages the first piece and then throws leaves exactly one\n * piece invisible, which is a real failure and one jsdom reproduces whenever\n * `Element.prototype.animate` is missing.\n */\n async hasStagedMotion(): Promise<boolean> {\n const styles = await Promise.all((await this.pieces()).map(piece => piece.getAttribute('style')));\n return styles.some(style => INLINE_OPACITY.test(style ?? ''));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -80,9 +80,22 @@ class WrBlurText {
80
80
  isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
81
81
  platform = inject(WrPlatform);
82
82
  hasAnimated = false;
83
+ /**
84
+ * The ONE observer, replaced rather than stacked.
85
+ *
86
+ * `startObserver()` used to `new IntersectionObserver(…)` and register its own
87
+ * `destroyRef.onDestroy` every time, and the effect below calls it on every
88
+ * `text` change after setting `hasAnimated = false` — so the guard at the top
89
+ * of `startObserver` never blocked that path. An off-screen element (the only
90
+ * case where the observer does not immediately disconnect itself) therefore
91
+ * leaked one live observer AND one destroy callback per change, all of them
92
+ * watching the same host and all of them able to fire `animate()`.
93
+ */
94
+ observer = null;
83
95
  constructor() {
84
96
  if (!this.isBrowser)
85
97
  return;
98
+ this.destroyRef.onDestroy(() => this.observer?.disconnect());
86
99
  afterNextRender(() => this.startObserver());
87
100
  effect(() => {
88
101
  this.text();
@@ -95,6 +108,7 @@ class WrBlurText {
95
108
  if (this.hasAnimated)
96
109
  return;
97
110
  const host = this.host.nativeElement;
111
+ this.observer?.disconnect();
98
112
  const io = new IntersectionObserver((entries, obs) => {
99
113
  for (const e of entries) {
100
114
  if (e.isIntersecting) {
@@ -104,8 +118,8 @@ class WrBlurText {
104
118
  }
105
119
  }
106
120
  }, { threshold: this.threshold(), rootMargin: this.rootMargin() });
121
+ this.observer = io;
107
122
  io.observe(host);
108
- this.destroyRef.onDestroy(() => io.disconnect());
109
123
  }
110
124
  animate() {
111
125
  if (this.hasAnimated)
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-blur-text.mjs","sources":["../../../projects/lib/blur-text/blur-text.ts","../../../projects/lib/blur-text/blur-text.html","../../../projects/lib/blur-text/ngwr-blur-text.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 BlurText effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/text-animations/blur-text\n *\n * The reactbits version uses `motion/react` (framer-motion successor).\n * This port is dependency-free — uses the Web Animations API for the\n * three-keyframe blur-and-fade-in, triggered by `IntersectionObserver`.\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n output,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr } from 'ngwr/utils';\n\ntype Unit = 'chars' | 'words';\ntype Direction = 'top' | 'bottom';\ntype Piece = { readonly kind: 'piece'; readonly text: string } | { readonly kind: 'space'; readonly text: string };\n\nfunction splitPieces(text: string, unit: Unit): readonly Piece[] {\n if (!text) return [];\n if (unit === 'words') {\n return text.split(/(\\s+)/).flatMap<Piece>(seg => {\n if (seg.length === 0) return [];\n if (/^\\s+$/.test(seg)) return [{ kind: 'space', text: seg }];\n return [{ kind: 'piece', text: seg }];\n });\n }\n return [...text].map<Piece>(ch => (/\\s/.test(ch) ? { kind: 'space', text: ch } : { kind: 'piece', text: ch }));\n}\n\n/**\n * Reveals text by splitting it into chars or words and animating each\n * piece in from a blurred / offset state through a brief mid-step into\n * the steady state. Triggers when the host enters the viewport.\n *\n * @example\n * ```html\n * <wr-blur-text\n * text=\"Welcome to ngwr\"\n * animateBy=\"words\"\n * direction=\"top\"\n * [delay]=\"120\"\n * [stepDuration]=\"0.4\"\n * />\n * ```\n *\n * @see https://www.reactbits.dev/text-animations/blur-text\n */\n@Component({\n selector: 'wr-blur-text',\n templateUrl: './blur-text.html',\n styleUrl: './blur-text.scss',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-blur-text' },\n})\nexport class WrBlurText {\n /** Text to animate. Required. */\n readonly text = input.required<string>();\n\n /** Split granularity. @default 'words' */\n readonly animateBy = input<Unit>('words');\n\n /** Entry direction. `'top'` slides down into place; `'bottom'` slides up. @default 'top' */\n readonly direction = input<Direction>('top');\n\n /** Per-piece stagger in ms. @default 200 */\n readonly delay = input(200, { transform: numAttr(200) });\n\n /** Duration of each keyframe step in seconds (total = 2 × stepDuration). @default 0.35 */\n readonly stepDuration = input(0.35, { transform: numAttr(0.35) });\n\n /** CSS easing function. @default 'linear' (matches reactbits' identity easing) */\n readonly easing = input('linear');\n\n /** `IntersectionObserver.threshold` (0..1). @default 0.1 */\n readonly threshold = input(0.1, { transform: numAttr(0.1) });\n\n /** `IntersectionObserver.rootMargin`. @default '0px' */\n readonly rootMargin = input('0px');\n\n /** Emitted once all pieces finish animating. */\n readonly animationComplete = output<void>();\n\n protected readonly pieces = computed(() => splitPieces(this.text(), this.animateBy()));\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly platform = inject(WrPlatform);\n\n private hasAnimated = false;\n\n constructor() {\n if (!this.isBrowser) return;\n afterNextRender(() => this.startObserver());\n effect(() => {\n this.text();\n this.animateBy();\n this.hasAnimated = false;\n queueMicrotask(() => this.startObserver());\n });\n }\n\n private startObserver(): void {\n if (this.hasAnimated) return;\n const host = this.host.nativeElement;\n const io = new IntersectionObserver(\n (entries, obs) => {\n for (const e of entries) {\n if (e.isIntersecting) {\n obs.disconnect();\n this.animate();\n break;\n }\n }\n },\n { threshold: this.threshold(), rootMargin: this.rootMargin() }\n );\n io.observe(host);\n this.destroyRef.onDestroy(() => io.disconnect());\n }\n\n private animate(): void {\n if (this.hasAnimated) return;\n this.hasAnimated = true;\n\n // Reduced motion: pieces already render clear and in place — skip\n // the blur tween and report completion right away.\n if (this.platform.prefersReducedMotion()) {\n this.animationComplete.emit();\n return;\n }\n\n const targets = this.host.nativeElement.querySelectorAll<HTMLElement>('.wr-blur-text__piece');\n if (targets.length === 0) {\n this.animationComplete.emit();\n return;\n }\n\n const dir = this.direction();\n const stepMs = this.stepDuration() * 1000;\n const totalMs = stepMs * 2;\n const stagger = this.delay();\n const easing = this.easing();\n\n // Three-keyframe shape per reactbits:\n // 0 → fully blurred, offset, transparent\n // 50% → half-blur, half-opacity, small reverse offset\n // 100% → clear, opaque, at rest\n const startY = dir === 'top' ? -50 : 50;\n const midY = dir === 'top' ? 5 : -5;\n const keyframes: Keyframe[] = [\n { opacity: 0, filter: 'blur(10px)', transform: `translate3d(0, ${startY}px, 0)` },\n { opacity: 0.5, filter: 'blur(5px)', transform: `translate3d(0, ${midY}px, 0)`, offset: 0.5 },\n { opacity: 1, filter: 'blur(0px)', transform: 'translate3d(0, 0, 0)' },\n ];\n\n let remaining = targets.length;\n targets.forEach((el, i) => {\n el.style.opacity = '0';\n el.style.filter = 'blur(10px)';\n el.style.transform = `translate3d(0, ${startY}px, 0)`;\n const animation = el.animate(keyframes, {\n duration: totalMs,\n delay: i * stagger,\n easing,\n fill: 'forwards',\n });\n animation.onfinish = (): void => {\n el.style.opacity = '1';\n el.style.filter = 'blur(0px)';\n el.style.transform = 'translate3d(0, 0, 0)';\n try {\n animation.cancel();\n } catch {\n /* noop */\n }\n remaining -= 1;\n if (remaining === 0) this.animationComplete.emit();\n };\n });\n }\n}\n","<!-- The readable text, once: the pieces below are per-character spans. -->\n<span class=\"wr-blur-text__sr-only\">{{ text() }}</span>\n@for (p of pieces(); track $index) {\n @if (p.kind === 'piece') {\n <span class=\"wr-blur-text__piece\" aria-hidden=\"true\">{{ p.text }}</span>\n } @else {\n <span class=\"wr-blur-text__space\" aria-hidden=\"true\">{{ p.text }}</span>\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-blur-text.mjs","sources":["../../../projects/lib/blur-text/blur-text.ts","../../../projects/lib/blur-text/blur-text.html","../../../projects/lib/blur-text/ngwr-blur-text.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 BlurText effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/text-animations/blur-text\n *\n * The reactbits version uses `motion/react` (framer-motion successor).\n * This port is dependency-free — uses the Web Animations API for the\n * three-keyframe blur-and-fade-in, triggered by `IntersectionObserver`.\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n output,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr } from 'ngwr/utils';\n\ntype Unit = 'chars' | 'words';\ntype Direction = 'top' | 'bottom';\ntype Piece = { readonly kind: 'piece'; readonly text: string } | { readonly kind: 'space'; readonly text: string };\n\nfunction splitPieces(text: string, unit: Unit): readonly Piece[] {\n if (!text) return [];\n if (unit === 'words') {\n return text.split(/(\\s+)/).flatMap<Piece>(seg => {\n if (seg.length === 0) return [];\n if (/^\\s+$/.test(seg)) return [{ kind: 'space', text: seg }];\n return [{ kind: 'piece', text: seg }];\n });\n }\n return [...text].map<Piece>(ch => (/\\s/.test(ch) ? { kind: 'space', text: ch } : { kind: 'piece', text: ch }));\n}\n\n/**\n * Reveals text by splitting it into chars or words and animating each\n * piece in from a blurred / offset state through a brief mid-step into\n * the steady state. Triggers when the host enters the viewport.\n *\n * @example\n * ```html\n * <wr-blur-text\n * text=\"Welcome to ngwr\"\n * animateBy=\"words\"\n * direction=\"top\"\n * [delay]=\"120\"\n * [stepDuration]=\"0.4\"\n * />\n * ```\n *\n * @see https://www.reactbits.dev/text-animations/blur-text\n */\n@Component({\n selector: 'wr-blur-text',\n templateUrl: './blur-text.html',\n styleUrl: './blur-text.scss',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-blur-text' },\n})\nexport class WrBlurText {\n /** Text to animate. Required. */\n readonly text = input.required<string>();\n\n /** Split granularity. @default 'words' */\n readonly animateBy = input<Unit>('words');\n\n /** Entry direction. `'top'` slides down into place; `'bottom'` slides up. @default 'top' */\n readonly direction = input<Direction>('top');\n\n /** Per-piece stagger in ms. @default 200 */\n readonly delay = input(200, { transform: numAttr(200) });\n\n /** Duration of each keyframe step in seconds (total = 2 × stepDuration). @default 0.35 */\n readonly stepDuration = input(0.35, { transform: numAttr(0.35) });\n\n /** CSS easing function. @default 'linear' (matches reactbits' identity easing) */\n readonly easing = input('linear');\n\n /** `IntersectionObserver.threshold` (0..1). @default 0.1 */\n readonly threshold = input(0.1, { transform: numAttr(0.1) });\n\n /** `IntersectionObserver.rootMargin`. @default '0px' */\n readonly rootMargin = input('0px');\n\n /** Emitted once all pieces finish animating. */\n readonly animationComplete = output<void>();\n\n protected readonly pieces = computed(() => splitPieces(this.text(), this.animateBy()));\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly platform = inject(WrPlatform);\n\n private hasAnimated = false;\n\n /**\n * The ONE observer, replaced rather than stacked.\n *\n * `startObserver()` used to `new IntersectionObserver(…)` and register its own\n * `destroyRef.onDestroy` every time, and the effect below calls it on every\n * `text` change after setting `hasAnimated = false` — so the guard at the top\n * of `startObserver` never blocked that path. An off-screen element (the only\n * case where the observer does not immediately disconnect itself) therefore\n * leaked one live observer AND one destroy callback per change, all of them\n * watching the same host and all of them able to fire `animate()`.\n */\n private observer: IntersectionObserver | null = null;\n\n constructor() {\n if (!this.isBrowser) return;\n this.destroyRef.onDestroy(() => this.observer?.disconnect());\n afterNextRender(() => this.startObserver());\n effect(() => {\n this.text();\n this.animateBy();\n this.hasAnimated = false;\n queueMicrotask(() => this.startObserver());\n });\n }\n\n private startObserver(): void {\n if (this.hasAnimated) return;\n const host = this.host.nativeElement;\n this.observer?.disconnect();\n const io = new IntersectionObserver(\n (entries, obs) => {\n for (const e of entries) {\n if (e.isIntersecting) {\n obs.disconnect();\n this.animate();\n break;\n }\n }\n },\n { threshold: this.threshold(), rootMargin: this.rootMargin() }\n );\n this.observer = io;\n io.observe(host);\n }\n\n private animate(): void {\n if (this.hasAnimated) return;\n this.hasAnimated = true;\n\n // Reduced motion: pieces already render clear and in place — skip\n // the blur tween and report completion right away.\n if (this.platform.prefersReducedMotion()) {\n this.animationComplete.emit();\n return;\n }\n\n const targets = this.host.nativeElement.querySelectorAll<HTMLElement>('.wr-blur-text__piece');\n if (targets.length === 0) {\n this.animationComplete.emit();\n return;\n }\n\n const dir = this.direction();\n const stepMs = this.stepDuration() * 1000;\n const totalMs = stepMs * 2;\n const stagger = this.delay();\n const easing = this.easing();\n\n // Three-keyframe shape per reactbits:\n // 0 → fully blurred, offset, transparent\n // 50% → half-blur, half-opacity, small reverse offset\n // 100% → clear, opaque, at rest\n const startY = dir === 'top' ? -50 : 50;\n const midY = dir === 'top' ? 5 : -5;\n const keyframes: Keyframe[] = [\n { opacity: 0, filter: 'blur(10px)', transform: `translate3d(0, ${startY}px, 0)` },\n { opacity: 0.5, filter: 'blur(5px)', transform: `translate3d(0, ${midY}px, 0)`, offset: 0.5 },\n { opacity: 1, filter: 'blur(0px)', transform: 'translate3d(0, 0, 0)' },\n ];\n\n let remaining = targets.length;\n targets.forEach((el, i) => {\n el.style.opacity = '0';\n el.style.filter = 'blur(10px)';\n el.style.transform = `translate3d(0, ${startY}px, 0)`;\n const animation = el.animate(keyframes, {\n duration: totalMs,\n delay: i * stagger,\n easing,\n fill: 'forwards',\n });\n animation.onfinish = (): void => {\n el.style.opacity = '1';\n el.style.filter = 'blur(0px)';\n el.style.transform = 'translate3d(0, 0, 0)';\n try {\n animation.cancel();\n } catch {\n /* noop */\n }\n remaining -= 1;\n if (remaining === 0) this.animationComplete.emit();\n };\n });\n }\n}\n","<!-- The readable text, once: the pieces below are per-character spans. -->\n<span class=\"wr-blur-text__sr-only\">{{ text() }}</span>\n@for (p of pieces(); track $index) {\n @if (p.kind === 'piece') {\n <span class=\"wr-blur-text__piece\" aria-hidden=\"true\">{{ p.text }}</span>\n } @else {\n <span class=\"wr-blur-text__space\" aria-hidden=\"true\">{{ p.text }}</span>\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}