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,196 @@
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-circular-text>` a harness query matches. */
11
+ interface WrCircularTextHarnessFilters extends BaseHarnessFilters {
12
+ /**
13
+ * Match the string the ring announces — the readable copy, not the characters.
14
+ * A string is an exact match, a RegExp is tested.
15
+ *
16
+ * Matched against the accessible copy rather than the host's text because the host
17
+ * carries both: one readable span AND one span per character, so its text reads back
18
+ * as the string twice over and a filter written against it would depend on the split.
19
+ */
20
+ readonly text?: string | RegExp;
21
+ /**
22
+ * Match on how many characters the ring was laid out from — the divisor every angle is
23
+ * derived from, and the blunt way to address one of two rings without spelling their
24
+ * strings out.
25
+ */
26
+ readonly characterCount?: number;
27
+ }
28
+
29
+ /**
30
+ * @license
31
+ *
32
+ * Use of this source code is governed by an MIT-style license that can be
33
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
34
+ */
35
+
36
+ /**
37
+ * Test harness for `<wr-circular-text>`.
38
+ *
39
+ * **The ring's geometry is the component's real output, and it is exact without a
40
+ * browser.** Each character is placed by an inline `transform` the component computes —
41
+ * `rotate(360 / N × i deg)` and then a push outward by the orbit radius — so
42
+ * {@link getCharacterAngles} reads the entire layout back as numbers. A broken circle
43
+ * shows up as uneven angles, as an offset that varies per character, or as a surrogate
44
+ * pair torn into two glyphs, and none of those need a single measured pixel.
45
+ *
46
+ * **The spin does not exist in the DOM, and every method that would report it is absent
47
+ * on purpose.** The rotation is one Web Animation on the ring wrapper, advanced by a
48
+ * compositor, and `spinDuration` reaches nothing but that animation's `duration` option
49
+ * — so there is no `getSpinDurationSeconds()` (it would report whatever the spec's own
50
+ * `animate` stub recorded, which is the test reading back its own fixture), no
51
+ * `isSpinning()` and no `getRotationAngle()` (both live on `Animation.currentTime`, in an
52
+ * API jsdom does not implement). There is no `hover()` either: hovering swaps that
53
+ * duration through listeners on the host and changes not one attribute, so a harness
54
+ * hover would appear to work and assert nothing. `pause()` / `resume()` are missing for
55
+ * the same reason — the state is the private `Animation` object, never the document.
56
+ * `respectsReducedMotion()` is the same shape once more: the component honours it by NOT
57
+ * calling `animate`, which is a spec assertion against a stub rather than something a
58
+ * harness can read.
59
+ *
60
+ * **The radius is exposed as text, not as pixels.** `--wr-circular-text-size` and
61
+ * `--wr-circular-text-radius` are declared in the stylesheet, so a test running no
62
+ * stylesheets reads them back empty — hence {@link getOrbitOffsets} hands over the CSS
63
+ * each character carries verbatim instead of a `getRadiusPx()` nobody could compute, and
64
+ * there is no `getCharacterPositions()`: every rect in a unit test is 0×0.
65
+ *
66
+ * **{@link isBonkers} is the one hover mode with a DOM footprint, and the INPUT sets it,
67
+ * not the pointer.** `onHover="goBonkers"` puts the modifier on the host at render time;
68
+ * the scale it enables is a CSS `:hover` rule the harness cannot see. Reading it as "the
69
+ * pointer is over the ring" is exactly backwards.
70
+ *
71
+ * **What is announced and what is drawn are two questions.** The string is carried once
72
+ * in a visually-hidden span, and the ring as a whole is `aria-hidden` so a screen reader
73
+ * hears the word rather than spelling it out — {@link getAccessibleText} is the first,
74
+ * {@link getText} the second, and a spec that read only the second would pass on a
75
+ * component announcing `N. G. W. R.`
76
+ *
77
+ * jsdom implements no Web Animations API and this component animates on mount, so a spec
78
+ * has to supply one of the two halves before the fixture is created — either the API, or
79
+ * a reason not to reach for it:
80
+ *
81
+ * ```ts
82
+ * // Either: mount under reduced motion, where the component never animates at all.
83
+ * // (jsdom has no matchMedia, so WrPlatform must be provided as a value.)
84
+ * TestBed.configureTestingModule({ providers: [{ provide: WrPlatform, useValue: reducedMotion }] });
85
+ *
86
+ * // Or: stub the API. Save the descriptor and put it back by hand in afterEach — this
87
+ * // is an assignment, not a spy, so it leaks into whichever file vitest runs next.
88
+ * Reflect.set(Element.prototype, 'animate', () => ({ cancel: () => undefined }));
89
+ * ```
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * const ring = await loader.getHarness(WrCircularTextHarness.with({ text: 'NGWR' }));
94
+ *
95
+ * expect(await ring.getCharacters()).toEqual(['N', 'G', 'W', 'R']);
96
+ * expect(await ring.getCharacterAngles()).toEqual([0, 90, 180, 270]);
97
+ * expect(await ring.getText()).toBe(await ring.getAccessibleText());
98
+ * ```
99
+ *
100
+ * @see https://ngwr.dev/guides/testing
101
+ */
102
+ declare class WrCircularTextHarness extends ComponentHarness {
103
+ static hostSelector: string;
104
+ /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */
105
+ static with(options?: WrCircularTextHarnessFilters): HarnessPredicate<WrCircularTextHarness>;
106
+ private readonly chars;
107
+ /**
108
+ * The whole string, as the one readable copy holds it.
109
+ *
110
+ * All a screen reader gets, the ring itself being out of the tree — lose this span and
111
+ * the component becomes a nameless pile of hidden letters, with no other symptom
112
+ * anywhere in the DOM.
113
+ *
114
+ * Untrimmed, deliberately: a ring is usually given a separator-padded string like
115
+ * `'HELLO * NGWR * '`, whose trailing space is a real character that takes a slot on
116
+ * the circle. {@link getText} is meant to compare equal to this, and a trimmed read on
117
+ * either side would forgive losing it.
118
+ */
119
+ getAccessibleText(): Promise<string>;
120
+ /**
121
+ * The characters laid out around the circle, in DOM order.
122
+ *
123
+ * Pins the splitting rule — `Array.from(text())`, so a surrogate pair is ONE slot and
124
+ * not two broken halves, and a space is its own slot rather than being collapsed away.
125
+ * DOM order is the only order there is: every character is absolutely positioned, so
126
+ * the sequence here is what the angles below are indexed by.
127
+ */
128
+ getCharacters(): Promise<string[]>;
129
+ /**
130
+ * Everything the ring draws, put back together in DOM order.
131
+ *
132
+ * The assertion this exists for is `getText() === getAccessibleText()`: the split has
133
+ * to be lossless. Read from the character spans rather than from the host, because the
134
+ * host also holds the readable copy and would report the string twice over.
135
+ */
136
+ getText(): Promise<string>;
137
+ /** How many slots the circle was divided into — the divisor every angle is derived from. */
138
+ getCharacterCount(): Promise<number>;
139
+ /**
140
+ * Where each character sits on the circle, in degrees, in DOM order.
141
+ *
142
+ * The component's whole geometric output, and the one thing that tells a ring from a
143
+ * crowded arc: the angles must start at 0 and be exactly `360 / N` apart. Empty text
144
+ * yields an empty array — no characters, no angles.
145
+ *
146
+ * Throws for a character carrying no rotation of its own rather than reporting `0` or
147
+ * `NaN` for it. Both of those describe a ring collapsed onto a single point as though
148
+ * nothing were wrong, and that is precisely the failure this method exists to catch.
149
+ */
150
+ getCharacterAngles(): Promise<number[]>;
151
+ /**
152
+ * How far each character is pushed out from the centre, as the CSS the component wrote.
153
+ *
154
+ * A string rather than a number, and that is the honest answer: the distance is
155
+ * `calc(-1 * var(--wr-circular-text-radius))`, a custom property the STYLESHEET
156
+ * declares, so there is no pixel value to resolve in a test that loads no stylesheet.
157
+ * What matters is assertable anyway — every character must be pushed out by the SAME
158
+ * offset, along the same axis. A per-character or axis-mixed one is the classic bug
159
+ * that turns the ring into a diagonal line, and it survives every other check here
160
+ * because the angles stay perfect while it happens.
161
+ *
162
+ * Throws on the same unplaced character {@link getCharacterAngles} does, for the same
163
+ * reason.
164
+ */
165
+ getOrbitOffsets(): Promise<string[]>;
166
+ /**
167
+ * Whether the host carries the `wr-circular-text--bonkers` modifier.
168
+ *
169
+ * Set from the `onHover` INPUT being `'goBonkers'` — at render time, before anyone has
170
+ * hovered anything — which is the one assumption a spec author makes backwards here.
171
+ * It is also the only one of the four hover modes with any DOM footprint at all; the
172
+ * other three exist purely as a number handed to a Web Animation.
173
+ */
174
+ isBonkers(): Promise<boolean>;
175
+ /**
176
+ * Whether the ring is out of the accessibility tree.
177
+ *
178
+ * ONE attribute answers this, and that is the contract rather than an approximation:
179
+ * `aria-hidden` sits on the `__spin` wrapper, so the letters underneath inherit it and
180
+ * none of them carries the attribute itself — which keeps it off the nodes the rotation
181
+ * touches. Asserting it per character would fail on a component that is behaving.
182
+ *
183
+ * Answers for the wrapper even when the text is empty, and that is deliberate: the
184
+ * wrapper is always rendered, so there is no vacuous case to guard against — a ring
185
+ * with nothing in it is still correctly hidden or incorrectly readable.
186
+ *
187
+ * Assert it together with {@link getAccessibleText}: hidden letters AND no readable
188
+ * copy is an element with no accessible name at all, which passes either check alone.
189
+ */
190
+ areCharactersHiddenFromAssistiveTech(): Promise<boolean>;
191
+ /** Each character's placement, parsed once out of the inline transform it carries. */
192
+ private placements;
193
+ }
194
+
195
+ export { WrCircularTextHarness };
196
+ export type { WrCircularTextHarnessFilters };
@@ -0,0 +1,291 @@
1
+ import { BaseHarnessFilters, ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';
2
+ import { WrSegmentedHarness } from 'ngwr/segmented/testing';
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
+ /** Which set of numeric fields the picker is showing. */
12
+ type WrColorPickerTab = 'hex' | 'rgb' | 'hsl';
13
+ /** Narrows which `<wr-color-picker>` a harness query matches. */
14
+ interface WrColorPickerHarnessFilters extends BaseHarnessFilters {
15
+ /**
16
+ * Match the picker's current colour, as the preview swatch paints it — always
17
+ * hex, whatever `format` writes into `value`. A string is an exact match, a
18
+ * RegExp is tested.
19
+ */
20
+ readonly color?: string | RegExp;
21
+ /** Match only pickers showing this tab. */
22
+ readonly tab?: WrColorPickerTab;
23
+ /** Match only enabled (`false`) or only disabled (`true`) pickers. */
24
+ readonly disabled?: boolean;
25
+ }
26
+ /** Narrows which `[wrColorPickerTrigger]` a harness query matches. */
27
+ interface WrColorPickerTriggerHarnessFilters extends BaseHarnessFilters {
28
+ /** Match the trigger's own text, an exact string or a RegExp. */
29
+ readonly text?: string | RegExp;
30
+ /** Match only open (`true`) or only closed (`false`) triggers. */
31
+ readonly open?: boolean;
32
+ }
33
+ /** The percentage each of the picker's thumbs sits at along its surface. */
34
+ interface WrColorPickerThumbs {
35
+ /** Saturation across, value down — both 0–100, and `y` is inverted (0 % is full brightness). */
36
+ readonly sv: {
37
+ readonly x: number;
38
+ readonly y: number;
39
+ };
40
+ /** Hue along its slider, 0–100 (so 50 is 180°). */
41
+ readonly hue: number;
42
+ /** Alpha along its slider, 0–100, or `null` when the picker has no alpha. */
43
+ readonly alpha: number | null;
44
+ }
45
+
46
+ /**
47
+ * @license
48
+ *
49
+ * Use of this source code is governed by an MIT-style license that can be
50
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
51
+ */
52
+
53
+ /**
54
+ * Test harness for `<wr-color-picker>` — the inline picker, and the one the
55
+ * `[wrColorPickerTrigger]` directive mounts in an overlay.
56
+ *
57
+ * **What this harness will not do: drag.** The SV canvas and the hue / alpha
58
+ * sliders are pointer surfaces that read `getBoundingClientRect()` and divide by
59
+ * its width. A unit test has no layout, so every box is 0×0 and the division is
60
+ * `NaN` — a `setHue()` built on a synthetic pointer event would report success and
61
+ * write nothing, or write garbage. The numeric fields reach the same state and are
62
+ * also the only keyboard path the component has, so they are the whole write API
63
+ * here. {@link getThumbs} reads where the thumbs ARE, from the percentages the
64
+ * component writes inline, which is how a spec checks a surface followed a value.
65
+ *
66
+ * **And it will not read the preview swatch.** The preview paints the current
67
+ * colour as a CSS background, and jsdom normalises that to `rgb(…)` — dropping the
68
+ * alpha channel entirely, so `#ff880080` and `#ff8800ff` come back identical. A
69
+ * `getColor()` reading it would answer one thing in a unit test and another in a
70
+ * browser. {@link getHex} is the colour: the field holds the canonical 8-digit
71
+ * string, alpha included, whatever `format` writes into `value`.
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * const picker = await loader.getHarness(WrColorPickerHarness);
76
+ *
77
+ * await picker.setHex('#3969e2');
78
+ * expect(await picker.getHex()).toBe('#3969e2ff');
79
+ *
80
+ * await picker.setTab('rgb');
81
+ * expect(await picker.getRgb()).toEqual({ r: 57, g: 105, b: 226 });
82
+ * ```
83
+ *
84
+ * @see https://ngwr.dev/guides/testing
85
+ */
86
+ declare class WrColorPickerHarness extends ComponentHarness {
87
+ static hostSelector: string;
88
+ /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */
89
+ static with(options?: WrColorPickerHarnessFilters): HarnessPredicate<WrColorPickerHarness>;
90
+ private readonly hexField;
91
+ private readonly tabs;
92
+ /**
93
+ * The tab switcher, as a {@link WrSegmentedHarness}.
94
+ *
95
+ * Handed back rather than re-queried: the switcher IS a `<wr-segmented>`, which
96
+ * already knows that the roving tab stop is not the selection and how the sliding
97
+ * thumb reports itself. Re-reading its buttons here would be a second, worse copy.
98
+ */
99
+ getTabs(): Promise<WrSegmentedHarness>;
100
+ /** Which set of numeric fields is showing. */
101
+ getTab(): Promise<WrColorPickerTab>;
102
+ /** Show a different set of numeric fields. An already-active tab is left alone. */
103
+ setTab(tab: WrColorPickerTab): Promise<void>;
104
+ /**
105
+ * The canonical hex string, as the HEX field shows it — 8 digits while `alpha` is
106
+ * on, 6 while it is off.
107
+ *
108
+ * This is the picker's colour, and deliberately not the same thing as its `value`:
109
+ * `format` decides whether `value` is written as hex, `rgb()` or `hsl()`, while the
110
+ * field always shows hex. A spec that cares about the emitted string should read
111
+ * the bound signal; one that cares about the COLOUR should read this.
112
+ *
113
+ * Throws off the HEX tab, where the field does not exist — see {@link setTab}.
114
+ */
115
+ getHex(): Promise<string>;
116
+ /**
117
+ * Type a colour into the HEX field.
118
+ *
119
+ * Typed rather than assigned, because that is the path the component is built
120
+ * around: it re-parses on every keystroke and applies the first spelling that
121
+ * parses, so a partial `#ff8` commits a colour on its way to `#ff8800`. An
122
+ * unparseable string leaves the committed colour ALONE and lives in the field
123
+ * until {@link blurHex} snaps it back — assert that rather than expecting a throw.
124
+ */
125
+ setHex(value: string): Promise<void>;
126
+ /**
127
+ * Blur the HEX field — re-syncing it with the committed colour and emitting
128
+ * `touch`.
129
+ *
130
+ * The re-sync is the visible half: a field left holding text that never parsed
131
+ * goes back to the canonical hex here, and nowhere else.
132
+ */
133
+ blurHex(): Promise<void>;
134
+ /** Whether the HEX field currently holds focus. */
135
+ isHexFocused(): Promise<boolean>;
136
+ /** The three RGB channels as the RGB tab shows them. Throws off that tab. */
137
+ getRgb(): Promise<{
138
+ r: number;
139
+ g: number;
140
+ b: number;
141
+ }>;
142
+ /** Write one RGB channel, 0–255. Out-of-range numbers are clamped by the component. */
143
+ setRgbChannel(channel: 'r' | 'g' | 'b', value: number): Promise<void>;
144
+ /**
145
+ * Hue, saturation and lightness as the HSL tab shows them — degrees and whole
146
+ * percents, already rounded for display. Throws off that tab.
147
+ */
148
+ getHsl(): Promise<{
149
+ h: number;
150
+ s: number;
151
+ l: number;
152
+ }>;
153
+ /** Write one HSL channel — `h` in degrees, `s` / `l` in percent. */
154
+ setHslChannel(channel: 'h' | 's' | 'l', value: number): Promise<void>;
155
+ /**
156
+ * Alpha as the whole percent the numeric tabs show, or `null` when the picker was
157
+ * built without alpha.
158
+ *
159
+ * Only the RGB and HSL tabs carry the field — the HEX tab spells alpha into the
160
+ * hex string instead — so this throws on the HEX tab rather than answering `null`,
161
+ * which would read as "this picker has no alpha".
162
+ */
163
+ getAlphaPercent(): Promise<number | null>;
164
+ /** Write alpha as a whole percent, 0–100. */
165
+ setAlphaPercent(value: number): Promise<void>;
166
+ /**
167
+ * Whether the picker offers alpha at all.
168
+ *
169
+ * From the host modifier, which is the one answer that holds on every tab: the
170
+ * alpha SLIDER is always rendered when it is on, while the `A%` field only exists
171
+ * on the two numeric tabs.
172
+ */
173
+ hasAlpha(): Promise<boolean>;
174
+ /**
175
+ * Whether the picker refuses interaction.
176
+ *
177
+ * Read from the modifier the host paints rather than from a field's `disabled`
178
+ * property, because the fields come and go with the tab while the state does not —
179
+ * and the drag surfaces, which have no `disabled` of their own, are gated by the
180
+ * same signal.
181
+ */
182
+ isDisabled(): Promise<boolean>;
183
+ /** The preset colours offered under the fields, as the strings the consumer passed. */
184
+ getSwatches(): Promise<string[]>;
185
+ /**
186
+ * Click a preset.
187
+ *
188
+ * Matched on the string the consumer passed, which is what the button publishes as
189
+ * its accessible name — the rendered background is a CSS colour and would come back
190
+ * normalised and alpha-stripped. Throws when nothing matches, naming what is offered.
191
+ */
192
+ pickSwatch(color: string): Promise<void>;
193
+ /**
194
+ * Where the three thumbs sit, in percent along their surfaces.
195
+ *
196
+ * The component writes these as inline `left` / `top` percentages, so they are
197
+ * readable with no layout at all — which makes them the only evidence a spec has
198
+ * that the surfaces followed a colour set through the fields. A measured position
199
+ * would be zero for all three.
200
+ */
201
+ getThumbs(): Promise<WrColorPickerThumbs>;
202
+ /** The HEX field's value without the tab check — for filters, which must not throw. */
203
+ private readHex;
204
+ private requireHexField;
205
+ private channelValues;
206
+ private writeChannel;
207
+ private requireTab;
208
+ /** Every numeric input of the current tab, keyed by the label printed under it. */
209
+ private inputsByLabel;
210
+ }
211
+
212
+ /**
213
+ * @license
214
+ *
215
+ * Use of this source code is governed by an MIT-style license that can be
216
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
217
+ */
218
+
219
+ /**
220
+ * Test harness for `[wrColorPickerTrigger]` — the element that opens a
221
+ * `<wr-color-picker>` in an overlay.
222
+ *
223
+ * The trigger stays in your fixture; the picker it opens does not. So this harness
224
+ * is loaded from the fixture loader like any element, and {@link getPicker} reaches
225
+ * ACROSS into the overlay container for you — scoped by the panel id the trigger
226
+ * publishes through `aria-controls`, never by `.wr-color-picker-overlay`, which
227
+ * answers with whichever trigger opened first.
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * const trigger = await loader.getHarness(WrColorPickerTriggerHarness);
232
+ * const picker = await trigger.open();
233
+ *
234
+ * await picker.setHex('#3969e2');
235
+ * await trigger.sendEscape();
236
+ * expect(await trigger.isOpen()).toBe(false);
237
+ * ```
238
+ *
239
+ * @see https://ngwr.dev/guides/testing
240
+ */
241
+ declare class WrColorPickerTriggerHarness extends ComponentHarness {
242
+ static hostSelector: string;
243
+ /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */
244
+ static with(options?: WrColorPickerTriggerHarnessFilters): HarnessPredicate<WrColorPickerTriggerHarness>;
245
+ /** The trigger's own text, trimmed. */
246
+ getText(): Promise<string>;
247
+ /** Whether the picker is showing, from the trigger's `aria-expanded`. */
248
+ isOpen(): Promise<boolean>;
249
+ /**
250
+ * Whether the trigger refuses to open.
251
+ *
252
+ * From `aria-disabled`, which the directive writes for its own `disabled` input.
253
+ * The host is usually a `<button>`, but the directive does not touch its `disabled`
254
+ * PROPERTY — a consumer who binds that as well turns the element off at the DOM
255
+ * level, which is a stronger thing and reads here too.
256
+ */
257
+ isDisabled(): Promise<boolean>;
258
+ /**
259
+ * The picker this trigger opened, or `null` while it is closed.
260
+ *
261
+ * Scoped by the id in `aria-controls`, so two triggers open at once — which
262
+ * nothing in the component prevents, since only an outside click closes one —
263
+ * answer with their own panels rather than with whichever came first.
264
+ */
265
+ getPicker(): Promise<WrColorPickerHarness | null>;
266
+ /**
267
+ * Open the picker and hand it back. An already-open trigger is left alone.
268
+ *
269
+ * Throws on a disabled trigger rather than answering `null` three lines later:
270
+ * `toggle()` returns early while `disabled` is set, so the click lands and nothing
271
+ * happens at all.
272
+ */
273
+ open(): Promise<WrColorPickerHarness>;
274
+ /** Close the picker. An already-closed trigger is left alone. */
275
+ close(): Promise<void>;
276
+ /** Click the trigger once, whichever way that takes it. */
277
+ toggle(): Promise<void>;
278
+ /**
279
+ * Press Escape.
280
+ *
281
+ * Sent to the TRIGGER, and it still reaches the picker: the overlay listens through
282
+ * the CDK's keyboard dispatcher, which keeps one document listener and routes to
283
+ * the top-most overlay — so this does not depend on where focus happens to be.
284
+ */
285
+ sendEscape(): Promise<void>;
286
+ /** The role the trigger claims to open — `dialog`. */
287
+ getHasPopup(): Promise<string | null>;
288
+ }
289
+
290
+ export { WrColorPickerHarness, WrColorPickerTriggerHarness };
291
+ export type { WrColorPickerHarnessFilters, WrColorPickerTab, WrColorPickerThumbs, WrColorPickerTriggerHarnessFilters };
@@ -236,7 +236,17 @@ declare class WrColorPicker implements FormValueControl<string> {
236
236
  protected readonly alphaPercent: _angular_core.Signal<number>;
237
237
  protected readonly tabOptions: readonly WrSegmentedOption<Tab>[];
238
238
  protected readonly classes: _angular_core.Signal<string>;
239
- /** Last string we pushed into `value`; lets the sync effect ignore our own writes. */
239
+ /**
240
+ * The one write we just made, held only until the sync effect has seen it.
241
+ *
242
+ * It exists so a drag keeps its continuous HSV values instead of being
243
+ * re-quantised through its own hex round-trip. It used to be set and never
244
+ * cleared, which turned "ignore this write" into "ignore this STRING,
245
+ * forever": after the picker emitted `#ff0000` once, an app writing
246
+ * `#ff0000` back — a reset button, a form patch, an undo — was silently
247
+ * dropped for the rest of the component's life, and the surface kept
248
+ * whatever colour the user had dragged to.
249
+ */
240
250
  private lastEmitted;
241
251
  constructor();
242
252
  protected onPointerDown(event: PointerEvent, surface: Edges): void;
@@ -301,6 +311,8 @@ declare class WrColorPickerTrigger {
301
311
  private readonly destroyRef;
302
312
  protected readonly isOpen: _angular_core.WritableSignal<boolean>;
303
313
  private overlayRef;
314
+ /** @internal Public so the host binding can read it. */
315
+ protected readonly panelId: string;
304
316
  constructor();
305
317
  /** Toggle the picker. Open if closed, close if open. */
306
318
  toggle(): void;
@@ -66,7 +66,21 @@ declare class WrContextMenu {
66
66
  */
67
67
  protected readonly openMenuId: _angular_core.WritableSignal<string | null>;
68
68
  private overlayRef;
69
- private closingTimer;
69
+ /**
70
+ * Panes waiting out their exit animation, keyed by the timer that disposes
71
+ * them.
72
+ *
73
+ * A SET, because one handle cannot hold two closings. It used to be a single
74
+ * `closingTimer`, and the second close inside the 220ms window cleared the
75
+ * first one's timer instead of its own — so the previous pane's `dispose()`
76
+ * never ran. `onContextMenu` closes and immediately re-opens, so right-clicking
77
+ * one target faster than the animation orphaned a pane per click: an
78
+ * `opacity: 0` box with `pointer-events: auto` eating clicks, a never-destroyed
79
+ * embedded view, a capture-phase document scroll listener still moving it, and
80
+ * an undetached ref still registered with CDK's keyboard dispatcher — which
81
+ * swallowed Escape for the whole page.
82
+ */
83
+ private readonly closingPanes;
70
84
  private leaveTimer;
71
85
  private pressTimer;
72
86
  private pressX;
@@ -1,4 +1,6 @@
1
- import { BaseHarnessFilters, ComponentHarness, HarnessPredicate, TestElement, HarnessLoader } from '@angular/cdk/testing';
1
+ import { WrCalendarDayHarness } from 'ngwr/calendar/testing';
2
+ export { WrCalendarDayHarness as WrDatePickerDayHarness } from 'ngwr/calendar/testing';
3
+ import { BaseHarnessFilters, ComponentHarness, TestElement, HarnessLoader, HarnessPredicate } from '@angular/cdk/testing';
2
4
 
3
5
  /**
4
6
  * @license
@@ -77,68 +79,6 @@ interface WrDatePickerTimeFields {
77
79
  readonly seconds?: number;
78
80
  }
79
81
 
80
- /**
81
- * @license
82
- *
83
- * Use of this source code is governed by an MIT-style license that can be
84
- * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
85
- */
86
-
87
- /**
88
- * Test harness for one day cell of the calendar inside a date-picker popup.
89
- *
90
- * Reached through `WrDatePickerHarness.getDay()` / `getDays()` (or the range
91
- * picker's), which scope it to ONE picker's panel — the cells are in the overlay
92
- * container, so a bare query would answer with whichever picker opened first.
93
- *
94
- * State is read the way a screen reader reads it (`role`, `aria-selected`,
95
- * `aria-disabled`) with the `.wr-calendar__day--*` modifiers filling in what
96
- * ARIA has no word for: today, the spill days of the neighbouring months, and
97
- * the interior of a picked range.
98
- *
99
- * @see https://ngwr.dev/guides/testing
100
- */
101
- declare class WrDatePickerDayHarness extends ComponentHarness {
102
- static hostSelector: string;
103
- /** Build a predicate that narrows the query. */
104
- static with(options?: WrDatePickerDayHarnessFilters): HarnessPredicate<WrDatePickerDayHarness>;
105
- /** The day number as rendered. */
106
- getText(): Promise<string>;
107
- /** The day number as a number, for mapping a list of cells to dates. */
108
- getDayOfMonth(): Promise<number>;
109
- /** `'gridcell'` — the cell is a button, and the grid semantics are what a spec should assert. */
110
- getRole(): Promise<string | null>;
111
- /** Whether this day is (one end of) the current selection. */
112
- isSelected(): Promise<boolean>;
113
- /**
114
- * Whether the day refuses selection — `min` / `max` or `dateFilter` ruled it
115
- * out, or the whole calendar is disabled.
116
- */
117
- isDisabled(): Promise<boolean>;
118
- /** Whether the cell is today's date. */
119
- isToday(): Promise<boolean>;
120
- /**
121
- * Whether the cell belongs to a neighbouring month. The grid is always 6x7, so
122
- * every month is padded with days that are selectable but not "in" the month —
123
- * which is why {@link WrDatePickerHarness.getDay} skips them.
124
- */
125
- isOutOfMonth(): Promise<boolean>;
126
- /** Whether the day lies strictly between the two ends of a picked range. */
127
- isInRange(): Promise<boolean>;
128
- /**
129
- * Whether this is the roving cell — the grid's single tab stop, and where the
130
- * arrow keys move from.
131
- *
132
- * At most ONE cell answers `true`, and it is not always one: the calendar's
133
- * roving date does not follow `next()` / `previous()`, so a grid navigated away
134
- * from that month has no active cell (and, deliberately or not, no tab stop)
135
- * until a cell is clicked or the arrows move it.
136
- */
137
- isActive(): Promise<boolean>;
138
- /** Click the cell. A disabled cell swallows the click — assert, do not assume. */
139
- click(): Promise<void>;
140
- }
141
-
142
82
  /**
143
83
  * @license
144
84
  *
@@ -277,12 +217,12 @@ declare abstract class WrDatePickerHarnessBase extends ComponentHarness {
277
217
  * them, since the grid is always six weeks and pads into the neighbouring
278
218
  * months.
279
219
  */
280
- getDays(filters?: WrDatePickerDayHarnessFilters): Promise<WrDatePickerDayHarness[]>;
220
+ getDays(filters?: WrDatePickerDayHarnessFilters): Promise<WrCalendarDayHarness[]>;
281
221
  /**
282
222
  * One day of the month on show. The padding days of the neighbouring months are
283
223
  * skipped, so `getDay(1)` is the 1st of THIS month and never the 1st of the next.
284
224
  */
285
- getDay(dayOfMonth: number): Promise<WrDatePickerDayHarness>;
225
+ getDay(dayOfMonth: number): Promise<WrCalendarDayHarness>;
286
226
  /** Click a day of the month on show. */
287
227
  selectDay(dayOfMonth: number): Promise<void>;
288
228
  /** The trigger button. Both components render it with the same class. */
@@ -514,5 +454,5 @@ declare class WrDateRangePickerHarness extends WrDatePickerHarnessBase {
514
454
  private timePanel;
515
455
  }
516
456
 
517
- export { WrDatePickerDayHarness, WrDatePickerHarness, WrDateRangePickerHarness };
457
+ export { WrDatePickerHarness, WrDateRangePickerHarness };
518
458
  export type { WrDatePickerDayHarnessFilters, WrDatePickerHarnessFilters, WrDatePickerTimeFields, WrDateRangePickerHarnessFilters };