@blotch/dolan 22.2.3 → 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 (185) hide show
  1. package/dist/commands/add.js +9 -1
  2. package/dist/commands/add.js.map +1 -1
  3. package/dist/commands/add.test.js +36 -3
  4. package/dist/commands/add.test.js.map +1 -1
  5. package/dist/commands/doctor.js +187 -7
  6. package/dist/commands/doctor.js.map +1 -1
  7. package/dist/commands/doctor.test.js +280 -7
  8. package/dist/commands/doctor.test.js.map +1 -1
  9. package/dist/commands/init.d.ts +2 -2
  10. package/dist/commands/init.js +38 -18
  11. package/dist/commands/init.js.map +1 -1
  12. package/dist/commands/init.test.js +107 -1
  13. package/dist/commands/init.test.js.map +1 -1
  14. package/dist/commands/theme.js +1 -1
  15. package/dist/commands/theme.js.map +1 -1
  16. package/dist/commands/update.d.ts +5 -0
  17. package/dist/commands/update.js +126 -45
  18. package/dist/commands/update.js.map +1 -1
  19. package/dist/commands/update.test.js +314 -2
  20. package/dist/commands/update.test.js.map +1 -1
  21. package/dist/lockfile.d.ts +7 -0
  22. package/dist/lockfile.js +36 -0
  23. package/dist/lockfile.js.map +1 -1
  24. package/dist/lockfile.test.d.ts +1 -0
  25. package/dist/lockfile.test.js +25 -0
  26. package/dist/lockfile.test.js.map +1 -0
  27. package/dist/registry.d.ts +6 -0
  28. package/dist/registry.js +48 -2
  29. package/dist/registry.js.map +1 -1
  30. package/dist/registry.test.js +45 -3
  31. package/dist/registry.test.js.map +1 -1
  32. package/dist/test-utils.d.ts +5 -0
  33. package/dist/test-utils.js +6 -1
  34. package/dist/test-utils.js.map +1 -1
  35. package/dist/testing/fire-lynx-event.d.ts +14 -0
  36. package/dist/testing/fire-lynx-event.js +19 -0
  37. package/dist/testing/fire-lynx-event.js.map +1 -0
  38. package/dist/testing/fire-lynx-event.test.d.ts +1 -0
  39. package/dist/testing/fire-lynx-event.test.js +44 -0
  40. package/dist/testing/fire-lynx-event.test.js.map +1 -0
  41. package/dist/testing/lynx-main-thread.d.ts +22 -0
  42. package/dist/testing/lynx-main-thread.js +11 -0
  43. package/dist/testing/lynx-main-thread.js.map +1 -0
  44. package/dist/testing/lynx-main-thread.test.d.ts +1 -0
  45. package/dist/testing/lynx-main-thread.test.js +24 -0
  46. package/dist/testing/lynx-main-thread.test.js.map +1 -0
  47. package/dist/testing/set-input-signal.d.ts +5 -4
  48. package/dist/testing/set-input-signal.js +5 -4
  49. package/dist/testing/set-input-signal.js.map +1 -1
  50. package/dist/ui/components/accordion/accordion.test.ts +158 -144
  51. package/dist/ui/components/accordion/accordion.ts +19 -14
  52. package/dist/ui/components/action-sheet/action-sheet.test.ts +272 -187
  53. package/dist/ui/components/action-sheet/action-sheet.ts +40 -34
  54. package/dist/ui/components/alert/alert.test.ts +67 -19
  55. package/dist/ui/components/alert/alert.ts +14 -16
  56. package/dist/ui/components/alert-dialog/alert-dialog.test.ts +206 -139
  57. package/dist/ui/components/alert-dialog/alert-dialog.ts +32 -26
  58. package/dist/ui/components/area-chart/area-chart.test.ts +81 -1
  59. package/dist/ui/components/avatar/avatar.test.ts +89 -45
  60. package/dist/ui/components/badge/badge.test.ts +57 -23
  61. package/dist/ui/components/badge/badge.ts +12 -14
  62. package/dist/ui/components/bar-chart/bar-chart.test.ts +82 -1
  63. package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +373 -297
  64. package/dist/ui/components/bottom-sheet/bottom-sheet.ts +34 -33
  65. package/dist/ui/components/button/button.test.ts +611 -2
  66. package/dist/ui/components/button/button.ts +431 -62
  67. package/dist/ui/components/button/index.ts +2 -0
  68. package/dist/ui/components/button-group/button-group.test.ts +484 -2
  69. package/dist/ui/components/button-group/button-group.ts +380 -68
  70. package/dist/ui/components/button-group/index.ts +3 -0
  71. package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +82 -1
  72. package/dist/ui/components/card/card.test.ts +82 -62
  73. package/dist/ui/components/chart/chart-presets.test.ts +197 -1
  74. package/dist/ui/components/chart/chart-presets.ts +8 -1
  75. package/dist/ui/components/chart/chart-theme.ts +12 -0
  76. package/dist/ui/components/chart/chart.test.ts +1017 -0
  77. package/dist/ui/components/chart/chart.ts +56 -43
  78. package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
  79. package/dist/ui/components/checkbox/checkbox.ts +23 -23
  80. package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
  81. package/dist/ui/components/collapsible/collapsible.ts +19 -14
  82. package/dist/ui/components/context-menu/context-menu.test.ts +224 -0
  83. package/dist/ui/components/context-menu/context-menu.ts +110 -0
  84. package/dist/ui/components/context-menu/index.ts +1 -0
  85. package/dist/ui/components/dialog/dialog.test.ts +210 -152
  86. package/dist/ui/components/dialog/dialog.ts +29 -23
  87. package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
  88. package/dist/ui/components/dock/dock-scroll.ts +133 -0
  89. package/dist/ui/components/dock/dock.test.ts +749 -0
  90. package/dist/ui/components/dock/dock.ts +960 -0
  91. package/dist/ui/components/dock/index.ts +2 -0
  92. package/dist/ui/components/empty-state/empty-state.test.ts +28 -39
  93. package/dist/ui/components/glass/concentric-radius.test.ts +13 -0
  94. package/dist/ui/components/glass/concentric-radius.ts +17 -0
  95. package/dist/ui/components/glass/glass-design.test.ts +78 -0
  96. package/dist/ui/components/glass/glass-design.ts +119 -0
  97. package/dist/ui/components/glass/glass.test.ts +338 -0
  98. package/dist/ui/components/glass/glass.ts +360 -0
  99. package/dist/ui/components/glass/index.ts +18 -0
  100. package/dist/ui/components/icon/icon-theme.test.ts +36 -0
  101. package/dist/ui/components/icon/icon-theme.ts +36 -13
  102. package/dist/ui/components/icon/icon.test.ts +52 -41
  103. package/dist/ui/components/input/input.test.ts +178 -101
  104. package/dist/ui/components/input/input.ts +15 -12
  105. package/dist/ui/components/label/label.test.ts +11 -24
  106. package/dist/ui/components/line-chart/line-chart.test.ts +76 -1
  107. package/dist/ui/components/list/list.test.ts +80 -49
  108. package/dist/ui/components/menu/index.ts +25 -0
  109. package/dist/ui/components/menu/menu-placement.test.ts +153 -0
  110. package/dist/ui/components/menu/menu-placement.ts +159 -0
  111. package/dist/ui/components/menu/menu-popup.test.ts +632 -0
  112. package/dist/ui/components/menu/menu-popup.ts +544 -0
  113. package/dist/ui/components/menu/menu.test.ts +429 -0
  114. package/dist/ui/components/menu/menu.ts +415 -0
  115. package/dist/ui/components/nav-drawer/nav-drawer.test.ts +425 -260
  116. package/dist/ui/components/nav-drawer/nav-drawer.ts +72 -56
  117. package/dist/ui/components/pagination/pagination.test.ts +136 -86
  118. package/dist/ui/components/progress/progress.test.ts +102 -46
  119. package/dist/ui/components/progress/progress.ts +52 -54
  120. package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
  121. package/dist/ui/components/radio-group/radio-group.ts +24 -24
  122. package/dist/ui/components/scatter-chart/scatter-chart.test.ts +74 -1
  123. package/dist/ui/components/scroll-area/scroll-area.test.ts +72 -36
  124. package/dist/ui/components/select/select.test.ts +138 -78
  125. package/dist/ui/components/select/select.ts +17 -11
  126. package/dist/ui/components/separator/separator.test.ts +18 -17
  127. package/dist/ui/components/sheet/sheet.test.ts +64 -11
  128. package/dist/ui/components/sheet/sheet.ts +9 -1
  129. package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
  130. package/dist/ui/components/skeleton/skeleton.ts +10 -16
  131. package/dist/ui/components/spinner/spinner.test.ts +64 -46
  132. package/dist/ui/components/spinner/spinner.ts +16 -19
  133. package/dist/ui/components/switch/switch.test.ts +116 -46
  134. package/dist/ui/components/switch/switch.ts +8 -13
  135. package/dist/ui/components/tab-bar/index.ts +1 -0
  136. package/dist/ui/components/tab-bar/tab-bar.test.ts +280 -0
  137. package/dist/ui/components/tab-bar/tab-bar.ts +230 -0
  138. package/dist/ui/components/tabs/tabs.test.ts +215 -157
  139. package/dist/ui/components/tabs/tabs.ts +110 -118
  140. package/dist/ui/components/textarea/textarea.test.ts +206 -100
  141. package/dist/ui/components/textarea/textarea.ts +14 -12
  142. package/dist/ui/components/toast/toast.test.ts +434 -350
  143. package/dist/ui/components/toast/toast.ts +42 -51
  144. package/dist/ui/components/toggle/toggle.test.ts +247 -140
  145. package/dist/ui/theme/chart-colors.test.ts +8 -3
  146. package/dist/ui/theme/dark.css +38 -2
  147. package/dist/ui/theme/default.css +55 -7
  148. package/dist/ui/theme/icon-colors.test.ts +8 -3
  149. package/dist/ui/theme/rgb-channels.test.ts +84 -0
  150. package/dist/ui/theme/tailwind-build.test.ts +114 -0
  151. package/dist/ui/theme/tailwind-css.test.ts +83 -0
  152. package/dist/ui/theme/tailwind-plugin.test.ts +42 -2
  153. package/dist/ui/theme/tailwind-plugin.ts +93 -29
  154. package/dist/ui/theme/tailwind.css +113 -0
  155. package/dist/ui/utils/animate.test.ts +13 -1
  156. package/dist/utils/analyze.d.ts +14 -0
  157. package/dist/utils/analyze.js +15 -0
  158. package/dist/utils/analyze.js.map +1 -1
  159. package/dist/utils/analyze.test.js +14 -1
  160. package/dist/utils/analyze.test.js.map +1 -1
  161. package/dist/utils/installed.d.ts +12 -0
  162. package/dist/utils/installed.js +16 -0
  163. package/dist/utils/installed.js.map +1 -1
  164. package/dist/utils/installed.test.js +17 -1
  165. package/dist/utils/installed.test.js.map +1 -1
  166. package/dist/utils/resolve-paths.d.ts +4 -4
  167. package/dist/utils/resolve-paths.js +5 -7
  168. package/dist/utils/resolve-paths.js.map +1 -1
  169. package/dist/utils/resolve-paths.test.js +9 -5
  170. package/dist/utils/resolve-paths.test.js.map +1 -1
  171. package/dist/utils/tailwind.d.ts +25 -0
  172. package/dist/utils/tailwind.js +51 -0
  173. package/dist/utils/tailwind.js.map +1 -0
  174. package/dist/utils/tailwind.test.d.ts +1 -0
  175. package/dist/utils/tailwind.test.js +71 -0
  176. package/dist/utils/tailwind.test.js.map +1 -0
  177. package/package.json +9 -1
  178. package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -63
  179. package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -58
  180. package/dist/ui/components/button/button-render.test.ts +0 -132
  181. package/dist/ui/components/button-group/button-group-render.test.ts +0 -146
  182. package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +0 -59
  183. package/dist/ui/components/chart/chart-render.test.ts +0 -730
  184. package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -83
  185. package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -64
