@blotch/dolan 22.2.2 → 22.2.4

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 (259) hide show
  1. package/dist/commands/add.d.ts +6 -2
  2. package/dist/commands/add.js +41 -11
  3. package/dist/commands/add.js.map +1 -1
  4. package/dist/commands/add.test.js +162 -9
  5. package/dist/commands/add.test.js.map +1 -1
  6. package/dist/commands/diff.d.ts +2 -1
  7. package/dist/commands/diff.js +38 -25
  8. package/dist/commands/diff.js.map +1 -1
  9. package/dist/commands/diff.test.js +72 -11
  10. package/dist/commands/diff.test.js.map +1 -1
  11. package/dist/commands/doctor.js +228 -34
  12. package/dist/commands/doctor.js.map +1 -1
  13. package/dist/commands/doctor.test.js +367 -10
  14. package/dist/commands/doctor.test.js.map +1 -1
  15. package/dist/commands/eject.js +7 -0
  16. package/dist/commands/eject.js.map +1 -1
  17. package/dist/commands/eject.test.js +39 -0
  18. package/dist/commands/eject.test.js.map +1 -1
  19. package/dist/commands/info.js +3 -2
  20. package/dist/commands/info.js.map +1 -1
  21. package/dist/commands/init.d.ts +37 -1
  22. package/dist/commands/init.js +169 -40
  23. package/dist/commands/init.js.map +1 -1
  24. package/dist/commands/init.test.js +392 -27
  25. package/dist/commands/init.test.js.map +1 -1
  26. package/dist/commands/install-packages.d.ts +6 -0
  27. package/dist/commands/install-packages.js +20 -0
  28. package/dist/commands/install-packages.js.map +1 -0
  29. package/dist/commands/install-packages.test.d.ts +1 -0
  30. package/dist/commands/install-packages.test.js +55 -0
  31. package/dist/commands/install-packages.test.js.map +1 -0
  32. package/dist/commands/list.js +21 -14
  33. package/dist/commands/list.js.map +1 -1
  34. package/dist/commands/list.test.js +62 -5
  35. package/dist/commands/list.test.js.map +1 -1
  36. package/dist/commands/outdated.js +11 -10
  37. package/dist/commands/outdated.js.map +1 -1
  38. package/dist/commands/outdated.test.js +20 -5
  39. package/dist/commands/outdated.test.js.map +1 -1
  40. package/dist/commands/remove.d.ts +4 -0
  41. package/dist/commands/remove.js +17 -4
  42. package/dist/commands/remove.js.map +1 -1
  43. package/dist/commands/remove.test.js +53 -2
  44. package/dist/commands/remove.test.js.map +1 -1
  45. package/dist/commands/theme.js +1 -1
  46. package/dist/commands/theme.js.map +1 -1
  47. package/dist/commands/update.d.ts +11 -0
  48. package/dist/commands/update.js +174 -58
  49. package/dist/commands/update.js.map +1 -1
  50. package/dist/commands/update.test.js +587 -33
  51. package/dist/commands/update.test.js.map +1 -1
  52. package/dist/index.js +7 -0
  53. package/dist/index.js.map +1 -1
  54. package/dist/lockfile.d.ts +7 -0
  55. package/dist/lockfile.js +36 -0
  56. package/dist/lockfile.js.map +1 -1
  57. package/dist/lockfile.test.d.ts +1 -0
  58. package/dist/lockfile.test.js +25 -0
  59. package/dist/lockfile.test.js.map +1 -0
  60. package/dist/registry.d.ts +14 -0
  61. package/dist/registry.js +78 -24
  62. package/dist/registry.js.map +1 -1
  63. package/dist/registry.test.js +119 -38
  64. package/dist/registry.test.js.map +1 -1
  65. package/dist/test-utils.d.ts +5 -0
  66. package/dist/test-utils.js +6 -1
  67. package/dist/test-utils.js.map +1 -1
  68. package/dist/testing/fire-lynx-event.d.ts +14 -0
  69. package/dist/testing/fire-lynx-event.js +19 -0
  70. package/dist/testing/fire-lynx-event.js.map +1 -0
  71. package/dist/testing/fire-lynx-event.test.d.ts +1 -0
  72. package/dist/testing/fire-lynx-event.test.js +44 -0
  73. package/dist/testing/fire-lynx-event.test.js.map +1 -0
  74. package/dist/testing/lynx-main-thread.d.ts +22 -0
  75. package/dist/testing/lynx-main-thread.js +11 -0
  76. package/dist/testing/lynx-main-thread.js.map +1 -0
  77. package/dist/testing/lynx-main-thread.test.d.ts +1 -0
  78. package/dist/testing/lynx-main-thread.test.js +24 -0
  79. package/dist/testing/lynx-main-thread.test.js.map +1 -0
  80. package/dist/testing/set-input-signal.d.ts +14 -0
  81. package/dist/testing/set-input-signal.js +21 -0
  82. package/dist/testing/set-input-signal.js.map +1 -0
  83. package/dist/testing/set-input-signal.test.d.ts +1 -0
  84. package/dist/testing/set-input-signal.test.js +20 -0
  85. package/dist/testing/set-input-signal.test.js.map +1 -0
  86. package/dist/ui/components/accordion/accordion.test.ts +158 -144
  87. package/dist/ui/components/accordion/accordion.ts +19 -14
  88. package/dist/ui/components/action-sheet/action-sheet.test.ts +283 -151
  89. package/dist/ui/components/action-sheet/action-sheet.ts +77 -43
  90. package/dist/ui/components/alert/alert.test.ts +67 -19
  91. package/dist/ui/components/alert/alert.ts +14 -16
  92. package/dist/ui/components/alert-dialog/alert-dialog.test.ts +208 -94
  93. package/dist/ui/components/alert-dialog/alert-dialog.ts +75 -38
  94. package/dist/ui/components/area-chart/area-chart.test.ts +134 -119
  95. package/dist/ui/components/area-chart/area-chart.ts +100 -315
  96. package/dist/ui/components/area-chart/index.ts +2 -3
  97. package/dist/ui/components/avatar/avatar.test.ts +89 -45
  98. package/dist/ui/components/badge/badge.test.ts +57 -23
  99. package/dist/ui/components/badge/badge.ts +12 -14
  100. package/dist/ui/components/bar-chart/bar-chart.test.ts +172 -45
  101. package/dist/ui/components/bar-chart/bar-chart.ts +140 -294
  102. package/dist/ui/components/bar-chart/index.ts +3 -3
  103. package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +386 -128
  104. package/dist/ui/components/bottom-sheet/bottom-sheet.ts +170 -53
  105. package/dist/ui/components/button/button.test.ts +611 -2
  106. package/dist/ui/components/button/button.ts +434 -49
  107. package/dist/ui/components/button/index.ts +2 -0
  108. package/dist/ui/components/button-group/button-group.test.ts +484 -2
  109. package/dist/ui/components/button-group/button-group.ts +380 -68
  110. package/dist/ui/components/button-group/index.ts +3 -0
  111. package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +183 -58
  112. package/dist/ui/components/candlestick-chart/candlestick-chart.ts +150 -323
  113. package/dist/ui/components/candlestick-chart/index.ts +3 -4
  114. package/dist/ui/components/card/card.test.ts +82 -62
  115. package/dist/ui/components/chart/chart-focus.test.ts +132 -0
  116. package/dist/ui/components/chart/chart-focus.ts +115 -0
  117. package/dist/ui/components/chart/chart-presets.test.ts +335 -0
  118. package/dist/ui/components/chart/chart-presets.ts +278 -0
  119. package/dist/ui/components/chart/chart-scene.test.ts +589 -0
  120. package/dist/ui/components/chart/chart-scene.ts +483 -0
  121. package/dist/ui/components/chart/chart-theme.test.ts +85 -0
  122. package/dist/ui/components/chart/chart-theme.ts +210 -0
  123. package/dist/ui/components/chart/chart-zoom.test.ts +159 -0
  124. package/dist/ui/components/chart/chart-zoom.ts +162 -0
  125. package/dist/ui/components/chart/chart.test.ts +1017 -0
  126. package/dist/ui/components/chart/chart.ts +934 -0
  127. package/dist/ui/components/chart/index.ts +24 -0
  128. package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
  129. package/dist/ui/components/checkbox/checkbox.ts +23 -23
  130. package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
  131. package/dist/ui/components/collapsible/collapsible.ts +19 -14
  132. package/dist/ui/components/context-menu/context-menu.test.ts +224 -0
  133. package/dist/ui/components/context-menu/context-menu.ts +110 -0
  134. package/dist/ui/components/context-menu/index.ts +1 -0
  135. package/dist/ui/components/dialog/dialog.test.ts +213 -108
  136. package/dist/ui/components/dialog/dialog.ts +72 -35
  137. package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
  138. package/dist/ui/components/dock/dock-scroll.ts +133 -0
  139. package/dist/ui/components/dock/dock.test.ts +749 -0
  140. package/dist/ui/components/dock/dock.ts +960 -0
  141. package/dist/ui/components/dock/index.ts +2 -0
  142. package/dist/ui/components/empty-state/empty-state.test.ts +28 -39
  143. package/dist/ui/components/glass/concentric-radius.test.ts +13 -0
  144. package/dist/ui/components/glass/concentric-radius.ts +17 -0
  145. package/dist/ui/components/glass/glass-design.test.ts +78 -0
  146. package/dist/ui/components/glass/glass-design.ts +119 -0
  147. package/dist/ui/components/glass/glass.test.ts +338 -0
  148. package/dist/ui/components/glass/glass.ts +360 -0
  149. package/dist/ui/components/glass/index.ts +18 -0
  150. package/dist/ui/components/icon/icon-theme.test.ts +169 -0
  151. package/dist/ui/components/icon/icon-theme.ts +227 -0
  152. package/dist/ui/components/icon/icon.test.ts +85 -25
  153. package/dist/ui/components/icon/icon.ts +38 -21
  154. package/dist/ui/components/input/input.test.ts +178 -101
  155. package/dist/ui/components/input/input.ts +15 -12
  156. package/dist/ui/components/label/label.test.ts +11 -24
  157. package/dist/ui/components/line-chart/index.ts +2 -3
  158. package/dist/ui/components/line-chart/line-chart.test.ts +149 -56
  159. package/dist/ui/components/line-chart/line-chart.ts +83 -241
  160. package/dist/ui/components/list/list.test.ts +119 -21
  161. package/dist/ui/components/list/list.ts +21 -15
  162. package/dist/ui/components/menu/index.ts +25 -0
  163. package/dist/ui/components/menu/menu-placement.test.ts +153 -0
  164. package/dist/ui/components/menu/menu-placement.ts +159 -0
  165. package/dist/ui/components/menu/menu-popup.test.ts +632 -0
  166. package/dist/ui/components/menu/menu-popup.ts +544 -0
  167. package/dist/ui/components/menu/menu.test.ts +429 -0
  168. package/dist/ui/components/menu/menu.ts +415 -0
  169. package/dist/ui/components/nav-drawer/nav-drawer.test.ts +435 -191
  170. package/dist/ui/components/nav-drawer/nav-drawer.ts +127 -56
  171. package/dist/ui/components/pagination/pagination.test.ts +177 -65
  172. package/dist/ui/components/pagination/pagination.ts +55 -8
  173. package/dist/ui/components/progress/progress.test.ts +102 -46
  174. package/dist/ui/components/progress/progress.ts +52 -54
  175. package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
  176. package/dist/ui/components/radio-group/radio-group.ts +24 -24
  177. package/dist/ui/components/scatter-chart/index.ts +2 -3
  178. package/dist/ui/components/scatter-chart/scatter-chart.test.ts +131 -26
  179. package/dist/ui/components/scatter-chart/scatter-chart.ts +104 -250
  180. package/dist/ui/components/scroll-area/scroll-area.test.ts +95 -24
  181. package/dist/ui/components/scroll-area/scroll-area.ts +8 -8
  182. package/dist/ui/components/select/select.test.ts +138 -78
  183. package/dist/ui/components/select/select.ts +17 -11
  184. package/dist/ui/components/separator/separator.test.ts +18 -17
  185. package/dist/ui/components/sheet/sheet.test.ts +64 -11
  186. package/dist/ui/components/sheet/sheet.ts +9 -1
  187. package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
  188. package/dist/ui/components/skeleton/skeleton.ts +10 -16
  189. package/dist/ui/components/spinner/spinner.test.ts +67 -22
  190. package/dist/ui/components/spinner/spinner.ts +48 -41
  191. package/dist/ui/components/switch/switch.test.ts +116 -46
  192. package/dist/ui/components/switch/switch.ts +8 -13
  193. package/dist/ui/components/tab-bar/index.ts +1 -0
  194. package/dist/ui/components/tab-bar/tab-bar.test.ts +280 -0
  195. package/dist/ui/components/tab-bar/tab-bar.ts +230 -0
  196. package/dist/ui/components/tabs/tabs.test.ts +215 -157
  197. package/dist/ui/components/tabs/tabs.ts +110 -118
  198. package/dist/ui/components/textarea/textarea.test.ts +206 -100
  199. package/dist/ui/components/textarea/textarea.ts +14 -12
  200. package/dist/ui/components/toast/toast.test.ts +436 -315
  201. package/dist/ui/components/toast/toast.ts +62 -55
  202. package/dist/ui/components/toggle/toggle.test.ts +247 -140
  203. package/dist/ui/theme/chart-colors.test.ts +35 -0
  204. package/dist/ui/theme/dark.css +46 -2
  205. package/dist/ui/theme/default.css +63 -7
  206. package/dist/ui/theme/icon-colors.test.ts +35 -0
  207. package/dist/ui/theme/rgb-channels.test.ts +84 -0
  208. package/dist/ui/theme/tailwind-build.test.ts +114 -0
  209. package/dist/ui/theme/tailwind-css.test.ts +83 -0
  210. package/dist/ui/theme/tailwind-plugin.test.ts +42 -2
  211. package/dist/ui/theme/tailwind-plugin.ts +98 -25
  212. package/dist/ui/theme/tailwind.css +113 -0
  213. package/dist/ui/utils/animate.test.ts +13 -1
  214. package/dist/utils/analyze.d.ts +14 -0
  215. package/dist/utils/analyze.js +15 -0
  216. package/dist/utils/analyze.js.map +1 -1
  217. package/dist/utils/analyze.test.js +14 -1
  218. package/dist/utils/analyze.test.js.map +1 -1
  219. package/dist/utils/installed.d.ts +29 -0
  220. package/dist/utils/installed.js +43 -0
  221. package/dist/utils/installed.js.map +1 -0
  222. package/dist/utils/installed.test.d.ts +1 -0
  223. package/dist/utils/installed.test.js +57 -0
  224. package/dist/utils/installed.test.js.map +1 -0
  225. package/dist/utils/interactive.d.ts +9 -0
  226. package/dist/utils/interactive.js +10 -0
  227. package/dist/utils/interactive.js.map +1 -0
  228. package/dist/utils/interactive.test.d.ts +1 -0
  229. package/dist/utils/interactive.test.js +24 -0
  230. package/dist/utils/interactive.test.js.map +1 -0
  231. package/dist/utils/packages.d.ts +36 -0
  232. package/dist/utils/packages.js +70 -0
  233. package/dist/utils/packages.js.map +1 -0
  234. package/dist/utils/packages.test.d.ts +1 -0
  235. package/dist/utils/packages.test.js +89 -0
  236. package/dist/utils/packages.test.js.map +1 -0
  237. package/dist/utils/resolve-paths.d.ts +17 -1
  238. package/dist/utils/resolve-paths.js +21 -3
  239. package/dist/utils/resolve-paths.js.map +1 -1
  240. package/dist/utils/resolve-paths.test.js +38 -2
  241. package/dist/utils/resolve-paths.test.js.map +1 -1
  242. package/dist/utils/tailwind.d.ts +25 -0
  243. package/dist/utils/tailwind.js +51 -0
  244. package/dist/utils/tailwind.js.map +1 -0
  245. package/dist/utils/tailwind.test.d.ts +1 -0
  246. package/dist/utils/tailwind.test.js +71 -0
  247. package/dist/utils/tailwind.test.js.map +1 -0
  248. package/package.json +13 -1
  249. package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -121
  250. package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -174
  251. package/dist/ui/components/button/button-render.test.ts +0 -113
  252. package/dist/ui/components/button-group/button-group-render.test.ts +0 -146
  253. package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +0 -174
  254. package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +0 -188
  255. package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +0 -525
  256. package/dist/ui/components/cartesian-chart/cartesian-chart.ts +0 -1140
  257. package/dist/ui/components/cartesian-chart/index.ts +0 -18
  258. package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -121
  259. package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -157
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  type ElementRef,
3
+ type OnDestroy,
3
4
  Component,
