@blotch/dolan 22.2.2 → 22.2.3

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 (154) hide show
  1. package/dist/commands/add.d.ts +6 -2
  2. package/dist/commands/add.js +32 -10
  3. package/dist/commands/add.js.map +1 -1
  4. package/dist/commands/add.test.js +129 -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 +41 -27
  12. package/dist/commands/doctor.js.map +1 -1
  13. package/dist/commands/doctor.test.js +93 -9
  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 +149 -40
  23. package/dist/commands/init.js.map +1 -1
  24. package/dist/commands/init.test.js +285 -26
  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/update.d.ts +6 -0
  46. package/dist/commands/update.js +59 -24
  47. package/dist/commands/update.js.map +1 -1
  48. package/dist/commands/update.test.js +273 -31
  49. package/dist/commands/update.test.js.map +1 -1
  50. package/dist/index.js +7 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/registry.d.ts +8 -0
  53. package/dist/registry.js +32 -24
  54. package/dist/registry.js.map +1 -1
  55. package/dist/registry.test.js +76 -37
  56. package/dist/registry.test.js.map +1 -1
  57. package/dist/testing/set-input-signal.d.ts +13 -0
  58. package/dist/testing/set-input-signal.js +20 -0
  59. package/dist/testing/set-input-signal.js.map +1 -0
  60. package/dist/testing/set-input-signal.test.d.ts +1 -0
  61. package/dist/testing/set-input-signal.test.js +20 -0
  62. package/dist/testing/set-input-signal.test.js.map +1 -0
  63. package/dist/ui/components/action-sheet/action-sheet.test.ts +48 -1
  64. package/dist/ui/components/action-sheet/action-sheet.ts +37 -9
  65. package/dist/ui/components/alert-dialog/alert-dialog.test.ts +49 -2
  66. package/dist/ui/components/alert-dialog/alert-dialog.ts +43 -12
  67. package/dist/ui/components/area-chart/area-chart-render.test.ts +52 -110
  68. package/dist/ui/components/area-chart/area-chart.test.ts +58 -123
  69. package/dist/ui/components/area-chart/area-chart.ts +100 -315
  70. package/dist/ui/components/area-chart/index.ts +2 -3
  71. package/dist/ui/components/bar-chart/bar-chart-render.test.ts +47 -163
  72. package/dist/ui/components/bar-chart/bar-chart.test.ts +83 -37
  73. package/dist/ui/components/bar-chart/bar-chart.ts +140 -294
  74. package/dist/ui/components/bar-chart/index.ts +3 -3
  75. package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +191 -9
  76. package/dist/ui/components/bottom-sheet/bottom-sheet.ts +136 -20
  77. package/dist/ui/components/button/button-render.test.ts +19 -0
  78. package/dist/ui/components/button/button.ts +18 -2
  79. package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +48 -163
  80. package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +106 -62
  81. package/dist/ui/components/candlestick-chart/candlestick-chart.ts +150 -323
  82. package/dist/ui/components/candlestick-chart/index.ts +3 -4
  83. package/dist/ui/components/chart/chart-focus.test.ts +132 -0
  84. package/dist/ui/components/chart/chart-focus.ts +115 -0
  85. package/dist/ui/components/chart/chart-presets.test.ts +139 -0
  86. package/dist/ui/components/chart/chart-presets.ts +271 -0
  87. package/dist/ui/components/chart/chart-render.test.ts +730 -0
  88. package/dist/ui/components/chart/chart-scene.test.ts +589 -0
  89. package/dist/ui/components/chart/chart-scene.ts +483 -0
  90. package/dist/ui/components/chart/chart-theme.test.ts +85 -0
  91. package/dist/ui/components/chart/chart-theme.ts +198 -0
  92. package/dist/ui/components/chart/chart-zoom.test.ts +159 -0
  93. package/dist/ui/components/chart/chart-zoom.ts +162 -0
  94. package/dist/ui/components/chart/chart.ts +921 -0
  95. package/dist/ui/components/chart/index.ts +24 -0
  96. package/dist/ui/components/dialog/dialog.test.ts +49 -2
  97. package/dist/ui/components/dialog/dialog.ts +43 -12
  98. package/dist/ui/components/icon/icon-theme.test.ts +133 -0
  99. package/dist/ui/components/icon/icon-theme.ts +204 -0
  100. package/dist/ui/components/icon/icon.test.ts +51 -2
  101. package/dist/ui/components/icon/icon.ts +38 -21
  102. package/dist/ui/components/line-chart/index.ts +2 -3
  103. package/dist/ui/components/line-chart/line-chart-render.test.ts +69 -107
  104. package/dist/ui/components/line-chart/line-chart.test.ts +63 -45
  105. package/dist/ui/components/line-chart/line-chart.ts +83 -241
  106. package/dist/ui/components/list/list.test.ts +69 -2
  107. package/dist/ui/components/list/list.ts +21 -15
  108. package/dist/ui/components/nav-drawer/nav-drawer.test.ts +80 -1
  109. package/dist/ui/components/nav-drawer/nav-drawer.ts +66 -11
  110. package/dist/ui/components/pagination/pagination.test.ts +62 -0
  111. package/dist/ui/components/pagination/pagination.ts +55 -8
  112. package/dist/ui/components/scatter-chart/index.ts +2 -3
  113. package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +51 -144
  114. package/dist/ui/components/scatter-chart/scatter-chart.test.ts +57 -25
  115. package/dist/ui/components/scatter-chart/scatter-chart.ts +104 -250
  116. package/dist/ui/components/scroll-area/scroll-area.test.ts +37 -2
  117. package/dist/ui/components/scroll-area/scroll-area.ts +8 -8
  118. package/dist/ui/components/spinner/spinner.test.ts +27 -0
  119. package/dist/ui/components/spinner/spinner.ts +33 -23
  120. package/dist/ui/components/toast/toast.test.ts +37 -0
  121. package/dist/ui/components/toast/toast.ts +20 -4
  122. package/dist/ui/theme/chart-colors.test.ts +30 -0
  123. package/dist/ui/theme/dark.css +8 -0
  124. package/dist/ui/theme/default.css +8 -0
  125. package/dist/ui/theme/icon-colors.test.ts +30 -0
  126. package/dist/ui/theme/tailwind-plugin.ts +10 -1
  127. package/dist/utils/installed.d.ts +17 -0
  128. package/dist/utils/installed.js +27 -0
  129. package/dist/utils/installed.js.map +1 -0
  130. package/dist/utils/installed.test.d.ts +1 -0
  131. package/dist/utils/installed.test.js +41 -0
  132. package/dist/utils/installed.test.js.map +1 -0
  133. package/dist/utils/interactive.d.ts +9 -0
  134. package/dist/utils/interactive.js +10 -0
  135. package/dist/utils/interactive.js.map +1 -0
  136. package/dist/utils/interactive.test.d.ts +1 -0
  137. package/dist/utils/interactive.test.js +24 -0
  138. package/dist/utils/interactive.test.js.map +1 -0
  139. package/dist/utils/packages.d.ts +36 -0
  140. package/dist/utils/packages.js +70 -0
  141. package/dist/utils/packages.js.map +1 -0
  142. package/dist/utils/packages.test.d.ts +1 -0
  143. package/dist/utils/packages.test.js +89 -0
  144. package/dist/utils/packages.test.js.map +1 -0
  145. package/dist/utils/resolve-paths.d.ts +17 -1
  146. package/dist/utils/resolve-paths.js +23 -3
  147. package/dist/utils/resolve-paths.js.map +1 -1
  148. package/dist/utils/resolve-paths.test.js +34 -2
  149. package/dist/utils/resolve-paths.test.js.map +1 -1
  150. package/package.json +5 -1
  151. package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +0 -188
  152. package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +0 -525
  153. package/dist/ui/components/cartesian-chart/cartesian-chart.ts +0 -1140
  154. package/dist/ui/components/cartesian-chart/index.ts +0 -18