@@ -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"
@@ -92,8 +92,10 @@ export class UiActionSheet implements OnDestroy {
92
92
  : 'position: fixed; overflow: visible; width: 100vw; height: 100vh; top: 0; left: 0; z-index: 999; display: none;',
93
93
  );
94
94
 
95
- readonly backdropRef = viewChild<ElementRef>('backdrop');
96
- 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');
97
99
  #backdropAnim?: AnimationHandle;
98
100
  #panelAnim?: AnimationHandle;
99
101
  #hasBeenOpen = false;
@@ -101,21 +103,25 @@ export class UiActionSheet implements OnDestroy {
101
103
  // them.
102
104
  readonly #timers = new Set<ReturnType<typeof setTimeout>>();
103
105
 
104
- constructor() {
105
- // `#hasBeenOpen` prevents the close animation from running on the initial
106
- // effect evaluation when `open` starts as false. Without this guard, the
107
- // first run would call #doClose() and emit `closed` before the action
108
- // sheet has ever been opened.
109
- effect(() => {
110
- const isOpen = this.open();
111
- if (isOpen) {
112
- this.#hasBeenOpen = true;
113
- this.#doOpen();
114
- } else if (this.#hasBeenOpen) {
115
- this.#doClose();
116
- }
117
- });
118
- }
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
+ });
119
125
 
