ngwr 11.1.0 → 11.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/circular-text/styles/_index.scss +11 -0
  2. package/command-palette/styles/_index.scss +12 -0
  3. package/fesm2022/ngwr-action-sheet-testing.mjs +313 -0
  4. package/fesm2022/ngwr-action-sheet-testing.mjs.map +1 -0
  5. package/fesm2022/ngwr-bar-chart-testing.mjs +89 -0
  6. package/fesm2022/ngwr-bar-chart-testing.mjs.map +1 -0
  7. package/fesm2022/ngwr-blur-text-testing.mjs +203 -0
  8. package/fesm2022/ngwr-blur-text-testing.mjs.map +1 -0
  9. package/fesm2022/ngwr-blur-text.mjs +15 -1
  10. package/fesm2022/ngwr-blur-text.mjs.map +1 -1
  11. package/fesm2022/ngwr-border-glow-testing.mjs +325 -0
  12. package/fesm2022/ngwr-border-glow-testing.mjs.map +1 -0
  13. package/fesm2022/ngwr-calendar-heatmap-testing.mjs +95 -0
  14. package/fesm2022/ngwr-calendar-heatmap-testing.mjs.map +1 -0
  15. package/fesm2022/ngwr-calendar-testing.mjs +368 -0
  16. package/fesm2022/ngwr-calendar-testing.mjs.map +1 -0
  17. package/fesm2022/ngwr-calendar.mjs +43 -3
  18. package/fesm2022/ngwr-calendar.mjs.map +1 -1
  19. package/fesm2022/ngwr-cascader.mjs +12 -0
  20. package/fesm2022/ngwr-cascader.mjs.map +1 -1
  21. package/fesm2022/ngwr-checkbox.mjs +8 -0
  22. package/fesm2022/ngwr-checkbox.mjs.map +1 -1
  23. package/fesm2022/ngwr-circular-text-testing.mjs +230 -0
  24. package/fesm2022/ngwr-circular-text-testing.mjs.map +1 -0
  25. package/fesm2022/ngwr-circular-text.mjs +2 -2
  26. package/fesm2022/ngwr-circular-text.mjs.map +1 -1
  27. package/fesm2022/ngwr-color-picker-testing.mjs +432 -0
  28. package/fesm2022/ngwr-color-picker-testing.mjs.map +1 -0
  29. package/fesm2022/ngwr-color-picker.mjs +29 -3
  30. package/fesm2022/ngwr-color-picker.mjs.map +1 -1
  31. package/fesm2022/ngwr-context-menu.mjs +25 -5
  32. package/fesm2022/ngwr-context-menu.mjs.map +1 -1
  33. package/fesm2022/ngwr-date-picker-testing.mjs +10 -79
  34. package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -1
  35. package/fesm2022/ngwr-decrypt-text-testing.mjs +261 -0
  36. package/fesm2022/ngwr-decrypt-text-testing.mjs.map +1 -0
  37. package/fesm2022/ngwr-decrypt-text.mjs +6 -1
  38. package/fesm2022/ngwr-decrypt-text.mjs.map +1 -1
  39. package/fesm2022/ngwr-donut-chart-testing.mjs +93 -0
  40. package/fesm2022/ngwr-donut-chart-testing.mjs.map +1 -0
  41. package/fesm2022/ngwr-drawer.mjs +36 -7
  42. package/fesm2022/ngwr-drawer.mjs.map +1 -1
  43. package/fesm2022/ngwr-dropdown.mjs +32 -2
  44. package/fesm2022/ngwr-dropdown.mjs.map +1 -1
  45. package/fesm2022/ngwr-event-calendar-testing.mjs +446 -0
  46. package/fesm2022/ngwr-event-calendar-testing.mjs.map +1 -0
  47. package/fesm2022/ngwr-event-calendar.mjs +43 -18
  48. package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
  49. package/fesm2022/ngwr-falling-text-testing.mjs +218 -0
  50. package/fesm2022/ngwr-falling-text-testing.mjs.map +1 -0
  51. package/fesm2022/ngwr-fuzzy-text-testing.mjs +134 -0
  52. package/fesm2022/ngwr-fuzzy-text-testing.mjs.map +1 -0
  53. package/fesm2022/ngwr-gauge-testing.mjs +86 -0
  54. package/fesm2022/ngwr-gauge-testing.mjs.map +1 -0
  55. package/fesm2022/ngwr-glitch-text-testing.mjs +181 -0
  56. package/fesm2022/ngwr-glitch-text-testing.mjs.map +1 -0
  57. package/fesm2022/ngwr-gradient-text-testing.mjs +223 -0
  58. package/fesm2022/ngwr-gradient-text-testing.mjs.map +1 -0
  59. package/fesm2022/ngwr-i18n-en.mjs +14 -0
  60. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  61. package/fesm2022/ngwr-i18n-ru.mjs +14 -0
  62. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  63. package/fesm2022/ngwr-image-cropper-testing.mjs +156 -0
  64. package/fesm2022/ngwr-image-cropper-testing.mjs.map +1 -0
  65. package/fesm2022/ngwr-image-cropper.mjs +10 -3
  66. package/fesm2022/ngwr-image-cropper.mjs.map +1 -1
  67. package/fesm2022/ngwr-input-number.mjs +73 -6
  68. package/fesm2022/ngwr-input-number.mjs.map +1 -1
  69. package/fesm2022/ngwr-knob-testing.mjs +220 -0
  70. package/fesm2022/ngwr-knob-testing.mjs.map +1 -0
  71. package/fesm2022/ngwr-lightbox-testing.mjs +223 -0
  72. package/fesm2022/ngwr-lightbox-testing.mjs.map +1 -0
  73. package/fesm2022/ngwr-lightbox.mjs +9 -2
  74. package/fesm2022/ngwr-lightbox.mjs.map +1 -1
  75. package/fesm2022/ngwr-line-chart-testing.mjs +119 -0
  76. package/fesm2022/ngwr-line-chart-testing.mjs.map +1 -0
  77. package/fesm2022/ngwr-marquee-testing.mjs +336 -0
  78. package/fesm2022/ngwr-marquee-testing.mjs.map +1 -0
  79. package/fesm2022/ngwr-meter-group-testing.mjs +80 -0
  80. package/fesm2022/ngwr-meter-group-testing.mjs.map +1 -0
  81. package/fesm2022/ngwr-overlay.mjs +28 -1
  82. package/fesm2022/ngwr-overlay.mjs.map +1 -1
  83. package/fesm2022/ngwr-popover.mjs +14 -0
  84. package/fesm2022/ngwr-popover.mjs.map +1 -1
  85. package/fesm2022/ngwr-rotating-text-testing.mjs +220 -0
  86. package/fesm2022/ngwr-rotating-text-testing.mjs.map +1 -0
  87. package/fesm2022/ngwr-rotating-text.mjs +2 -2
  88. package/fesm2022/ngwr-rotating-text.mjs.map +1 -1
  89. package/fesm2022/ngwr-select.mjs +70 -11
  90. package/fesm2022/ngwr-select.mjs.map +1 -1
  91. package/fesm2022/ngwr-shiny-text-testing.mjs +237 -0
  92. package/fesm2022/ngwr-shiny-text-testing.mjs.map +1 -0
  93. package/fesm2022/ngwr-slider.mjs +44 -6
  94. package/fesm2022/ngwr-slider.mjs.map +1 -1
  95. package/fesm2022/ngwr-sparkline-testing.mjs +76 -0
  96. package/fesm2022/ngwr-sparkline-testing.mjs.map +1 -0
  97. package/fesm2022/ngwr-speed-dial-testing.mjs +279 -0
  98. package/fesm2022/ngwr-speed-dial-testing.mjs.map +1 -0
  99. package/fesm2022/ngwr-splash-cursor-testing.mjs +89 -0
  100. package/fesm2022/ngwr-splash-cursor-testing.mjs.map +1 -0
  101. package/fesm2022/ngwr-split-text-testing.mjs +224 -0
  102. package/fesm2022/ngwr-split-text-testing.mjs.map +1 -0
  103. package/fesm2022/ngwr-split-text.mjs +15 -1
  104. package/fesm2022/ngwr-split-text.mjs.map +1 -1
  105. package/fesm2022/ngwr-splitter-testing.mjs +245 -0
  106. package/fesm2022/ngwr-splitter-testing.mjs.map +1 -0
  107. package/fesm2022/ngwr-spotlight-card-testing.mjs +368 -0
  108. package/fesm2022/ngwr-spotlight-card-testing.mjs.map +1 -0
  109. package/fesm2022/ngwr-star-border-testing.mjs +172 -0
  110. package/fesm2022/ngwr-star-border-testing.mjs.map +1 -0
  111. package/fesm2022/ngwr-table.mjs +13 -3
  112. package/fesm2022/ngwr-table.mjs.map +1 -1
  113. package/fesm2022/ngwr-tabs.mjs +15 -1
  114. package/fesm2022/ngwr-tabs.mjs.map +1 -1
  115. package/fesm2022/ngwr-tilt-card-testing.mjs +190 -0
  116. package/fesm2022/ngwr-tilt-card-testing.mjs.map +1 -0
  117. package/fesm2022/ngwr-tilt-card.mjs +19 -1
  118. package/fesm2022/ngwr-tilt-card.mjs.map +1 -1
  119. package/fesm2022/ngwr-toast.mjs +36 -7
  120. package/fesm2022/ngwr-toast.mjs.map +1 -1
  121. package/fesm2022/ngwr-tour-testing.mjs +173 -0
  122. package/fesm2022/ngwr-tour-testing.mjs.map +1 -0
  123. package/fesm2022/ngwr-tour.mjs +2 -2
  124. package/fesm2022/ngwr-tour.mjs.map +1 -1
  125. package/fesm2022/ngwr-tree.mjs +10 -3
  126. package/fesm2022/ngwr-tree.mjs.map +1 -1
  127. package/fesm2022/ngwr-typewriter-testing.mjs +228 -0
  128. package/fesm2022/ngwr-typewriter-testing.mjs.map +1 -0
  129. package/fesm2022/ngwr-version.mjs +1 -1
  130. package/fesm2022/ngwr-version.mjs.map +1 -1
  131. package/fesm2022/ngwr-waves-testing.mjs +128 -0
  132. package/fesm2022/ngwr-waves-testing.mjs.map +1 -0
  133. package/fesm2022/ngwr-window-testing.mjs +361 -0
  134. package/fesm2022/ngwr-window-testing.mjs.map +1 -0
  135. package/fesm2022/ngwr-window.mjs +84 -10
  136. package/fesm2022/ngwr-window.mjs.map +1 -1
  137. package/i18n/en.json +16 -2
  138. package/i18n/ru.json +16 -2
  139. package/llms-full.txt +147 -0
  140. package/llms.txt +27 -8
  141. package/mcp/providers.js +29 -0
  142. package/mcp/tools.js +1 -29
  143. package/package.json +145 -1
  144. package/skills/ngwr/SKILL.md +115 -0
  145. package/skills/ngwr/references/catalog.md +212 -0
  146. package/skills/ngwr/references/setup.md +73 -0
  147. package/types/ngwr-action-sheet-testing.d.ts +260 -0
  148. package/types/ngwr-bar-chart-testing.d.ts +77 -0
  149. package/types/ngwr-blur-text-testing.d.ts +180 -0
  150. package/types/ngwr-blur-text.d.ts +12 -0
  151. package/types/ngwr-border-glow-testing.d.ts +219 -0
  152. package/types/ngwr-calendar-heatmap-testing.d.ts +79 -0
  153. package/types/ngwr-calendar-testing.d.ts +277 -0
  154. package/types/ngwr-calendar.d.ts +10 -0
  155. package/types/ngwr-checkbox.d.ts +13 -0
  156. package/types/ngwr-circular-text-testing.d.ts +196 -0
  157. package/types/ngwr-color-picker-testing.d.ts +291 -0
  158. package/types/ngwr-color-picker.d.ts +13 -1
  159. package/types/ngwr-context-menu.d.ts +15 -1
  160. package/types/ngwr-date-picker-testing.d.ts +6 -66
  161. package/types/ngwr-decrypt-text-testing.d.ts +222 -0
  162. package/types/ngwr-donut-chart-testing.d.ts +79 -0
  163. package/types/ngwr-drawer.d.ts +12 -0
  164. package/types/ngwr-dropdown.d.ts +9 -0
  165. package/types/ngwr-event-calendar-testing.d.ts +301 -0
  166. package/types/ngwr-event-calendar.d.ts +9 -6
  167. package/types/ngwr-falling-text-testing.d.ts +186 -0
  168. package/types/ngwr-fuzzy-text-testing.d.ts +130 -0
  169. package/types/ngwr-gauge-testing.d.ts +71 -0
  170. package/types/ngwr-glitch-text-testing.d.ts +172 -0
  171. package/types/ngwr-gradient-text-testing.d.ts +173 -0
  172. package/types/ngwr-image-cropper-testing.d.ts +134 -0
  173. package/types/ngwr-image-cropper.d.ts +1 -0
  174. package/types/ngwr-input-number.d.ts +33 -1
  175. package/types/ngwr-knob-testing.d.ts +150 -0
  176. package/types/ngwr-lightbox-testing.d.ts +154 -0
  177. package/types/ngwr-lightbox.d.ts +6 -0
  178. package/types/ngwr-line-chart-testing.d.ts +93 -0
  179. package/types/ngwr-marquee-testing.d.ts +280 -0
  180. package/types/ngwr-meter-group-testing.d.ts +67 -0
  181. package/types/ngwr-overlay.d.ts +19 -1
  182. package/types/ngwr-rotating-text-testing.d.ts +191 -0
  183. package/types/ngwr-select.d.ts +32 -0
  184. package/types/ngwr-shiny-text-testing.d.ts +165 -0
  185. package/types/ngwr-slider.d.ts +15 -0
  186. package/types/ngwr-sparkline-testing.d.ts +69 -0
  187. package/types/ngwr-speed-dial-testing.d.ts +195 -0
  188. package/types/ngwr-splash-cursor-testing.d.ts +95 -0
  189. package/types/ngwr-split-text-testing.d.ts +209 -0
  190. package/types/ngwr-split-text.d.ts +12 -0
  191. package/types/ngwr-splitter-testing.d.ts +174 -0
  192. package/types/ngwr-spotlight-card-testing.d.ts +303 -0
  193. package/types/ngwr-star-border-testing.d.ts +135 -0
  194. package/types/ngwr-table.d.ts +10 -1
  195. package/types/ngwr-tilt-card-testing.d.ts +158 -0
  196. package/types/ngwr-tilt-card.d.ts +2 -0
  197. package/types/ngwr-toast.d.ts +14 -5
  198. package/types/ngwr-tour-testing.d.ts +143 -0
  199. package/types/ngwr-tree.d.ts +7 -0
  200. package/types/ngwr-typewriter-testing.d.ts +201 -0
  201. package/types/ngwr-version.d.ts +1 -1
  202. package/types/ngwr-waves-testing.d.ts +121 -0
  203. package/types/ngwr-window-testing.d.ts +263 -0
  204. package/types/ngwr-window.d.ts +27 -10
  205. package/window/styles/_index.scss +44 -16