4
5
  ViewEncapsulation,
5
6
  computed,
@@ -29,7 +30,6 @@ import { cn } from '../../utils/cn';
29
30
 
30
31
  @Component({
31
32
  selector: 'ui-action-sheet',
32
- standalone: true,
33
33
  imports: [LYNX_ELEMENTS],
34
34
  encapsulation: ViewEncapsulation.None,
35
35
  template: `
@@ -37,9 +37,9 @@ import { cn } from '../../utils/cn';
37
37
  <!-- bg-black/50 dims the content behind the sheet. The fadeIn/fadeOut in
38
38
  #animateIn/#animateOut already tween this element's opacity over
39
39
  DURATION.normal, so the dim animates in lockstep with the panel slide —
40
- matching the dialog/bottom-sheet backdrop. A named color with an opacity
41
- modifier is required here: bg-black/50 compiles to rgb(0 0 0 / 0.5),
42
- which Lynx accepts, whereas opacity modifiers on semantic tokens don't. -->
40
+ matching the dialog/bottom-sheet backdrop. It's a named color, not a
41
+ theme one, because the dim is black in light and dark mode alike;
42
+ bg-black/50 compiles to rgb(0 0 0 / 0.5), which Lynx accepts. -->
43
43
  <view
44
44
  #backdrop
45
45
  class="h-full w-full bg-black/50"
@@ -49,6 +49,7 @@ import { cn } from '../../utils/cn';
49
49
  #panel
50
50
  [class]="panelClass()"
51
51
  [style]="panelPositionStyle()"
52
+ [accessibility-exclusive-focus]="overlayVisible()"
52
53
  (catchtap)="onPanelTap()"
53
54
  >
54
55
  <!-- The elevation shadow is an inline rgba() box-shadow rather than a
@@ -72,7 +73,7 @@ import { cn } from '../../utils/cn';
72
73
  </overlay>
73
74
  `,
74
75
  })