120
126
  protected readonly panelClass = computed(() =>
121
127
  cn('w-full flex-col gap-2 px-4 pb-6 flex', this.userClass()),
@@ -190,9 +196,8 @@ export class UiActionSheet implements OnDestroy {
190
196
  }
191
197
 
192
198
  #animateIn(): void {
193
- const backdrop = this.backdropRef()?.nativeElement;
194
- const panel = this.panelRef()?.nativeElement;
195
- if (!backdrop || !panel) return;
199
+ const backdrop = this.backdropRef().nativeElement;
200
+ const panel = this.panelRef().nativeElement;
196
201
 
197
202
  this.#backdropAnim?.cancel();
198
203
  this.#panelAnim?.cancel();
@@ -210,9 +215,8 @@ export class UiActionSheet implements OnDestroy {
210
215
  }
211
216
 
212
217
  #animateOut(): void {
213
- const backdrop = this.backdropRef()?.nativeElement;
214
- const panel = this.panelRef()?.nativeElement;
215
- if (!backdrop || !panel) return;
218
+ const backdrop = this.backdropRef().nativeElement;
219
+ const panel = this.panelRef().nativeElement;
216
220
 
217
221
  this.#backdropAnim?.cancel();
218
222
  this.#panelAnim?.cancel();
@@ -275,7 +279,8 @@ export class UiActionSheetItem {
275
279
 
276
280
  readonly pressed = output<void>();
277
281
 
278
- readonly containerRef = viewChild<ElementRef>('container');
282
+ // Required: the container is always in the template.
283
+ readonly containerRef = viewChild.required<ElementRef>('container');
279
284
  #pressAnim?: AnimationHandle;
280
285
 
281
286
  protected readonly containerClass = computed(() =>
@@ -295,19 +300,19 @@ export class UiActionSheetItem {
295
300
  protected onPressStart(): void {
296
301
  this.#pressAnim?.cancel();
297
302
  this.#pressAnim = pressDown(
298
- this.containerRef()?.nativeElement,
303
+ this.containerRef().nativeElement,
299
304
  SCALE.pressDownLight,
300
305
  );
301
306
  }
302
307
 
303
308
  protected onPressEnd(): void {
304
309
  this.#pressAnim?.cancel();
305
- this.#pressAnim = pressRelease(this.containerRef()?.nativeElement);
310
+ this.#pressAnim = pressRelease(this.containerRef().nativeElement);
306
311
  }
307
312
 
308
313
  protected onPressCancel(): void {
309
314
  this.#pressAnim?.cancel();
310
- this.#pressAnim = pressRelease(this.containerRef()?.nativeElement);
315
+ this.#pressAnim = pressRelease(this.containerRef().nativeElement);
311
316
  }
312
317
 
313
318
  protected onTap(): void {
@@ -343,7 +348,8 @@ export class UiActionSheetCancel {
343
348
  readonly label = input('Cancel');
344
349
  readonly userClass = input<string>('', { alias: 'class' });
345
350
 
346
- readonly containerRef = viewChild<ElementRef>('container');
351
+ // Required: the container is always in the template.
352
+ readonly containerRef = viewChild.required<ElementRef>('container');
347
353
  #pressAnim?: AnimationHandle;
348
354
 
349
355
  protected readonly containerClass = computed(() =>
@@ -360,19 +366,19 @@ export class UiActionSheetCancel {
360
366
  protected onPressStart(): void {
361
367
  this.#pressAnim?.cancel();
362
368
  this.#pressAnim = pressDown(
363
- this.containerRef()?.nativeElement,
369
+ this.containerRef().nativeElement,
364
370
  SCALE.pressDownLight,
365
371
  );
366
372
  }
367
373
 
368
374
  protected onPressEnd(): void {
369
375
  this.#pressAnim?.cancel();
370
- this.#pressAnim = pressRelease(this.containerRef()?.nativeElement);
376
+ this.#pressAnim = pressRelease(this.containerRef().nativeElement);
371
377
  }
372
378
 
373
379
  protected onPressCancel(): void {
374
380
  this.#pressAnim?.cancel();
375
- this.#pressAnim = pressRelease(this.containerRef()?.nativeElement);
381
+ this.#pressAnim = pressRelease(this.containerRef().nativeElement);
376
382
  }
377
383
 
378
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()),