@@ -0,0 +1,173 @@
1
+ import { BaseHarnessFilters, ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';
2
+ import { WrGradientTextDirection } from 'ngwr/gradient-text';
3
+
4
+ /**
5
+ * @license
6
+ *
7
+ * Use of this source code is governed by an MIT-style license that can be
8
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
9
+ */
10
+
11
+ /** Narrows which `<wr-gradient-text>` a harness query matches. */
12
+ interface WrGradientTextHarnessFilters extends BaseHarnessFilters {
13
+ /** Match the projected text — a string is an exact match, a RegExp is tested. */
14
+ readonly text?: string | RegExp;
15
+ /**
16
+ * Match the sweep direction. `'horizontal'` is the ABSENCE of a modifier rather than a
17
+ * class of its own — see `WrGradientTextHarness.getDirection`, which resolves it.
18
+ */
19
+ readonly direction?: WrGradientTextDirection;
20
+ /** Match only the pill variant (`true`) or only the bare one (`false`). */
21
+ readonly border?: boolean;
22
+ }
23
+
24
+ /**
25
+ * @license
26
+ *
27
+ * Use of this source code is governed by an MIT-style license that can be
28
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
29
+ */
30
+
31
+ /**
32
+ * Test harness for `<wr-gradient-text>`.
33
+ *
34
+ * **The gradient string is the component.** Everything `[colors]` and `[direction]`
35
+ * decide ends up in one inline custom property — the angle keyword, the stop list, and
36
+ * the first colour repeated at the end, which is what makes the loop seamless instead of
37
+ * snapping back at the wrap. {@link getColors} returns that list AS WRITTEN, wrap stop
38
+ * included, because the duplicate is the thing worth asserting and stripping it would
39
+ * hide the day it goes missing.
40
+ *
41
+ * **Direction is two facts, not one.** The modifier class says which way the gradient
42
+ * travels and {@link getBackgroundSize} says which axis it was stretched along, and they
43
+ * are computed separately: a direction change that forgets the size leaves every class
44
+ * looking right while the effect is squashed flat. Note too that `horizontal` writes NO
45
+ * class at all — {@link getDirection} resolves the absence, which is the mapping a
46
+ * refactor breaks most easily.
47
+ *
48
+ * **What the harness deliberately does not offer**, and why. There is no `isAnimating()`
49
+ * / `isPaused()`: the sweep is a stylesheet keyframe, and a unit test loads no
50
+ * stylesheet, so the answer would be the same constant for a working component and a
51
+ * broken one. There is no `getBackgroundPosition()`: the swept value exists only
52
+ * mid-animation, and the static declaration it would return corresponds to no frame the
53
+ * user ever sees. {@link pausesOnHover} claims the CLASS and not the pause, deliberately
54
+ * — `:hover` is a selector, and the DOM after a dispatched mouse event is identical to
55
+ * the DOM before it. There is no `respectsReducedMotion()`: the only handling is a
56
+ * `@media` block with no TypeScript counterpart, so nothing in the DOM changes in either
57
+ * state. And there is nothing that reports the painted text colour: the fill comes from
58
+ * `background-clip: text` over a `color: transparent` declared in the stylesheet, so a
59
+ * read would describe the inherited colour and call the text readable where the browser
60
+ * paints a gradient.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * const gradient = await loader.getHarness(WrGradientTextHarness);
65
+ *
66
+ * expect(await gradient.getColors()).toEqual(['#111111', '#222222', '#111111']);
67
+ * expect(await gradient.getBackgroundSize()).toBe('300% 100%');
68
+ * ```
69
+ *
70
+ * @see https://ngwr.dev/guides/testing
71
+ */
72
+ declare class WrGradientTextHarness extends ComponentHarness {
73
+ static hostSelector: string;
74
+ /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */
75
+ static with(options?: WrGradientTextHarnessFilters): HarnessPredicate<WrGradientTextHarness>;
76
+ private readonly text;
77
+ private readonly ring;
78
+ /**
79
+ * The projected text, trimmed.
80
+ *
81
+ * Read from the text span rather than from the host, so the answer stays correct on the
82
+ * day the decorative ring gains content of its own — it is a sibling of this span, and
83
+ * the host's text would include both.
84
+ */
85
+ getText(): Promise<string>;
86
+ /**
87
+ * Which way the gradient travels.
88
+ *
89
+ * `horizontal` is reported for the absence of both modifiers, because the component
90
+ * writes no `--horizontal` class — there is nothing to read for the default, only the
91
+ * two exceptions.
92
+ */
93
+ getDirection(): Promise<WrGradientTextDirection>;
94
+ /**
95
+ * The whole gradient the component computed, as written.
96
+ *
97
+ * Unresolved on purpose: a consumer may pass theme tokens, and resolving `var()` needs
98
+ * the stylesheet. The stops come back spelled as they were written — a custom property
99
+ * holds an unparsed token stream, so `#111111` stays `#111111` rather than being
100
+ * re-serialised as `rgb()` the way it would be inside a standard `background-image`.
101
+ * Whitespace is the exception, so prefer {@link getColors} over string-equalling the
102
+ * whole gradient.
103
+ */
104
+ getGradient(): Promise<string>;
105
+ /**
106
+ * The gradient's colour stops, in order, INCLUDING the repeated first colour at the end.
107
+ *
108
+ * So the list is one longer than the `[colors]` input, and that extra entry is the
109
+ * point: it is what lets the sweep wrap without a visible snap, and nothing else in the
110
+ * DOM records it. An empty `[colors]` falls back to the three built-in stops, which
111
+ * shows up here as four entries rather than one.
112
+ *
113
+ * Throws rather than guessing when the property holds something that is not a
114
+ * `linear-gradient()` — an empty read means the host binding is gone, and a list of
115
+ * fragments parsed out of the wreckage would look like a colour set.
116
+ */
117
+ getColors(): Promise<string[]>;
118
+ /**
119
+ * How far the gradient is stretched, as the `background-size` pair.
120
+ *
121
+ * The other half of direction: the gradient has to be oversized along the axis it
122
+ * travels — `300% 100%` horizontal, `100% 300%` vertical, `300% 300%` diagonal — or
123
+ * there is nothing outside the box to slide in. Worth asserting beside
124
+ * {@link getDirection}, since the two are computed independently and only agree by
125
+ * intent.
126
+ */
127
+ getBackgroundSize(): Promise<string>;
128
+ /**
129
+ * One full sweep in seconds, from `[animationSpeed]`.
130
+ *
131
+ * Proves the input reaches CSS at all, `numAttr` coercion included — a string attribute
132
+ * arrives as a number and garbage falls back to the documented default. Throws when the
133
+ * property holds no number, which is the only way the read can fail.
134
+ */
135
+ getAnimationDurationSeconds(): Promise<number>;
136
+ /**
137
+ * Whether the decorative ring element was rendered (`showBorder`).
138
+ *
139
+ * Strictly more than the `wr-gradient-text--border` class says: the class is a host
140
+ * binding and the ring is an `@if` branch in the template, two mechanisms reading one
141
+ * input, and they can drift. This asks whether the element that paints the pill is
142
+ * actually there.
143
+ */
144
+ hasBorderRing(): Promise<boolean>;
145
+ /**
146
+ * Whether the ring is hidden from assistive tech, or `null` when there is no ring.
147
+ *
148
+ * The ring is an empty, nameless span. Paired with {@link hasBorderRing} this is the
149
+ * component's whole a11y contract: if it ever loses `aria-hidden`, a screen reader stops
150
+ * on an element with nothing to say. `null` rather than `false` for the absent case, so
151
+ * "no ring" cannot be mistaken for "an exposed ring".
152
+ */
153
+ isBorderDecorative(): Promise<boolean | null>;
154
+ /**
155
+ * Whether the sweep bounces instead of restarting (`yoyo`).
156
+ *
157
+ * Defaults to `true` here, unlike every other boolean on this component and unlike the
158
+ * same-named input on `wr-shiny-text` — worth pinning in both directions. The class is
159
+ * the only thing the `animation-direction: alternate` rule keys off.
160
+ */
161
+ isYoyo(): Promise<boolean>;
162
+ /**
163
+ * Whether the opt-in reached the class the `:hover` rule needs.
164
+ *
165
+ * Named for the class and not for the pause on purpose: the pause itself is a CSS
166
+ * selector match, which no unit test can observe. What this catches is the input never
167
+ * arriving, which is the half that can go wrong in TypeScript.
168
+ */
169
+ pausesOnHover(): Promise<boolean>;
170
+ }
171
+
172
+ export { WrGradientTextHarness };
173
+ export type { WrGradientTextHarnessFilters };
@@ -0,0 +1,134 @@
1
+ import { BaseHarnessFilters, ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';
2
+ import { WrCropHandle } from 'ngwr/image-cropper';
3
+
4
+ /**
5
+ * @license
6
+ *
7
+ * Use of this source code is governed by an MIT-style license that can be
8
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
9
+ */
10
+
11
+ /** Narrows which `<wr-image-cropper>` a harness query matches. */
12
+ interface WrImageCropperHarnessFilters extends BaseHarnessFilters {
13
+ /**
14
+ * Match croppers holding an image (`false`) or showing the empty state (`true`).
15
+ * A cropper with a `src` it has not measured yet is NOT empty — see
16
+ * `WrImageCropperHarness.isReady`.
17
+ */
18
+ readonly empty?: boolean;
19
+ }
20
+
21
+ /**
22
+ * @license
23
+ *
24
+ * Use of this source code is governed by an MIT-style license that can be
25
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
26
+ */
27
+
28
+ /** Where the crop window sits over the DISPLAYED image, in CSS pixels. */
29
+ interface WrCropWindowBox {
30
+ readonly x: number;
31
+ readonly y: number;
32
+ readonly width: number;
33
+ readonly height: number;
34
+ }
35
+ /**
36
+ * Test harness for `<wr-image-cropper>`.
37
+ *
38
+ * **This is the smallest honest surface of any harness here, and the reason is worth
39
+ * reading before you reach for a method that is not on it.** Almost everything the
40
+ * cropper does is measured: it reads the `<img>`'s rendered box and its
41
+ * `naturalWidth`, converts between display and source pixels, and moves the crop
42
+ * window with pointer deltas. jsdom measures nothing — every rect is 0×0 and
43
+ * `naturalWidth` is 0 — so an image never "loads", the crop UI never renders, and a
44
+ * synthetic drag divides by zero. There is deliberately no `moveCrop()` or
45
+ * `resizeCrop()`: either would write `NaN` and report success.
46
+ *
47
+ * What a spec CAN do is give the image a box and a natural size itself, then call
48
+ * {@link dispatchImageLoad} — the two numbers `onImageLoad` reads are the whole
49
+ * stub, and from there {@link getCropBox} and {@link getHandles} are real answers,
50
+ * because the component writes the crop window's geometry as inline pixels.
51
+ *
52
+ * ```ts
53
+ * // In your spec, before dispatching the load:
54
+ * const img = fixture.nativeElement.querySelector('.wr-image-cropper__image');
55
+ * img.getBoundingClientRect = () => ({ width: 400, height: 400, x: 0, y: 0 }) as DOMRect;
56
+ * Object.defineProperty(img, 'naturalWidth', { value: 800 });
57
+ * Object.defineProperty(img, 'naturalHeight', { value: 800 });
58
+ * ```
59
+ *
60
+ * **There is also nothing to click.** The cropper renders no buttons: a consumer
61
+ * takes the result by calling `crop()` on the component through a `viewChild`, and
62
+ * reads `cropRect()` the same way. Those are component API rather than DOM, so they
63
+ * are outside what a harness can reach at all — which is worth knowing rather than
64
+ * discovering.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const cropper = await loader.getHarness(WrImageCropperHarness);
69
+ *
70
+ * expect(await cropper.isEmpty()).toBe(false);
71
+ * expect(await cropper.isReady()).toBe(false); // nothing measured yet
72
+ *
73
+ * await cropper.dispatchImageLoad();
74
+ * expect(await cropper.getCropBox()).toEqual({ x: 80, y: 80, width: 240, height: 240 });
75
+ * ```
76
+ *
77
+ * @see https://ngwr.dev/guides/testing
78
+ */
79
+ declare class WrImageCropperHarness extends ComponentHarness {
80
+ static hostSelector: string;
81
+ /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */
82
+ static with(options?: WrImageCropperHarnessFilters): HarnessPredicate<WrImageCropperHarness>;
83
+ private readonly image;
84
+ private readonly cropWindow;
85
+ /** Whether the cropper has no source at all, and is showing its placeholder. */
86
+ isEmpty(): Promise<boolean>;
87
+ /** The placeholder's text, or `null` when there is an image. Comes from the catalog. */
88
+ getEmptyText(): Promise<string | null>;
89
+ /**
90
+ * Whether the crop UI is up.
91
+ *
92
+ * Not the same question as {@link isEmpty}: a cropper with a perfectly good `src`
93
+ * shows nothing but the image until it has been MEASURED, because the window's size
94
+ * is a fraction of the rendered box. In a browser that happens on load; in a unit
95
+ * test it happens when the spec stubs the box and calls
96
+ * {@link dispatchImageLoad}.
97
+ */
98
+ isReady(): Promise<boolean>;
99
+ /**
100
+ * What the `<img>` is pointing at.
101
+ *
102
+ * A `File` or `Blob` source becomes an object URL, so this is `blob:…` for those
103
+ * and the plain string for a string `src`. Comparing the two is not useful; asking
104
+ * whether it CHANGED is.
105
+ */
106
+ getImageSrc(): Promise<string | null>;
107
+ /**
108
+ * Fire the image's `load` event.
109
+ *
110
+ * The event, not a real load: jsdom fetches nothing, and the handler reads
111
+ * `getBoundingClientRect()` and `naturalWidth` off the element. Stub those first —
112
+ * see this class's docs — or the crop window will still not render and
113
+ * {@link isReady} will stay `false`, which is exactly what a real cropper does with
114
+ * a zero-sized image.
115
+ */
116
+ dispatchImageLoad(): Promise<void>;
117
+ /**
118
+ * Where the crop window sits over the displayed image, in CSS pixels.
119
+ *
120
+ * From the inline styles the component writes — a measured box would be 0×0 — and
121
+ * in DISPLAY pixels, which is not what a consumer receives: `cropRect()` converts
122
+ * to the image's natural pixels, and that conversion is the component's own contract
123
+ * rather than something the DOM shows.
124
+ */
125
+ getCropBox(): Promise<WrCropWindowBox>;
126
+ /** The eight resize handles, by their compass suffix. Throws before the crop UI is up. */
127
+ getHandles(): Promise<WrCropHandle[]>;
128
+ /** Whether the dimming backdrop with the cut-out is drawn. */
129
+ hasBackdrop(): Promise<boolean>;
130
+ private requireReady;
131
+ }
132
+
133
+ export { WrImageCropperHarness };
134
+ export type { WrImageCropperHarnessFilters };
@@ -61,6 +61,7 @@ declare class WrImageCropper {
61
61
  readonly cropped: _angular_core.OutputEmitterRef<Blob>;
62
62
  protected readonly imgEl: _angular_core.Signal<ElementRef<HTMLImageElement>>;
63
63
  /** Resolved object URL for `src` (so File / Blob render in `<img>`). */
64
+ protected readonly emptyText: _angular_core.Signal<string>;
64
65
  protected readonly objectUrl: _angular_core.WritableSignal<string | null>;
65
66
  /** Previous object URL we created — kept off-signal so the resolve
66
67
  * effect doesn't depend on its own writes (which would loop). */
@@ -79,6 +79,17 @@ declare class WrInputNumber implements FormValueControl<number | null> {
79
79
  */
80
80
  readonly ariaLabel: _angular_core.InputSignal<string | null>;
81
81
  protected readonly resolvedAriaLabel: _angular_core.Signal<string | null>;
82
+ /** Increment button aria-label. Falls back to `inputNumber.increment`. */
83
+ readonly incrementLabel: _angular_core.InputSignal<string | null>;
84
+ /** Decrement button aria-label. Falls back to `inputNumber.decrement`. */
85
+ readonly decrementLabel: _angular_core.InputSignal<string | null>;
86
+ /**
87
+ * The steppers are icon-only buttons, so these ARE their accessible names.
88
+ * They were hard-coded English while the catalog carried the keys for them
89
+ * unread — a Russian app announced "Increment".
90
+ */
91
+ protected readonly resolvedIncrement: _angular_core.Signal<string>;
92
+ protected readonly resolvedDecrement: _angular_core.Signal<string>;
82
93
  /** Disable interaction. @default false */
83
94
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, any>;
84
95
  /** Read-only — values cannot be changed (steppers + typing disabled). @default false */
@@ -97,6 +108,27 @@ declare class WrInputNumber implements FormValueControl<number | null> {
97
108
  protected readonly atMin: _angular_core.Signal<boolean>;
98
109
  protected readonly atMax: _angular_core.Signal<boolean>;
99
110
  protected readonly classes: _angular_core.Signal<string>;
111
+ /**
112
+ * Fraction digits the stepper rounds its arithmetic to when `decimals` says
113
+ * nothing. High enough to leave a real value alone, low enough to erase the
114
+ * binary-float tail that repeated stepping accumulates.
115
+ */
116
+ private static readonly STEP_PRECISION;
117
+ /**
118
+ * Above this, the anti-drift round is skipped — it would MANUFACTURE drift.
119
+ *
120
+ * `round(v, 10)` is `Math.round((v + EPSILON) * 1e10) / 1e10`, and the scale
121
+ * factor is only exact while `v * 1e10` stays inside 2^53, i.e. |v| under
122
+ * about 900,720. In practice the round-trip still recovers up to ~9.2e8 and
123
+ * then breaks: `round(1000000001, 10)` is `1000000000.9999999`, and since
124
+ * `format` prints up to 20 fraction digits that tail reaches the screen —
125
+ * and compounds, because the next press starts from it. Integer stepping at
126
+ * that magnitude is exact in IEEE754 and needed no help; the only thing this
127
+ * rounding exists to defend is `0.1 + 0.1 + 0.1`.
128
+ */
129
+ private static readonly SAFE_ROUND_MAGNITUDE;
130
+ /** Steps finer than this would be rounded away by {@link STEP_PRECISION}. */
131
+ private static readonly SAFE_ROUND_STEP;
100
132
  constructor();
101
133
  protected onInput(event: Event): void;
102
134
  protected onFocus(): void;
@@ -112,7 +144,7 @@ declare class WrInputNumber implements FormValueControl<number | null> {
112
144
  /** Format a value using `Intl.NumberFormat` for the active locale. */
113
145
  private format;
114
146
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<WrInputNumber, never>;
115
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrInputNumber, "wr-input-number", never, { "min": { "alias": "min"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "step": { "alias": "step"; "required": false; "isSignal": true; }; "decimals": { "alias": "decimals"; "required": false; "isSignal": true; }; "showSteppers": { "alias": "showSteppers"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "rounded": { "alias": "rounded"; "required": false; "isSignal": true; }; "prefix": { "alias": "prefix"; "required": false; "isSignal": true; }; "suffix": { "alias": "suffix"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "touch": "touch"; }, never, never, true, never>;
147
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrInputNumber, "wr-input-number", never, { "min": { "alias": "min"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "step": { "alias": "step"; "required": false; "isSignal": true; }; "decimals": { "alias": "decimals"; "required": false; "isSignal": true; }; "showSteppers": { "alias": "showSteppers"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "rounded": { "alias": "rounded"; "required": false; "isSignal": true; }; "prefix": { "alias": "prefix"; "required": false; "isSignal": true; }; "suffix": { "alias": "suffix"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "incrementLabel": { "alias": "incrementLabel"; "required": false; "isSignal": true; }; "decrementLabel": { "alias": "decrementLabel"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "touch": "touch"; }, never, never, true, never>;
116
148
  }
117
149
 
118
150
  export { WrInputNumber };
@@ -0,0 +1,150 @@
1
+ import { BaseHarnessFilters, ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';
2
+
3
+ /**
4
+ * @license
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
8
+ */
9
+
10
+ /** Narrows which `<wr-knob>` a harness query matches. */
11
+ interface WrKnobHarnessFilters extends BaseHarnessFilters {
12
+ /** Match the dial's accessible name — a string is an exact match, a RegExp is tested. */
13
+ readonly label?: string | RegExp;
14
+ /** Match the current value, as `aria-valuenow` reports it. */
15
+ readonly value?: number;
16
+ /** Match only enabled (`false`) or only disabled (`true`) dials. */
17
+ readonly disabled?: boolean;
18
+ /** Match only writable (`false`) or only read-only (`true`) dials. */
19
+ readonly readonly?: boolean;
20
+ }
21
+
22
+ /**
23
+ * @license
24
+ *
25
+ * Use of this source code is governed by an MIT-style license that can be
26
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
27
+ */
28
+
29
+ /** The two arrow keys that raise the value, and the two that lower it. */
30
+ type Arrow = 'left' | 'right' | 'up' | 'down';
31
+ /**
32
+ * Test harness for `<wr-knob>` — the radial dial.
33
+ *
34
+ * **Driven by the keyboard, and the drag is not merely unsupported.** Turning the
35
+ * dial with a pointer takes `getBoundingClientRect()`, measures the angle from the
36
+ * centre to the cursor, and maps it onto a 270° arc. jsdom lays nothing out, so the
37
+ * centre is `(0, 0)` and every synthetic gesture writes the same corner of the arc
38
+ * whatever coordinates it is given — a `turnTo()` built on one would pass while
39
+ * proving nothing. The arrows reach every value on the grid and are the accessible
40
+ * path anyway.
41
+ *
42
+ * **Two questions about the value, not one.** {@link getValue} is the number
43
+ * `aria-valuenow` carries; {@link getDisplayValue} is the string in the middle of the
44
+ * dial, which the `suffix` input can make `"50%"` while the announced value stays
45
+ * `50`. They are separate methods because they can disagree, and because
46
+ * `showValue` can remove the text entirely without touching the value.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * const knob = await loader.getHarness(WrKnobHarness);
51
+ *
52
+ * await knob.setValue(60);
53
+ * expect(await knob.getValue()).toBe(60);
54
+ * expect(await knob.getDisplayValue()).toBe('60%');
55
+ * ```
56
+ *
57
+ * @see https://ngwr.dev/guides/testing
58
+ */
59
+ declare class WrKnobHarness extends ComponentHarness {
60
+ static hostSelector: string;
61
+ /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */
62
+ static with(options?: WrKnobHarnessFilters): HarnessPredicate<WrKnobHarness>;
63
+ private readonly surface;
64
+ /** The value the dial announces, from `aria-valuenow`. */
65
+ getValue(): Promise<number>;
66
+ /** The bottom of the range, from `aria-valuemin`. */
67
+ getMin(): Promise<number>;
68
+ /** The top of the range, from `aria-valuemax`. */
69
+ getMax(): Promise<number>;
70
+ /**
71
+ * The text printed in the middle of the dial, suffix included, or `null` when the
72
+ * knob was built with `[showValue]="false"`.
73
+ *
74
+ * Not the same question as {@link getValue}: with a `suffix` the two disagree by
75
+ * design, and a screen reader is told the bare number — the component publishes no
76
+ * `aria-valuetext`, so `50` is what gets announced for a dial that reads `50%`.
77
+ */
78
+ getDisplayValue(): Promise<string | null>;
79
+ /** The suffix alone, or `null` when there is none (or no value text at all). */
80
+ getSuffix(): Promise<string | null>;
81
+ /** The dial's accessible name — `role="slider"` needs one. */
82
+ getLabel(): Promise<string | null>;
83
+ /** Whether the dial is disabled, from `aria-disabled`. */
84
+ isDisabled(): Promise<boolean>;
85
+ /** Whether the dial is read-only, from `aria-readonly`. */
86
+ isReadonly(): Promise<boolean>;
87
+ /**
88
+ * Whether the dial is a tab stop.
89
+ *
90
+ * The component drops it out of the tab order for BOTH disabled and read-only,
91
+ * which is worth knowing: a read-only slider that a keyboard user cannot reach is
92
+ * also one they cannot read, and this is the method that says so.
93
+ */
94
+ isFocusable(): Promise<boolean>;
95
+ /** Move keyboard focus to the dial. */
96
+ focus(): Promise<void>;
97
+ /** Whether the dial currently holds focus. */
98
+ isFocused(): Promise<boolean>;
99
+ /** Blur the dial — which is what emits `touch` for a knob moved with the keyboard. */
100
+ blur(): Promise<void>;
101
+ /**
102
+ * Press one arrow key.
103
+ *
104
+ * Right and Up raise the value, Left and Down lower it — all four are live on this
105
+ * component, which has one axis and no mirroring to do. `shift` multiplies the step
106
+ * by ten.
107
+ */
108
+ pressArrow(arrow: Arrow, options?: {
109
+ shift?: boolean;
110
+ }): Promise<void>;
111
+ /** Press Home — the bottom of the range. */
112
+ pressHome(): Promise<void>;
113
+ /**
114
+ * Press End — the top of the range, or the last value ON the grid below it: a range
115
+ * that is not a whole number of steps cannot reach its own maximum.
116
+ */
117
+ pressEnd(): Promise<void>;
118
+ /**
119
+ * Walk the dial to a value with the arrow keys, and assert it landed there.
120
+ *
121
+ * It measures the step as it goes rather than being told it — `step` is an input
122
+ * with no DOM presence, and the value can arrive off the grid, since an external
123
+ * write is clamped but never snapped. So the first press may move by less than a
124
+ * step (it snaps onto the grid) and the walk adapts, which also means the ten-step
125
+ * hops only start once a real step has been seen.
126
+ *
127
+ * Throws rather than half-arriving, and the two reasons read differently: a target
128
+ * outside `[min, max]` is refused before anything moves, while a target BETWEEN two
129
+ * grid points is only discovered by overshooting it, and says where it landed. A
130
+ * dial that will not move at all — disabled, read-only — is the third.
131
+ */
132
+ setValue(target: number): Promise<void>;
133
+ /**
134
+ * Where the handle dot sits, in the SVG's own 100×100 viewBox units.
135
+ *
136
+ * The dial's whole visual state is an arc path and this dot, and neither can be
137
+ * measured in a unit test — but the dot's centre is written as plain `cx` / `cy`
138
+ * attributes, so it reads with no layout at all. That makes it the evidence that
139
+ * the drawing followed the value: at `min` the handle is at the 7 o'clock end of
140
+ * the arc, at `max` the 5 o'clock end.
141
+ */
142
+ getHandlePosition(): Promise<{
143
+ x: number;
144
+ y: number;
145
+ }>;
146
+ private numeric;
147
+ }
148
+
149
+ export { WrKnobHarness };
150
+ export type { WrKnobHarnessFilters };