ngwr 15.1.0 → 15.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 (206) hide show
  1. package/bits/aurora/styles/_rules.scss +11 -0
  2. package/bits/dot-grid/styles/_rules.scss +9 -1
  3. package/bits/iridescence/iridescence.scss +1 -0
  4. package/bits/iridescence/styles/_index.scss +8 -0
  5. package/bits/iridescence/styles/_rules.scss +72 -0
  6. package/bits/marquee/styles/_rules.scss +7 -3
  7. package/bits/ripple-grid/styles/_rules.scss +10 -2
  8. package/bits/spotlight-card/styles/_rules.scss +7 -4
  9. package/bits/star-border/styles/_rules.scss +5 -2
  10. package/bits/threads/styles/_rules.scss +9 -1
  11. package/bits/waves/styles/_rules.scss +8 -1
  12. package/charts/candlestick-chart/styles/_index.scss +2 -2
  13. package/fesm2022/ngwr-affix.mjs +3 -3
  14. package/fesm2022/ngwr-alert.mjs +3 -3
  15. package/fesm2022/ngwr-anchor.mjs +3 -3
  16. package/fesm2022/ngwr-avatar.mjs +3 -3
  17. package/fesm2022/ngwr-back-top.mjs +3 -3
  18. package/fesm2022/ngwr-badge.mjs +6 -6
  19. package/fesm2022/ngwr-bits-aurora.mjs +30 -38
  20. package/fesm2022/ngwr-bits-aurora.mjs.map +1 -1
  21. package/fesm2022/ngwr-bits-blur-text.mjs +3 -3
  22. package/fesm2022/ngwr-bits-border-glow-testing.mjs.map +1 -1
  23. package/fesm2022/ngwr-bits-border-glow.mjs +11 -5
  24. package/fesm2022/ngwr-bits-border-glow.mjs.map +1 -1
  25. package/fesm2022/ngwr-bits-circular-text.mjs +3 -3
  26. package/fesm2022/ngwr-bits-click-spark.mjs +3 -3
  27. package/fesm2022/ngwr-bits-confetti.mjs +3 -3
  28. package/fesm2022/ngwr-bits-decrypt-text.mjs +3 -3
  29. package/fesm2022/ngwr-bits-dot-grid.mjs +12 -9
  30. package/fesm2022/ngwr-bits-dot-grid.mjs.map +1 -1
  31. package/fesm2022/ngwr-bits-falling-text.mjs +3 -3
  32. package/fesm2022/ngwr-bits-fuzzy-text.mjs +3 -3
  33. package/fesm2022/ngwr-bits-glitch-text.mjs +3 -3
  34. package/fesm2022/ngwr-bits-gradient-text.mjs +3 -3
  35. package/fesm2022/ngwr-bits-iridescence-testing.mjs +31 -0
  36. package/fesm2022/ngwr-bits-iridescence-testing.mjs.map +1 -0
  37. package/fesm2022/ngwr-bits-iridescence.mjs +591 -0
  38. package/fesm2022/ngwr-bits-iridescence.mjs.map +1 -0
  39. package/fesm2022/ngwr-bits-marquee-testing.mjs +1 -1
  40. package/fesm2022/ngwr-bits-marquee-testing.mjs.map +1 -1
  41. package/fesm2022/ngwr-bits-marquee.mjs +7 -7
  42. package/fesm2022/ngwr-bits-marquee.mjs.map +1 -1
  43. package/fesm2022/ngwr-bits-ripple-grid.mjs +8 -11
  44. package/fesm2022/ngwr-bits-ripple-grid.mjs.map +1 -1
  45. package/fesm2022/ngwr-bits-rotating-text.mjs +3 -3
  46. package/fesm2022/ngwr-bits-shiny-text.mjs +6 -6
  47. package/fesm2022/ngwr-bits-splash-cursor.mjs +3 -3
  48. package/fesm2022/ngwr-bits-split-text.mjs +3 -3
  49. package/fesm2022/ngwr-bits-spotlight-card-testing.mjs +1 -1
  50. package/fesm2022/ngwr-bits-spotlight-card-testing.mjs.map +1 -1
  51. package/fesm2022/ngwr-bits-spotlight-card.mjs +10 -10
  52. package/fesm2022/ngwr-bits-spotlight-card.mjs.map +1 -1
  53. package/fesm2022/ngwr-bits-star-border-testing.mjs +1 -1
  54. package/fesm2022/ngwr-bits-star-border-testing.mjs.map +1 -1
  55. package/fesm2022/ngwr-bits-star-border.mjs +7 -7
  56. package/fesm2022/ngwr-bits-star-border.mjs.map +1 -1
  57. package/fesm2022/ngwr-bits-threads.mjs +17 -12
  58. package/fesm2022/ngwr-bits-threads.mjs.map +1 -1
  59. package/fesm2022/ngwr-bits-tilt-card.mjs +6 -6
  60. package/fesm2022/ngwr-bits-typewriter.mjs +3 -3
  61. package/fesm2022/ngwr-bits-waves.mjs +15 -11
  62. package/fesm2022/ngwr-bits-waves.mjs.map +1 -1
  63. package/fesm2022/ngwr-breadcrumbs.mjs +6 -6
  64. package/fesm2022/ngwr-burger.mjs +3 -3
  65. package/fesm2022/ngwr-button.mjs +6 -6
  66. package/fesm2022/ngwr-calendar.mjs +3 -3
  67. package/fesm2022/ngwr-card.mjs +9 -9
  68. package/fesm2022/ngwr-carousel.mjs +6 -6
  69. package/fesm2022/ngwr-cascader.mjs +3 -3
  70. package/fesm2022/ngwr-charts-bar-chart.mjs +3 -3
  71. package/fesm2022/ngwr-charts-boxplot-chart.mjs +3 -3
  72. package/fesm2022/ngwr-charts-bullet-chart.mjs +3 -3
  73. package/fesm2022/ngwr-charts-calendar-heatmap.mjs +3 -3
  74. package/fesm2022/ngwr-charts-candlestick-chart.mjs +7 -7
  75. package/fesm2022/ngwr-charts-candlestick-chart.mjs.map +1 -1
  76. package/fesm2022/ngwr-charts-chart-legend.mjs +6 -6
  77. package/fesm2022/ngwr-charts-core.mjs +3 -3
  78. package/fesm2022/ngwr-charts-donut-chart.mjs +3 -3
  79. package/fesm2022/ngwr-charts-funnel-chart.mjs +3 -3
  80. package/fesm2022/ngwr-charts-gauge.mjs +3 -3
  81. package/fesm2022/ngwr-charts-heatmap-chart.mjs +3 -3
  82. package/fesm2022/ngwr-charts-line-chart.mjs +3 -3
  83. package/fesm2022/ngwr-charts-meter-group.mjs +3 -3
  84. package/fesm2022/ngwr-charts-radar-chart.mjs +3 -3
  85. package/fesm2022/ngwr-charts-scatter-chart.mjs +3 -3
  86. package/fesm2022/ngwr-charts-sparkline.mjs +3 -3
  87. package/fesm2022/ngwr-charts-treemap-chart.mjs +3 -3
  88. package/fesm2022/ngwr-charts-waterfall-chart.mjs +3 -3
  89. package/fesm2022/ngwr-checkbox.mjs +6 -6
  90. package/fesm2022/ngwr-clipboard.mjs +3 -3
  91. package/fesm2022/ngwr-collapse.mjs +9 -9
  92. package/fesm2022/ngwr-color-picker.mjs +6 -6
  93. package/fesm2022/ngwr-command-palette.mjs +3 -3
  94. package/fesm2022/ngwr-compare.mjs +3 -3
  95. package/fesm2022/ngwr-context-menu.mjs +12 -12
  96. package/fesm2022/ngwr-cookie.mjs +3 -3
  97. package/fesm2022/ngwr-counter.mjs +6 -6
  98. package/fesm2022/ngwr-date-picker.mjs +21 -21
  99. package/fesm2022/ngwr-date.mjs +6 -6
  100. package/fesm2022/ngwr-density.mjs +6 -6
  101. package/fesm2022/ngwr-descriptions.mjs +6 -6
  102. package/fesm2022/ngwr-dialog.mjs +15 -15
  103. package/fesm2022/ngwr-directives.mjs +12 -12
  104. package/fesm2022/ngwr-divider.mjs +3 -3
  105. package/fesm2022/ngwr-drawer.mjs +18 -18
  106. package/fesm2022/ngwr-dropdown.mjs +9 -9
  107. package/fesm2022/ngwr-editor.mjs +3 -3
  108. package/fesm2022/ngwr-empty.mjs +3 -3
  109. package/fesm2022/ngwr-event-calendar.mjs +6 -6
  110. package/fesm2022/ngwr-file-upload.mjs +3 -3
  111. package/fesm2022/ngwr-form.mjs +9 -9
  112. package/fesm2022/ngwr-graph.mjs +6 -6
  113. package/fesm2022/ngwr-hotkey.mjs +6 -6
  114. package/fesm2022/ngwr-i18n.mjs +9 -9
  115. package/fesm2022/ngwr-icon.mjs +6 -6
  116. package/fesm2022/ngwr-image-cropper.mjs +3 -3
  117. package/fesm2022/ngwr-input-number.mjs +3 -3
  118. package/fesm2022/ngwr-input-otp.mjs +3 -3
  119. package/fesm2022/ngwr-input.mjs +15 -15
  120. package/fesm2022/ngwr-keyboard.mjs +3 -3
  121. package/fesm2022/ngwr-knob.mjs +3 -3
  122. package/fesm2022/ngwr-layout.mjs +15 -15
  123. package/fesm2022/ngwr-lightbox.mjs +3 -3
  124. package/fesm2022/ngwr-list.mjs +6 -6
  125. package/fesm2022/ngwr-loading-bar.mjs +6 -6
  126. package/fesm2022/ngwr-mention.mjs +6 -6
  127. package/fesm2022/ngwr-meta.mjs +6 -6
  128. package/fesm2022/ngwr-overlay.mjs +6 -6
  129. package/fesm2022/ngwr-page-header.mjs +3 -3
  130. package/fesm2022/ngwr-pagination.mjs +3 -3
  131. package/fesm2022/ngwr-pipes.mjs +21 -21
  132. package/fesm2022/ngwr-platform.mjs +6 -6
  133. package/fesm2022/ngwr-popconfirm.mjs +6 -6
  134. package/fesm2022/ngwr-popover.mjs +12 -12
  135. package/fesm2022/ngwr-progress.mjs +3 -3
  136. package/fesm2022/ngwr-pull-to-refresh.mjs +3 -3
  137. package/fesm2022/ngwr-qr.mjs +4 -3
  138. package/fesm2022/ngwr-qr.mjs.map +1 -1
  139. package/fesm2022/ngwr-radio.mjs +6 -6
  140. package/fesm2022/ngwr-rating.mjs +3 -3
  141. package/fesm2022/ngwr-result.mjs +12 -12
  142. package/fesm2022/ngwr-schema-form.mjs +3 -3
  143. package/fesm2022/ngwr-scroll.mjs +3 -3
  144. package/fesm2022/ngwr-segmented.mjs +3 -3
  145. package/fesm2022/ngwr-select.mjs +12 -12
  146. package/fesm2022/ngwr-sidebar.mjs +3 -3
  147. package/fesm2022/ngwr-skeleton.mjs +3 -3
  148. package/fesm2022/ngwr-slider.mjs +3 -3
  149. package/fesm2022/ngwr-sortable-list.mjs +9 -9
  150. package/fesm2022/ngwr-speed-dial.mjs +3 -3
  151. package/fesm2022/ngwr-spinner.mjs +3 -3
  152. package/fesm2022/ngwr-splitter.mjs +3 -3
  153. package/fesm2022/ngwr-statistic.mjs +9 -9
  154. package/fesm2022/ngwr-stepper.mjs +6 -6
  155. package/fesm2022/ngwr-storage.mjs +3 -3
  156. package/fesm2022/ngwr-switch.mjs +3 -3
  157. package/fesm2022/ngwr-table.mjs +18 -18
  158. package/fesm2022/ngwr-tabs-router.mjs +3 -3
  159. package/fesm2022/ngwr-tabs.mjs +6 -6
  160. package/fesm2022/ngwr-textarea.mjs +3 -3
  161. package/fesm2022/ngwr-theme.mjs +3 -3
  162. package/fesm2022/ngwr-timeline.mjs +6 -6
  163. package/fesm2022/ngwr-toast.mjs +9 -9
  164. package/fesm2022/ngwr-toolbar.mjs +3 -3
  165. package/fesm2022/ngwr-tour.mjs +6 -6
  166. package/fesm2022/ngwr-transfer.mjs +3 -3
  167. package/fesm2022/ngwr-tree.mjs +3 -3
  168. package/fesm2022/ngwr-typography.mjs +3 -3
  169. package/fesm2022/ngwr-utils.mjs +31 -1
  170. package/fesm2022/ngwr-utils.mjs.map +1 -1
  171. package/fesm2022/ngwr-version.mjs +1 -1
  172. package/fesm2022/ngwr-version.mjs.map +1 -1
  173. package/fesm2022/ngwr-virtual-scroll.mjs +3 -3
  174. package/fesm2022/ngwr-window.mjs +12 -12
  175. package/llms-full.txt +12 -1
  176. package/mcp/server.js +15 -1
  177. package/mcp/tools.js +3 -3
  178. package/package.json +10 -1
  179. package/schematics/ng-add/index.js +2 -2
  180. package/schematics/use/symbol-map.json +1 -0
  181. package/skills/ngwr/SKILL.md +1 -1
  182. package/skills/ngwr/references/catalog.md +2 -0
  183. package/types/ngwr-bits-aurora.d.ts +11 -0
  184. package/types/ngwr-bits-border-glow-testing.d.ts +5 -6
  185. package/types/ngwr-bits-border-glow.d.ts +5 -1
  186. package/types/ngwr-bits-dot-grid.d.ts +7 -2
  187. package/types/ngwr-bits-iridescence-testing.d.ts +85 -0
  188. package/types/ngwr-bits-iridescence.d.ts +244 -0
  189. package/types/ngwr-bits-ripple-grid.d.ts +4 -7
  190. package/types/ngwr-bits-splash-cursor.d.ts +0 -1
  191. package/types/ngwr-bits-spotlight-card-testing.d.ts +3 -1
  192. package/types/ngwr-bits-star-border-testing.d.ts +7 -7
  193. package/types/ngwr-bits-threads.d.ts +11 -8
  194. package/types/ngwr-bits-waves.d.ts +6 -3
  195. package/types/ngwr-charts-core.d.ts +0 -10
  196. package/types/ngwr-color-picker.d.ts +0 -21
  197. package/types/ngwr-dropdown.d.ts +0 -52
  198. package/types/ngwr-editor.d.ts +2 -84
  199. package/types/ngwr-i18n.d.ts +0 -15
  200. package/types/ngwr-mention.d.ts +0 -13
  201. package/types/ngwr-popconfirm.d.ts +0 -33
  202. package/types/ngwr-popover.d.ts +0 -51
  203. package/types/ngwr-tour.d.ts +0 -2
  204. package/types/ngwr-utils.d.ts +49 -7
  205. package/types/ngwr-version.d.ts +1 -1
  206. package/types/ngwr-window.d.ts +0 -56
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-bits-ripple-grid.mjs","names":[],"sources":["../../../projects/lib/bits/ripple-grid/ripple-grid.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 * After the Ripple Grid background on reactbits.dev\n * (https://www.reactbits.dev/backgrounds/ripple-grid). The idea is the same;\n * the code is ngwr's own: the original is a WebGL shader, this is a 2D canvas\n * of grid lines bent by rings that travel out from the pointer, and a frame\n * loop that sleeps whenever no ring is alive.\n *\n * A 2D canvas rather than WebGL, deliberately. The picture is a few dozen\n * hairlines, which a 2D context antialiases on its own; the bend is evaluated\n * only along the stretch of each line a ring can reach, so a still grid is\n * two points per line; it boots with no shader to compile, and it still draws\n * on the machines that blocklist WebGL2.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n NgZone,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { clamp, numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\n/** One ring, in the host's layout box. */\ninterface Ripple {\n readonly x: number;\n readonly y: number;\n /** Milliseconds since it left its origin. */\n age: number;\n /** Its strength as a share of `strength`: a press is 1, a move or an idle ripple less. */\n readonly share: number;\n}\n\n/** A ring asked for since the last frame; the next frame places it. */\ninterface Pending {\n readonly x: number;\n readonly y: number;\n readonly share: number;\n /** Whether `x` and `y` are viewport coordinates (a pointer) rather than the box's own (the idle ripple). */\n readonly client: boolean;\n}\n\n/**\n * One alpha step's lit stretches, as polylines packed end to end into buffers that\n * keep their capacity frame to frame: `points` holds `[x, y, x, y, …]` with `length`\n * entries in use, `starts` the index in `points` where each run begins, `runs` in use.\n */\ninterface GlowRuns {\n points: Float64Array;\n length: number;\n starts: Uint32Array;\n runs: number;\n}\n\n/** What a frame needs to know about a live ring, worked out once per frame rather than once per sample. */\ninterface Ring {\n x: number;\n y: number;\n /** Distance the crest has travelled, in px. */\n radius: number;\n /** The band the ring reaches, from `inner` to `outer` px from its origin, and both squared. */\n inner: number;\n outer: number;\n inner2: number;\n outer2: number;\n /** Displacement at the crest before the overlap budget, in px; negative pulls inward. */\n raw: number;\n /** Displacement at the crest as drawn, within the overlap budget, in px. */\n push: number;\n /** Glow at the crest, 0..1. */\n glow: number;\n}\n\n/**\n * The most bitmap pixels the canvas is backed with, the bound `wr-waves` and\n * `wr-dot-grid` keep. On a 2x screen a box up to 625,000 CSS px² (1000 × 625)\n * keeps the full 2x; past that the canvas drops to 1x outright rather than to a\n * fraction between, because a fractional bitmap is resampled onto the screen and\n * draws neighbouring hairlines at visibly different weights (see `pixelRatio()`).\n */\nconst MAX_CANVAS_PIXELS = 2_500_000;\n\n/**\n * The smallest cell the grid is laid out at. Under 12px the lines read as a\n * fill rather than a grid, and a full-page host would be hundreds of lines for\n * every ring to bend.\n */\nconst MIN_CELL = 12;\n\n/** The thinnest line drawn, in px. A canvas ignores a zero or negative `lineWidth` and keeps the last one. */\nconst MIN_LINE_WIDTH = 0.25;\n\n/** A 60 Hz frame, in ms: what the first frame of a run advances by, with no frame before it to measure from. */\nconst FRAME_MS = 1000 / 60;\n\n/** The longest step one frame may take, in ms, so a tab that wakes after a pause does not jump the rings. */\nconst MAX_FRAME_MS = 50;\n\n/** A ring's half-width as a share of the cell, so a fine grid and a coarse one show the same ripple. */\nconst RING_SHARE = 0.75;\n\n/** Bounds on that half-width, in px. */\nconst MIN_RING = 16;\nconst MAX_RING = 60;\n\n/** How far either side of its crest a ring still bends anything, in half-widths: past 3 the bell is under 0.02%. */\nconst RING_REACH = 3;\n\n/** The shortest distance between the points a bent line is drawn through, in px. */\nconst MIN_SAMPLE = 6;\n\n/** Distance the pointer travels between two ripples it leaves behind, in cells, and never under 32px. */\nconst MOVE_SPACING = 2;\nconst MIN_MOVE_SPACING = 32;\n\n/** Strength of a ripple a moving pointer leaves, and of an idle ripple, as a share of a press. */\nconst MOVE_SHARE = 0.4;\nconst IDLE_SHARE = 0.6;\n\n/** The shortest idle interval, in ms: shorter would stack rings at the centre faster than they spread. */\nconst MIN_IDLE_MS = 500;\n\n/**\n * The most rings alive at once. A new one past it replaces the faintest live\n * ring, the oldest of the faintest on a tie, and is dropped itself when every\n * live ring is still stronger than it starts out — a trail ring among presses.\n */\nconst MAX_RIPPLES = 16;\n\n/**\n * The longest a ring lives, in ms, fading out over the last `FADE_MS` of it.\n * Only `speed` 0 with `decay` 0 ever reaches it — a ring that neither travels\n * nor fades would otherwise keep the loop running for good.\n */\nconst MAX_AGE_MS = 6000;\nconst FADE_MS = 1000;\n\n/**\n * The bend at a ring's crest, as a share of the ring's half-width, that the\n * rings reaching any one point may add up to: what keeps the grid from folding.\n *\n * A line folds back over itself where the field's slope reaches 1. One ring's\n * slope is at most 2.3 × its crest bend ÷ its half-width — measured over every\n * radius, the steepest point being where the ease into the origin meets the\n * crest's flank — and the slopes of overlapping rings add. Held to 0.39 of a\n * half-width, the summed slope stays under 0.9 wherever the rings overlap, so a\n * line keeps its course and its neighbours' order however hard it is pressed:\n * 11.7px at the default cell. Bounding the summed BEND instead, point by point,\n * folds anyway, and kinks the lines where the sum crosses the bound.\n */\nconst PUSH_PER_WIDTH = 0.39;\n\n/**\n * How long a ring takes to reach its full bend, in ms. Born at full strength it\n * would take its share of the overlap budget in one frame and shrink every ring\n * it overlaps by a step; ramped in, they give way smoothly. At the default speed\n * the crest is still inside its first half-width by then, where the ease keeps\n * the bend small anyway.\n */\nconst BIRTH_MS = 100;\n\n/** Alpha steps the lit stretches are batched into, one path and one stroke each. */\nconst GLOW_LEVELS = 16;\n\n/** Below this a ring's glow rounds to no alpha step at all. */\nconst GLOW_REST = 1 / (2 * GLOW_LEVELS);\n\n/** Below this, in px, a ring's displacement counts as gone. */\nconst PUSH_REST = 0.1;\n\n/** What the lines fall back to when the colour cannot be read: `--wr-border-base` in light. */\nconst LINE_FALLBACK = 'rgb(209, 213, 219)';\n\n/** What the lit stretches fall back to: the shipped primary. */\nconst RIPPLE_FALLBACK = 'rgb(57, 105, 226)';\n\n/** The colours an unset, empty or blank input leaves to the hooks. */\nconst LINE_TOKEN = 'var(--wr-ripple-grid-line-color)';\nconst RIPPLE_TOKEN = 'var(--wr-ripple-grid-ripple-color)';\n\n/** An empty run list for one alpha step, with room for a few lit stretches before it first grows. */\nconst glowRuns = (): GlowRuns => ({ points: new Float64Array(256), length: 0, starts: new Uint32Array(32), runs: 0 });\n\n/** Open a new lit run at `(x0, y0)` – `(x1, y1)`, growing the buffers only when they are full. */\nfunction startRun(level: GlowRuns, x0: number, y0: number, x1: number, y1: number): void {\n if (level.runs === level.starts.length) {\n const starts = new Uint32Array(level.starts.length * 2);\n starts.set(level.starts);\n level.starts = starts;\n }\n level.starts[level.runs++] = level.length;\n addPoint(level, x0, y0);\n addPoint(level, x1, y1);\n}\n\n/** Carry the open run on to `(x, y)`. */\nfunction addPoint(level: GlowRuns, x: number, y: number): void {\n if (level.length + 2 > level.points.length) {\n const points = new Float64Array(level.points.length * 2);\n points.set(level.points);\n level.points = points;\n }\n level.points[level.length++] = x;\n level.points[level.length++] = y;\n}\n\n/**\n * A line position moved so its stroke's edges fall on bitmap pixel boundaries:\n * `stroke` bitmap pixels wide at `ratio` bitmap pixels per CSS pixel. A 1px line\n * left on a pixel boundary covers two pixels at half alpha — at 1x every line of\n * the grid did — and at a fractional ratio neighbouring lines land on different\n * fractions and draw at visibly different weights. A stroke under one bitmap\n * pixel goes on a pixel's centre: Skia draws it as a one-pixel hairline at\n * reduced alpha, which only lands inside a single pixel from there.\n */\nfunction snap(position: number, ratio: number, stroke: number): number {\n const width = Math.max(1, stroke);\n return (Math.round(position * ratio - width / 2) + width / 2) / ratio;\n}\n\n/**\n * Ripple-grid background — thin grid lines on a 2D canvas that the pointer\n * sends ripples through. A press sends out a ring that travels outward, bends\n * the lines it passes away from where it started and lights them in the ripple\n * colour, and fades as it goes; with `rippleOnMove` a moving pointer leaves a\n * trail of softer rings behind it. Fills its nearest positioned ancestor; put\n * it behind hero copy or section content.\n *\n * With nothing pressed or moved the grid is still, and asks for no frames at\n * all. `idleInterval` sends a softer ring out from the centre every so many\n * milliseconds on its own; it is off by default, so a page that wants a grid\n * which moves before anyone touches it asks for one. Each ring lives until it\n * has faded or left the box, and never longer than six seconds.\n *\n * The pointer is followed on the host's PARENT, with passive listeners, so the\n * grid answers a pointer that is over the content in front of it rather than\n * over the canvas — the host itself takes no pointer events and never steals a\n * click. Placed directly in a shadow root, where it has no parent element, it\n * follows the pointer on the shadow host; with neither it listens nowhere, and\n * only an idle ring ever moves it. Content placed after it needs\n * `position: relative` (or any z-index) to paint above it, as with any\n * absolutely positioned layer.\n *\n * The grid is laid out in the host's own box, before transforms, so one that\n * boots inside a scaling-in panel (a dialog opening) is not stretched when the\n * animation ends, and the pointer is mapped back through the transform.\n *\n * Decorative: the canvas is `aria-hidden`, the host has no role and nothing in\n * it takes focus. The frame loop runs only while a ring is alive, and never\n * while the host is off screen; an idle ring comes due only while frames run,\n * so a hidden tab does not queue them up.\n *\n * Under `prefers-reduced-motion` one still grid is drawn, in the line colour,\n * with no ripples, no idle rings and no pointer listeners; it is drawn again\n * when an input, the box or the page's colour scheme changes. The preference\n * is read live, and switching it on drops the rings already travelling.\n *\n * Colours are theme tokens by default — the lines `--wr-border-base`, the lit\n * stretches `--wr-color-primary` — through the `--wr-ripple-grid-line-color`\n * and `--wr-ripple-grid-ripple-color` hooks, and resolved before they reach\n * the canvas, which reads neither a `var()` nor a `light-dark()`.\n *\n * Until the canvas has drawn — the seconds before hydration on a prerendered\n * page — the stylesheet paints the same grid, at the same cell, width and\n * colour, and `.wr-ripple-grid--painted` retires it.\n *\n * @example\n * ```html\n * <section style=\"position: relative; min-height: 24rem; overflow: hidden\">\n * <wr-ripple-grid />\n * <h1 style=\"position: relative\">Hero copy above the grid</h1>\n * </section>\n * ```\n *\n * @see https://ngwr.dev/bits/ripple-grid\n */\n@Component({\n selector: 'wr-ripple-grid',\n template: '<canvas #canvas class=\"wr-ripple-grid__canvas\" aria-hidden=\"true\"></canvas>',\n styleUrl: './ripple-grid.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-ripple-grid',\n // Feed the CSS stand-in grid (see `styles/_rules.scss`) so it draws at the\n // real cell, width and colour, and switch it off once the canvas has painted.\n '[style.--wr-ripple-grid-cell-size.px]': 'cell()',\n '[style.--wr-ripple-grid-line-width.px]': 'stroke()',\n '[style.--wr-ripple-grid-line-color]': 'standInColor()',\n '[class.wr-ripple-grid--painted]': 'painted()',\n },\n})\nexport class WrRippleGrid {\n /** Distance between grid lines in px, never under 12. @default 40 */\n readonly cellSize = input(40, { transform: numAttr(40) });\n\n /** Line colour, any CSS colour. When unset, `--wr-ripple-grid-line-color` decides. */\n readonly lineColor = input<string | null>(null);\n\n /** Colour a line takes where a ripple passes, any CSS colour. When unset, `--wr-ripple-grid-ripple-color` decides. */\n readonly rippleColor = input<string | null>(null);\n\n /** Line width in px, never under 0.25. @default 1 */\n readonly lineWidth = input(1, { transform: numAttr(1) });\n\n /**\n * How far a fresh ripple from a press bends the lines at its crest, in px;\n * a ripple the moving pointer leaves bends them 40% as far. `0` keeps the\n * lines straight and only lights them, and a negative value bends them\n * inward. A bend steep enough to fold a line back over itself is never\n * drawn: one ring, or several where they overlap, bends a line at most about\n * 0.3 of a cell for cells from 22 to 80px — 11.7px at the default 40 — and\n * rings that overlap share that bound rather than adding past it. @default 10\n */\n readonly strength = input(10, { transform: numAttr(10) });\n\n /** How fast a ripple travels outward, in px per second; `0` keeps it where it started. @default 320 */\n readonly speed = input(320, { transform: numAttr(320) });\n\n /**\n * How fast a ripple fades, per second: its strength is multiplied by\n * `e^-decay` every second, so `1.4` halves it in half a second. `0` keeps it\n * at full strength until it leaves the box. @default 1.4\n */\n readonly decay = input(1.4, { transform: numAttr(1.4) });\n\n /** Whether a moving pointer leaves ripples behind it, and not only a press. @default true */\n readonly rippleOnMove = input(true, { transform: coerceBooleanProperty });\n\n /**\n * Milliseconds between the softer ripples sent out from the centre on their\n * own, never under 500; `0` sends none, and the grid then stays still until\n * the pointer moves it. @default 0\n */\n readonly idleInterval = input(0, { transform: numAttr(0) });\n\n /** The cell the grid is laid out at: `cellSize`, floored. */\n protected readonly cell = computed(() => Math.max(MIN_CELL, this.cellSize()));\n\n /** The line width drawn: `lineWidth`, floored. */\n protected readonly stroke = computed(() => Math.max(MIN_LINE_WIDTH, this.lineWidth()));\n\n /**\n * A line colour the author chose, written onto the host's own hook so the\n * stand-in draws in it too; `null` while the input is unset, which leaves the\n * stylesheet's value in charge. Also `null` for an expression built ON the\n * hook — `color-mix(in srgb, var(--wr-ripple-grid-line-color) 50%,\n * transparent)` — which, written onto the hook itself, would be a cycle and\n * invalidate it: the canvas resolves such an expression against the theme's\n * value, and the stand-in keeps that value.\n */\n protected readonly standInColor = computed(() => {\n const color = this.lineColor()?.trim() ?? '';\n return color === '' || color.includes('--wr-ripple-grid-line-color') ? null : color;\n });\n\n /** The colours the canvas resolves: an input that is unset, empty or blank leaves the hook in charge. */\n private readonly lineExpression = computed(() => (this.lineColor() ?? '').trim() || LINE_TOKEN);\n private readonly rippleExpression = computed(() => (this.rippleColor() ?? '').trim() || RIPPLE_TOKEN);\n\n /** The idle interval in force, in ms; 0 when there is none. */\n private readonly idle = computed(() => {\n const interval = this.idleInterval();\n return interval > 0 ? Math.max(MIN_IDLE_MS, interval) : 0;\n });\n\n /** A ring's half-width, in px: a share of the cell, within bounds. */\n private readonly ringWidth = computed(() => clamp(this.cell() * RING_SHARE, MIN_RING, MAX_RING));\n\n /**\n * Flips once the canvas has drawn, which retires the CSS stand-in grid.\n *\n * A canvas cannot be prerendered, so on an SSR'd page it is blank until the\n * bundle has loaded and hydrated. The stylesheet paints the same lines at the\n * same cell from the prerendered HTML in the meantime, and this hands over.\n */\n protected readonly painted = signal(false);\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 ctx: CanvasRenderingContext2D | null = null;\n /** The host's layout box, in CSS pixels before any transform: what the grid is laid out in. */\n private box = { width: 0, height: 0 };\n /** Bitmap pixels per CSS pixel the canvas is backed at; 0 until the first fit. */\n private ratio = 0;\n private readonly ripples: Ripple[] = [];\n private readonly pending: Pending[] = [];\n /** The live rings as this frame draws them, reused frame to frame. */\n private readonly rings: Ring[] = [];\n private ringCount = 0;\n /** The rings that reach the line being traced, reused line to line. */\n private readonly near: Ring[] = [];\n /** Lit stretches by alpha step, reused frame to frame. */\n private readonly lit: GlowRuns[] = Array.from({ length: GLOW_LEVELS }, glowRuns);\n private anyLit = false;\n /** Where the last trail ripple was left, in viewport coordinates, while the pointer is over the parent. */\n private readonly anchor = { x: 0, y: 0, set: false };\n /** The ring half-width and sample step this frame draws with, read once rather than per sample. */\n private frameRing = MIN_RING;\n private frameStep = MIN_SAMPLE;\n /** One sample, displaced: written by `displace()`, read straight after. */\n private sx = 0;\n private sy = 0;\n private sg = 0;\n private rafId = 0;\n private lastTime = -1;\n /** The frame callback, made once rather than a closure per frame. */\n private readonly frame = (time: number): void => this.tick(time);\n /** Whether the idle timer has fired and the next frame owes the centre a ring. */\n private idleDue = false;\n /** Schedules a frame while motion is allowed; `null` under reduced motion. */\n private wake: (() => void) | null = null;\n /** Arms the next idle ring while motion is allowed; `null` under reduced motion. */\n private armIdle: (() => void) | null = null;\n\n /** Flips once `boot()` has a context and a box — everything after waits for it. */\n private readonly booted = signal(false);\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));\n\n // Everything that changes the picture only changes what the next frame\n // draws. A sleeping loop or a still grid has no next frame, so ask for one.\n // `speed` and `decay` are left out: with no ring alive they change nothing\n // on screen, and with one alive the loop is already running.\n effect(() => {\n this.cell();\n this.stroke();\n this.lineColor();\n this.rippleColor();\n this.strength();\n untracked(() => this.requestDraw());\n });\n\n // A drawn grid keeps the colours it was drawn in, and the loop sleeps when\n // nothing moves, so a scheme switch asks for a frame to re-resolve them.\n watchColorScheme(() => this.requestDraw());\n\n // Motion or a still grid, decided live. Toggling `rippleOnMove` or the idle\n // interval restarts the listeners and the timer; rings already travelling\n // carry on.\n effect(onCleanup => {\n if (!this.booted()) return;\n if (this.platform.prefersReducedMotion()) {\n untracked(() => {\n this.rest();\n this.draw();\n });\n return;\n }\n const trail = this.rippleOnMove();\n const idle = this.idle();\n onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.interact(trail, idle))));\n });\n }\n\n // Setup\n\n private boot(): void {\n const canvas = this.canvasRef().nativeElement;\n this.ctx = canvas.getContext('2d');\n if (!this.ctx) return;\n\n // `clientWidth` and not `getBoundingClientRect()`: the rect is the box after\n // transforms, so a grid booted inside a dialog's scale-in would be laid out\n // for 96% of its box and stretched by CSS once the animation ended. The\n // observer's first entry follows with the exact, sub-pixel size.\n const hostEl = this.host.nativeElement;\n this.fit(hostEl.clientWidth, hostEl.clientHeight);\n\n // The box is the observer's to follow, transforms excluded. A window resize\n // is for the pixel ratio, which browser zoom and a move to another screen\n // change with no box change at all.\n const onResize = (): void => this.resize(this.box.width, this.box.height);\n window.addEventListener('resize', onResize);\n const ro = new ResizeObserver(entries => {\n const rect = entries[entries.length - 1]?.contentRect;\n if (rect) this.resize(rect.width, rect.height);\n });\n ro.observe(hostEl);\n this.destroyRef.onDestroy(() => {\n window.removeEventListener('resize', onResize);\n ro.disconnect();\n });\n this.booted.set(true);\n }\n\n /** Follow the pointer, keep the idle timer and draw on demand; returns what stops all three. */\n private interact(trail: boolean, interval: number): () => void {\n const hostEl = this.host.nativeElement;\n // The parent, not the host: content in front of the canvas receives the\n // pointer, and its events bubble to the parent, never to a sibling. Placed\n // directly in a shadow root it has none, and the shadow host is where those\n // composed events land. Never the host itself, which takes no pointer\n // events and so could never hear one.\n const root = hostEl.getRootNode();\n const target =\n hostEl.parentElement ?? (root instanceof ShadowRoot && root.host instanceof HTMLElement ? root.host : null);\n\n // Off screen the grid asks for no frames, keeps no timer and takes no\n // pointer. Without an observer (jsdom) it counts as visible.\n let visible = typeof IntersectionObserver === 'undefined';\n let idleTimer = 0;\n const wake = (): void => {\n if (visible && this.rafId === 0) this.rafId = requestAnimationFrame(this.frame);\n };\n // Re-armed from the frame that places the ring rather than from the timer\n // itself, so a background tab — no frames, timers still firing — owes the\n // centre one ring at most, not one per interval it was away.\n const armIdle = (): void => {\n if (interval <= 0 || !visible || idleTimer !== 0 || this.idleDue) return;\n idleTimer = window.setTimeout(() => {\n idleTimer = 0;\n this.idleDue = true;\n wake();\n }, interval);\n };\n const halt = (): void => {\n cancelAnimationFrame(this.rafId);\n this.rafId = 0;\n this.lastTime = -1;\n window.clearTimeout(idleTimer);\n idleTimer = 0;\n };\n\n const onDown = (event: PointerEvent): void => {\n if (!visible) return;\n this.emit(event.clientX, event.clientY, 1);\n wake();\n };\n // The first move only marks where the pointer came in: a ring on arrival\n // would ripple the grid every time the pointer crossed into the section.\n const onMove = (event: PointerEvent): void => {\n if (!visible) return;\n const anchor = this.anchor;\n const spacing = Math.max(MIN_MOVE_SPACING, this.cell() * MOVE_SPACING);\n if (anchor.set && Math.hypot(event.clientX - anchor.x, event.clientY - anchor.y) < spacing) return;\n if (anchor.set) {\n this.emit(event.clientX, event.clientY, MOVE_SHARE);\n wake();\n } else {\n anchor.x = event.clientX;\n anchor.y = event.clientY;\n anchor.set = true;\n }\n };\n const onLeave = (): void => {\n this.anchor.set = false;\n };\n target?.addEventListener('pointerdown', onDown, { passive: true });\n if (trail) {\n target?.addEventListener('pointermove', onMove, { passive: true });\n target?.addEventListener('pointerleave', onLeave, { passive: true });\n target?.addEventListener('pointercancel', onLeave, { passive: true });\n }\n\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(entries => {\n visible = entries[entries.length - 1].isIntersecting;\n if (visible) {\n wake();\n armIdle();\n } else {\n halt();\n }\n });\n io?.observe(hostEl);\n\n this.wake = wake;\n this.armIdle = armIdle;\n wake();\n armIdle();\n\n return () => {\n target?.removeEventListener('pointerdown', onDown);\n if (trail) {\n target?.removeEventListener('pointermove', onMove);\n target?.removeEventListener('pointerleave', onLeave);\n target?.removeEventListener('pointercancel', onLeave);\n }\n io?.disconnect();\n halt();\n this.wake = null;\n this.armIdle = null;\n this.idleDue = false;\n this.anchor.set = false;\n };\n }\n\n /** Queue a ring at a viewport point for the next frame, and mark the point for the trail. */\n private emit(clientX: number, clientY: number, share: number): void {\n this.pending.push({ x: clientX, y: clientY, share, client: true });\n this.anchor.x = clientX;\n this.anchor.y = clientY;\n this.anchor.set = true;\n }\n\n /** Draw the grid as it should look now: a frame while motion is allowed, at once under reduced motion. */\n private requestDraw(): void {\n if (!this.booted()) return;\n if (this.wake) this.wake();\n else if (this.platform.prefersReducedMotion()) this.draw();\n }\n\n /** Fit the canvas to a new box or pixel ratio and, when that cleared it, repaint at once. */\n private resize(width: number, height: number): void {\n if (!this.fit(width, height)) return;\n // Observer entries arrive after the frame's own callbacks and before its\n // paint, so a frame asked for here would leave this one blank — with the\n // stand-in already retired, a resize drag would flicker every frame. The\n // rings keep their place in the box; a running loop simply carries on.\n this.draw();\n }\n\n /**\n * Back the canvas for a `width` × `height` box, in CSS pixels, at the screen's\n * pixel ratio. Returns whether anything changed: writing `canvas.width` clears\n * the bitmap even to the value it already has, so an unchanged box is left alone.\n */\n private fit(width: number, height: number): boolean {\n const ratio = this.pixelRatio(width * height);\n if (width === this.box.width && height === this.box.height && ratio === this.ratio) return false;\n\n this.box = { width, height };\n this.ratio = ratio;\n const canvas = this.canvasRef().nativeElement;\n canvas.width = Math.round(width * ratio);\n canvas.height = Math.round(height * ratio);\n // Resizing the bitmap resets the transform, so the scale goes on after it.\n // Every coordinate the draw uses is in CSS pixels.\n this.ctx?.setTransform(ratio, 0, 0, ratio, 0, 0);\n return true;\n }\n\n /**\n * Bitmap pixels per CSS pixel for a box of `area` CSS px². The screen's own\n * ratio when it fits under 2 and the pixel budget, so a bitmap pixel is a\n * device pixel and a snapped line lands on whole device pixels; otherwise the\n * largest whole fraction of it that fits — a half, a third — so each bitmap\n * pixel still covers a whole number of device pixels and every line keeps one\n * weight. A ratio between (1.31 on a 2x screen) is resampled onto the screen\n * by a fraction, which no snapping survives. Never below 1 on a 1x screen or\n * better: a 1.5x screen past the budget takes 1 and accepts the resampling,\n * rather than a bitmap coarser than the CSS box.\n */\n private pixelRatio(area: number): number {\n const device = window.devicePixelRatio || 1;\n const limit = Math.min(2, area > 0 ? Math.sqrt(MAX_CANVAS_PIXELS / area) : 2);\n let ratio = device;\n for (let k = 2; ratio > limit; k++) ratio = device / k;\n return ratio < 1 ? Math.min(1, device) : ratio;\n }\n\n /** No ring alive, none queued, none owed. */\n private rest(): void {\n this.ripples.length = 0;\n this.pending.length = 0;\n this.idleDue = false;\n this.anchor.set = false;\n }\n\n // Frame loop\n\n private tick(time: number): void {\n this.rafId = 0;\n // A ring is a function of its age, so stepping it by the time that really\n // passed is what keeps a 120 Hz screen from running it twice as fast. A\n // timestamp that did not advance counts as one 60 Hz frame, and so does\n // the first frame of a run, which has nothing to measure from.\n const elapsed = time - this.lastTime;\n const step = this.lastTime < 0 || elapsed <= 0 ? FRAME_MS : Math.min(MAX_FRAME_MS, elapsed);\n this.lastTime = time;\n\n this.advance(step);\n this.draw();\n\n // No ring left: sleep until the pointer, the idle timer, a resize or a new\n // input asks for another frame. The frame that retired the last ring has\n // just drawn the grid straight.\n if (this.ripples.length > 0 && this.wake) this.rafId = requestAnimationFrame(this.frame);\n else this.lastTime = -1;\n }\n\n /** Age every ring by `step` ms, place the queued ones, and retire the ones with nothing left to show. */\n private advance(step: number): void {\n for (const ripple of this.ripples) ripple.age += step;\n\n if (this.idleDue) {\n this.idleDue = false;\n this.pending.push({ x: this.box.width / 2, y: this.box.height / 2, share: IDLE_SHARE, client: false });\n this.armIdle?.();\n }\n if (this.pending.length > 0) {\n // The rect is the box as drawn, after any transform on it or an ancestor,\n // and the grid is laid out in the box before them, so a pointer is scaled\n // back into it. Read once a frame, not once an event.\n let needsRect = false;\n for (let i = 0; i < this.pending.length && !needsRect; i++) needsRect = this.pending[i].client;\n const rect = needsRect ? this.host.nativeElement.getBoundingClientRect() : null;\n const sx = rect && rect.width > 0 ? this.box.width / rect.width : 1;\n const sy = rect && rect.height > 0 ? this.box.height / rect.height : 1;\n for (const entry of this.pending) {\n const x = entry.client && rect ? (entry.x - rect.left) * sx : entry.x;\n const y = entry.client && rect ? (entry.y - rect.top) * sy : entry.y;\n this.place(x, y, entry.share);\n }\n this.pending.length = 0;\n }\n\n const speed = Math.max(0, this.speed());\n const strength = Math.abs(this.strength());\n const reach = this.ringWidth() * RING_REACH;\n const { width, height } = this.box;\n for (let i = this.ripples.length - 1; i >= 0; i--) {\n const ripple = this.ripples[i];\n const energy = this.energy(ripple);\n // Past its farthest corner (plus the margin a bend can reach) the ring\n // has nothing left in the box to bend.\n const far = Math.hypot(Math.max(ripple.x, width - ripple.x), Math.max(ripple.y, height - ripple.y));\n const gone = (speed * ripple.age) / 1000 - reach > far + this.margin();\n // A ring at the end of its life has faded to nothing, so this retires it too.\n if (gone || (energy < GLOW_REST && energy * strength < PUSH_REST)) {\n this.ripples.splice(i, 1);\n }\n }\n }\n\n /**\n * Start a ring at a box point. Past `MAX_RIPPLES` it replaces the faintest live\n * ring — the oldest of the faintest on a tie — so a press in the middle of a\n * fast drag is not the one that vanishes; when every live ring is still\n * stronger than this one starts out, this one is the faintest, and it is dropped.\n */\n private place(x: number, y: number, share: number): void {\n const ripples = this.ripples;\n if (ripples.length < MAX_RIPPLES) {\n ripples.push({ x, y, age: 0, share });\n return;\n }\n let faintest = 0;\n let least = Infinity;\n for (let i = 0; i < ripples.length; i++) {\n const energy = this.energy(ripples[i]);\n if (energy < least || (energy === least && ripples[i].age > ripples[faintest].age)) {\n faintest = i;\n least = energy;\n }\n }\n if (share < least) return;\n ripples[faintest] = { x, y, age: 0, share };\n }\n\n /** How strong a ring still is, 0..1: its share, faded by `decay` and by the end of its life. */\n private energy(ripple: Ripple): number {\n const decay = Math.max(0, this.decay());\n const end = clamp((MAX_AGE_MS - ripple.age) / FADE_MS, 0, 1);\n return ripple.share * Math.exp((-decay * ripple.age) / 1000) * end;\n }\n\n /**\n * How far past each edge the lines run, so a bend never pulls a line's end into\n * view: a cell, plus the most the rings at one point can bend it — the overlap\n * budget, or every ring at full strength when that is less.\n */\n private margin(): number {\n return this.cell() + Math.min(Math.abs(this.strength()) * MAX_RIPPLES, PUSH_PER_WIDTH * this.ringWidth());\n }\n\n // Drawing\n\n private draw(): void {\n const ctx = this.ctx;\n if (!ctx) return;\n // Retire the stand-in only once something is really painted. Runs outside\n // Angular, so the `set` schedules the one host-binding update it needs.\n if (!this.painted()) this.painted.set(true);\n\n const canvas = this.canvasRef().nativeElement;\n const { width, height } = this.box;\n ctx.clearRect(0, 0, width, height);\n this.prepareRings();\n for (let i = 0; i < GLOW_LEVELS; i++) {\n this.lit[i].length = 0;\n this.lit[i].runs = 0;\n }\n this.anyLit = false;\n\n // The stroke in whole bitmap pixels, and every line moved so its edges fall\n // on them (see `snap()`). Under one bitmap pixel the width is kept, so a\n // thin line stays faint, and lands centred in a single pixel.\n const ratio = this.ratio || 1;\n const scaled = this.stroke() * ratio;\n const stroke = scaled >= 1 ? Math.round(scaled) : scaled;\n const cell = this.cell();\n const margin = this.margin();\n ctx.globalAlpha = 1;\n ctx.lineWidth = stroke / ratio;\n ctx.lineCap = 'butt';\n ctx.lineJoin = 'round';\n ctx.strokeStyle = resolveCssColor(canvas, this.lineExpression(), LINE_FALLBACK);\n ctx.beginPath();\n // A cell centred on the box, then every `cell` out, far enough past each\n // edge that a bend never brings an undrawn line into view — where the\n // stand-in's centred tiles put them, give or take the half device pixel\n // the snap moves a line by.\n const cx = width / 2;\n const cy = height / 2;\n const columns = Math.ceil((cx + margin) / cell);\n const rows = Math.ceil((cy + margin) / cell);\n for (let i = -columns; i < columns; i++) {\n this.trace(ctx, snap(cx + (i + 0.5) * cell, ratio, stroke), -margin, height + margin, true);\n }\n for (let i = -rows; i < rows; i++) {\n this.trace(ctx, snap(cy + (i + 0.5) * cell, ratio, stroke), -margin, width + margin, false);\n }\n ctx.stroke();\n if (!this.anyLit) return;\n\n // Lit stretches go over their own lines in the ripple colour, at their glow\n // as alpha: the blend of the two colours without parsing either, so an\n // `oklch()` or a `display-p3` colour works as well as an `rgb()`.\n ctx.strokeStyle = resolveCssColor(canvas, this.rippleExpression(), RIPPLE_FALLBACK);\n for (let i = 0; i < GLOW_LEVELS; i++) {\n const level = this.lit[i];\n if (level.runs === 0) continue;\n const points = level.points;\n ctx.globalAlpha = (i + 1) / GLOW_LEVELS;\n ctx.beginPath();\n for (let run = 0; run < level.runs; run++) {\n const start = level.starts[run];\n const end = run + 1 < level.runs ? level.starts[run + 1] : level.length;\n ctx.moveTo(points[start], points[start + 1]);\n for (let p = start + 2; p < end; p += 2) ctx.lineTo(points[p], points[p + 1]);\n }\n ctx.stroke();\n }\n ctx.globalAlpha = 1;\n }\n\n /**\n * Work out where every live ring stands at its current age, for this frame's\n * samples, and hold the rings that overlap to the bend budget together.\n *\n * Each ring's bend is scaled by its share of the budget against every ring\n * whose band meets its own: wherever two rings both reach a point their bands\n * meet, so the rings acting on any point add up to the budget at most (see\n * `PUSH_PER_WIDTH`), while a ring that meets no other keeps its full bend. A\n * pair counts in full once its bands meet and fades out over one half-width of\n * gap before that, so two rings drifting into each other give way smoothly\n * rather than both shrinking in one frame.\n */\n private prepareRings(): void {\n const speed = Math.max(0, this.speed());\n const strength = this.strength();\n const width = this.ringWidth();\n this.frameRing = width;\n this.frameStep = Math.max(MIN_SAMPLE, width / 4);\n const reach = width * RING_REACH;\n const rings = this.rings;\n this.ringCount = 0;\n for (const ripple of this.ripples) {\n const energy = this.energy(ripple);\n const radius = (speed * ripple.age) / 1000;\n const inner = Math.max(0, radius - reach);\n const outer = radius + reach;\n const ring = (rings[this.ringCount] ??= {\n x: 0,\n y: 0,\n radius: 0,\n inner: 0,\n outer: 0,\n inner2: 0,\n outer2: 0,\n raw: 0,\n push: 0,\n glow: 0,\n });\n ring.x = ripple.x;\n ring.y = ripple.y;\n ring.radius = radius;\n ring.inner = inner;\n ring.outer = outer;\n ring.inner2 = inner * inner;\n ring.outer2 = outer * outer;\n ring.raw = strength * energy * Math.min(1, ripple.age / BIRTH_MS);\n ring.glow = energy;\n this.ringCount++;\n }\n\n const budget = PUSH_PER_WIDTH * width;\n for (let i = 0; i < this.ringCount; i++) {\n const ring = rings[i];\n let shared = 0;\n for (let j = 0; j < this.ringCount; j++) {\n const other = rings[j];\n const between = Math.hypot(ring.x - other.x, ring.y - other.y);\n const gap = Math.max(\n between - ring.outer - other.outer,\n ring.inner - other.outer - between,\n other.inner - ring.outer - between\n );\n if (gap < width) shared += Math.abs(other.raw) * (gap <= 0 ? 1 : 1 - gap / width);\n }\n ring.push = shared > budget ? (ring.raw * budget) / shared : ring.raw;\n }\n }\n\n /**\n * Add one grid line to the path — vertical at `x = fixed` or horizontal at\n * `y = fixed`, from `from` to `to` along it — and file its lit stretches by\n * alpha step. A line no ring reaches is two points; one a ring reaches is\n * sampled only across the stretch the rings can bend, and straight either\n * side of it. Where that stretch runs to an end of the line, the end is the\n * displaced sample itself: drawing the undisplaced end before it would step\n * the line back along itself.\n */\n private trace(ctx: CanvasRenderingContext2D, fixed: number, from: number, to: number, vertical: boolean): void {\n const near = this.near;\n let count = 0;\n // The stretch of the line any ring's band can reach.\n let start = to;\n let end = from;\n for (let i = 0; i < this.ringCount; i++) {\n const ring = this.rings[i];\n const across = Math.abs(fixed - (vertical ? ring.x : ring.y));\n if (across >= ring.outer) continue;\n const along = vertical ? ring.y : ring.x;\n // Every point of the line already inside the band's inner edge: the ring\n // has passed it.\n if (ring.inner >= Math.hypot(across, Math.max(along - from, to - along))) continue;\n const half = Math.sqrt(ring.outer2 - across * across);\n start = Math.min(start, along - half);\n end = Math.max(end, along + half);\n near[count++] = ring;\n }\n\n if (count === 0 || start >= end) {\n if (vertical) {\n ctx.moveTo(fixed, from);\n ctx.lineTo(fixed, to);\n } else {\n ctx.moveTo(from, fixed);\n ctx.lineTo(to, fixed);\n }\n return;\n }\n\n // Samples on a lattice fixed to the line rather than to the band, so a\n // travelling ring does not slide the points it bends through.\n const step = this.frameStep;\n const first = Math.max(from, from + Math.floor((start - from) / step) * step);\n const last = Math.min(to, from + Math.ceil((end - from) / step) * step);\n if (first > from) {\n if (vertical) ctx.moveTo(fixed, from);\n else ctx.moveTo(from, fixed);\n }\n\n // The alpha step of the lit run being extended, 0 while none is open.\n let runLevel = 0;\n let px = 0;\n let py = 0;\n let pg = 0;\n for (let t = first, s = 0; ; s++, t = Math.min(last, first + s * step)) {\n if (vertical) this.displace(fixed, t, count);\n else this.displace(t, fixed, count);\n const x = this.sx;\n const y = this.sy;\n const g = this.sg;\n if (s === 0 && first <= from) ctx.moveTo(x, y);\n else ctx.lineTo(x, y);\n if (s > 0) {\n const level = Math.min(GLOW_LEVELS, Math.round(((pg + g) / 2) * GLOW_LEVELS));\n if (level === 0) {\n runLevel = 0;\n } else if (runLevel === level) {\n addPoint(this.lit[level - 1], x, y);\n } else {\n startRun(this.lit[level - 1], px, py, x, y);\n runLevel = level;\n this.anyLit = true;\n }\n }\n px = x;\n py = y;\n pg = g;\n if (t >= last) break;\n }\n if (last < to) {\n if (vertical) ctx.lineTo(fixed, to);\n else ctx.lineTo(to, fixed);\n }\n }\n\n /**\n * Where the grid point `(x, y)` is drawn and how lit it is, with the first\n * `count` rings in `near` acting on it; written to `sx`, `sy` and `sg`.\n *\n * Each ring pushes the point along the line from its origin. The push is a\n * bell around the crest, shaped by a cosine so the crest bends outward and a\n * shallow trough either side bends back in — what reads as a ripple rather\n * than a bulge — and eased in over the first half-width, so the lines through\n * the origin itself do not tear. The glow is the bell alone. Overlapping rings\n * add up; `prepareRings()` has already held them to a budget that cannot fold\n * the grid, so nothing is clamped here.\n */\n private displace(x: number, y: number, count: number): void {\n const width = this.frameRing;\n let dx = 0;\n let dy = 0;\n let glow = 0;\n for (let i = 0; i < count; i++) {\n const ring = this.near[i];\n const ox = x - ring.x;\n const oy = y - ring.y;\n const d2 = ox * ox + oy * oy;\n if (d2 <= ring.inner2 || d2 >= ring.outer2) continue;\n const d = Math.sqrt(d2);\n const u = (d - ring.radius) / width;\n const bell = Math.exp(-u * u);\n const ease = d < width ? d / width : 1;\n const amount = (ring.push * bell * Math.cos(2 * u) * ease) / d;\n dx += ox * amount;\n dy += oy * amount;\n glow += ring.glow * bell * ease;\n }\n this.sx = x + dx;\n this.sy = y + dy;\n this.sg = Math.min(1, glow);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-bits-ripple-grid.mjs","names":[],"sources":["../../../projects/lib/bits/ripple-grid/ripple-grid.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 * After the Ripple Grid background on reactbits.dev\n * (https://www.reactbits.dev/backgrounds/ripple-grid). The idea is the same;\n * the code is ngwr's own: the original is a WebGL shader, this is a 2D canvas\n * of grid lines bent by rings that travel out from the pointer, and a frame\n * loop that sleeps whenever no ring is alive.\n *\n * A 2D canvas rather than WebGL, deliberately. The picture is a few dozen\n * hairlines, which a 2D context antialiases on its own; the bend is evaluated\n * only along the stretch of each line a ring can reach, so a still grid is\n * two points per line; it boots with no shader to compile, and it still draws\n * on the machines that blocklist WebGL2.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n NgZone,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { clamp, numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\n/** One ring, in the host's layout box. */\ninterface Ripple {\n readonly x: number;\n readonly y: number;\n /** Milliseconds since it left its origin. */\n age: number;\n /** Its strength as a share of `strength`: a press is 1, a move or an idle ripple less. */\n readonly share: number;\n}\n\n/** A ring asked for since the last frame; the next frame places it. */\ninterface Pending {\n readonly x: number;\n readonly y: number;\n readonly share: number;\n /** Whether `x` and `y` are viewport coordinates (a pointer) rather than the box's own (the idle ripple). */\n readonly client: boolean;\n}\n\n/**\n * One alpha step's lit stretches, as polylines packed end to end into buffers that\n * keep their capacity frame to frame: `points` holds `[x, y, x, y, …]` with `length`\n * entries in use, `starts` the index in `points` where each run begins, `runs` in use.\n */\ninterface GlowRuns {\n points: Float64Array;\n length: number;\n starts: Uint32Array;\n runs: number;\n}\n\n/** What a frame needs to know about a live ring, worked out once per frame rather than once per sample. */\ninterface Ring {\n x: number;\n y: number;\n /** Distance the crest has travelled, in px. */\n radius: number;\n /** The band the ring reaches, from `inner` to `outer` px from its origin, and both squared. */\n inner: number;\n outer: number;\n inner2: number;\n outer2: number;\n /** Displacement at the crest before the overlap budget, in px; negative pulls inward. */\n raw: number;\n /** Displacement at the crest as drawn, within the overlap budget, in px. */\n push: number;\n /** Glow at the crest, 0..1. */\n glow: number;\n}\n\n/**\n * The most bitmap pixels the canvas is backed with, the bound `wr-waves` and\n * `wr-dot-grid` keep. On a 2x screen a box up to 625,000 CSS px² (1000 × 625)\n * keeps the full 2x; past that the canvas drops to 1x outright rather than to a\n * fraction between, because a fractional bitmap is resampled onto the screen and\n * draws neighbouring hairlines at visibly different weights (see `pixelRatio()`).\n */\nconst MAX_CANVAS_PIXELS = 2_500_000;\n\n/**\n * The smallest cell the grid is laid out at. Under 12px the lines read as a\n * fill rather than a grid, and a full-page host would be hundreds of lines for\n * every ring to bend.\n */\nconst MIN_CELL = 12;\n\n/** The thinnest line drawn, in px. A canvas ignores a zero or negative `lineWidth` and keeps the last one. */\nconst MIN_LINE_WIDTH = 0.25;\n\n/** A 60 Hz frame, in ms: what the first frame of a run advances by, with no frame before it to measure from. */\nconst FRAME_MS = 1000 / 60;\n\n/** The longest step one frame may take, in ms, so a tab that wakes after a pause does not jump the rings. */\nconst MAX_FRAME_MS = 50;\n\n/** A ring's half-width as a share of the cell, so a fine grid and a coarse one show the same ripple. */\nconst RING_SHARE = 0.75;\n\n/** Bounds on that half-width, in px. */\nconst MIN_RING = 16;\nconst MAX_RING = 60;\n\n/** How far either side of its crest a ring still bends anything, in half-widths: past 3 the bell is under 0.02%. */\nconst RING_REACH = 3;\n\n/** The shortest distance between the points a bent line is drawn through, in px. */\nconst MIN_SAMPLE = 6;\n\n/** Distance the pointer travels between two ripples it leaves behind, in cells, and never under 32px. */\nconst MOVE_SPACING = 2;\nconst MIN_MOVE_SPACING = 32;\n\n/** Strength of a ripple a moving pointer leaves, and of an idle ripple, as a share of a press. */\nconst MOVE_SHARE = 0.4;\nconst IDLE_SHARE = 0.6;\n\n/** The shortest idle interval, in ms: shorter would stack rings at the centre faster than they spread. */\nconst MIN_IDLE_MS = 500;\n\n/**\n * The most rings alive at once. A new one past it replaces the faintest live\n * ring, the oldest of the faintest on a tie, and is dropped itself when every\n * live ring is still stronger than it starts out — a trail ring among presses.\n */\nconst MAX_RIPPLES = 16;\n\n/**\n * The longest a ring lives, in ms, fading out over the last `FADE_MS` of it.\n * Only `speed` 0 with `decay` 0 ever reaches it — a ring that neither travels\n * nor fades would otherwise keep the loop running for good.\n */\nconst MAX_AGE_MS = 6000;\nconst FADE_MS = 1000;\n\n/**\n * The bend at a ring's crest, as a share of the ring's half-width, that the\n * rings reaching any one point may add up to: what keeps the grid from folding.\n *\n * A line folds back over itself where the field's slope reaches 1. One ring's\n * slope is at most 2.3 × its crest bend ÷ its half-width — measured over every\n * radius, the steepest point being where the ease into the origin meets the\n * crest's flank — and the slopes of overlapping rings add. Held to 0.39 of a\n * half-width, the summed slope stays under 0.9 wherever the rings overlap, so a\n * line keeps its course and its neighbours' order however hard it is pressed:\n * 11.7px at the default cell. Bounding the summed BEND instead, point by point,\n * folds anyway, and kinks the lines where the sum crosses the bound.\n */\nconst PUSH_PER_WIDTH = 0.39;\n\n/**\n * How long a ring takes to reach its full bend, in ms. Born at full strength it\n * would take its share of the overlap budget in one frame and shrink every ring\n * it overlaps by a step; ramped in, they give way smoothly. At the default speed\n * the crest is still inside its first half-width by then, where the ease keeps\n * the bend small anyway.\n */\nconst BIRTH_MS = 100;\n\n/** Alpha steps the lit stretches are batched into, one path and one stroke each. */\nconst GLOW_LEVELS = 16;\n\n/** Below this a ring's glow rounds to no alpha step at all. */\nconst GLOW_REST = 1 / (2 * GLOW_LEVELS);\n\n/** Below this, in px, a ring's displacement counts as gone. */\nconst PUSH_REST = 0.1;\n\n/** What the lines fall back to when the colour cannot be read: `--wr-border-base` in light. */\nconst LINE_FALLBACK = 'rgb(209, 213, 219)';\n\n/** What the lit stretches fall back to: the shipped primary. */\nconst RIPPLE_FALLBACK = 'rgb(57, 105, 226)';\n\n/** The colours an unset, empty or blank input leaves to the hooks. */\nconst LINE_TOKEN = 'var(--wr-ripple-grid-line-color)';\nconst RIPPLE_TOKEN = 'var(--wr-ripple-grid-ripple-color)';\n\n/** An empty run list for one alpha step, with room for a few lit stretches before it first grows. */\nconst glowRuns = (): GlowRuns => ({ points: new Float64Array(256), length: 0, starts: new Uint32Array(32), runs: 0 });\n\n/** Open a new lit run at `(x0, y0)` – `(x1, y1)`, growing the buffers only when they are full. */\nfunction startRun(level: GlowRuns, x0: number, y0: number, x1: number, y1: number): void {\n if (level.runs === level.starts.length) {\n const starts = new Uint32Array(level.starts.length * 2);\n starts.set(level.starts);\n level.starts = starts;\n }\n level.starts[level.runs++] = level.length;\n addPoint(level, x0, y0);\n addPoint(level, x1, y1);\n}\n\n/** Carry the open run on to `(x, y)`. */\nfunction addPoint(level: GlowRuns, x: number, y: number): void {\n if (level.length + 2 > level.points.length) {\n const points = new Float64Array(level.points.length * 2);\n points.set(level.points);\n level.points = points;\n }\n level.points[level.length++] = x;\n level.points[level.length++] = y;\n}\n\n/**\n * A line position moved so its stroke's edges fall on bitmap pixel boundaries:\n * `stroke` bitmap pixels wide at `ratio` bitmap pixels per CSS pixel. A 1px line\n * left on a pixel boundary covers two pixels at half alpha — at 1x every line of\n * the grid did — and at a fractional ratio neighbouring lines land on different\n * fractions and draw at visibly different weights. A stroke under one bitmap\n * pixel goes on a pixel's centre: Skia draws it as a one-pixel hairline at\n * reduced alpha, which only lands inside a single pixel from there.\n */\nfunction snap(position: number, ratio: number, stroke: number): number {\n const width = Math.max(1, stroke);\n return (Math.round(position * ratio - width / 2) + width / 2) / ratio;\n}\n\n/**\n * Ripple-grid background — thin grid lines on a 2D canvas that the pointer\n * sends ripples through. A press sends out a ring that travels outward, bends\n * the lines it passes away from where it started and lights them in the ripple\n * colour, and fades as it goes; with `rippleOnMove` a moving pointer leaves a\n * trail of softer rings behind it. Fills its nearest positioned ancestor; put\n * it behind hero copy or section content.\n *\n * With nothing pressed or moved the grid is still, and asks for no frames at\n * all. `idleInterval` sends a softer ring out from the centre every so many\n * milliseconds on its own; it is off by default, so a page that wants a grid\n * which moves before anyone touches it asks for one. Each ring lives until it\n * has faded or left the box, and never longer than six seconds.\n *\n * The pointer is followed on the host's PARENT, with passive listeners, so the\n * grid answers a pointer that is over the content in front of it rather than\n * over the canvas — the host itself takes no pointer events and never steals a\n * click. Placed directly in a shadow root, where it has no parent element, it\n * follows the pointer on the shadow host; with neither it listens nowhere, and\n * only an idle ring ever moves it. Content placed after it needs\n * `position: relative` (or any z-index) to paint above it, as with any\n * absolutely positioned layer.\n *\n * The grid is laid out in the host's own box, before transforms, so one that\n * boots inside a scaling-in panel (a dialog opening) is not stretched when the\n * animation ends, and the pointer is mapped back through the transform.\n *\n * Decorative: the canvas is `aria-hidden`, the host has no role and nothing in\n * it takes focus. The frame loop runs only while a ring is alive, and never\n * while the host is off screen; an idle ring comes due only while frames run,\n * so a hidden tab does not queue them up.\n *\n * Under `prefers-reduced-motion` one still grid is drawn, in the line colour,\n * with no ripples, no idle rings and no pointer listeners; it is drawn again\n * when an input, the box or the page's colour scheme changes. The preference\n * is read live, and switching it on drops the rings already travelling.\n *\n * Colours are theme tokens by default — the lines `--wr-border-base`, the lit\n * stretches `--wr-color-primary` — through the `--wr-ripple-grid-line-color`\n * and `--wr-ripple-grid-ripple-color` hooks, and resolved before they reach\n * the canvas, which reads neither a `var()` nor a `light-dark()`.\n *\n * Until the canvas has drawn — the seconds before hydration on a prerendered\n * page — the stylesheet paints the same grid, at the same cell, width and\n * colour, and `.wr-ripple-grid--painted` retires it.\n *\n * @example\n * ```html\n * <section style=\"position: relative; min-height: 24rem; overflow: hidden\">\n * <wr-ripple-grid />\n * <h1 style=\"position: relative\">Hero copy above the grid</h1>\n * </section>\n * ```\n *\n * @see https://ngwr.dev/bits/ripple-grid\n */\n@Component({\n selector: 'wr-ripple-grid',\n template: '<canvas #canvas class=\"wr-ripple-grid__canvas\" aria-hidden=\"true\"></canvas>',\n styleUrl: './ripple-grid.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-ripple-grid',\n // Feed the CSS stand-in grid (see `styles/_rules.scss`) so it draws at the\n // real cell, width and colour, and switch it off once the canvas has painted.\n '[style.--wr-ripple-grid-cell-size.px]': 'cell()',\n '[style.--wr-ripple-grid-line-width.px]': 'stroke()',\n '[style.--wr-ripple-grid-input-line-color]': 'standInColor()',\n '[class.wr-ripple-grid--painted]': 'painted()',\n },\n})\nexport class WrRippleGrid {\n /** Distance between grid lines in px, never under 12. @default 40 */\n readonly cellSize = input(40, { transform: numAttr(40) });\n\n /** Line colour, any CSS colour. When unset, `--wr-ripple-grid-line-color` decides. */\n readonly lineColor = input<string | null>(null);\n\n /** Colour a line takes where a ripple passes, any CSS colour. When unset, `--wr-ripple-grid-ripple-color` decides. */\n readonly rippleColor = input<string | null>(null);\n\n /** Line width in px, never under 0.25. @default 1 */\n readonly lineWidth = input(1, { transform: numAttr(1) });\n\n /**\n * How far a fresh ripple from a press bends the lines at its crest, in px;\n * a ripple the moving pointer leaves bends them 40% as far. `0` keeps the\n * lines straight and only lights them, and a negative value bends them\n * inward. A bend steep enough to fold a line back over itself is never\n * drawn: one ring, or several where they overlap, bends a line at most about\n * 0.3 of a cell for cells from 22 to 80px — 11.7px at the default 40 — and\n * rings that overlap share that bound rather than adding past it. @default 10\n */\n readonly strength = input(10, { transform: numAttr(10) });\n\n /** How fast a ripple travels outward, in px per second; `0` keeps it where it started. @default 320 */\n readonly speed = input(320, { transform: numAttr(320) });\n\n /**\n * How fast a ripple fades, per second: its strength is multiplied by\n * `e^-decay` every second, so `1.4` halves it in half a second. `0` keeps it\n * at full strength until it leaves the box. @default 1.4\n */\n readonly decay = input(1.4, { transform: numAttr(1.4) });\n\n /** Whether a moving pointer leaves ripples behind it, and not only a press. @default true */\n readonly rippleOnMove = input(true, { transform: coerceBooleanProperty });\n\n /**\n * Milliseconds between the softer ripples sent out from the centre on their\n * own, never under 500; `0` sends none, and the grid then stays still until\n * the pointer moves it. @default 0\n */\n readonly idleInterval = input(0, { transform: numAttr(0) });\n\n /** The cell the grid is laid out at: `cellSize`, floored. */\n protected readonly cell = computed(() => Math.max(MIN_CELL, this.cellSize()));\n\n /** The line width drawn: `lineWidth`, floored. */\n protected readonly stroke = computed(() => Math.max(MIN_LINE_WIDTH, this.lineWidth()));\n\n /**\n * A line colour the author chose, published so the stand-in draws in it too;\n * `null` while the input is unset. On a variable of its own rather than on the\n * hook, which an author sets from outside: a `null` binding wipes a value of its\n * own name from the host's `style` attribute.\n */\n protected readonly standInColor = computed(() => (this.lineColor() ?? '').trim() || null);\n\n /** The colours the canvas resolves: an input that is unset, empty or blank leaves the hook in charge. */\n private readonly lineExpression = computed(() => (this.lineColor() ?? '').trim() || LINE_TOKEN);\n private readonly rippleExpression = computed(() => (this.rippleColor() ?? '').trim() || RIPPLE_TOKEN);\n\n /** The idle interval in force, in ms; 0 when there is none. */\n private readonly idle = computed(() => {\n const interval = this.idleInterval();\n return interval > 0 ? Math.max(MIN_IDLE_MS, interval) : 0;\n });\n\n /** A ring's half-width, in px: a share of the cell, within bounds. */\n private readonly ringWidth = computed(() => clamp(this.cell() * RING_SHARE, MIN_RING, MAX_RING));\n\n /**\n * Flips once the canvas has drawn, which retires the CSS stand-in grid.\n *\n * A canvas cannot be prerendered, so on an SSR'd page it is blank until the\n * bundle has loaded and hydrated. The stylesheet paints the same lines at the\n * same cell from the prerendered HTML in the meantime, and this hands over.\n */\n protected readonly painted = signal(false);\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 ctx: CanvasRenderingContext2D | null = null;\n /** The host's layout box, in CSS pixels before any transform: what the grid is laid out in. */\n private box = { width: 0, height: 0 };\n /** Bitmap pixels per CSS pixel the canvas is backed at; 0 until the first fit. */\n private ratio = 0;\n private readonly ripples: Ripple[] = [];\n private readonly pending: Pending[] = [];\n /** The live rings as this frame draws them, reused frame to frame. */\n private readonly rings: Ring[] = [];\n private ringCount = 0;\n /** The rings that reach the line being traced, reused line to line. */\n private readonly near: Ring[] = [];\n /** Lit stretches by alpha step, reused frame to frame. */\n private readonly lit: GlowRuns[] = Array.from({ length: GLOW_LEVELS }, glowRuns);\n private anyLit = false;\n /** Where the last trail ripple was left, in viewport coordinates, while the pointer is over the parent. */\n private readonly anchor = { x: 0, y: 0, set: false };\n /** The ring half-width and sample step this frame draws with, read once rather than per sample. */\n private frameRing = MIN_RING;\n private frameStep = MIN_SAMPLE;\n /** One sample, displaced: written by `displace()`, read straight after. */\n private sx = 0;\n private sy = 0;\n private sg = 0;\n private rafId = 0;\n private lastTime = -1;\n /** The frame callback, made once rather than a closure per frame. */\n private readonly frame = (time: number): void => this.tick(time);\n /** Whether the idle timer has fired and the next frame owes the centre a ring. */\n private idleDue = false;\n /** Schedules a frame while motion is allowed; `null` under reduced motion. */\n private wake: (() => void) | null = null;\n /** Arms the next idle ring while motion is allowed; `null` under reduced motion. */\n private armIdle: (() => void) | null = null;\n\n /** Flips once `boot()` has a context and a box — everything after waits for it. */\n private readonly booted = signal(false);\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));\n\n // Everything that changes the picture only changes what the next frame\n // draws. A sleeping loop or a still grid has no next frame, so ask for one.\n // `speed` and `decay` are left out: with no ring alive they change nothing\n // on screen, and with one alive the loop is already running.\n effect(() => {\n this.cell();\n this.stroke();\n this.lineColor();\n this.rippleColor();\n this.strength();\n untracked(() => this.requestDraw());\n });\n\n // A drawn grid keeps the colours it was drawn in, and the loop sleeps when\n // nothing moves, so a scheme switch asks for a frame to re-resolve them.\n watchColorScheme(() => this.requestDraw());\n\n // Motion or a still grid, decided live. Toggling `rippleOnMove` or the idle\n // interval restarts the listeners and the timer; rings already travelling\n // carry on.\n effect(onCleanup => {\n if (!this.booted()) return;\n if (this.platform.prefersReducedMotion()) {\n untracked(() => {\n this.rest();\n this.draw();\n });\n return;\n }\n const trail = this.rippleOnMove();\n const idle = this.idle();\n onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.interact(trail, idle))));\n });\n }\n\n // Setup\n\n private boot(): void {\n const canvas = this.canvasRef().nativeElement;\n this.ctx = canvas.getContext('2d');\n if (!this.ctx) return;\n\n // `clientWidth` and not `getBoundingClientRect()`: the rect is the box after\n // transforms, so a grid booted inside a dialog's scale-in would be laid out\n // for 96% of its box and stretched by CSS once the animation ended. The\n // observer's first entry follows with the exact, sub-pixel size.\n const hostEl = this.host.nativeElement;\n this.fit(hostEl.clientWidth, hostEl.clientHeight);\n\n // The box is the observer's to follow, transforms excluded. A window resize\n // is for the pixel ratio, which browser zoom and a move to another screen\n // change with no box change at all.\n const onResize = (): void => this.resize(this.box.width, this.box.height);\n window.addEventListener('resize', onResize);\n const ro = new ResizeObserver(entries => {\n const rect = entries[entries.length - 1]?.contentRect;\n if (rect) this.resize(rect.width, rect.height);\n });\n ro.observe(hostEl);\n this.destroyRef.onDestroy(() => {\n window.removeEventListener('resize', onResize);\n ro.disconnect();\n });\n this.booted.set(true);\n }\n\n /** Follow the pointer, keep the idle timer and draw on demand; returns what stops all three. */\n private interact(trail: boolean, interval: number): () => void {\n const hostEl = this.host.nativeElement;\n // The parent, not the host: content in front of the canvas receives the\n // pointer, and its events bubble to the parent, never to a sibling. Placed\n // directly in a shadow root it has none, and the shadow host is where those\n // composed events land. Never the host itself, which takes no pointer\n // events and so could never hear one.\n const root = hostEl.getRootNode();\n const target =\n hostEl.parentElement ?? (root instanceof ShadowRoot && root.host instanceof HTMLElement ? root.host : null);\n\n // Off screen the grid asks for no frames, keeps no timer and takes no\n // pointer. Without an observer (jsdom) it counts as visible.\n let visible = typeof IntersectionObserver === 'undefined';\n let idleTimer = 0;\n const wake = (): void => {\n if (visible && this.rafId === 0) this.rafId = requestAnimationFrame(this.frame);\n };\n // Re-armed from the frame that places the ring rather than from the timer\n // itself, so a background tab — no frames, timers still firing — owes the\n // centre one ring at most, not one per interval it was away.\n const armIdle = (): void => {\n if (interval <= 0 || !visible || idleTimer !== 0 || this.idleDue) return;\n idleTimer = window.setTimeout(() => {\n idleTimer = 0;\n this.idleDue = true;\n wake();\n }, interval);\n };\n const halt = (): void => {\n cancelAnimationFrame(this.rafId);\n this.rafId = 0;\n this.lastTime = -1;\n window.clearTimeout(idleTimer);\n idleTimer = 0;\n };\n\n const onDown = (event: PointerEvent): void => {\n if (!visible) return;\n this.emit(event.clientX, event.clientY, 1);\n wake();\n };\n // The first move only marks where the pointer came in: a ring on arrival\n // would ripple the grid every time the pointer crossed into the section.\n const onMove = (event: PointerEvent): void => {\n if (!visible) return;\n const anchor = this.anchor;\n const spacing = Math.max(MIN_MOVE_SPACING, this.cell() * MOVE_SPACING);\n if (anchor.set && Math.hypot(event.clientX - anchor.x, event.clientY - anchor.y) < spacing) return;\n if (anchor.set) {\n this.emit(event.clientX, event.clientY, MOVE_SHARE);\n wake();\n } else {\n anchor.x = event.clientX;\n anchor.y = event.clientY;\n anchor.set = true;\n }\n };\n const onLeave = (): void => {\n this.anchor.set = false;\n };\n target?.addEventListener('pointerdown', onDown, { passive: true });\n if (trail) {\n target?.addEventListener('pointermove', onMove, { passive: true });\n target?.addEventListener('pointerleave', onLeave, { passive: true });\n target?.addEventListener('pointercancel', onLeave, { passive: true });\n }\n\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(entries => {\n visible = entries[entries.length - 1].isIntersecting;\n if (visible) {\n wake();\n armIdle();\n } else {\n halt();\n }\n });\n io?.observe(hostEl);\n\n this.wake = wake;\n this.armIdle = armIdle;\n wake();\n armIdle();\n\n return () => {\n target?.removeEventListener('pointerdown', onDown);\n if (trail) {\n target?.removeEventListener('pointermove', onMove);\n target?.removeEventListener('pointerleave', onLeave);\n target?.removeEventListener('pointercancel', onLeave);\n }\n io?.disconnect();\n halt();\n this.wake = null;\n this.armIdle = null;\n this.idleDue = false;\n this.anchor.set = false;\n };\n }\n\n /** Queue a ring at a viewport point for the next frame, and mark the point for the trail. */\n private emit(clientX: number, clientY: number, share: number): void {\n this.pending.push({ x: clientX, y: clientY, share, client: true });\n this.anchor.x = clientX;\n this.anchor.y = clientY;\n this.anchor.set = true;\n }\n\n /** Draw the grid as it should look now: a frame while motion is allowed, at once under reduced motion. */\n private requestDraw(): void {\n if (!this.booted()) return;\n if (this.wake) this.wake();\n else if (this.platform.prefersReducedMotion()) this.draw();\n }\n\n /** Fit the canvas to a new box or pixel ratio and, when that cleared it, repaint at once. */\n private resize(width: number, height: number): void {\n if (!this.fit(width, height)) return;\n // Observer entries arrive after the frame's own callbacks and before its\n // paint, so a frame asked for here would leave this one blank — with the\n // stand-in already retired, a resize drag would flicker every frame. The\n // rings keep their place in the box; a running loop simply carries on.\n this.draw();\n }\n\n /**\n * Back the canvas for a `width` × `height` box, in CSS pixels, at the screen's\n * pixel ratio. Returns whether anything changed: writing `canvas.width` clears\n * the bitmap even to the value it already has, so an unchanged box is left alone.\n */\n private fit(width: number, height: number): boolean {\n const ratio = this.pixelRatio(width * height);\n if (width === this.box.width && height === this.box.height && ratio === this.ratio) return false;\n\n this.box = { width, height };\n this.ratio = ratio;\n const canvas = this.canvasRef().nativeElement;\n canvas.width = Math.round(width * ratio);\n canvas.height = Math.round(height * ratio);\n // Resizing the bitmap resets the transform, so the scale goes on after it.\n // Every coordinate the draw uses is in CSS pixels.\n this.ctx?.setTransform(ratio, 0, 0, ratio, 0, 0);\n return true;\n }\n\n /**\n * Bitmap pixels per CSS pixel for a box of `area` CSS px². The screen's own\n * ratio when it fits under 2 and the pixel budget, so a bitmap pixel is a\n * device pixel and a snapped line lands on whole device pixels; otherwise the\n * largest whole fraction of it that fits — a half, a third — so each bitmap\n * pixel still covers a whole number of device pixels and every line keeps one\n * weight. A ratio between (1.31 on a 2x screen) is resampled onto the screen\n * by a fraction, which no snapping survives. Never below 1 on a 1x screen or\n * better: a 1.5x screen past the budget takes 1 and accepts the resampling,\n * rather than a bitmap coarser than the CSS box.\n */\n private pixelRatio(area: number): number {\n const device = window.devicePixelRatio || 1;\n const limit = Math.min(2, area > 0 ? Math.sqrt(MAX_CANVAS_PIXELS / area) : 2);\n let ratio = device;\n for (let k = 2; ratio > limit; k++) ratio = device / k;\n return ratio < 1 ? Math.min(1, device) : ratio;\n }\n\n /** No ring alive, none queued, none owed. */\n private rest(): void {\n this.ripples.length = 0;\n this.pending.length = 0;\n this.idleDue = false;\n this.anchor.set = false;\n }\n\n // Frame loop\n\n private tick(time: number): void {\n this.rafId = 0;\n // A ring is a function of its age, so stepping it by the time that really\n // passed is what keeps a 120 Hz screen from running it twice as fast. A\n // timestamp that did not advance counts as one 60 Hz frame, and so does\n // the first frame of a run, which has nothing to measure from.\n const elapsed = time - this.lastTime;\n const step = this.lastTime < 0 || elapsed <= 0 ? FRAME_MS : Math.min(MAX_FRAME_MS, elapsed);\n this.lastTime = time;\n\n this.advance(step);\n this.draw();\n\n // No ring left: sleep until the pointer, the idle timer, a resize or a new\n // input asks for another frame. The frame that retired the last ring has\n // just drawn the grid straight.\n if (this.ripples.length > 0 && this.wake) this.rafId = requestAnimationFrame(this.frame);\n else this.lastTime = -1;\n }\n\n /** Age every ring by `step` ms, place the queued ones, and retire the ones with nothing left to show. */\n private advance(step: number): void {\n for (const ripple of this.ripples) ripple.age += step;\n\n if (this.idleDue) {\n this.idleDue = false;\n this.pending.push({ x: this.box.width / 2, y: this.box.height / 2, share: IDLE_SHARE, client: false });\n this.armIdle?.();\n }\n if (this.pending.length > 0) {\n // The rect is the box as drawn, after any transform on it or an ancestor,\n // and the grid is laid out in the box before them, so a pointer is scaled\n // back into it. Read once a frame, not once an event.\n let needsRect = false;\n for (let i = 0; i < this.pending.length && !needsRect; i++) needsRect = this.pending[i].client;\n const rect = needsRect ? this.host.nativeElement.getBoundingClientRect() : null;\n const sx = rect && rect.width > 0 ? this.box.width / rect.width : 1;\n const sy = rect && rect.height > 0 ? this.box.height / rect.height : 1;\n for (const entry of this.pending) {\n const x = entry.client && rect ? (entry.x - rect.left) * sx : entry.x;\n const y = entry.client && rect ? (entry.y - rect.top) * sy : entry.y;\n this.place(x, y, entry.share);\n }\n this.pending.length = 0;\n }\n\n const speed = Math.max(0, this.speed());\n const strength = Math.abs(this.strength());\n const reach = this.ringWidth() * RING_REACH;\n const { width, height } = this.box;\n for (let i = this.ripples.length - 1; i >= 0; i--) {\n const ripple = this.ripples[i];\n const energy = this.energy(ripple);\n // Past its farthest corner (plus the margin a bend can reach) the ring\n // has nothing left in the box to bend.\n const far = Math.hypot(Math.max(ripple.x, width - ripple.x), Math.max(ripple.y, height - ripple.y));\n const gone = (speed * ripple.age) / 1000 - reach > far + this.margin();\n // A ring at the end of its life has faded to nothing, so this retires it too.\n if (gone || (energy < GLOW_REST && energy * strength < PUSH_REST)) {\n this.ripples.splice(i, 1);\n }\n }\n }\n\n /**\n * Start a ring at a box point. Past `MAX_RIPPLES` it replaces the faintest live\n * ring — the oldest of the faintest on a tie — so a press in the middle of a\n * fast drag is not the one that vanishes; when every live ring is still\n * stronger than this one starts out, this one is the faintest, and it is dropped.\n */\n private place(x: number, y: number, share: number): void {\n const ripples = this.ripples;\n if (ripples.length < MAX_RIPPLES) {\n ripples.push({ x, y, age: 0, share });\n return;\n }\n let faintest = 0;\n let least = Infinity;\n for (let i = 0; i < ripples.length; i++) {\n const energy = this.energy(ripples[i]);\n if (energy < least || (energy === least && ripples[i].age > ripples[faintest].age)) {\n faintest = i;\n least = energy;\n }\n }\n if (share < least) return;\n ripples[faintest] = { x, y, age: 0, share };\n }\n\n /** How strong a ring still is, 0..1: its share, faded by `decay` and by the end of its life. */\n private energy(ripple: Ripple): number {\n const decay = Math.max(0, this.decay());\n const end = clamp((MAX_AGE_MS - ripple.age) / FADE_MS, 0, 1);\n return ripple.share * Math.exp((-decay * ripple.age) / 1000) * end;\n }\n\n /**\n * How far past each edge the lines run, so a bend never pulls a line's end into\n * view: a cell, plus the most the rings at one point can bend it — the overlap\n * budget, or every ring at full strength when that is less.\n */\n private margin(): number {\n return this.cell() + Math.min(Math.abs(this.strength()) * MAX_RIPPLES, PUSH_PER_WIDTH * this.ringWidth());\n }\n\n // Drawing\n\n private draw(): void {\n const ctx = this.ctx;\n if (!ctx) return;\n // Retire the stand-in only once something is really painted. Runs outside\n // Angular, so the `set` schedules the one host-binding update it needs.\n if (!this.painted()) this.painted.set(true);\n\n const canvas = this.canvasRef().nativeElement;\n const { width, height } = this.box;\n ctx.clearRect(0, 0, width, height);\n this.prepareRings();\n for (let i = 0; i < GLOW_LEVELS; i++) {\n this.lit[i].length = 0;\n this.lit[i].runs = 0;\n }\n this.anyLit = false;\n\n // The stroke in whole bitmap pixels, and every line moved so its edges fall\n // on them (see `snap()`). Under one bitmap pixel the width is kept, so a\n // thin line stays faint, and lands centred in a single pixel.\n const ratio = this.ratio || 1;\n const scaled = this.stroke() * ratio;\n const stroke = scaled >= 1 ? Math.round(scaled) : scaled;\n const cell = this.cell();\n const margin = this.margin();\n ctx.globalAlpha = 1;\n ctx.lineWidth = stroke / ratio;\n ctx.lineCap = 'butt';\n ctx.lineJoin = 'round';\n ctx.strokeStyle = resolveCssColor(canvas, this.lineExpression(), LINE_FALLBACK);\n ctx.beginPath();\n // A cell centred on the box, then every `cell` out, far enough past each\n // edge that a bend never brings an undrawn line into view — where the\n // stand-in's centred tiles put them, give or take the half device pixel\n // the snap moves a line by.\n const cx = width / 2;\n const cy = height / 2;\n const columns = Math.ceil((cx + margin) / cell);\n const rows = Math.ceil((cy + margin) / cell);\n for (let i = -columns; i < columns; i++) {\n this.trace(ctx, snap(cx + (i + 0.5) * cell, ratio, stroke), -margin, height + margin, true);\n }\n for (let i = -rows; i < rows; i++) {\n this.trace(ctx, snap(cy + (i + 0.5) * cell, ratio, stroke), -margin, width + margin, false);\n }\n ctx.stroke();\n if (!this.anyLit) return;\n\n // Lit stretches go over their own lines in the ripple colour, at their glow\n // as alpha: the blend of the two colours without parsing either, so an\n // `oklch()` or a `display-p3` colour works as well as an `rgb()`.\n ctx.strokeStyle = resolveCssColor(canvas, this.rippleExpression(), RIPPLE_FALLBACK);\n for (let i = 0; i < GLOW_LEVELS; i++) {\n const level = this.lit[i];\n if (level.runs === 0) continue;\n const points = level.points;\n ctx.globalAlpha = (i + 1) / GLOW_LEVELS;\n ctx.beginPath();\n for (let run = 0; run < level.runs; run++) {\n const start = level.starts[run];\n const end = run + 1 < level.runs ? level.starts[run + 1] : level.length;\n ctx.moveTo(points[start], points[start + 1]);\n for (let p = start + 2; p < end; p += 2) ctx.lineTo(points[p], points[p + 1]);\n }\n ctx.stroke();\n }\n ctx.globalAlpha = 1;\n }\n\n /**\n * Work out where every live ring stands at its current age, for this frame's\n * samples, and hold the rings that overlap to the bend budget together.\n *\n * Each ring's bend is scaled by its share of the budget against every ring\n * whose band meets its own: wherever two rings both reach a point their bands\n * meet, so the rings acting on any point add up to the budget at most (see\n * `PUSH_PER_WIDTH`), while a ring that meets no other keeps its full bend. A\n * pair counts in full once its bands meet and fades out over one half-width of\n * gap before that, so two rings drifting into each other give way smoothly\n * rather than both shrinking in one frame.\n */\n private prepareRings(): void {\n const speed = Math.max(0, this.speed());\n const strength = this.strength();\n const width = this.ringWidth();\n this.frameRing = width;\n this.frameStep = Math.max(MIN_SAMPLE, width / 4);\n const reach = width * RING_REACH;\n const rings = this.rings;\n this.ringCount = 0;\n for (const ripple of this.ripples) {\n const energy = this.energy(ripple);\n const radius = (speed * ripple.age) / 1000;\n const inner = Math.max(0, radius - reach);\n const outer = radius + reach;\n const ring = (rings[this.ringCount] ??= {\n x: 0,\n y: 0,\n radius: 0,\n inner: 0,\n outer: 0,\n inner2: 0,\n outer2: 0,\n raw: 0,\n push: 0,\n glow: 0,\n });\n ring.x = ripple.x;\n ring.y = ripple.y;\n ring.radius = radius;\n ring.inner = inner;\n ring.outer = outer;\n ring.inner2 = inner * inner;\n ring.outer2 = outer * outer;\n ring.raw = strength * energy * Math.min(1, ripple.age / BIRTH_MS);\n ring.glow = energy;\n this.ringCount++;\n }\n\n const budget = PUSH_PER_WIDTH * width;\n for (let i = 0; i < this.ringCount; i++) {\n const ring = rings[i];\n let shared = 0;\n for (let j = 0; j < this.ringCount; j++) {\n const other = rings[j];\n const between = Math.hypot(ring.x - other.x, ring.y - other.y);\n const gap = Math.max(\n between - ring.outer - other.outer,\n ring.inner - other.outer - between,\n other.inner - ring.outer - between\n );\n if (gap < width) shared += Math.abs(other.raw) * (gap <= 0 ? 1 : 1 - gap / width);\n }\n ring.push = shared > budget ? (ring.raw * budget) / shared : ring.raw;\n }\n }\n\n /**\n * Add one grid line to the path — vertical at `x = fixed` or horizontal at\n * `y = fixed`, from `from` to `to` along it — and file its lit stretches by\n * alpha step. A line no ring reaches is two points; one a ring reaches is\n * sampled only across the stretch the rings can bend, and straight either\n * side of it. Where that stretch runs to an end of the line, the end is the\n * displaced sample itself: drawing the undisplaced end before it would step\n * the line back along itself.\n */\n private trace(ctx: CanvasRenderingContext2D, fixed: number, from: number, to: number, vertical: boolean): void {\n const near = this.near;\n let count = 0;\n // The stretch of the line any ring's band can reach.\n let start = to;\n let end = from;\n for (let i = 0; i < this.ringCount; i++) {\n const ring = this.rings[i];\n const across = Math.abs(fixed - (vertical ? ring.x : ring.y));\n if (across >= ring.outer) continue;\n const along = vertical ? ring.y : ring.x;\n // Every point of the line already inside the band's inner edge: the ring\n // has passed it.\n if (ring.inner >= Math.hypot(across, Math.max(along - from, to - along))) continue;\n const half = Math.sqrt(ring.outer2 - across * across);\n start = Math.min(start, along - half);\n end = Math.max(end, along + half);\n near[count++] = ring;\n }\n\n if (count === 0 || start >= end) {\n if (vertical) {\n ctx.moveTo(fixed, from);\n ctx.lineTo(fixed, to);\n } else {\n ctx.moveTo(from, fixed);\n ctx.lineTo(to, fixed);\n }\n return;\n }\n\n // Samples on a lattice fixed to the line rather than to the band, so a\n // travelling ring does not slide the points it bends through.\n const step = this.frameStep;\n const first = Math.max(from, from + Math.floor((start - from) / step) * step);\n const last = Math.min(to, from + Math.ceil((end - from) / step) * step);\n if (first > from) {\n if (vertical) ctx.moveTo(fixed, from);\n else ctx.moveTo(from, fixed);\n }\n\n // The alpha step of the lit run being extended, 0 while none is open.\n let runLevel = 0;\n let px = 0;\n let py = 0;\n let pg = 0;\n for (let t = first, s = 0; ; s++, t = Math.min(last, first + s * step)) {\n if (vertical) this.displace(fixed, t, count);\n else this.displace(t, fixed, count);\n const x = this.sx;\n const y = this.sy;\n const g = this.sg;\n if (s === 0 && first <= from) ctx.moveTo(x, y);\n else ctx.lineTo(x, y);\n if (s > 0) {\n const level = Math.min(GLOW_LEVELS, Math.round(((pg + g) / 2) * GLOW_LEVELS));\n if (level === 0) {\n runLevel = 0;\n } else if (runLevel === level) {\n addPoint(this.lit[level - 1], x, y);\n } else {\n startRun(this.lit[level - 1], px, py, x, y);\n runLevel = level;\n this.anyLit = true;\n }\n }\n px = x;\n py = y;\n pg = g;\n if (t >= last) break;\n }\n if (last < to) {\n if (vertical) ctx.lineTo(fixed, to);\n else ctx.lineTo(to, fixed);\n }\n }\n\n /**\n * Where the grid point `(x, y)` is drawn and how lit it is, with the first\n * `count` rings in `near` acting on it; written to `sx`, `sy` and `sg`.\n *\n * Each ring pushes the point along the line from its origin. The push is a\n * bell around the crest, shaped by a cosine so the crest bends outward and a\n * shallow trough either side bends back in — what reads as a ripple rather\n * than a bulge — and eased in over the first half-width, so the lines through\n * the origin itself do not tear. The glow is the bell alone. Overlapping rings\n * add up; `prepareRings()` has already held them to a budget that cannot fold\n * the grid, so nothing is clamped here.\n */\n private displace(x: number, y: number, count: number): void {\n const width = this.frameRing;\n let dx = 0;\n let dy = 0;\n let glow = 0;\n for (let i = 0; i < count; i++) {\n const ring = this.near[i];\n const ox = x - ring.x;\n const oy = y - ring.y;\n const d2 = ox * ox + oy * oy;\n if (d2 <= ring.inner2 || d2 >= ring.outer2) continue;\n const d = Math.sqrt(d2);\n const u = (d - ring.radius) / width;\n const bell = Math.exp(-u * u);\n const ease = d < width ? d / width : 1;\n const amount = (ring.push * bell * Math.cos(2 * u) * ease) / d;\n dx += ox * amount;\n dy += oy * amount;\n glow += ring.glow * bell * ease;\n }\n this.sx = x + dx;\n this.sy = y + dy;\n this.sg = Math.min(1, glow);\n }\n}\n"],"mappings":"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"}
@@ -228,7 +228,7 @@ var WrRotatingText = class WrRotatingText {
228
228
  }
229
229
  static ɵfac = i0.ɵɵngDeclareFactory({
230
230
  minVersion: "12.0.0",
231
- version: "22.2.3",
231
+ version: "22.2.4",
232
232
  ngImport: i0,
233
233
  type: WrRotatingText,
234
234
  deps: [],
@@ -236,7 +236,7 @@ var WrRotatingText = class WrRotatingText {
236
236
  });
237
237
  static ɵcmp = i0.ɵɵngDeclareComponent({
238
238
  minVersion: "17.0.0",
239
- version: "22.2.3",
239
+ version: "22.2.4",
240
240
  type: WrRotatingText,
241
241
  isStandalone: true,
242
242
  selector: "wr-rotating-text",
@@ -315,7 +315,7 @@ var WrRotatingText = class WrRotatingText {
315
315
  };
316
316
  i0.ɵɵngDeclareClassMetadata({
317
317
  minVersion: "12.0.0",
318
- version: "22.2.3",
318
+ version: "22.2.4",
319
319
  ngImport: i0,
320
320
  type: WrRotatingText,
321
321
  decorators: [{
@@ -62,7 +62,7 @@ var WrShinyText = class WrShinyText {
62
62
  }
63
63
  static ɵfac = i0.ɵɵngDeclareFactory({
64
64
  minVersion: "12.0.0",
65
- version: "22.2.3",
65
+ version: "22.2.4",
66
66
  ngImport: i0,
67
67
  type: WrShinyText,
68
68
  deps: [],
@@ -70,7 +70,7 @@ var WrShinyText = class WrShinyText {
70
70
  });
71
71
  static ɵcmp = i0.ɵɵngDeclareComponent({
72
72
  minVersion: "17.1.0",
73
- version: "22.2.3",
73
+ version: "22.2.4",
74
74
  type: WrShinyText,
75
75
  isStandalone: true,
76
76
  selector: "wr-shiny-text",
@@ -167,7 +167,7 @@ var WrShinyText = class WrShinyText {
167
167
  };
168
168
  i0.ɵɵngDeclareClassMetadata({
169
169
  minVersion: "12.0.0",
170
- version: "22.2.3",
170
+ version: "22.2.4",
171
171
  ngImport: i0,
172
172
  type: WrShinyText,
173
173
  decorators: [{
@@ -281,7 +281,7 @@ i0.ɵɵngDeclareClassMetadata({
281
281
  var WrShimmer = class WrShimmer {
282
282
  static ɵfac = i0.ɵɵngDeclareFactory({
283
283
  minVersion: "12.0.0",
284
- version: "22.2.3",
284
+ version: "22.2.4",
285
285
  ngImport: i0,
286
286
  type: WrShimmer,
287
287
  deps: [],
@@ -289,7 +289,7 @@ var WrShimmer = class WrShimmer {
289
289
  });
290
290
  static ɵdir = i0.ɵɵngDeclareDirective({
291
291
  minVersion: "14.0.0",
292
- version: "22.2.3",
292
+ version: "22.2.4",
293
293
  type: WrShimmer,
294
294
  isStandalone: true,
295
295
  selector: "[wrShimmer]",
@@ -299,7 +299,7 @@ var WrShimmer = class WrShimmer {
299
299
  };
300
300
  i0.ɵɵngDeclareClassMetadata({
301
301
  minVersion: "12.0.0",
302
- version: "22.2.3",
302
+ version: "22.2.4",
303
303
  ngImport: i0,
304
304
  type: WrShimmer,
305
305
  decorators: [{
@@ -998,7 +998,7 @@ var WrSplashCursor = class WrSplashCursor {
998
998
  }
999
999
  static ɵfac = i0.ɵɵngDeclareFactory({
1000
1000
  minVersion: "12.0.0",
1001
- version: "22.2.3",
1001
+ version: "22.2.4",
1002
1002
  ngImport: i0,
1003
1003
  type: WrSplashCursor,
1004
1004
  deps: [],
@@ -1006,7 +1006,7 @@ var WrSplashCursor = class WrSplashCursor {
1006
1006
  });
1007
1007
  static ɵcmp = i0.ɵɵngDeclareComponent({
1008
1008
  minVersion: "17.2.0",
1009
- version: "22.2.3",
1009
+ version: "22.2.4",
1010
1010
  type: WrSplashCursor,
1011
1011
  isStandalone: true,
1012
1012
  selector: "wr-splash-cursor",
@@ -1130,7 +1130,7 @@ var WrSplashCursor = class WrSplashCursor {
1130
1130
  };
1131
1131
  i0.ɵɵngDeclareClassMetadata({
1132
1132
  minVersion: "12.0.0",
1133
- version: "22.2.3",
1133
+ version: "22.2.4",
1134
1134
  ngImport: i0,
1135
1135
  type: WrSplashCursor,
1136
1136
  decorators: [{
@@ -170,7 +170,7 @@ var WrSplitText = class WrSplitText {
170
170
  }
171
171
  static ɵfac = i0.ɵɵngDeclareFactory({
172
172
  minVersion: "12.0.0",
173
- version: "22.2.3",
173
+ version: "22.2.4",
174
174
  ngImport: i0,
175
175
  type: WrSplitText,
176
176
  deps: [],
@@ -178,7 +178,7 @@ var WrSplitText = class WrSplitText {
178
178
  });
179
179
  static ɵcmp = i0.ɵɵngDeclareComponent({
180
180
  minVersion: "17.0.0",
181
- version: "22.2.3",
181
+ version: "22.2.4",
182
182
  type: WrSplitText,
183
183
  isStandalone: true,
184
184
  selector: "wr-split-text",
@@ -268,7 +268,7 @@ var WrSplitText = class WrSplitText {
268
268
  };
269
269
  i0.ɵɵngDeclareClassMetadata({
270
270
  minVersion: "12.0.0",
271
- version: "22.2.3",
271
+ version: "22.2.4",
272
272
  ngImport: i0,
273
273
  type: WrSplitText,
274
274
  decorators: [{
@@ -35,7 +35,7 @@ var WrSpotlightCardHarness = class WrSpotlightCardHarness extends ComponentHarne
35
35
  return Number.parseFloat(match[1]);
36
36
  }
37
37
  async getSpotlightColor() {
38
- return (await this.vars()).get("--wr-spotlight-color") ?? null;
38
+ return (await this.vars()).get("--wr-spotlight-input-color") ?? null;
39
39
  }
40
40
  async getSpotlightPosition() {
41
41
  const vars = await this.vars();
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-bits-spotlight-card-testing.mjs","names":[],"sources":["../../../projects/lib/bits/spotlight-card/testing/wr-spotlight-harness-vars.ts","../../../projects/lib/bits/spotlight-card/testing/wr-spotlight-card-harness.ts","../../../projects/lib/bits/spotlight-card/testing/wr-spotlight-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\n/**\n * Split an inline `style` attribute into its declarations.\n *\n * Read from the attribute rather than through `getCssValue()`, and the difference is not\n * cosmetic. `getCssValue()` is `getComputedStyle()`, which in a browser resolves the\n * stylesheet's own `--wr-spotlight-x: 50%` / `--wr-spotlight-color: light-dark(…)` defaults —\n * so \"the component has written nothing here yet, and the theme decides\" would come back\n * as a concrete value and the assertion would invert. jsdom loads no stylesheet, so the\n * computed read happens to agree today and would start lying the moment these harnesses\n * ran anywhere else. The attribute holds what the component or the directive itself\n * wrote, and nothing else.\n *\n * Shared by both harnesses in this entry point because both read the same three\n * variables off their host — in different units. See each class for which.\n */\nexport function wrSpotlightInlineVars(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 * @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 { WrSpotlightCardHarnessFilters } from './types';\nimport { wrSpotlightInlineVars } from './wr-spotlight-harness-vars';\n\n/** `120px` — the card writes its pointer coordinates as a pixel length, always. */\nconst PIXEL_LENGTH = /^(-?[\\d.]+)px$/;\n\n/** `80%` — the radius, whose `%` suffix is the half that makes the gradient legal. */\nconst PERCENTAGE = /^(-?[\\d.]+)%$/;\n\n/**\n * Test harness for `<wr-spotlight-card>` — a card with a soft radial highlight that\n * follows the cursor.\n *\n * **The component's entire output is three custom properties on its own host, and this\n * harness reads them off the inline declaration.** There is no DOM for the highlight at\n * all: it is a `::before` pseudo-element, painted by the stylesheet from those three\n * values. So the paint is out of reach here and always will be — what is in reach is the\n * arithmetic and the exact spelling that feeds it, which is where this component breaks.\n *\n * **This harness reads PIXELS. {@link WrSpotlightHarness}, the `[wrSpotlight]` directive\n * shipped from the same entry point, reads PERCENTAGES.** Both write\n * `--wr-spotlight-x` / `--wr-spotlight-y`, in different units, off different events —\n * the card listens for `mousemove`, the directive for `pointermove`. That is deliberate\n * and documented, and it is also the mistake waiting to happen: putting `wrSpotlight` on\n * a `<wr-spotlight-card>` gives you two handlers overwriting one variable in units that\n * do not agree. Reach for the harness that matches the thing under test.\n *\n * **{@link movePointerTo} needs the card to have a box, and in a unit test it has none.**\n * The component subtracts its own `getBoundingClientRect()` from the client coordinate,\n * and jsdom answers that with a 0×0 rect at the origin — so the subtraction is a no-op\n * and the coordinate comes back unchanged. No throw here, unlike the directive's version:\n * the answer is degenerate rather than wrong, and it is legitimately what a real card\n * with no box would report. But a spec that asserts `x === clientX` against an unstubbed\n * rect has proved nothing about the box-relative conversion, which is the only thing\n * {@link getSpotlightPosition} is for. Give the host a box first:\n *\n * ```ts\n * const card = fixture.nativeElement.querySelector('wr-spotlight-card');\n * card.getBoundingClientRect = () => ({ left: 40, top: 20, width: 200, height: 100 }) as DOMRect;\n * ```\n *\n * Several plausible methods are deliberately absent. There is no `isSpotlightVisible()`\n * or `getSpotlightOpacity()`: the highlight sits at `opacity: 0` and is lifted to `0.6`\n * only under `:hover` / `:focus-within`, both of which are stylesheet selectors on a\n * pseudo-element — jsdom loads no stylesheet, matches no `:hover`, and cannot read a\n * pseudo-element's style at any time, so any answer would be invented. No `getGradient()`\n * for the same reason. No `hover()` / `mouseAway()` wrappers, which would be worse than\n * missing: the CDK's versions dispatch `mouseenter` / `mouseover` / `mouseleave`, none of\n * which this component listens to and none of which changes a `:hover` match in a test —\n * a spec calling them would look like it exercised the reveal and would have exercised\n * nothing. No `getSpotlightRect()` or `getCardSize()`, which are layout. And no\n * `respectsReducedMotion()`: this is the one animation component in the library that\n * deliberately does not implement the preference — a gradient tracks the cursor and no\n * content moves — so the method would answer identically for a component that honoured it\n * and one that ignored it.\n *\n * @example\n * ```ts\n * const card = await loader.getHarness(WrSpotlightCardHarness.with({ text: 'Hover me' }));\n *\n * expect(await card.getRadiusPercent()).toBe(80);\n * expect(await card.getSpotlightColor()).toBeNull(); // the theme decides\n * expect(await card.getSpotlightPosition()).toBeNull(); // nothing has pointed at it\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrSpotlightCardHarness extends ComponentHarness {\n static hostSelector = 'wr-spotlight-card';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrSpotlightCardHarnessFilters = {}): HarnessPredicate<WrSpotlightCardHarness> {\n return new HarnessPredicate(WrSpotlightCardHarness, options).addOption('text', options.text, (harness, text) =>\n HarnessPredicate.stringMatches(harness.getContentText(), text)\n );\n }\n\n /**\n * Where the gradient fades out, as a percentage of its own radius.\n *\n * Returns the number, but the assertion it really makes is about the `%` that was\n * stripped to get it — this method refuses a bare `80`. The stylesheet interpolates the\n * variable straight into `transparent var(--wr-spotlight-radius, 80%)`, where a unitless\n * value is not a length, so the whole `radial-gradient()` declaration is dropped and the\n * highlight disappears with nothing in the console to say why. Reading this as a plain\n * number would sail past exactly that.\n *\n * Always present, whatever the input says: a `[radius]` that is not a number coerces\n * back to the default 80 rather than writing nonsense into the sheet.\n */\n async getRadiusPercent(): Promise<number> {\n const raw = (await this.vars()).get('--wr-spotlight-radius') ?? null;\n const match = raw === null ? null : PERCENTAGE.exec(raw);\n if (!match) {\n throw new Error(\n `WrSpotlightCardHarness.getRadiusPercent(): \\`--wr-spotlight-radius\\` reads \"${raw ?? '(absent)'}\" on the ` +\n 'host, and the stylesheet needs a percentage there — it drops the whole gradient for a bare number. The ' +\n 'card writes this one on every render whatever the input says, so a missing or unsuffixed value means ' +\n 'the binding itself stopped reaching the element.'\n );\n }\n return Number.parseFloat(match[1]);\n }\n\n /**\n * The highlight colour the author asked for, or `null` when they left it to the theme.\n *\n * `null` IS the contract for an unset `[spotlightColor]`, not a gap in it. The\n * stylesheet's own default is a `light-dark()` over `--wr-color-on-surface`, and that\n * token flips per colour scheme — which is what makes one component read as a dark glow\n * on a light surface and a light beam on a dark one. A card that started writing a\n * concrete colour here would look right in whichever scheme it was authored against and\n * wrong in the other, and this is the only method that can tell.\n */\n async getSpotlightColor(): Promise<string | null> {\n return (await this.vars()).get('--wr-spotlight-color') ?? null;\n }\n\n /**\n * Where the highlight last sat, in pixels from the card's own top-left corner. `null`\n * until something has pointed at the card.\n *\n * Pixels, not percentages — see this class's docs for why the distinction matters\n * inside this one entry point.\n *\n * `null` is a state of its own rather than a stand-in for `{ x: 0, y: 0 }`: before the\n * first move the stylesheet's `50% / 50%` default applies, and a pointer at the card's\n * own origin legitimately reports zero, so folding the two together would make an\n * untouched card indistinguishable from one being pointed at in its corner.\n *\n * Note there is no reset when the cursor leaves, and that is not an oversight the\n * harness papers over: the stylesheet fades the whole overlay to `opacity: 0` on leave,\n * so nothing is parked visibly — but the coordinate stays written for the life of the\n * element. `null` therefore means \"no pointer has EVER arrived\", not \"none is here now\".\n */\n async getSpotlightPosition(): Promise<{ x: number; y: number } | null> {\n const vars = await this.vars();\n const x = vars.get('--wr-spotlight-x');\n const y = vars.get('--wr-spotlight-y');\n if (x === undefined || y === undefined) return null;\n\n return { x: this.parsePixels(x, '--wr-spotlight-x'), y: this.parsePixels(y, '--wr-spotlight-y') };\n }\n\n /**\n * Point at the card, in client coordinates.\n *\n * A `mousemove`, not a `pointermove` — the component listens for the former, and the\n * directive in the same entry point for the latter, so a pointer event dispatched here\n * is silently ignored. It goes to the host itself, so it needs no hit test and cannot\n * miss in a test that has no layout; the coordinates only ever reach the component's\n * own subtraction.\n *\n * Stub the host's `getBoundingClientRect` before calling this, or the subtraction is\n * invisible — see this class's docs.\n */\n async movePointerTo(clientX: number, clientY: number): Promise<void> {\n await (await this.host()).dispatchEvent('mousemove', { clientX, clientY });\n }\n\n /**\n * The projected content's text, trimmed.\n *\n * Read from the host, which is the whole assertion: the template is a bare\n * `<ng-content />` with no wrapper element, so content lands as a direct child and an\n * interactive one keeps its own name and role. The component adds no ARIA and no node\n * of its own — there is nothing else here for a harness to ask about.\n */\n async getContentText(): Promise<string> {\n return (await this.host()).text();\n }\n\n /** The host's inline declarations — the component's whole written output. */\n private async vars(): Promise<Map<string, string>> {\n return wrSpotlightInlineVars((await (await this.host()).getAttribute('style')) ?? '');\n }\n\n /** One written coordinate, as a number, insisting on the `px` the card promises. */\n private parsePixels(raw: string, name: string): number {\n const match = PIXEL_LENGTH.exec(raw);\n if (!match) {\n throw new Error(\n `WrSpotlightCardHarness.getSpotlightPosition(): \\`${name}\\` reads \"${raw}\", which is not the pixel length ` +\n 'the card writes. A percentage here is the tell that `[wrSpotlight]` is on the same element — the two ' +\n 'exports of this entry point share these variables and disagree about the unit.'\n );\n }\n return Number.parseFloat(match[1]);\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrSpotlightHarnessFilters } from './types';\nimport { wrSpotlightInlineVars } from './wr-spotlight-harness-vars';\n\n/**\n * Test harness for `[wrSpotlight]` — the directive that makes a cursor-following\n * gradient possible on an element the consumer owns.\n *\n * **This harness reads PERCENTAGES. {@link WrSpotlightCardHarness}, shipped from the same\n * entry point, reads PIXELS.** Both write `--wr-spotlight-x` / `--wr-spotlight-y`; the\n * directive writes them as a share of its own box off `pointermove`, the card as a pixel\n * offset off `mousemove`. That is why {@link getSpotlightPosition} hands back raw strings\n * and not numbers — collapsing `'37.5%'` to `37.5` would hide the one thing that decides\n * whether the gradient lands where the cursor is, and would make the two halves of this\n * entry point look interchangeable when they are not.\n *\n * **The directive ships no styles and adds no node.** There is no class to assert, no\n * wrapper, no ARIA: whatever the element announced before the directive applies is\n * exactly what it announces after, and the gradient is entirely the consumer's own CSS.\n * So there is no `isSpotlightVisible()` — there is nothing here to be visible — and no\n * `getResetX()` / `getResetY()`, which would echo the inputs back rather than report the\n * coordinate the directive actually WROTE. The echo would pass while the written value\n * was wrong, which is not hypothetical (see {@link getSpotlightPosition}). And no\n * `respectsReducedMotion()`: neither export in this entry point implements the\n * preference — it is the library's one documented exemption, since a gradient tracks the\n * cursor and no content moves — so the method would answer the same for a directive that\n * honoured it and one that ignored it.\n *\n * @example\n * ```ts\n * const spotlight = await loader.getHarness(WrSpotlightHarness.with({ hostClass: 'card' }));\n *\n * expect(await spotlight.getSpotlightPosition()).toEqual({ x: '50%', y: '50%' });\n * await spotlight.movePointerTo(150, 25); // stub the host's rect first\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrSpotlightHarness extends ComponentHarness {\n static hostSelector = '[wrSpotlight]';\n\n /**\n * Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`.\n *\n * Both options describe the HOST rather than the directive, because the directive\n * leaves no mark of its own to match on.\n */\n static with(options: WrSpotlightHarnessFilters = {}): HarnessPredicate<WrSpotlightHarness> {\n return new HarnessPredicate(WrSpotlightHarness, options)\n .addOption('text', options.text, async (harness, text) =>\n HarnessPredicate.stringMatches((await harness.host()).text(), text)\n )\n .addOption('hostClass', options.hostClass, async (harness, hostClass) =>\n (await harness.host()).hasClass(hostClass)\n );\n }\n\n /**\n * The coordinates the directive has written, as the raw strings it wrote them.\n *\n * Strings on purpose — see this class's docs. The values are percentages of the host's\n * own box while a pointer is over it, and whatever `[resetX]` / `[resetY]` say\n * otherwise, so a `px` appearing here means something else is writing these variables.\n *\n * **Present from the first render, before any pointer arrives.** The directive writes\n * the reset coordinates as soon as its inputs are bound, so the first read is whatever\n * `[resetX]` / `[resetY]` say — `50%` / `50%` by default — on a fresh host; do not\n * mistake that for a pointer having been tracked. It used to seed them in its\n * constructor, before signal inputs are bound, so a custom rest point appeared only\n * after the first {@link leave}. Reading the written coordinate rather than the input\n * is what made that visible at all.\n *\n * Throws once the directive has been destroyed, rather than answering with the last\n * value it happened to leave behind — {@link hasTrackedPointer} is the question to ask\n * when the host may be gone.\n */\n async getSpotlightPosition(): Promise<{ x: string; y: string }> {\n const vars = await this.vars();\n const x = vars.get('--wr-spotlight-x');\n const y = vars.get('--wr-spotlight-y');\n if (x === undefined || y === undefined) {\n throw new Error(\n 'WrSpotlightHarness.getSpotlightPosition(): the host carries no `--wr-spotlight-x` / `--wr-spotlight-y`. ' +\n 'The directive writes both on its first render and removes both on destroy, so this is a torn-down ' +\n 'directive rather than one that has not started — ask hasTrackedPointer() when the host may be gone.'\n );\n }\n return { x, y };\n }\n\n /**\n * Point at the host, in client coordinates.\n *\n * **Refuses on a host with no box, which in a unit test is every host.** The directive\n * divides the cursor offset by the element's own width and height, so a 0×0 rect writes\n * literally `Infinity%` or — at the origin — `NaN%` into the style. jsdom accepts either\n * string without complaint, so an unguarded harness would hand back something that looks\n * like a coordinate and describes nothing. Give the host a box first:\n *\n * ```ts\n * const el = fixture.nativeElement.querySelector('[wrSpotlight]');\n * el.getBoundingClientRect = () => ({ left: 0, top: 0, width: 200, height: 100 }) as DOMRect;\n * ```\n *\n * A `pointermove`, not a `mousemove` — the directive listens for the former, and the\n * component in the same entry point for the latter. It is dispatched on the host itself,\n * so it needs no hit test and cannot miss.\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 `WrSpotlightHarness.movePointerTo(${clientX}, ${clientY}): this host measures ${width}×${height}, so a ` +\n 'pointer position over it means nothing — the directive divides the offset by the box, and the result ' +\n 'goes into the style as `Infinity%` or `NaN%`. Stub `getBoundingClientRect` on the host first.'\n );\n }\n await host.dispatchEvent('pointermove', { clientX, clientY, pointerId: 1, isPrimary: true });\n }\n\n /**\n * Take the pointer away.\n *\n * Dispatches `pointerleave` — the directive's reset path, where `[resetX]` / `[resetY]`\n * are written back over the pointer's position. Needs no box: the handler writes the reset\n * strings straight through without measuring anything, which is why this one does not\n * refuse the way {@link movePointerTo} does.\n *\n * Not the CDK's `mouseAway()`, though that dispatches `pointerleave` too. This says what\n * it does, and it does not also fire the `mouseleave` / `mouseout` that the CDK helper\n * sends alongside it — events this directive ignores, and that would make a spec look\n * like it had covered more than it did.\n */\n async leave(): Promise<void> {\n await (await this.host()).dispatchEvent('pointerleave');\n }\n\n /**\n * Whether the directive still owns the host's spotlight variables.\n *\n * **This is \"the directive is alive\", not \"a pointer has moved over it\".** It is `true`\n * from the directive's first render, which writes the reset coordinates — there is no\n * earlier state to catch. What it distinguishes is a live\n * directive from a torn-down one: destroy removes both declarations from the element,\n * and nothing else in the repo asserts that cleanup. A directive that stopped removing\n * them would leave a stale gradient position on an element the consumer went on using,\n * and the only trace would be these two declarations outliving it.\n */\n async hasTrackedPointer(): Promise<boolean> {\n const vars = await this.vars();\n return vars.has('--wr-spotlight-x') && vars.has('--wr-spotlight-y');\n }\n\n /** The host's inline declarations — the directive's whole written output. */\n private async vars(): Promise<Map<string, string>> {\n return wrSpotlightInlineVars((await (await this.host()).getAttribute('style')) ?? '');\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-bits-spotlight-card-testing.mjs","names":[],"sources":["../../../projects/lib/bits/spotlight-card/testing/wr-spotlight-harness-vars.ts","../../../projects/lib/bits/spotlight-card/testing/wr-spotlight-card-harness.ts","../../../projects/lib/bits/spotlight-card/testing/wr-spotlight-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\n/**\n * Split an inline `style` attribute into its declarations.\n *\n * Read from the attribute rather than through `getCssValue()`, and the difference is not\n * cosmetic. `getCssValue()` is `getComputedStyle()`, which in a browser resolves the\n * stylesheet's own `--wr-spotlight-x: 50%` default — so \"the component has written nothing\n * here yet\" would come back as a concrete value and the assertion would invert. jsdom loads no stylesheet, so the\n * computed read happens to agree today and would start lying the moment these harnesses\n * ran anywhere else. The attribute holds what the component or the directive itself\n * wrote, and nothing else.\n *\n * Shared by both harnesses in this entry point because both read the same three\n * variables off their host — in different units. See each class for which.\n */\nexport function wrSpotlightInlineVars(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 * @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 { WrSpotlightCardHarnessFilters } from './types';\nimport { wrSpotlightInlineVars } from './wr-spotlight-harness-vars';\n\n/** `120px` — the card writes its pointer coordinates as a pixel length, always. */\nconst PIXEL_LENGTH = /^(-?[\\d.]+)px$/;\n\n/** `80%` — the radius, whose `%` suffix is the half that makes the gradient legal. */\nconst PERCENTAGE = /^(-?[\\d.]+)%$/;\n\n/**\n * Test harness for `<wr-spotlight-card>` — a card with a soft radial highlight that\n * follows the cursor.\n *\n * **The component's entire output is three custom properties on its own host, and this\n * harness reads them off the inline declaration.** There is no DOM for the highlight at\n * all: it is a `::before` pseudo-element, painted by the stylesheet from those three\n * values. So the paint is out of reach here and always will be — what is in reach is the\n * arithmetic and the exact spelling that feeds it, which is where this component breaks.\n *\n * **This harness reads PIXELS. {@link WrSpotlightHarness}, the `[wrSpotlight]` directive\n * shipped from the same entry point, reads PERCENTAGES.** Both write\n * `--wr-spotlight-x` / `--wr-spotlight-y`, in different units, off different events —\n * the card listens for `mousemove`, the directive for `pointermove`. That is deliberate\n * and documented, and it is also the mistake waiting to happen: putting `wrSpotlight` on\n * a `<wr-spotlight-card>` gives you two handlers overwriting one variable in units that\n * do not agree. Reach for the harness that matches the thing under test.\n *\n * **{@link movePointerTo} needs the card to have a box, and in a unit test it has none.**\n * The component subtracts its own `getBoundingClientRect()` from the client coordinate,\n * and jsdom answers that with a 0×0 rect at the origin — so the subtraction is a no-op\n * and the coordinate comes back unchanged. No throw here, unlike the directive's version:\n * the answer is degenerate rather than wrong, and it is legitimately what a real card\n * with no box would report. But a spec that asserts `x === clientX` against an unstubbed\n * rect has proved nothing about the box-relative conversion, which is the only thing\n * {@link getSpotlightPosition} is for. Give the host a box first:\n *\n * ```ts\n * const card = fixture.nativeElement.querySelector('wr-spotlight-card');\n * card.getBoundingClientRect = () => ({ left: 40, top: 20, width: 200, height: 100 }) as DOMRect;\n * ```\n *\n * Several plausible methods are deliberately absent. There is no `isSpotlightVisible()`\n * or `getSpotlightOpacity()`: the highlight sits at `opacity: 0` and is lifted to `0.6`\n * only under `:hover` / `:focus-within`, both of which are stylesheet selectors on a\n * pseudo-element — jsdom loads no stylesheet, matches no `:hover`, and cannot read a\n * pseudo-element's style at any time, so any answer would be invented. No `getGradient()`\n * for the same reason. No `hover()` / `mouseAway()` wrappers, which would be worse than\n * missing: the CDK's versions dispatch `mouseenter` / `mouseover` / `mouseleave`, none of\n * which this component listens to and none of which changes a `:hover` match in a test —\n * a spec calling them would look like it exercised the reveal and would have exercised\n * nothing. No `getSpotlightRect()` or `getCardSize()`, which are layout. And no\n * `respectsReducedMotion()`: this is the one animation component in the library that\n * deliberately does not implement the preference — a gradient tracks the cursor and no\n * content moves — so the method would answer identically for a component that honoured it\n * and one that ignored it.\n *\n * @example\n * ```ts\n * const card = await loader.getHarness(WrSpotlightCardHarness.with({ text: 'Hover me' }));\n *\n * expect(await card.getRadiusPercent()).toBe(80);\n * expect(await card.getSpotlightColor()).toBeNull(); // the theme decides\n * expect(await card.getSpotlightPosition()).toBeNull(); // nothing has pointed at it\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrSpotlightCardHarness extends ComponentHarness {\n static hostSelector = 'wr-spotlight-card';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrSpotlightCardHarnessFilters = {}): HarnessPredicate<WrSpotlightCardHarness> {\n return new HarnessPredicate(WrSpotlightCardHarness, options).addOption('text', options.text, (harness, text) =>\n HarnessPredicate.stringMatches(harness.getContentText(), text)\n );\n }\n\n /**\n * Where the gradient fades out, as a percentage of its own radius.\n *\n * Returns the number, but the assertion it really makes is about the `%` that was\n * stripped to get it — this method refuses a bare `80`. The stylesheet interpolates the\n * variable straight into `transparent var(--wr-spotlight-radius, 80%)`, where a unitless\n * value is not a length, so the whole `radial-gradient()` declaration is dropped and the\n * highlight disappears with nothing in the console to say why. Reading this as a plain\n * number would sail past exactly that.\n *\n * Always present, whatever the input says: a `[radius]` that is not a number coerces\n * back to the default 80 rather than writing nonsense into the sheet.\n */\n async getRadiusPercent(): Promise<number> {\n const raw = (await this.vars()).get('--wr-spotlight-radius') ?? null;\n const match = raw === null ? null : PERCENTAGE.exec(raw);\n if (!match) {\n throw new Error(\n `WrSpotlightCardHarness.getRadiusPercent(): \\`--wr-spotlight-radius\\` reads \"${raw ?? '(absent)'}\" on the ` +\n 'host, and the stylesheet needs a percentage there — it drops the whole gradient for a bare number. The ' +\n 'card writes this one on every render whatever the input says, so a missing or unsuffixed value means ' +\n 'the binding itself stopped reaching the element.'\n );\n }\n return Number.parseFloat(match[1]);\n }\n\n /**\n * The colour `[spotlightColor]` published on the host, or `null` when the input is unset.\n * The `--wr-spotlight-color` hook then decides, which is the theme's value unless the\n * author set it; this method does not read the hook.\n *\n * `null` IS the contract for an unset `[spotlightColor]`, not a gap in it. The\n * stylesheet's own default is a `light-dark()` over `--wr-color-on-surface`, and that\n * token flips per colour scheme — which is what makes one component read as a dark glow\n * on a light surface and a light beam on a dark one. A card that started writing a\n * concrete colour here would look right in whichever scheme it was authored against and\n * wrong in the other, and this is the only method that can tell.\n */\n async getSpotlightColor(): Promise<string | null> {\n return (await this.vars()).get('--wr-spotlight-input-color') ?? null;\n }\n\n /**\n * Where the highlight last sat, in pixels from the card's own top-left corner. `null`\n * until something has pointed at the card.\n *\n * Pixels, not percentages — see this class's docs for why the distinction matters\n * inside this one entry point.\n *\n * `null` is a state of its own rather than a stand-in for `{ x: 0, y: 0 }`: before the\n * first move the stylesheet's `50% / 50%` default applies, and a pointer at the card's\n * own origin legitimately reports zero, so folding the two together would make an\n * untouched card indistinguishable from one being pointed at in its corner.\n *\n * Note there is no reset when the cursor leaves, and that is not an oversight the\n * harness papers over: the stylesheet fades the whole overlay to `opacity: 0` on leave,\n * so nothing is parked visibly — but the coordinate stays written for the life of the\n * element. `null` therefore means \"no pointer has EVER arrived\", not \"none is here now\".\n */\n async getSpotlightPosition(): Promise<{ x: number; y: number } | null> {\n const vars = await this.vars();\n const x = vars.get('--wr-spotlight-x');\n const y = vars.get('--wr-spotlight-y');\n if (x === undefined || y === undefined) return null;\n\n return { x: this.parsePixels(x, '--wr-spotlight-x'), y: this.parsePixels(y, '--wr-spotlight-y') };\n }\n\n /**\n * Point at the card, in client coordinates.\n *\n * A `mousemove`, not a `pointermove` — the component listens for the former, and the\n * directive in the same entry point for the latter, so a pointer event dispatched here\n * is silently ignored. It goes to the host itself, so it needs no hit test and cannot\n * miss in a test that has no layout; the coordinates only ever reach the component's\n * own subtraction.\n *\n * Stub the host's `getBoundingClientRect` before calling this, or the subtraction is\n * invisible — see this class's docs.\n */\n async movePointerTo(clientX: number, clientY: number): Promise<void> {\n await (await this.host()).dispatchEvent('mousemove', { clientX, clientY });\n }\n\n /**\n * The projected content's text, trimmed.\n *\n * Read from the host, which is the whole assertion: the template is a bare\n * `<ng-content />` with no wrapper element, so content lands as a direct child and an\n * interactive one keeps its own name and role. The component adds no ARIA and no node\n * of its own — there is nothing else here for a harness to ask about.\n */\n async getContentText(): Promise<string> {\n return (await this.host()).text();\n }\n\n /** The host's inline declarations — the component's whole written output. */\n private async vars(): Promise<Map<string, string>> {\n return wrSpotlightInlineVars((await (await this.host()).getAttribute('style')) ?? '');\n }\n\n /** One written coordinate, as a number, insisting on the `px` the card promises. */\n private parsePixels(raw: string, name: string): number {\n const match = PIXEL_LENGTH.exec(raw);\n if (!match) {\n throw new Error(\n `WrSpotlightCardHarness.getSpotlightPosition(): \\`${name}\\` reads \"${raw}\", which is not the pixel length ` +\n 'the card writes. A percentage here is the tell that `[wrSpotlight]` is on the same element — the two ' +\n 'exports of this entry point share these variables and disagree about the unit.'\n );\n }\n return Number.parseFloat(match[1]);\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrSpotlightHarnessFilters } from './types';\nimport { wrSpotlightInlineVars } from './wr-spotlight-harness-vars';\n\n/**\n * Test harness for `[wrSpotlight]` — the directive that makes a cursor-following\n * gradient possible on an element the consumer owns.\n *\n * **This harness reads PERCENTAGES. {@link WrSpotlightCardHarness}, shipped from the same\n * entry point, reads PIXELS.** Both write `--wr-spotlight-x` / `--wr-spotlight-y`; the\n * directive writes them as a share of its own box off `pointermove`, the card as a pixel\n * offset off `mousemove`. That is why {@link getSpotlightPosition} hands back raw strings\n * and not numbers — collapsing `'37.5%'` to `37.5` would hide the one thing that decides\n * whether the gradient lands where the cursor is, and would make the two halves of this\n * entry point look interchangeable when they are not.\n *\n * **The directive ships no styles and adds no node.** There is no class to assert, no\n * wrapper, no ARIA: whatever the element announced before the directive applies is\n * exactly what it announces after, and the gradient is entirely the consumer's own CSS.\n * So there is no `isSpotlightVisible()` — there is nothing here to be visible — and no\n * `getResetX()` / `getResetY()`, which would echo the inputs back rather than report the\n * coordinate the directive actually WROTE. The echo would pass while the written value\n * was wrong, which is not hypothetical (see {@link getSpotlightPosition}). And no\n * `respectsReducedMotion()`: neither export in this entry point implements the\n * preference — it is the library's one documented exemption, since a gradient tracks the\n * cursor and no content moves — so the method would answer the same for a directive that\n * honoured it and one that ignored it.\n *\n * @example\n * ```ts\n * const spotlight = await loader.getHarness(WrSpotlightHarness.with({ hostClass: 'card' }));\n *\n * expect(await spotlight.getSpotlightPosition()).toEqual({ x: '50%', y: '50%' });\n * await spotlight.movePointerTo(150, 25); // stub the host's rect first\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrSpotlightHarness extends ComponentHarness {\n static hostSelector = '[wrSpotlight]';\n\n /**\n * Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`.\n *\n * Both options describe the HOST rather than the directive, because the directive\n * leaves no mark of its own to match on.\n */\n static with(options: WrSpotlightHarnessFilters = {}): HarnessPredicate<WrSpotlightHarness> {\n return new HarnessPredicate(WrSpotlightHarness, options)\n .addOption('text', options.text, async (harness, text) =>\n HarnessPredicate.stringMatches((await harness.host()).text(), text)\n )\n .addOption('hostClass', options.hostClass, async (harness, hostClass) =>\n (await harness.host()).hasClass(hostClass)\n );\n }\n\n /**\n * The coordinates the directive has written, as the raw strings it wrote them.\n *\n * Strings on purpose — see this class's docs. The values are percentages of the host's\n * own box while a pointer is over it, and whatever `[resetX]` / `[resetY]` say\n * otherwise, so a `px` appearing here means something else is writing these variables.\n *\n * **Present from the first render, before any pointer arrives.** The directive writes\n * the reset coordinates as soon as its inputs are bound, so the first read is whatever\n * `[resetX]` / `[resetY]` say — `50%` / `50%` by default — on a fresh host; do not\n * mistake that for a pointer having been tracked. It used to seed them in its\n * constructor, before signal inputs are bound, so a custom rest point appeared only\n * after the first {@link leave}. Reading the written coordinate rather than the input\n * is what made that visible at all.\n *\n * Throws once the directive has been destroyed, rather than answering with the last\n * value it happened to leave behind — {@link hasTrackedPointer} is the question to ask\n * when the host may be gone.\n */\n async getSpotlightPosition(): Promise<{ x: string; y: string }> {\n const vars = await this.vars();\n const x = vars.get('--wr-spotlight-x');\n const y = vars.get('--wr-spotlight-y');\n if (x === undefined || y === undefined) {\n throw new Error(\n 'WrSpotlightHarness.getSpotlightPosition(): the host carries no `--wr-spotlight-x` / `--wr-spotlight-y`. ' +\n 'The directive writes both on its first render and removes both on destroy, so this is a torn-down ' +\n 'directive rather than one that has not started — ask hasTrackedPointer() when the host may be gone.'\n );\n }\n return { x, y };\n }\n\n /**\n * Point at the host, in client coordinates.\n *\n * **Refuses on a host with no box, which in a unit test is every host.** The directive\n * divides the cursor offset by the element's own width and height, so a 0×0 rect writes\n * literally `Infinity%` or — at the origin — `NaN%` into the style. jsdom accepts either\n * string without complaint, so an unguarded harness would hand back something that looks\n * like a coordinate and describes nothing. Give the host a box first:\n *\n * ```ts\n * const el = fixture.nativeElement.querySelector('[wrSpotlight]');\n * el.getBoundingClientRect = () => ({ left: 0, top: 0, width: 200, height: 100 }) as DOMRect;\n * ```\n *\n * A `pointermove`, not a `mousemove` — the directive listens for the former, and the\n * component in the same entry point for the latter. It is dispatched on the host itself,\n * so it needs no hit test and cannot miss.\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 `WrSpotlightHarness.movePointerTo(${clientX}, ${clientY}): this host measures ${width}×${height}, so a ` +\n 'pointer position over it means nothing — the directive divides the offset by the box, and the result ' +\n 'goes into the style as `Infinity%` or `NaN%`. Stub `getBoundingClientRect` on the host first.'\n );\n }\n await host.dispatchEvent('pointermove', { clientX, clientY, pointerId: 1, isPrimary: true });\n }\n\n /**\n * Take the pointer away.\n *\n * Dispatches `pointerleave` — the directive's reset path, where `[resetX]` / `[resetY]`\n * are written back over the pointer's position. Needs no box: the handler writes the reset\n * strings straight through without measuring anything, which is why this one does not\n * refuse the way {@link movePointerTo} does.\n *\n * Not the CDK's `mouseAway()`, though that dispatches `pointerleave` too. This says what\n * it does, and it does not also fire the `mouseleave` / `mouseout` that the CDK helper\n * sends alongside it — events this directive ignores, and that would make a spec look\n * like it had covered more than it did.\n */\n async leave(): Promise<void> {\n await (await this.host()).dispatchEvent('pointerleave');\n }\n\n /**\n * Whether the directive still owns the host's spotlight variables.\n *\n * **This is \"the directive is alive\", not \"a pointer has moved over it\".** It is `true`\n * from the directive's first render, which writes the reset coordinates — there is no\n * earlier state to catch. What it distinguishes is a live\n * directive from a torn-down one: destroy removes both declarations from the element,\n * and nothing else in the repo asserts that cleanup. A directive that stopped removing\n * them would leave a stale gradient position on an element the consumer went on using,\n * and the only trace would be these two declarations outliving it.\n */\n async hasTrackedPointer(): Promise<boolean> {\n const vars = await this.vars();\n return vars.has('--wr-spotlight-x') && vars.has('--wr-spotlight-y');\n }\n\n /** The host's inline declarations — the directive's whole written output. */\n private async vars(): Promise<Map<string, string>> {\n return wrSpotlightInlineVars((await (await this.host()).getAttribute('style')) ?? '');\n }\n}\n"],"mappings":"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"}