75
- export class UiActionSheet {
76
+ export class UiActionSheet implements OnDestroy {
76
77
  readonly open = model(false);
77
78
  readonly userClass = input<string>('', { alias: 'class' });
78
79
 
@@ -91,27 +92,36 @@ export class UiActionSheet {
91
92
  : 'position: fixed; overflow: visible; width: 100vw; height: 100vh; top: 0; left: 0; z-index: 999; display: none;',
92
93
  );
93
94
 
94
- readonly backdropRef = viewChild<ElementRef>('backdrop');
95
- readonly panelRef = viewChild<ElementRef>('panel');
95
+ // Required: both are always in the template (the overlay is hidden, never
96
+ // removed), and nothing reads them before the first render.
97
+ readonly backdropRef = viewChild.required<ElementRef>('backdrop');
98
+ readonly panelRef = viewChild.required<ElementRef>('panel');
96
99
  #backdropAnim?: AnimationHandle;
97
100
  #panelAnim?: AnimationHandle;
98
101
  #hasBeenOpen = false;
102
+ // The open and close sequences' pending timers, so ngOnDestroy can stop
103
+ // them.
104
+ readonly #timers = new Set<ReturnType<typeof setTimeout>>();
99
105
 
100
- constructor() {
101
- // `#hasBeenOpen` prevents the close animation from running on the initial
102
- // effect evaluation when `open` starts as false. Without this guard, the
103
- // first run would call #doClose() and emit `closed` before the action
104
- // sheet has ever been opened.
105
- effect(() => {
106
- const isOpen = this.open();
107
- if (isOpen) {
108
- this.#hasBeenOpen = true;
109
- this.#doOpen();
110
- } else if (this.#hasBeenOpen) {
111
- this.#doClose();
112
- }
113
- });
114
- }
106
+ /**
107
+ * Opens or closes the action sheet as `open` changes. A field rather than a
108
+ * constructor: Angular's test build keeps a function for a constructor that
109
+ * nothing calls, which an app's coverage report would show as uncovered.
110
+ *
111
+ * `#hasBeenOpen` prevents the close animation from running on the initial
112
+ * effect evaluation when `open` starts as false. Without this guard, the
113
+ * first run would call #doClose() and emit `closed` before the action
114
+ * sheet has ever been opened.
115
+ */
116
+ protected readonly openOrClose = effect(() => {
117
+ const isOpen = this.open();
118
+ if (isOpen) {
119
+ this.#hasBeenOpen = true;
120
+ this.#doOpen();
121
+ } else if (this.#hasBeenOpen) {
122
+ this.#doClose();
123
+ }
124
+ });
115
125
 
116
126
  protected readonly panelClass = computed(() =>
117
127
  cn('w-full flex-col gap-2 px-4 pb-6 flex', this.userClass()),
@@ -137,15 +147,37 @@ export class UiActionSheet {
137
147
  */
138
148
  protected onPanelTap(): void {}
139
149
 
150
+ /**
151
+ * The action sheet can be destroyed while its open or close sequence is still
152
+ * running, for example by an `@if` around it or a route change right after
153
+ * closing it. The rest of the sequence then ran on the destroyed component:
154
+ * it animated detached elements and emitted `closed` from a destroyed
155
+ * output, which Angular warns about in dev builds (NG0953).
156
+ */
157
+ ngOnDestroy(): void {
158
+ for (const timer of this.#timers) clearTimeout(timer);
159
+ }
160
+
161
+ /**
162
+ * `setTimeout`, tracked so that ngOnDestroy can cancel it.
163
+ */
164
+ #later(callback: () => void, ms: number): void {
165
+ const timer = setTimeout(() => {
166
+ this.#timers.delete(timer);
167
+ callback();
168
+ }, ms);
169
+ this.#timers.add(timer);
170
+ }
171
+
140
172
  /**
141
173
  * Two-phase open: make overlay visible first (so native elements exist in
142
174
  * the tree), then animate on the next frame. See nav-drawer for the same
143
175
  * pattern — Lynx animate() requires elements to be mounted before use.
144
176
  */
145
177
  #doOpen(): void {
146
- setTimeout(() => {
178
+ this.#later(() => {
147
179
  this.overlayVisible.set(true);
148
- setTimeout(() => this.#animateIn(), 0);
180
+ this.#later(() => this.#animateIn(), 0);
149
181
  }, 0);
150
182
  }
151
183
 
@@ -154,9 +186,9 @@ export class UiActionSheet {
154
186
  * causing elements to disappear before the animation finishes.
155
187
  */
156
188
  #doClose(): void {
157
- setTimeout(() => {
189
+ this.#later(() => {
158
190
  this.#animateOut();
159
- setTimeout(() => {
191
+ this.#later(() => {
160
192
  this.overlayVisible.set(false);
161
193
  this.closed.emit();
162
194
  }, DURATION.normal + 20);
@@ -164,14 +196,18 @@ export class UiActionSheet {
164
196
  }
165
197
 
166
198
  #animateIn(): void {
167
- const backdrop = this.backdropRef()?.nativeElement;
168
- const panel = this.panelRef()?.nativeElement;
169
- if (!backdrop || !panel) return;
199
+ const backdrop = this.backdropRef().nativeElement;
200
+ const panel = this.panelRef().nativeElement;
170
201
 
171
202
  this.#backdropAnim?.cancel();
172
203
  this.#panelAnim?.cancel();
173
204
 
174
205
  this.#backdropAnim = fadeIn(backdrop, { duration: DURATION.normal });
206
+ // Move a screen reader into the panel. It would otherwise stay on whatever
207
+ // opened it, which is now behind the overlay. With the panel's
208
+ // accessibility-exclusive-focus (see the template), it then can't leave
209
+ // the panel until it closes.
210
+ panel.invoke?.('requestAccessibilityFocus');
175
211
  this.#panelAnim = slideIn(panel, 'up', {
176
212
  duration: DURATION.slow,
177
213
  easing: EASING.sheet,
@@ -179,9 +215,8 @@ export class UiActionSheet {
179
215
  }
180
216
 
181
217
  #animateOut(): void {
182
- const backdrop = this.backdropRef()?.nativeElement;
183
- const panel = this.panelRef()?.nativeElement;
184
- if (!backdrop || !panel) return;
218
+ const backdrop = this.backdropRef().nativeElement;
219
+ const panel = this.panelRef().nativeElement;
185
220
 
186
221
  this.#backdropAnim?.cancel();
187
222
  this.#panelAnim?.cancel();
@@ -196,7 +231,6 @@ export class UiActionSheet {
196
231
 
197
232
  @Component({
198
233
  selector: 'ui-action-sheet-title',
199
- standalone: true,
200
234
  imports: [LYNX_ELEMENTS],
201
235
  encapsulation: ViewEncapsulation.None,
202
236
  template: `
@@ -219,7 +253,6 @@ export class UiActionSheetTitle {
219
253
 
220
254
  @Component({
221
255
  selector: 'ui-action-sheet-item',
222
- standalone: true,
223
256
  imports: [LYNX_ELEMENTS],
224
257
  encapsulation: ViewEncapsulation.None,
225
258
  template: `
@@ -246,7 +279,8 @@ export class UiActionSheetItem {
246
279
 
247
280
  readonly pressed = output<void>();
248
281
 
249
- readonly containerRef = viewChild<ElementRef>('container');
282
+ // Required: the container is always in the template.
283
+ readonly containerRef = viewChild.required<ElementRef>('container');
250
284
  #pressAnim?: AnimationHandle;
251
285
 
252
286
  protected readonly containerClass = computed(() =>
@@ -266,19 +300,19 @@ export class UiActionSheetItem {
266
300
  protected onPressStart(): void {
267
301
  this.#pressAnim?.cancel();
268
302
  this.#pressAnim = pressDown(
269
- this.containerRef()?.nativeElement,
303
+ this.containerRef().nativeElement,
270
304
  SCALE.pressDownLight,
271
305
  );
272
306
  }
273
307
 
274
308
  protected onPressEnd(): void {
275
309
  this.#pressAnim?.cancel();
276
- this.#pressAnim = pressRelease(this.containerRef()?.nativeElement);
310
+ this.#pressAnim = pressRelease(this.containerRef().nativeElement);
277
311
  }
278
312
 
279
313
  protected onPressCancel(): void {
280
314
  this.#pressAnim?.cancel();
281
- this.#pressAnim = pressRelease(this.containerRef()?.nativeElement);
315
+ this.#pressAnim = pressRelease(this.containerRef().nativeElement);
282
316
  }
283
317
 
284
318
  protected onTap(): void {
@@ -289,7 +323,6 @@ export class UiActionSheetItem {
289
323
 
290
324
  @Component({
291
325
  selector: 'ui-action-sheet-cancel',
292
- standalone: true,
293
326
  imports: [LYNX_ELEMENTS],
294
327
  encapsulation: ViewEncapsulation.None,
295
328
  template: `
@@ -315,7 +348,8 @@ export class UiActionSheetCancel {
315
348
  readonly label = input('Cancel');
316
349
  readonly userClass = input<string>('', { alias: 'class' });
317
350
 
318
- readonly containerRef = viewChild<ElementRef>('container');
351
+ // Required: the container is always in the template.
352
+ readonly containerRef = viewChild.required<ElementRef>('container');
319
353
  #pressAnim?: AnimationHandle;
320
354
 
321
355
  protected readonly containerClass = computed(() =>
@@ -332,19 +366,19 @@ export class UiActionSheetCancel {
332
366
  protected onPressStart(): void {
333
367
  this.#pressAnim?.cancel();
334
368
  this.#pressAnim = pressDown(
335
- this.containerRef()?.nativeElement,
369
+ this.containerRef().nativeElement,
336
370
  SCALE.pressDownLight,
337
371
  );
338
372
  }
339
373
 
340
374
  protected onPressEnd(): void {
341
375
  this.#pressAnim?.cancel();
342
- this.#pressAnim = pressRelease(this.containerRef()?.nativeElement);
376
+ this.#pressAnim = pressRelease(this.containerRef().nativeElement);
343
377
  }
344
378
 
345
379
  protected onPressCancel(): void {
346
380
  this.#pressAnim?.cancel();
347
- this.#pressAnim = pressRelease(this.containerRef()?.nativeElement);
381
+ this.#pressAnim = pressRelease(this.containerRef().nativeElement);
348
382
  }
349
383
 
350
384
  protected onTap(): void {
@@ -1,19 +1,38 @@
1
- import { Component } from '@angular/core';
1
+ import { Component, signal } from '@angular/core';
2
2
  import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';
3
3
  import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
4
- import { describe, expect, it } from 'vitest';
5
- import { setInputSignal } from '../../../test-utils/set-input-signal';
6
- import { UiAlert, alertVariants } from './alert';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+ import { lynxMainThread } from '../../../test-utils/lynx-main-thread';
6
+ import { type AlertVariant, UiAlert, alertVariants } from './alert';
7
7
 
8
8
  @Component({
9
- standalone: true,
10
9
  imports: [UiAlert, LYNX_ELEMENTS],
11
- template: `<ui-alert [variant]="variant"><text>Message</text></ui-alert>`,
10
+ template: `
11
+ <ui-alert
12
+ [variant]="variant()"
13
+ [title]="title()"
14
+ [description]="description()"
15
+ class="mt-2"
16
+ >
17
+ <text>Message</text>
18
+ </ui-alert>
19
+ `,
12
20
  })
13
21
  class AlertHost {
14
- variant: 'default' | 'destructive' = 'default';
22
+ readonly variant = signal<AlertVariant>('default');
23
+ readonly title = signal('');
24
+ readonly description = signal('');
15
25
  }
16
26
 
27
+ const renderAlert = async () => {
28
+ const result = await render(AlertHost);
29
+ return {
30
+ ...result,
31
+ host: result.componentRef.instance as AlertHost,
32
+ alert: () => result.container.querySelector('.rounded-lg')!,
33
+ };
34
+ };
35
+
17
36
  describe('alertVariants', () => {
18
37
  it('returns default variant classes', () => {
19
38
  expect(alertVariants()).toContain('bg-background');
@@ -27,22 +46,51 @@ describe('alertVariants', () => {
27
46
  });
28
47
 
29
48
  describe('UiAlert', () => {
30
- it('renders projected content', async () => {
31
- const { getByText } = await render(AlertHost);
49
+ it('renders projected content, without a title or description', async () => {
50
+ const { alert, getByText } = await renderAlert();
32
51
  expect(getByText('Message')).toBeTruthy();
52
+ expect(alert().querySelectorAll('text')).toHaveLength(1);
53
+ expect(alert().classList).toContain('mt-2');
33
54
  });
34
55
 
35
- it('renders title and description slots when their inputs are set', async () => {
36
- // JIT template bindings don't propagate through input(); drive the signals
37
- // directly so the @if(title()) / @if(description()) branches render and
38
- // their computed class functions run.
39
- const { componentRef, container } = await render(UiAlert);
40
- const inst = componentRef.instance as UiAlert;
41
- setInputSignal(inst.title, 'Heads up');
42
- setInputSignal(inst.description, 'Your download is ready.');
56
+ it('renders the title and description when set', async () => {
57
+ const { host, getByText } = await renderAlert();
58
+ host.title.set('Heads up');
59
+ host.description.set('Your download is ready.');
43
60
  await waitForUpdate();
61
+ expect(getByText('Heads up').getAttribute('class')).toContain(
62
+ 'text-foreground',
63
+ );
64
+ expect(
65
+ getByText('Your download is ready.').getAttribute('class'),
66
+ ).toContain('text-muted-foreground');
67
+ });
68
+
69
+ it('colors every part for the destructive variant', async () => {
70
+ const { host, alert, getByText } = await renderAlert();
71
+ host.variant.set('destructive');
72
+ host.title.set('Error');
73
+ host.description.set('Something went wrong.');
74
+ await waitForUpdate();
75
+ expect(alert().classList).toContain('border-destructive');
76
+ expect(getByText('Error').classList).toContain('text-destructive');
77
+ expect(getByText('Something went wrong.').classList).toContain(
78
+ 'text-destructive',
79
+ );
80
+ });
44
81
 
45
- const texts = container.querySelectorAll('text');
46
- expect(texts.length).toBeGreaterThanOrEqual(2);
82
+ it('reveals itself on first render', async () => {
83
+ const animate = vi.spyOn(lynxMainThread(), '__ElementAnimate');
84
+ const { alert } = await renderAlert();
85
+ expect(animate).toHaveBeenCalledWith(alert(), [
86
+ 0, // Starts an animation.
87
+ expect.any(String),
88
+ [
89
+ { transform: 'translateY(4px)', opacity: 0 },
90
+ { transform: 'translateY(0)', opacity: 1 },
91
+ ],
92
+ expect.any(Object),
93
+ ]);
94
+ animate.mockRestore();
47
95
  });
48
96
  });
@@ -17,9 +17,10 @@ export const alertVariants = cva('flex-col rounded-lg border p-4 flex', {
17
17
  variants: {
18
18
  variant: {
19
19
  default: 'border-border bg-background',
20
- // Opacity modifiers on semantic colors don't work on Lynx (opaque rgba
21
- // values with no separable channels): use a solid destructive border and
22
- // the pre-composed translucent bg-destructive-subtle for the tint.
20
+ // A solid destructive border over red at 10%. The tint is the
21
+ // pre-composed bg-destructive-subtle, not bg-destructive/10: a modifier
22
+ // needs the theme's --destructive-rgb, which themes copied before it
23
+ // existed don't have.
23
24
  destructive: 'border-destructive bg-destructive-subtle',
24
25
  },
25
26
  },
@@ -75,20 +76,17 @@ export class UiAlert {
75
76
  readonly description = input<string>('');
76
77
  readonly userClass = input<string>('', { alias: 'class' });
77
78
 
78
- readonly containerRef = viewChild<ElementRef>('container');
79
+ readonly containerRef = viewChild.required<ElementRef>('container');
79
80
 
80
- constructor() {
81
- // Subtle entrance animation on first render
82
- effect(() => {
83
- const el = this.containerRef()?.nativeElement;
84
- // revealIn calls el.animate() which is inert in jsdom — the animation
85
- // never plays and the effect can't be verified from a JIT render test.
86
- /* v8 ignore next 3 */
87
- if (el) {
88
- revealIn(el, { fromY: 4 });
89
- }
90
- });
91
- }
81
+ /**
82
+ * Reveals the alert with a subtle entrance on first render. A field rather
83
+ * than a constructor: Angular's test build keeps a function for a
84
+ * constructor that nothing calls, which an app's coverage report would show
85
+ * as uncovered.
86
+ */
87
+ protected readonly entrance = effect(() => {
88
+ revealIn(this.containerRef().nativeElement, { fromY: 4 });
89
+ });
92
90
 
93
91
  protected readonly containerClass = computed(() =>
94
92
  cn(alertVariants({ variant: this.variant() }), this.userClass()),