@@ -1 +1 @@
1
- {"version":3,"file":"registry.test.js","sourceRoot":"","sources":["../src/registry.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"registry.test.js","sourceRoot":"","sources":["../src/registry.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Sets an Angular `input()` / `model()` signal's value from a test.
3
+ *
4
+ * The Vitest JIT harness does not wire up signal inputs for template binding or
5
+ * `ComponentRef.setInput()` (that metadata is only emitted by Angular's AOT
6
+ * compiler), so the usual ways of feeding a value into an `input()` are inert
7
+ * here. Angular's own reactive-node API is the supported escape hatch: reach the
8
+ * input's SIGNAL node and call `applyValueToInputSignal`, exactly as a parent
9
+ * template binding would at runtime. This drives a PUBLIC input to a value — it
10
+ * does not touch a component's private (`#`) state — and mirrors the existing
11
+ * pattern in packages/runtime/src/lib/transition/lynx-transition.test.ts.
12
+ */
13
+ export declare const setInputSignal: (signalFn: unknown, value: unknown) => void;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Sets an Angular `input()` / `model()` signal's value from a test.
3
+ *
4
+ * The Vitest JIT harness does not wire up signal inputs for template binding or
5
+ * `ComponentRef.setInput()` (that metadata is only emitted by Angular's AOT
6
+ * compiler), so the usual ways of feeding a value into an `input()` are inert
7
+ * here. Angular's own reactive-node API is the supported escape hatch: reach the
8
+ * input's SIGNAL node and call `applyValueToInputSignal`, exactly as a parent
9
+ * template binding would at runtime. This drives a PUBLIC input to a value — it
10
+ * does not touch a component's private (`#`) state — and mirrors the existing
11
+ * pattern in packages/runtime/src/lib/transition/lynx-transition.test.ts.
12
+ */
13
+ export const setInputSignal = (signalFn, value) => {
14
+ const symbols = Object.getOwnPropertySymbols(signalFn);
15
+ const signalSymbol = symbols.find((s) => s.toString() === 'Symbol(SIGNAL)');
16
+ const node = signalFn[signalSymbol];
17
+ const proto = Object.getPrototypeOf(node);
18
+ proto.applyValueToInputSignal(node, value);
19
+ };
20
+ //# sourceMappingURL=set-input-signal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"set-input-signal.js","sourceRoot":"","sources":["../../src/testing/set-input-signal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,QAAiB,EAAE,KAAc,EAAQ,EAAE;IACxE,MAAM,OAAO,GAAG,MAAM,CAAC,qBAAqB,CAAC,QAAkB,CAAC,CAAC;IACjE,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,gBAAgB,CAAE,CAAC;IAC7E,MAAM,IAAI,GAAI,QAAoC,CAAC,YAAY,CAAC,CAAC;IACjE,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,IAAI,CAEvC,CAAC;IACF,KAAK,CAAC,uBAAuB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC7C,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import { Injector, input, runInInjectionContext } from '@angular/core';
2
+ import { readFileSync } from 'node:fs';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { setInputSignal } from './set-input-signal';
5
+ describe('setInputSignal', () => {
6
+ it("sets an input signal's value", () => {
7
+ const count = runInInjectionContext(Injector.create({ providers: [] }), () => input(0));
8
+ setInputSignal(count, 5);
9
+ expect(count()).toBe(5);
10
+ });
11
+ it('is the helper the component tests import from packages/ui', () => {
12
+ // Copied tests import `../../../test-utils/set-input-signal`, which dolan
13
+ // rewrites to this file. A copy that drifted would test differently in an
14
+ // app than in the library.
15
+ const shipped = readFileSync(new URL('./set-input-signal.ts', import.meta.url), 'utf-8');
16
+ const library = readFileSync(new URL('../../../ui/src/test-utils/set-input-signal.ts', import.meta.url), 'utf-8');
17
+ expect(shipped).toBe(library);
18
+ });
19
+ });
20
+ //# sourceMappingURL=set-input-signal.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"set-input-signal.test.js","sourceRoot":"","sources":["../../src/testing/set-input-signal.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,KAAK,GAAG,qBAAqB,CACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,EAClC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CACf,CAAC;QACF,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACzB,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,0EAA0E;QAC1E,0EAA0E;QAC1E,2BAA2B;QAC3B,MAAM,OAAO,GAAG,YAAY,CAC1B,IAAI,GAAG,CAAC,uBAAuB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EACjD,OAAO,CACR,CAAC;QACF,MAAM,OAAO,GAAG,YAAY,CAC1B,IAAI,GAAG,CACL,gDAAgD,EAChD,MAAM,CAAC,IAAI,CAAC,GAAG,CAChB,EACD,OAAO,CACR,CAAC;QACF,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -2,6 +2,7 @@ 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
4
  import { describe, expect, it, vi } from 'vitest';
5
+ import { DURATION } from '../../utils/animate';
5
6
  import { setInputSignal } from '../../../test-utils/set-input-signal';
6
7
  import {
7
8
  UiActionSheet,
@@ -11,7 +12,6 @@ import {
11
12
  } from './action-sheet';
12
13
 
13
14
  @Component({
14
- standalone: true,
15
15
  imports: [
16
16
  UiActionSheet,
17
17
  UiActionSheetTitle,
@@ -32,6 +32,47 @@ class ActionSheetHost {
32
32
  }
33
33
 
34
34
  describe('UiActionSheet', () => {
35
+ it('stops its close timers when it is destroyed while closing', async () => {
36
+ const warn = vi.spyOn(console, 'warn');
37
+ const { componentRef, unmount } = await render(UiActionSheet);
38
+ const inst = componentRef.instance as UiActionSheet;
39
+ inst.open.set(true);
40
+ for (let i = 0; i < 5; i++) await waitForUpdate();
41
+ inst.open.set(false);
42
+ await waitForUpdate();
43
+ // Destroyed during the close animation, as an `@if` around the action sheet
44
+ // would do. Its close timer (DURATION.normal + 20) used to fire anyway
45
+ // and emit `closed` from the destroyed output.
46
+ unmount();
47
+ await new Promise((r) => setTimeout(r, DURATION.normal + 50));
48
+ const warnings = warn.mock.calls.map(([message]) => String(message));
49
+ warn.mockRestore();
50
+ expect(warnings.filter((message) => message.includes('NG0953'))).toEqual(
51
+ [],
52
+ );
53
+ });
54
+
55
+ it('moves a screen reader into the panel and keeps it there while open', async () => {
56
+ const { componentRef, container } = await render(UiActionSheet);
57
+ const inst = componentRef.instance as UiActionSheet;
58
+ const panel = container.querySelector(
59
+ '[accessibility-exclusive-focus]',
60
+ ) as Element & { invoke?: unknown };
61
+ expect(panel.getAttribute('accessibility-exclusive-focus')).toBe('false');
62
+ const invoke = vi.fn();
63
+ panel.invoke = invoke;
64
+ (inst as unknown as { backdropRef: unknown }).backdropRef = signal({
65
+ nativeElement: panel,
66
+ });
67
+ (inst as unknown as { panelRef: unknown }).panelRef = signal({
68
+ nativeElement: panel,
69
+ });
70
+ inst.open.set(true);
71
+ for (let i = 0; i < 5; i++) await waitForUpdate();
72
+ expect(invoke).toHaveBeenCalledWith('requestAccessibilityFocus');
73
+ expect(panel.getAttribute('accessibility-exclusive-focus')).toBe('true');
74
+ });
75
+
35
76
  const patchRefs = (inst: UiActionSheet, view: Element) => {
36
77
  (inst as unknown as { backdropRef: unknown }).backdropRef = signal({
37
78
  nativeElement: view,
@@ -93,12 +134,18 @@ describe('UiActionSheet', () => {
93
134
  it('open effect bails when backdrop/panel refs are undefined', async () => {
94
135
  const { componentRef } = await render(UiActionSheet);
95
136
  const inst = componentRef.instance as UiActionSheet;
137
+ const closed = vi.fn();
138
+ inst.closed.subscribe(closed);
96
139
  inst.open.set(true);
97
140
  await waitForUpdate();
98
141
  await waitForUpdate();
99
142
  inst.open.set(false);
100
143
  await waitForUpdate();
144
+ // Wait out the close sequence, so #animateOut runs (and bails) while the
145
+ // component is still alive. Destroying it clears the sequence's timers.
146
+ await new Promise((r) => setTimeout(r, DURATION.normal + 50));
101
147
  expect(inst.open()).toBe(false);
148
+ expect(closed).toHaveBeenCalledOnce();
102
149
  });
103
150
 
104
151
  it('applies user class on the panel', async () => {
@@ -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: `
@@ -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
 
@@ -96,6 +97,9 @@ export class UiActionSheet {
96
97
  #backdropAnim?: AnimationHandle;
97
98
  #panelAnim?: AnimationHandle;
98
99
  #hasBeenOpen = false;
100
+ // The open and close sequences' pending timers, so ngOnDestroy can stop
101
+ // them.
102
+ readonly #timers = new Set<ReturnType<typeof setTimeout>>();
99
103
 
100
104
  constructor() {
101
105
  // `#hasBeenOpen` prevents the close animation from running on the initial
@@ -137,15 +141,37 @@ export class UiActionSheet {
137
141
  */
138
142
  protected onPanelTap(): void {}
139
143
 
144
+ /**
145
+ * The action sheet can be destroyed while its open or close sequence is still
146
+ * running, for example by an `@if` around it or a route change right after
147
+ * closing it. The rest of the sequence then ran on the destroyed component:
148
+ * it animated detached elements and emitted `closed` from a destroyed
149
+ * output, which Angular warns about in dev builds (NG0953).
150
+ */
151
+ ngOnDestroy(): void {
152
+ for (const timer of this.#timers) clearTimeout(timer);
153
+ }
154
+
155
+ /**
156
+ * `setTimeout`, tracked so that ngOnDestroy can cancel it.
157
+ */
158
+ #later(callback: () => void, ms: number): void {
159
+ const timer = setTimeout(() => {
160
+ this.#timers.delete(timer);
161
+ callback();
162
+ }, ms);
163
+ this.#timers.add(timer);
164
+ }
165
+
140
166
  /**
141
167
  * Two-phase open: make overlay visible first (so native elements exist in
142
168
  * the tree), then animate on the next frame. See nav-drawer for the same
143
169
  * pattern — Lynx animate() requires elements to be mounted before use.
144
170
  */
145
171
  #doOpen(): void {
146
- setTimeout(() => {
172
+ this.#later(() => {
147
173
  this.overlayVisible.set(true);
148
- setTimeout(() => this.#animateIn(), 0);
174
+ this.#later(() => this.#animateIn(), 0);
149
175
  }, 0);
150
176
  }
151
177
 
@@ -154,9 +180,9 @@ export class UiActionSheet {
154
180
  * causing elements to disappear before the animation finishes.
155
181
  */
156
182
  #doClose(): void {
157
- setTimeout(() => {
183
+ this.#later(() => {
158
184
  this.#animateOut();
159
- setTimeout(() => {
185
+ this.#later(() => {
160
186
  this.overlayVisible.set(false);
161
187
  this.closed.emit();
162
188
  }, DURATION.normal + 20);
@@ -172,6 +198,11 @@ export class UiActionSheet {
172
198
  this.#panelAnim?.cancel();
173
199
 
174
200
  this.#backdropAnim = fadeIn(backdrop, { duration: DURATION.normal });
201
+ // Move a screen reader into the panel. It would otherwise stay on whatever
202
+ // opened it, which is now behind the overlay. With the panel's
203
+ // accessibility-exclusive-focus (see the template), it then can't leave
204
+ // the panel until it closes.
205
+ panel.invoke?.('requestAccessibilityFocus');
175
206
  this.#panelAnim = slideIn(panel, 'up', {
176
207
  duration: DURATION.slow,
177
208
  easing: EASING.sheet,
@@ -196,7 +227,6 @@ export class UiActionSheet {
196
227
 
197
228
  @Component({
198
229
  selector: 'ui-action-sheet-title',
199
- standalone: true,
200
230
  imports: [LYNX_ELEMENTS],
201
231
  encapsulation: ViewEncapsulation.None,
202
232
  template: `
@@ -219,7 +249,6 @@ export class UiActionSheetTitle {
219
249
 
220
250
  @Component({
221
251
  selector: 'ui-action-sheet-item',
222
- standalone: true,
223
252
  imports: [LYNX_ELEMENTS],
224
253
  encapsulation: ViewEncapsulation.None,
225
254
  template: `
@@ -289,7 +318,6 @@ export class UiActionSheetItem {
289
318
 
290
319
  @Component({
291
320
  selector: 'ui-action-sheet-cancel',
292
- standalone: true,
293
321
  imports: [LYNX_ELEMENTS],
294
322
  encapsulation: ViewEncapsulation.None,
295
323
  template: `
@@ -1,7 +1,8 @@
1
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';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+ import { DURATION } from '../../utils/animate';
5
6
  import { setInputSignal } from '../../../test-utils/set-input-signal';
6
7
  import {
7
8
  UiAlertDialog,
@@ -12,7 +13,6 @@ import {
12
13
  } from './alert-dialog';
13
14
 
14
15
  @Component({
15
- standalone: true,
16
16
  imports: [
17
17
  UiAlertDialog,
18
18
  UiAlertDialogHeader,
@@ -36,6 +36,47 @@ class AlertDialogHost {
36
36
  }
37
37
 
38
38
  describe('UiAlertDialog', () => {
39
+ it('stops its close timers when it is destroyed while closing', async () => {
40
+ const warn = vi.spyOn(console, 'warn');
41
+ const { componentRef, unmount } = await render(UiAlertDialog);
42
+ const inst = componentRef.instance as UiAlertDialog;
43
+ inst.open.set(true);
44
+ for (let i = 0; i < 5; i++) await waitForUpdate();
45
+ inst.open.set(false);
46
+ await waitForUpdate();
47
+ // Destroyed during the close animation, as an `@if` around the alert dialog
48
+ // would do. Its close timer (DURATION.normal + 20) used to fire anyway
49
+ // and emit `closed` from the destroyed output.
50
+ unmount();
51
+ await new Promise((r) => setTimeout(r, DURATION.normal + 50));
52
+ const warnings = warn.mock.calls.map(([message]) => String(message));
53
+ warn.mockRestore();
54
+ expect(warnings.filter((message) => message.includes('NG0953'))).toEqual(
55
+ [],
56
+ );
57
+ });
58
+
59
+ it('moves a screen reader into the panel and keeps it there while open', async () => {
60
+ const { componentRef, container } = await render(UiAlertDialog);
61
+ const inst = componentRef.instance as UiAlertDialog;
62
+ const panel = container.querySelector(
63
+ '[accessibility-exclusive-focus]',
64
+ ) as Element & { invoke?: unknown };
65
+ expect(panel.getAttribute('accessibility-exclusive-focus')).toBe('false');
66
+ const invoke = vi.fn();
67
+ panel.invoke = invoke;
68
+ (inst as unknown as { backdropRef: unknown }).backdropRef = signal({
69
+ nativeElement: panel,
70
+ });
71
+ (inst as unknown as { panelRef: unknown }).panelRef = signal({
72
+ nativeElement: panel,
73
+ });
74
+ inst.open.set(true);
75
+ for (let i = 0; i < 5; i++) await waitForUpdate();
76
+ expect(invoke).toHaveBeenCalledWith('requestAccessibilityFocus');
77
+ expect(panel.getAttribute('accessibility-exclusive-focus')).toBe('true');
78
+ });
79
+
39
80
  it('renders header, title, description, and footer', async () => {
40
81
  const { getByText } = await render(AlertDialogHost);
41
82
  expect(getByText('Sure?')).toBeTruthy();
@@ -79,12 +120,18 @@ describe('UiAlertDialog', () => {
79
120
  // Covers the early-return branches in #animateIn / #animateOut.
80
121
  const { componentRef } = await render(UiAlertDialog);
81
122
  const inst = componentRef.instance as UiAlertDialog;
123
+ const closed = vi.fn();
124
+ inst.closed.subscribe(closed);
82
125
  inst.open.set(true);
83
126
  await waitForUpdate();
84
127
  await waitForUpdate();
85
128
  inst.open.set(false);
86
129
  await waitForUpdate();
130
+ // Wait out the close sequence, so #animateOut runs (and bails) while the
131
+ // component is still alive. Destroying it clears the sequence's timers.
132
+ await new Promise((r) => setTimeout(r, DURATION.normal + 50));
87
133
  expect(inst.open()).toBe(false);
134
+ expect(closed).toHaveBeenCalledOnce();
88
135
  });
89
136
 
90
137
  it('overlayStyle switches between visible and hidden variants', async () => {
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  type ElementRef,
3
+ type OnDestroy,
3
4
  Component,
4
5
  ViewEncapsulation,
5
6
  computed,
@@ -26,20 +27,24 @@ import { cn } from '../../utils/cn';
26
27
 
27
28
  @Component({
28
29
  selector: 'ui-alert-dialog',
29
- standalone: true,
30
30
  imports: [LYNX_ELEMENTS],
31
31
  encapsulation: ViewEncapsulation.None,
32
32
  template: `
33
33
  <overlay [attr.visible]="overlayVisible()" [style]="overlayStyle()">
34
34
  <view #backdrop [class]="backdropClass()">
35
- <view #panel [class]="panelClass()" (catchtap)="onPanelTap()">
35
+ <view
36
+ #panel
37
+ [class]="panelClass()"
38
+ [accessibility-exclusive-focus]="overlayVisible()"
39
+ (catchtap)="onPanelTap()"
40
+ >
36
41
  <ng-content />
37
42
  </view>
38
43
  </view>
39
44
  </overlay>
40
45
  `,
41
46
  })
42
- export class UiAlertDialog {
47
+ export class UiAlertDialog implements OnDestroy {
43
48
  readonly open = model(false);
44
49
  readonly userClass = input<string>('', { alias: 'class' });
45
50
 
@@ -52,6 +57,9 @@ export class UiAlertDialog {
52
57
  #backdropAnim?: AnimationHandle;
53
58
  #panelAnim?: AnimationHandle;
54
59
  #hasBeenOpen = false;
60
+ // The open and close sequences' pending timers, so ngOnDestroy can stop
61
+ // them.
62
+ readonly #timers = new Set<ReturnType<typeof setTimeout>>();
55
63
 
56
64
  constructor() {
57
65
  // `#hasBeenOpen` prevents the close animation from running on the initial
@@ -106,15 +114,37 @@ export class UiAlertDialog {
106
114
  */
107
115
  protected onPanelTap(): void {}
108
116
 
117
+ /**
118
+ * The alert dialog can be destroyed while its open or close sequence is still
119
+ * running, for example by an `@if` around it or a route change right after
120
+ * closing it. The rest of the sequence then ran on the destroyed component:
121
+ * it animated detached elements and emitted `closed` from a destroyed
122
+ * output, which Angular warns about in dev builds (NG0953).
123
+ */
124
+ ngOnDestroy(): void {
125
+ for (const timer of this.#timers) clearTimeout(timer);
126
+ }
127
+
128
+ /**
129
+ * `setTimeout`, tracked so that ngOnDestroy can cancel it.
130
+ */
131
+ #later(callback: () => void, ms: number): void {
132
+ const timer = setTimeout(() => {
133
+ this.#timers.delete(timer);
134
+ callback();
135
+ }, ms);
136
+ this.#timers.add(timer);
137
+ }
138
+
109
139
  /**
110
140
  * Two-phase open: make overlay visible first (so native elements exist in
111
141
  * the tree), then animate on the next frame. See nav-drawer for the same
112
142
  * pattern — Lynx animate() requires elements to be mounted before use.
113
143
  */
114
144
  #doOpen(): void {
115
- setTimeout(() => {
145
+ this.#later(() => {
116
146
  this.overlayVisible.set(true);
117
- setTimeout(() => this.#animateIn(), 0);
147
+ this.#later(() => this.#animateIn(), 0);
118
148
  }, 0);
119
149
  }
120
150
 
@@ -123,9 +153,9 @@ export class UiAlertDialog {
123
153
  * causing elements to disappear before the animation finishes.
124
154
  */
125
155
  #doClose(): void {
126
- setTimeout(() => {
156
+ this.#later(() => {
127
157
  this.#animateOut();
128
- setTimeout(() => {
158
+ this.#later(() => {
129
159
  this.overlayVisible.set(false);
130
160
  this.closed.emit();
131
161
  }, DURATION.normal + 20);
@@ -141,11 +171,16 @@ export class UiAlertDialog {
141
171
  this.#panelAnim?.cancel();
142
172
 
143
173
  this.#backdropAnim = fadeIn(backdrop, { duration: DURATION.normal });
174
+ // Move a screen reader into the panel. It would otherwise stay on whatever
175
+ // opened it, which is now behind the overlay. With the panel's
176
+ // accessibility-exclusive-focus (see the template), it then can't leave
177
+ // the panel until it closes.
178
+ panel.invoke?.('requestAccessibilityFocus');
144
179
 
145
180
  // 30ms stagger: let the backdrop fade start first so the panel scale-in
146
181
  // feels like it's emerging from behind the dimmed overlay rather than
147
182
  // appearing simultaneously with it.
148
- setTimeout(() => {
183
+ this.#later(() => {
149
184
  this.#panelAnim = scaleIn(panel, {
150
185
  duration: DURATION.slow,
151
186
  easing: EASING.springSubtle,
@@ -176,7 +211,6 @@ export class UiAlertDialog {
176
211
 
177
212
  @Component({
178
213
  selector: 'ui-alert-dialog-header',
179
- standalone: true,
180
214
  imports: [LYNX_ELEMENTS],
181
215
  encapsulation: ViewEncapsulation.None,
182
216
  template: `
@@ -195,7 +229,6 @@ export class UiAlertDialogHeader {
195
229
 
196
230
  @Component({
197
231
  selector: 'ui-alert-dialog-title',
198
- standalone: true,
199
232
  imports: [LYNX_ELEMENTS],
200
233
  encapsulation: ViewEncapsulation.None,
201
234
  template: `<text [class]="textClass()"><ng-content /></text>`,
@@ -210,7 +243,6 @@ export class UiAlertDialogTitle {
210
243
 
211
244
  @Component({
212
245
  selector: 'ui-alert-dialog-description',
213
- standalone: true,
214
246
  imports: [LYNX_ELEMENTS],
215
247
  encapsulation: ViewEncapsulation.None,
216
248
  template: `<text [class]="textClass()"><ng-content /></text>`,
@@ -225,7 +257,6 @@ export class UiAlertDialogDescription {
225
257
 
226
258
  @Component({
227
259
  selector: 'ui-alert-dialog-footer',
228
- standalone: true,
229
260
  imports: [LYNX_ELEMENTS],
230
261
  encapsulation: ViewEncapsulation.None,
231
262
  template: `