forty-cdk 0.2.0 → 0.3.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 (220) hide show
  1. package/accordion/README.md +122 -0
  2. package/aspect-ratio/README.md +76 -0
  3. package/avatar/README.md +100 -0
  4. package/breadcrumbs/README.md +49 -0
  5. package/breakpoints/README.md +81 -0
  6. package/button/README.md +49 -0
  7. package/calendar/README.md +458 -0
  8. package/carousel/README.md +358 -0
  9. package/checkbox/README.md +146 -0
  10. package/combobox/README.md +535 -0
  11. package/context-menu/README.md +139 -0
  12. package/date-field/README.md +184 -0
  13. package/date-picker/README.md +338 -0
  14. package/dialog/README.md +388 -0
  15. package/disclosure/README.md +114 -0
  16. package/drag-drop/README.md +359 -0
  17. package/drawer/README.md +560 -0
  18. package/dropdown-menu/README.md +176 -0
  19. package/fesm2022/forty-cdk-accordion.mjs +348 -0
  20. package/fesm2022/forty-cdk-accordion.mjs.map +1 -0
  21. package/fesm2022/forty-cdk-aspect-ratio.mjs +74 -0
  22. package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -0
  23. package/fesm2022/forty-cdk-avatar.mjs +308 -0
  24. package/fesm2022/forty-cdk-avatar.mjs.map +1 -0
  25. package/fesm2022/forty-cdk-breadcrumbs.mjs +125 -0
  26. package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -0
  27. package/fesm2022/forty-cdk-breakpoints.mjs +117 -0
  28. package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -0
  29. package/fesm2022/forty-cdk-button.mjs +134 -0
  30. package/fesm2022/forty-cdk-button.mjs.map +1 -0
  31. package/fesm2022/forty-cdk-calendar.mjs +2034 -0
  32. package/fesm2022/forty-cdk-calendar.mjs.map +1 -0
  33. package/fesm2022/forty-cdk-carousel.mjs +968 -0
  34. package/fesm2022/forty-cdk-carousel.mjs.map +1 -0
  35. package/fesm2022/forty-cdk-checkbox.mjs +226 -0
  36. package/fesm2022/forty-cdk-checkbox.mjs.map +1 -0
  37. package/fesm2022/forty-cdk-combobox.mjs +2596 -0
  38. package/fesm2022/forty-cdk-combobox.mjs.map +1 -0
  39. package/fesm2022/forty-cdk-context-menu.mjs +413 -0
  40. package/fesm2022/forty-cdk-context-menu.mjs.map +1 -0
  41. package/fesm2022/forty-cdk-core.mjs +9022 -0
  42. package/fesm2022/forty-cdk-core.mjs.map +1 -0
  43. package/fesm2022/forty-cdk-date-field.mjs +744 -0
  44. package/fesm2022/forty-cdk-date-field.mjs.map +1 -0
  45. package/fesm2022/forty-cdk-date-picker.mjs +1011 -0
  46. package/fesm2022/forty-cdk-date-picker.mjs.map +1 -0
  47. package/fesm2022/forty-cdk-dialog.mjs +707 -0
  48. package/fesm2022/forty-cdk-dialog.mjs.map +1 -0
  49. package/fesm2022/forty-cdk-disclosure.mjs +190 -0
  50. package/fesm2022/forty-cdk-disclosure.mjs.map +1 -0
  51. package/fesm2022/forty-cdk-drag-drop.mjs +1180 -0
  52. package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -0
  53. package/fesm2022/forty-cdk-drawer.mjs +1641 -0
  54. package/fesm2022/forty-cdk-drawer.mjs.map +1 -0
  55. package/fesm2022/forty-cdk-dropdown-menu.mjs +350 -0
  56. package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -0
  57. package/fesm2022/forty-cdk-field.mjs +425 -0
  58. package/fesm2022/forty-cdk-field.mjs.map +1 -0
  59. package/fesm2022/forty-cdk-fieldset.mjs +164 -0
  60. package/fesm2022/forty-cdk-fieldset.mjs.map +1 -0
  61. package/fesm2022/forty-cdk-file-upload.mjs +221 -0
  62. package/fesm2022/forty-cdk-file-upload.mjs.map +1 -0
  63. package/fesm2022/forty-cdk-hover-card.mjs +496 -0
  64. package/fesm2022/forty-cdk-hover-card.mjs.map +1 -0
  65. package/fesm2022/forty-cdk-input.mjs +274 -0
  66. package/fesm2022/forty-cdk-input.mjs.map +1 -0
  67. package/fesm2022/forty-cdk-internationalized-date.mjs +1 -1
  68. package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
  69. package/fesm2022/forty-cdk-listbox.mjs +1279 -0
  70. package/fesm2022/forty-cdk-listbox.mjs.map +1 -0
  71. package/fesm2022/forty-cdk-menu.mjs +1439 -0
  72. package/fesm2022/forty-cdk-menu.mjs.map +1 -0
  73. package/fesm2022/forty-cdk-menubar.mjs +787 -0
  74. package/fesm2022/forty-cdk-menubar.mjs.map +1 -0
  75. package/fesm2022/forty-cdk-meter.mjs +211 -0
  76. package/fesm2022/forty-cdk-meter.mjs.map +1 -0
  77. package/fesm2022/forty-cdk-navigation-menu.mjs +1145 -0
  78. package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -0
  79. package/fesm2022/forty-cdk-number-input.mjs +559 -0
  80. package/fesm2022/forty-cdk-number-input.mjs.map +1 -0
  81. package/fesm2022/forty-cdk-otp-input.mjs +527 -0
  82. package/fesm2022/forty-cdk-otp-input.mjs.map +1 -0
  83. package/fesm2022/forty-cdk-pagination.mjs +323 -0
  84. package/fesm2022/forty-cdk-pagination.mjs.map +1 -0
  85. package/fesm2022/forty-cdk-pane-resizer.mjs +297 -0
  86. package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -0
  87. package/fesm2022/forty-cdk-popover.mjs +698 -0
  88. package/fesm2022/forty-cdk-popover.mjs.map +1 -0
  89. package/fesm2022/forty-cdk-progress.mjs +226 -0
  90. package/fesm2022/forty-cdk-progress.mjs.map +1 -0
  91. package/fesm2022/forty-cdk-radio-group.mjs +378 -0
  92. package/fesm2022/forty-cdk-radio-group.mjs.map +1 -0
  93. package/fesm2022/forty-cdk-scroll-area.mjs +640 -0
  94. package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -0
  95. package/fesm2022/forty-cdk-search.mjs +205 -0
  96. package/fesm2022/forty-cdk-search.mjs.map +1 -0
  97. package/fesm2022/forty-cdk-select.mjs +1661 -0
  98. package/fesm2022/forty-cdk-select.mjs.map +1 -0
  99. package/fesm2022/forty-cdk-separator.mjs +82 -0
  100. package/fesm2022/forty-cdk-separator.mjs.map +1 -0
  101. package/fesm2022/forty-cdk-signal-forms.mjs +97 -0
  102. package/fesm2022/forty-cdk-signal-forms.mjs.map +1 -0
  103. package/fesm2022/forty-cdk-slider.mjs +803 -0
  104. package/fesm2022/forty-cdk-slider.mjs.map +1 -0
  105. package/fesm2022/forty-cdk-stepper.mjs +886 -0
  106. package/fesm2022/forty-cdk-stepper.mjs.map +1 -0
  107. package/fesm2022/forty-cdk-switch.mjs +137 -0
  108. package/fesm2022/forty-cdk-switch.mjs.map +1 -0
  109. package/fesm2022/forty-cdk-table.mjs +1518 -0
  110. package/fesm2022/forty-cdk-table.mjs.map +1 -0
  111. package/fesm2022/forty-cdk-tabs.mjs +400 -0
  112. package/fesm2022/forty-cdk-tabs.mjs.map +1 -0
  113. package/fesm2022/forty-cdk-time-field.mjs +593 -0
  114. package/fesm2022/forty-cdk-time-field.mjs.map +1 -0
  115. package/fesm2022/forty-cdk-time-picker.mjs +1013 -0
  116. package/fesm2022/forty-cdk-time-picker.mjs.map +1 -0
  117. package/fesm2022/forty-cdk-toast.mjs +1153 -0
  118. package/fesm2022/forty-cdk-toast.mjs.map +1 -0
  119. package/fesm2022/forty-cdk-toggle.mjs +516 -0
  120. package/fesm2022/forty-cdk-toggle.mjs.map +1 -0
  121. package/fesm2022/forty-cdk-toolbar.mjs +374 -0
  122. package/fesm2022/forty-cdk-toolbar.mjs.map +1 -0
  123. package/fesm2022/forty-cdk-tooltip.mjs +672 -0
  124. package/fesm2022/forty-cdk-tooltip.mjs.map +1 -0
  125. package/fesm2022/forty-cdk-tree.mjs +2007 -0
  126. package/fesm2022/forty-cdk-tree.mjs.map +1 -0
  127. package/fesm2022/forty-cdk-virtualization.mjs +1 -1
  128. package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
  129. package/fesm2022/forty-cdk.mjs +0 -43310
  130. package/fesm2022/forty-cdk.mjs.map +1 -1
  131. package/field/README.md +97 -0
  132. package/fieldset/README.md +86 -0
  133. package/file-upload/README.md +73 -0
  134. package/hover-card/README.md +171 -0
  135. package/input/README.md +156 -0
  136. package/listbox/README.md +424 -0
  137. package/menu/README.md +181 -0
  138. package/menubar/README.md +140 -0
  139. package/meter/README.md +128 -0
  140. package/navigation-menu/README.md +253 -0
  141. package/number-input/README.md +171 -0
  142. package/otp-input/README.md +198 -0
  143. package/package.json +213 -1
  144. package/pagination/README.md +61 -0
  145. package/pane-resizer/README.md +136 -0
  146. package/popover/README.md +262 -0
  147. package/progress/README.md +115 -0
  148. package/radio-group/README.md +129 -0
  149. package/scroll-area/README.md +184 -0
  150. package/search/README.md +42 -0
  151. package/select/README.md +488 -0
  152. package/separator/README.md +84 -0
  153. package/signal-forms/README.md +72 -0
  154. package/slider/README.md +152 -0
  155. package/stepper/README.md +292 -0
  156. package/switch/README.md +116 -0
  157. package/table/README.md +769 -0
  158. package/tabs/README.md +130 -0
  159. package/time-field/README.md +157 -0
  160. package/time-picker/README.md +172 -0
  161. package/toast/README.md +398 -0
  162. package/toggle/README.md +224 -0
  163. package/toolbar/README.md +109 -0
  164. package/tooltip/README.md +274 -0
  165. package/tree/README.md +708 -0
  166. package/types/forty-cdk-accordion.d.ts +242 -0
  167. package/types/forty-cdk-aspect-ratio.d.ts +59 -0
  168. package/types/forty-cdk-avatar.d.ts +133 -0
  169. package/types/forty-cdk-breadcrumbs.d.ts +92 -0
  170. package/types/forty-cdk-breakpoints.d.ts +141 -0
  171. package/types/forty-cdk-button.d.ts +80 -0
  172. package/types/forty-cdk-calendar.d.ts +914 -0
  173. package/types/forty-cdk-carousel.d.ts +530 -0
  174. package/types/forty-cdk-checkbox.d.ts +141 -0
  175. package/types/forty-cdk-combobox.d.ts +1259 -0
  176. package/types/forty-cdk-context-menu.d.ts +313 -0
  177. package/types/forty-cdk-core.d.ts +5774 -0
  178. package/types/forty-cdk-date-field.d.ts +307 -0
  179. package/types/forty-cdk-date-picker.d.ts +622 -0
  180. package/types/forty-cdk-dialog.d.ts +546 -0
  181. package/types/forty-cdk-disclosure.d.ts +127 -0
  182. package/types/forty-cdk-drag-drop.d.ts +456 -0
  183. package/types/forty-cdk-drawer.d.ts +871 -0
  184. package/types/forty-cdk-dropdown-menu.d.ts +242 -0
  185. package/types/forty-cdk-field.d.ts +236 -0
  186. package/types/forty-cdk-fieldset.d.ts +119 -0
  187. package/types/forty-cdk-file-upload.d.ts +124 -0
  188. package/types/forty-cdk-hover-card.d.ts +320 -0
  189. package/types/forty-cdk-input.d.ts +169 -0
  190. package/types/forty-cdk-internationalized-date.d.ts +1 -1
  191. package/types/forty-cdk-listbox.d.ts +513 -0
  192. package/types/forty-cdk-menu.d.ts +629 -0
  193. package/types/forty-cdk-menubar.d.ts +451 -0
  194. package/types/forty-cdk-meter.d.ts +122 -0
  195. package/types/forty-cdk-navigation-menu.d.ts +514 -0
  196. package/types/forty-cdk-number-input.d.ts +319 -0
  197. package/types/forty-cdk-otp-input.d.ts +248 -0
  198. package/types/forty-cdk-pagination.d.ts +214 -0
  199. package/types/forty-cdk-pane-resizer.d.ts +145 -0
  200. package/types/forty-cdk-popover.d.ts +509 -0
  201. package/types/forty-cdk-progress.d.ts +143 -0
  202. package/types/forty-cdk-radio-group.d.ts +222 -0
  203. package/types/forty-cdk-scroll-area.d.ts +258 -0
  204. package/types/forty-cdk-search.d.ts +142 -0
  205. package/types/forty-cdk-select.d.ts +899 -0
  206. package/types/forty-cdk-separator.d.ts +59 -0
  207. package/types/forty-cdk-signal-forms.d.ts +58 -0
  208. package/types/forty-cdk-slider.d.ts +379 -0
  209. package/types/forty-cdk-stepper.d.ts +650 -0
  210. package/types/forty-cdk-switch.d.ts +87 -0
  211. package/types/forty-cdk-table.d.ts +723 -0
  212. package/types/forty-cdk-tabs.d.ts +235 -0
  213. package/types/forty-cdk-time-field.d.ts +307 -0
  214. package/types/forty-cdk-time-picker.d.ts +578 -0
  215. package/types/forty-cdk-toast.d.ts +598 -0
  216. package/types/forty-cdk-toggle.d.ts +310 -0
  217. package/types/forty-cdk-toolbar.d.ts +217 -0
  218. package/types/forty-cdk-tooltip.d.ts +436 -0
  219. package/types/forty-cdk-tree.d.ts +688 -0
  220. package/types/forty-cdk.d.ts +1 -19743
@@ -0,0 +1,1153 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, ElementRef, DestroyRef, input, numberAttribute, booleanAttribute, output, signal, computed, effect, untracked, Directive, DOCUMENT, Injectable, afterEveryRender, Injector, isDevMode, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { LiveAnnouncer, injectPauseController, attachSwipeDismiss, createDefaults, IdGenerator, registerHandle, resolveHostId, resolveConfigClass } from 'forty-cdk/core';
4
+ import { NgTemplateOutlet } from '@angular/common';
5
+
6
+ /**
7
+ * Region a toast belongs to. Toasts route to the `<for-toast-viewport>` whose
8
+ * `[region]` matches; an unspecified region falls through to this default, so
9
+ * the common single-viewport setup needs no `region` anywhere.
10
+ */
11
+ const DEFAULT_TOAST_REGION = 'default';
12
+ const FOR_TOAST_CONTEXT = new InjectionToken('FOR_TOAST_CONTEXT');
13
+ function injectToastContext(piece) {
14
+ const ctx = inject(FOR_TOAST_CONTEXT, { optional: true });
15
+ if (!ctx) {
16
+ throw new Error(`[forty-cdk/toast] ${piece} must be used inside a [forToast] element.`);
17
+ }
18
+ return ctx;
19
+ }
20
+
21
+ /**
22
+ * One toast notification. Apply on a `<div>` (or `<output>`, `<section>`)
23
+ * — the directive sets `role="status"` (`'info'` / `'success'` / `'warning'`)
24
+ * or `role="alert"` (`'error'`), `aria-live`, and the timer / pause
25
+ * machinery.
26
+ *
27
+ * The directive does **not** control its own visibility. The consumer
28
+ * mounts it (typically through `<for-toast-viewport>` for programmatic
29
+ * toasts, or directly with `@if` for declarative ones) and unmounts it
30
+ * when `(dismiss)` fires. Pair the unmount with `animate.leave="…"` for
31
+ * exit animations.
32
+ *
33
+ * Behavior:
34
+ * - Auto-dismisses after `duration` ms (default 5000). `0` is sticky.
35
+ * - Hovering or focusing the toast pauses the timer; leaving / blurring
36
+ * resumes with the remaining time.
37
+ * - Escape (while focus is inside) closes the toast.
38
+ * - When `[swipeDirection]` is set, the user can drag past
39
+ * `[swipeThreshold]` (default 50 px) to dismiss. While the gesture is
40
+ * live the host reflects `data-swipe="start" | "move" | "cancel" | "end"`,
41
+ * `data-swipe-direction`, and the CSS variables
42
+ * `--for-toast-swipe-movement-x` / `--for-toast-swipe-movement-y`.
43
+ * - The host carries `data-state="open"` while alive (no `closed` state —
44
+ * the consumer unmounts on close).
45
+ */
46
+ class ForToast {
47
+ #host = inject(ElementRef);
48
+ #destroyRef = inject(DestroyRef);
49
+ #announcer = inject(LiveAnnouncer);
50
+ variant = input('info', /* @ts-ignore */
51
+ ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
52
+ /** Auto-dismiss timer in ms. `0` keeps the toast sticky. */
53
+ duration = input(5000, { ...(ngDevMode ? { debugName: "duration" } : /* istanbul ignore next */ {}), transform: numberAttribute });
54
+ /**
55
+ * Whether the user can dismiss the toast through an ambient gesture —
56
+ * Escape, the close button, the auto-dismiss timer, and swipe. Defaults to
57
+ * `true`.
58
+ *
59
+ * Set `false` for a sticky / forced-action toast: every ambient gesture is
60
+ * suppressed, no auto-dismiss timer is scheduled, and a `[forToastClose]`
61
+ * button (if present) becomes inert. The **action button stays live and is
62
+ * the sanctioned dismissal path** — `[forToastAction]` still emits `(dismiss)`
63
+ * with reason `'action'` so a forced-action toast (e.g. "Update available —
64
+ * Reload") can be dismissed by the only control the user is meant to use.
65
+ * Programmatic close via `ForToastRef.dismiss()` is also always honored.
66
+ */
67
+ closable = input(true, { ...(ngDevMode ? { debugName: "closable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
68
+ /**
69
+ * Direction(s) the user can swipe to dismiss the toast. Pass a
70
+ * single direction (`'right'`) or an array of directions
71
+ * (`['right', 'down']`). `null` (default) disables swipe.
72
+ *
73
+ * The swipe gesture uses pointer events, so it works for both touch
74
+ * and mouse drags. While the user is swiping, the host reflects
75
+ * `data-swipe="start" | "move" | "cancel" | "end"` and
76
+ * `data-swipe-direction` plus the CSS custom properties
77
+ * `--for-toast-swipe-movement-x` / `--for-toast-swipe-movement-y` so the
78
+ * consumer can drive a transform-based animation entirely from CSS.
79
+ */
80
+ swipeDirection = input(null, /* @ts-ignore */
81
+ ...(ngDevMode ? [{ debugName: "swipeDirection" }] : /* istanbul ignore next */ []));
82
+ /**
83
+ * Pixels of pointer travel along the active swipe direction needed
84
+ * to commit a dismissal. Defaults to `50`.
85
+ */
86
+ swipeThreshold = input(50, { ...(ngDevMode ? { debugName: "swipeThreshold" } : /* istanbul ignore next */ {}), transform: numberAttribute });
87
+ /** Emitted when the toast wants to be unmounted. The consumer reacts by removing it from the rendered list. */
88
+ dismiss = output();
89
+ /** Fired once on the move that arms the swipe. */
90
+ swipeStart = output();
91
+ /** Fired on every pointer move while the swipe is active. */
92
+ swipeMove = output();
93
+ /** Fired on pointer-up after the threshold is crossed (immediately followed by `(dismiss)`). */
94
+ swipeEnd = output();
95
+ /** Fired on pointer-up before the threshold, or on `pointercancel`. */
96
+ swipeCancel = output();
97
+ #pause;
98
+ paused;
99
+ #swipeState = signal(null, /* @ts-ignore */
100
+ ...(ngDevMode ? [{ debugName: "#swipeState" }] : /* istanbul ignore next */ []));
101
+ #swipeActiveDirection = signal(null, /* @ts-ignore */
102
+ ...(ngDevMode ? [{ debugName: "#swipeActiveDirection" }] : /* istanbul ignore next */ []));
103
+ #swipeMovementX = signal(0, /* @ts-ignore */
104
+ ...(ngDevMode ? [{ debugName: "#swipeMovementX" }] : /* istanbul ignore next */ []));
105
+ #swipeMovementY = signal(0, /* @ts-ignore */
106
+ ...(ngDevMode ? [{ debugName: "#swipeMovementY" }] : /* istanbul ignore next */ []));
107
+ swipeState = this.#swipeState.asReadonly();
108
+ swipeActiveDirection = this.#swipeActiveDirection.asReadonly();
109
+ swipeMovementX = this.#swipeMovementX.asReadonly();
110
+ swipeMovementY = this.#swipeMovementY.asReadonly();
111
+ #normalizedSwipeDirections = computed(() => {
112
+ // A non-closable toast forbids user-initiated dismissal of any kind —
113
+ // swipe is exactly that, so disable it regardless of the input.
114
+ if (!this.closable()) {
115
+ return [];
116
+ }
117
+ const raw = this.swipeDirection();
118
+ if (raw === null || raw === undefined) {
119
+ return [];
120
+ }
121
+ if (typeof raw === 'string') {
122
+ return [raw];
123
+ }
124
+ return raw;
125
+ }, /* @ts-ignore */
126
+ ...(ngDevMode ? [{ debugName: "#normalizedSwipeDirections" }] : /* istanbul ignore next */ []));
127
+ #labels = signal([], /* @ts-ignore */
128
+ ...(ngDevMode ? [{ debugName: "#labels" }] : /* istanbul ignore next */ []));
129
+ #descriptions = signal([], /* @ts-ignore */
130
+ ...(ngDevMode ? [{ debugName: "#descriptions" }] : /* istanbul ignore next */ []));
131
+ #actions = signal([], /* @ts-ignore */
132
+ ...(ngDevMode ? [{ debugName: "#actions" }] : /* istanbul ignore next */ []));
133
+ labelledBy = computed(() => this.#labels()
134
+ .map((h) => h.id)
135
+ .join(' ') || null, /* @ts-ignore */
136
+ ...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
137
+ describedBy = computed(() => this.#descriptions()
138
+ .map((h) => h.id)
139
+ .join(' ') || null, /* @ts-ignore */
140
+ ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
141
+ /**
142
+ * `true` when at least one registered `[forToastAction]` carries a
143
+ * non-empty `altText`. In that mode the host's `aria-live` is silenced
144
+ * and the announcement is composed and routed through `LiveAnnouncer`.
145
+ */
146
+ #hasActionAltText = computed(() => this.#actions().some((a) => a.altText().trim() !== ''), /* @ts-ignore */
147
+ ...(ngDevMode ? [{ debugName: "#hasActionAltText" }] : /* istanbul ignore next */ []));
148
+ /**
149
+ * The synthesized announcement, composed reactively from the registered
150
+ * title / description text and action `altText` signals (never re-read from
151
+ * the DOM). Recomputes when any of those change — late-bound `altText` or a
152
+ * `ref.update()` text change flows through here, so the announcement effect
153
+ * re-fires on the edge instead of only once on first render.
154
+ */
155
+ #announcement = computed(() => {
156
+ const titles = this.#labels()
157
+ .map((h) => h.text().trim())
158
+ .filter(Boolean);
159
+ const descriptions = this.#descriptions()
160
+ .map((h) => h.text().trim())
161
+ .filter(Boolean);
162
+ const altTexts = this.#actions()
163
+ .map((a) => a.altText().trim())
164
+ .filter(Boolean);
165
+ return [...titles, ...descriptions, ...altTexts].join('. ');
166
+ }, /* @ts-ignore */
167
+ ...(ngDevMode ? [{ debugName: "#announcement" }] : /* istanbul ignore next */ []));
168
+ computedRole = computed(() => this.variant() === 'error' ? 'alert' : 'status', /* @ts-ignore */
169
+ ...(ngDevMode ? [{ debugName: "computedRole" }] : /* istanbul ignore next */ []));
170
+ ariaLive = computed(() => {
171
+ if (this.#hasActionAltText()) {
172
+ return 'off';
173
+ }
174
+ return this.variant() === 'error' ? 'assertive' : 'polite';
175
+ }, /* @ts-ignore */
176
+ ...(ngDevMode ? [{ debugName: "ariaLive" }] : /* istanbul ignore next */ []));
177
+ // Timer state — kept off the reactive graph because pause/resume are
178
+ // imperative and we don't want to trip change detection on every tick.
179
+ #timerHandle = null;
180
+ #timerEndsAt = 0;
181
+ #remainingMs = 0;
182
+ // True once the auto-dismiss countdown has begun, so `#remainingMs` holds a
183
+ // meaningful value. While paused this guards the duration effect from
184
+ // clobbering a captured remaining time on an unrelated re-run (e.g. an
185
+ // `update()` that re-renders the toast); see `#onPausedChange`.
186
+ #timerStarted = false;
187
+ constructor() {
188
+ // Multi-reason pause: pointer hover, focus, and page visibility each
189
+ // pause/resume independently. The remaining-ms capture/reschedule is
190
+ // layered on the `paused` transition via `onChange`, keeping the timer off
191
+ // the reactive graph (pause/resume are imperative, so a tick must not trip
192
+ // change detection). The shared controller wires the page-visibility source
193
+ // and tears its subscription down with this injector.
194
+ this.#pause = injectPauseController({
195
+ onChange: (paused) => this.#onPausedChange(paused),
196
+ });
197
+ this.paused = this.#pause.paused;
198
+ // Start (or reset) the auto-dismiss timer whenever `duration` /
199
+ // `closable` changes. Pause / resume are handled imperatively in
200
+ // `#onPausedChange`, so we read `paused()` via `untracked()` here —
201
+ // otherwise the effect would re-run on hover and clobber the in-flight
202
+ // remaining-ms capture. While paused with a countdown already in flight we
203
+ // bail out entirely so the captured remaining time survives a re-render;
204
+ // resume reschedules with that captured value rather than the full
205
+ // duration.
206
+ effect(() => {
207
+ const ms = this.duration();
208
+ const closable = this.closable();
209
+ const paused = untracked(this.paused);
210
+ if (paused && this.#timerStarted) {
211
+ return;
212
+ }
213
+ this.#cancelTimer();
214
+ this.#remainingMs = ms;
215
+ this.#timerStarted = ms > 0 && closable;
216
+ if (this.#timerStarted && !paused) {
217
+ this.#scheduleTimer();
218
+ }
219
+ });
220
+ this.#destroyRef.onDestroy(() => this.#cancelTimer());
221
+ // Route the synthesized announcement through LiveAnnouncer reactively, so
222
+ // a late-bound `altText` and `ref.update()` text changes are announced —
223
+ // not only the value present on first render. Only active when at least
224
+ // one action carries `altText` (the host's silenced `aria-live` path);
225
+ // otherwise the host's own `aria-live` reads the toast and a manual
226
+ // announce would duplicate it. A guard against re-announcing identical
227
+ // text keeps an unrelated re-render from re-reading the same message.
228
+ // Pure side effect (an imperative `LiveAnnouncer` write), so `effect()` is
229
+ // the correct primitive here.
230
+ let lastAnnounced = null;
231
+ effect(() => {
232
+ if (!this.#hasActionAltText()) {
233
+ lastAnnounced = null;
234
+ return;
235
+ }
236
+ const message = this.#announcement();
237
+ if (!message || message === lastAnnounced) {
238
+ return;
239
+ }
240
+ lastAnnounced = message;
241
+ this.#announcer.announce(message, this.variant() === 'error' ? 'assertive' : 'polite');
242
+ });
243
+ const detachSwipe = attachSwipeDismiss({
244
+ element: this.#host.nativeElement,
245
+ getDirections: () => this.#normalizedSwipeDirections(),
246
+ getThreshold: () => this.swipeThreshold(),
247
+ onSwipeStart: (detail) => {
248
+ this.#swipeActiveDirection.set(detail.direction);
249
+ this.#swipeState.set('start');
250
+ this.#swipeMovementX.set(detail.delta.x);
251
+ this.#swipeMovementY.set(detail.delta.y);
252
+ this.swipeStart.emit(detail);
253
+ },
254
+ onSwipeMove: (detail) => {
255
+ this.#swipeState.set('move');
256
+ this.#swipeMovementX.set(detail.delta.x);
257
+ this.#swipeMovementY.set(detail.delta.y);
258
+ this.swipeMove.emit(detail);
259
+ },
260
+ onSwipeCancel: (detail) => {
261
+ this.#swipeState.set('cancel');
262
+ // Movement vars stay at the released delta so the consumer's CSS
263
+ // transition can spring them back to zero on its own timeline. The
264
+ // parked `data-swipe="cancel"` is cleared on the next `pointerdown`
265
+ // (see `onPointerDown`) so it never lingers into an unrelated gesture.
266
+ this.swipeCancel.emit(detail);
267
+ },
268
+ onSwipeEnd: (detail) => {
269
+ this.#swipeState.set('end');
270
+ this.#swipeMovementX.set(detail.delta.x);
271
+ this.#swipeMovementY.set(detail.delta.y);
272
+ this.swipeEnd.emit(detail);
273
+ this.requestClose('swipe');
274
+ },
275
+ });
276
+ this.#destroyRef.onDestroy(detachSwipe);
277
+ }
278
+ registerLabel(handle) {
279
+ this.#labels.update((arr) => (arr.includes(handle) ? arr : [...arr, handle]));
280
+ }
281
+ unregisterLabel(handle) {
282
+ this.#labels.update((arr) => arr.filter((h) => h !== handle));
283
+ }
284
+ registerDescription(handle) {
285
+ this.#descriptions.update((arr) => (arr.includes(handle) ? arr : [...arr, handle]));
286
+ }
287
+ unregisterDescription(handle) {
288
+ this.#descriptions.update((arr) => arr.filter((h) => h !== handle));
289
+ }
290
+ registerAction(handle) {
291
+ this.#actions.update((arr) => (arr.includes(handle) ? arr : [...arr, handle]));
292
+ }
293
+ unregisterAction(handle) {
294
+ this.#actions.update((arr) => arr.filter((h) => h !== handle));
295
+ }
296
+ requestClose(reason) {
297
+ if (!this.closable() && reason !== 'action' && reason !== 'programmatic') {
298
+ return;
299
+ }
300
+ this.#cancelTimer();
301
+ this.dismiss.emit(reason);
302
+ }
303
+ /**
304
+ * Clear a parked swipe-cancel state at the start of the next pointer
305
+ * interaction. After a cancel the host keeps `data-swipe="cancel"` and the
306
+ * released movement vars so the consumer's CSS can spring the toast back; a
307
+ * fresh `pointerdown` is the terminal reset that returns the host to a clean
308
+ * slate (no stale `data-swipe`, zeroed movement vars) before a new gesture
309
+ * arms. A re-armed swipe overwrites the state anyway, so this only matters
310
+ * for the parked-after-cancel window.
311
+ */
312
+ onPointerDown() {
313
+ if (this.#swipeState() === 'cancel') {
314
+ this.#resetSwipeState();
315
+ }
316
+ }
317
+ #resetSwipeState() {
318
+ this.#swipeState.set(null);
319
+ this.#swipeActiveDirection.set(null);
320
+ this.#swipeMovementX.set(0);
321
+ this.#swipeMovementY.set(0);
322
+ }
323
+ onPause(reason) {
324
+ this.#pause.apply(reason);
325
+ }
326
+ onResume(reason) {
327
+ this.#pause.release(reason);
328
+ }
329
+ onMaybeResumeFocus(event) {
330
+ const next = event.relatedTarget;
331
+ if (next && this.#host.nativeElement.contains(next)) {
332
+ return;
333
+ }
334
+ this.onResume('focus');
335
+ }
336
+ onKeyDown(event) {
337
+ if (event.key !== 'Escape' || !this.closable()) {
338
+ return;
339
+ }
340
+ event.preventDefault();
341
+ event.stopPropagation();
342
+ this.requestClose('escape');
343
+ }
344
+ #onPausedChange(paused) {
345
+ if (paused) {
346
+ // Pause: capture remaining time and cancel the running timer.
347
+ if (this.#timerHandle !== null) {
348
+ this.#remainingMs = Math.max(0, this.#timerEndsAt - Date.now());
349
+ this.#cancelTimer();
350
+ }
351
+ }
352
+ else if (this.duration() > 0 && this.closable() && this.#remainingMs > 0) {
353
+ this.#scheduleTimer();
354
+ }
355
+ }
356
+ #scheduleTimer() {
357
+ this.#cancelTimer();
358
+ this.#timerEndsAt = Date.now() + this.#remainingMs;
359
+ this.#timerHandle = setTimeout(() => {
360
+ this.#timerHandle = null;
361
+ this.requestClose('auto');
362
+ }, this.#remainingMs);
363
+ }
364
+ #cancelTimer() {
365
+ if (this.#timerHandle !== null) {
366
+ clearTimeout(this.#timerHandle);
367
+ this.#timerHandle = null;
368
+ }
369
+ }
370
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToast, deps: [], target: i0.ɵɵFactoryTarget.Directive });
371
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForToast, isStandalone: true, selector: "[forToast]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, duration: { classPropertyName: "duration", publicName: "duration", isSignal: true, isRequired: false, transformFunction: null }, closable: { classPropertyName: "closable", publicName: "closable", isSignal: true, isRequired: false, transformFunction: null }, swipeDirection: { classPropertyName: "swipeDirection", publicName: "swipeDirection", isSignal: true, isRequired: false, transformFunction: null }, swipeThreshold: { classPropertyName: "swipeThreshold", publicName: "swipeThreshold", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dismiss: "dismiss", swipeStart: "swipeStart", swipeMove: "swipeMove", swipeEnd: "swipeEnd", swipeCancel: "swipeCancel" }, host: { attributes: { "aria-atomic": "true", "tabindex": "0" }, listeners: { "pointerdown": "onPointerDown()", "pointerenter": "onPause(\"hover\")", "pointerleave": "onResume(\"hover\")", "focusin": "onPause(\"focus\")", "focusout": "onMaybeResumeFocus($event)", "keydown": "onKeyDown($event)" }, properties: { "attr.role": "computedRole()", "attr.aria-live": "ariaLive()", "attr.aria-labelledby": "labelledBy()", "attr.aria-describedby": "describedBy()", "attr.data-state": "\"open\"", "attr.data-variant": "variant()", "attr.data-paused": "paused() ? \"\" : null", "attr.data-swipe": "swipeState()", "attr.data-swipe-direction": "swipeActiveDirection()", "style.--for-toast-swipe-movement-x.px": "swipeMovementX()", "style.--for-toast-swipe-movement-y.px": "swipeMovementY()" } }, providers: [{ provide: FOR_TOAST_CONTEXT, useExisting: ForToast }], exportAs: ["forToast"], ngImport: i0 });
372
+ }
373
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToast, decorators: [{
374
+ type: Directive,
375
+ args: [{
376
+ selector: '[forToast]',
377
+ exportAs: 'forToast',
378
+ host: {
379
+ '[attr.role]': 'computedRole()',
380
+ '[attr.aria-live]': 'ariaLive()',
381
+ 'aria-atomic': 'true',
382
+ '[attr.aria-labelledby]': 'labelledBy()',
383
+ '[attr.aria-describedby]': 'describedBy()',
384
+ tabindex: '0',
385
+ '[attr.data-state]': '"open"',
386
+ '[attr.data-variant]': 'variant()',
387
+ '[attr.data-paused]': 'paused() ? "" : null',
388
+ '[attr.data-swipe]': 'swipeState()',
389
+ '[attr.data-swipe-direction]': 'swipeActiveDirection()',
390
+ '[style.--for-toast-swipe-movement-x.px]': 'swipeMovementX()',
391
+ '[style.--for-toast-swipe-movement-y.px]': 'swipeMovementY()',
392
+ '(pointerdown)': 'onPointerDown()',
393
+ '(pointerenter)': 'onPause("hover")',
394
+ '(pointerleave)': 'onResume("hover")',
395
+ '(focusin)': 'onPause("focus")',
396
+ '(focusout)': 'onMaybeResumeFocus($event)',
397
+ '(keydown)': 'onKeyDown($event)',
398
+ },
399
+ providers: [{ provide: FOR_TOAST_CONTEXT, useExisting: ForToast }],
400
+ }]
401
+ }], ctorParameters: () => [], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], duration: [{ type: i0.Input, args: [{ isSignal: true, alias: "duration", required: false }] }], closable: [{ type: i0.Input, args: [{ isSignal: true, alias: "closable", required: false }] }], swipeDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "swipeDirection", required: false }] }], swipeThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "swipeThreshold", required: false }] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }], swipeStart: [{ type: i0.Output, args: ["swipeStart"] }], swipeMove: [{ type: i0.Output, args: ["swipeMove"] }], swipeEnd: [{ type: i0.Output, args: ["swipeEnd"] }], swipeCancel: [{ type: i0.Output, args: ["swipeCancel"] }] } });
402
+
403
+ const FALLBACK = {
404
+ duration: 5000,
405
+ hotkey: 'F6',
406
+ maxVisible: Infinity,
407
+ };
408
+ const { token, provideDefaults } = createDefaults('FOR_TOAST_DEFAULTS', FALLBACK);
409
+ /**
410
+ * Token holding the resolved toast defaults for the current injector scope.
411
+ * The library always provides a fully-populated value (the fallback at the
412
+ * root, or the merged result of the nearest `provideForToastDefaults`).
413
+ */
414
+ const FOR_TOAST_DEFAULTS = token;
415
+ /**
416
+ * Configures forty-cdk toast defaults for this injector scope. Partial
417
+ * overrides inherit unspecified keys from the parent scope (or library
418
+ * defaults at the root).
419
+ */
420
+ function provideForToastDefaults(defaults) {
421
+ return provideDefaults(defaults);
422
+ }
423
+
424
+ /**
425
+ * Handle returned by `ForToastManager.show()`. Drives `dismiss()` /
426
+ * `update()` imperatively from anywhere. Both reactive (`isClosed`) and
427
+ * awaitable (`closed`) APIs are exposed — pick what fits the call site.
428
+ *
429
+ * `R` is the close-result type, settable via `dismiss(reason, value)`.
430
+ * Default `unknown`; specify on `manager.show<R, D>(...)` for type safety.
431
+ */
432
+ class ForToastRef {
433
+ #closed = signal(false, /* @ts-ignore */
434
+ ...(ngDevMode ? [{ debugName: "#closed" }] : /* istanbul ignore next */ []));
435
+ #result = signal(undefined, /* @ts-ignore */
436
+ ...(ngDevMode ? [{ debugName: "#result" }] : /* istanbul ignore next */ []));
437
+ #config = signal({}, /* @ts-ignore */
438
+ ...(ngDevMode ? [{ debugName: "#config" }] : /* istanbul ignore next */ []));
439
+ /** Reactive: `true` once `dismiss()` has been called. */
440
+ isClosed = this.#closed.asReadonly();
441
+ /** Reactive: the close result, populated on `dismiss(reason, value)`. */
442
+ result = this.#result.asReadonly();
443
+ /** Reactive: the toast's current config (mutable via `update()`). */
444
+ config = this.#config.asReadonly();
445
+ /** Awaitable: resolves with `{ reason, result }` the first time `dismiss()` runs. */
446
+ closed;
447
+ #resolveClosed;
448
+ #teardown;
449
+ /** @internal */
450
+ constructor(initialConfig, teardown) {
451
+ this.#config.set(initialConfig);
452
+ this.#teardown = teardown;
453
+ this.closed = new Promise((resolve) => {
454
+ this.#resolveClosed = resolve;
455
+ });
456
+ }
457
+ /**
458
+ * Dismiss the toast, optionally returning a result. Idempotent — calling
459
+ * twice is a no-op. Reason defaults to `'programmatic'`.
460
+ */
461
+ dismiss(reason = 'programmatic', result) {
462
+ if (this.#closed()) {
463
+ return;
464
+ }
465
+ this.#closed.set(true);
466
+ this.#result.set(result);
467
+ this.#resolveClosed({ reason, result });
468
+ this.#teardown(reason);
469
+ }
470
+ /**
471
+ * Mutate the toast in-place (e.g. to update text on long-running
472
+ * operations: "Saving…" → "Saved"). No-op once dismissed.
473
+ *
474
+ * `id` and `region` are normalized at `show()` and are **immutable** — they
475
+ * are ignored here. `id` is the toast's identity (used for dedupe and
476
+ * external dismissal); `region` decides which `<for-toast-viewport>` renders
477
+ * the toast, and changing it would silently unmount and remount the toast in
478
+ * another viewport (resetting its timer and announcement). To move a toast
479
+ * to a different region, dismiss it and `show()` a new one.
480
+ */
481
+ update(patch) {
482
+ if (this.#closed()) {
483
+ return;
484
+ }
485
+ this.#config.update((current) => {
486
+ const { id: _id, region: _region, ...mutable } = patch;
487
+ return { ...current, ...mutable };
488
+ });
489
+ }
490
+ }
491
+
492
+ /**
493
+ * Programmatic toast opener — show a transient notification from TypeScript.
494
+ * Inject anywhere, call `show({ title, ... })` and get a `ForToastRef`
495
+ * back. Toasts render through `<for-toast-viewport>`, which the consumer
496
+ * mounts once near the root of their app.
497
+ *
498
+ * The manager owns the live array of toasts as a signal; the viewport
499
+ * iterates with `@for` so add / remove transitions are pure DOM control
500
+ * flow — consumers attach `animate.enter` / `animate.leave` directly.
501
+ */
502
+ class ForToastManager {
503
+ #idGen = inject(IdGenerator);
504
+ #defaults = inject(FOR_TOAST_DEFAULTS);
505
+ #doc = inject(DOCUMENT);
506
+ #entries = signal([], /* @ts-ignore */
507
+ ...(ngDevMode ? [{ debugName: "#entries" }] : /* istanbul ignore next */ []));
508
+ // O(1) id → entry index kept in sync with `#entries`. Prevents same-tick
509
+ // id-collision races where two `show({ id: 'X' })` calls would both miss
510
+ // an array `find` and end up pushing duplicate entries.
511
+ #byId = new Map();
512
+ // Live viewports in registration order. The first registration for a given
513
+ // region is its active renderer; the rest stay dormant (see
514
+ // `isActiveViewport`). A signal so each viewport's activeness recomputes
515
+ // when viewports mount / unmount or change region.
516
+ #viewports = signal([], /* @ts-ignore */
517
+ ...(ngDevMode ? [{ debugName: "#viewports" }] : /* istanbul ignore next */ []));
518
+ #hotkeyListener = null;
519
+ /** Reactive view of all live toasts, in insertion order. */
520
+ toasts = computed(() => this.#entries().map((e) => this.#instance(e)), /* @ts-ignore */
521
+ ...(ngDevMode ? [{ debugName: "toasts" }] : /* istanbul ignore next */ []));
522
+ /** Reactive count of currently open toasts. */
523
+ count = computed(() => this.#entries().length, /* @ts-ignore */
524
+ ...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
525
+ /**
526
+ * Open a toast. Returns a `ForToastRef` you can use to dismiss / update
527
+ * the toast imperatively.
528
+ *
529
+ * If `config.id` matches an existing toast, that toast is updated in place
530
+ * instead of duplicating — useful for "saving / saved" sequences.
531
+ */
532
+ show(config = {}) {
533
+ const id = config.id ?? this.#idGen.next('for-toast');
534
+ const existing = this.#byId.get(id);
535
+ if (existing) {
536
+ existing.ref.update(config);
537
+ return existing.ref;
538
+ }
539
+ // Normalize the region up front so `instance.config.region` is always a
540
+ // concrete string the viewport can filter on (an explicit `undefined`
541
+ // collapses to the default too).
542
+ const region = config.region ?? DEFAULT_TOAST_REGION;
543
+ const ref = new ForToastRef({ ...config, id, region }, (reason) => this.#removeById(id, reason));
544
+ const entry = { id, ref: ref };
545
+ this.#byId.set(id, entry);
546
+ this.#entries.update((arr) => [...arr, entry]);
547
+ return ref;
548
+ }
549
+ /** Dismiss a toast by id. No-op when unknown. */
550
+ dismiss(id, reason = 'programmatic') {
551
+ this.#byId.get(id)?.ref.dismiss(reason);
552
+ }
553
+ /** Dismiss every live toast. */
554
+ dismissAll(reason = 'programmatic') {
555
+ for (const entry of [...this.#entries()]) {
556
+ entry.ref.dismiss(reason);
557
+ }
558
+ }
559
+ /** @internal Used by the viewport to read the configured hotkey. */
560
+ hotkey() {
561
+ return this.#defaults.hotkey;
562
+ }
563
+ /** @internal Default duration applied when a toast omits it. */
564
+ defaultDuration() {
565
+ return this.#defaults.duration;
566
+ }
567
+ /** @internal Default `maxVisible` applied to the viewport. */
568
+ defaultMaxVisible() {
569
+ return this.#defaults.maxVisible;
570
+ }
571
+ /**
572
+ * @internal Register a viewport for hotkey + region coordination. Returns a
573
+ * teardown the viewport runs on destroy. Installs the single document-level
574
+ * hotkey listener on the first registration and tears it down with the last.
575
+ */
576
+ registerViewport(registration) {
577
+ this.#viewports.update((arr) => [...arr, registration]);
578
+ this.#ensureHotkeyListener();
579
+ return () => {
580
+ this.#viewports.update((arr) => arr.filter((r) => r !== registration));
581
+ if (this.#viewports().length === 0) {
582
+ this.#teardownHotkeyListener();
583
+ }
584
+ };
585
+ }
586
+ /**
587
+ * @internal Whether `registration` is the active renderer for its region —
588
+ * the first registered viewport for that region. Later viewports for the
589
+ * same region stay dormant so a single `show()` produces exactly one toast
590
+ * node regardless of how many viewports are mounted.
591
+ */
592
+ isActiveViewport(registration) {
593
+ const region = registration.region();
594
+ const first = this.#viewports().find((r) => r.region() === region);
595
+ return first === registration;
596
+ }
597
+ #ensureHotkeyListener() {
598
+ if (this.#hotkeyListener) {
599
+ return;
600
+ }
601
+ const listener = (event) => {
602
+ // First viewport (in registration order) whose hotkey matches and that
603
+ // actually has a toast to focus wins — a single handler for every
604
+ // viewport, so the hotkey can't double-fire across mounted viewports.
605
+ for (const viewport of this.#viewports()) {
606
+ if (event.key !== viewport.hotkey()) {
607
+ continue;
608
+ }
609
+ if (viewport.focusFirst()) {
610
+ event.preventDefault();
611
+ return;
612
+ }
613
+ }
614
+ };
615
+ this.#hotkeyListener = listener;
616
+ this.#doc.addEventListener('keydown', listener);
617
+ }
618
+ #teardownHotkeyListener() {
619
+ if (!this.#hotkeyListener) {
620
+ return;
621
+ }
622
+ this.#doc.removeEventListener('keydown', this.#hotkeyListener);
623
+ this.#hotkeyListener = null;
624
+ }
625
+ #removeById(id, _reason) {
626
+ this.#byId.delete(id);
627
+ this.#entries.update((arr) => arr.filter((e) => e.id !== id));
628
+ }
629
+ #instance(entry) {
630
+ const ref = entry.ref;
631
+ return {
632
+ id: entry.id,
633
+ get config() {
634
+ return ref.config();
635
+ },
636
+ dismiss(reason = 'manual') {
637
+ ref.dismiss(reason);
638
+ },
639
+ };
640
+ }
641
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToastManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
642
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToastManager, providedIn: 'root' });
643
+ }
644
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToastManager, decorators: [{
645
+ type: Injectable,
646
+ args: [{ providedIn: 'root' }]
647
+ }] });
648
+
649
+ /**
650
+ * Action button inside a toast (e.g. "Undo"). Apply on a
651
+ * `<button type="button">` so Space / Enter dispatch a native click.
652
+ *
653
+ * Clicking emits `(dismiss)` from the parent `[forToast]` with reason
654
+ * `'action'`. Wire your action handler with `(click)` on this element
655
+ * itself — the close fires after your handler runs (via event order).
656
+ *
657
+ * The action is the **sanctioned dismissal path even when the toast is
658
+ * `closable=false`**: a forced-action toast suppresses Escape / swipe / the
659
+ * close button but keeps the action button live so the user always has a way
660
+ * out through the control they are meant to use.
661
+ *
662
+ * Set `[altText]` for WCAG 2.2.1 compliance: the visible label (e.g.
663
+ * "Undo") rarely carries enough context to recover the action once the
664
+ * toast disappears. The alt text replaces the toast's automatic live
665
+ * announcement with a self-contained string, e.g. `altText="Undo (Cmd+Z)"`.
666
+ * When at least one action carries an alt text, the parent toast routes
667
+ * its announcement through `LiveAnnouncer` and silences the host
668
+ * `aria-live` to avoid a duplicate readout.
669
+ */
670
+ class ForToastAction {
671
+ ctx = injectToastContext('ForToastAction');
672
+ /**
673
+ * Self-contained text read aloud in place of the visible label so the
674
+ * user knows how to recover the action after the toast is gone. Required
675
+ * for time-limited toasts per WCAG 2.2.1; defaults to `''` (no alt text).
676
+ */
677
+ altText = input('', /* @ts-ignore */
678
+ ...(ngDevMode ? [{ debugName: "altText" }] : /* istanbul ignore next */ []));
679
+ constructor() {
680
+ const handle = { altText: this.altText };
681
+ registerHandle(handle, (h) => this.ctx.registerAction(h), (h) => this.ctx.unregisterAction(h));
682
+ }
683
+ onClick() {
684
+ this.ctx.requestClose('action');
685
+ }
686
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToastAction, deps: [], target: i0.ɵɵFactoryTarget.Directive });
687
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForToastAction, isStandalone: true, selector: "[forToastAction]", inputs: { altText: { classPropertyName: "altText", publicName: "altText", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "button" }, listeners: { "click": "onClick()" } }, exportAs: ["forToastAction"], ngImport: i0 });
688
+ }
689
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToastAction, decorators: [{
690
+ type: Directive,
691
+ args: [{
692
+ selector: '[forToastAction]',
693
+ exportAs: 'forToastAction',
694
+ host: {
695
+ type: 'button',
696
+ '(click)': 'onClick()',
697
+ },
698
+ }]
699
+ }], ctorParameters: () => [], propDecorators: { altText: [{ type: i0.Input, args: [{ isSignal: true, alias: "altText", required: false }] }] } });
700
+
701
+ /**
702
+ * Close button inside a toast. Apply on a `<button type="button">` so
703
+ * Space / Enter dispatch a native click. Clicking emits `(dismiss)` from
704
+ * the parent `[forToast]` with reason `'manual'`.
705
+ *
706
+ * The host carries `aria-label="Close"` by default; override with your
707
+ * own `[attr.aria-label]` when the toast's language requires localization.
708
+ */
709
+ class ForToastClose {
710
+ ctx = injectToastContext('ForToastClose');
711
+ onClick() {
712
+ this.ctx.requestClose('manual');
713
+ }
714
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToastClose, deps: [], target: i0.ɵɵFactoryTarget.Directive });
715
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForToastClose, isStandalone: true, selector: "[forToastClose]", host: { attributes: { "type": "button", "aria-label": "Close" }, listeners: { "click": "onClick()" } }, exportAs: ["forToastClose"], ngImport: i0 });
716
+ }
717
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToastClose, decorators: [{
718
+ type: Directive,
719
+ args: [{
720
+ selector: '[forToastClose]',
721
+ exportAs: 'forToastClose',
722
+ host: {
723
+ type: 'button',
724
+ 'aria-label': 'Close',
725
+ '(click)': 'onClick()',
726
+ },
727
+ }]
728
+ }] });
729
+
730
+ /**
731
+ * Supplementary description for the toast. Registers its generated id
732
+ * with the parent `[forToast]` so `aria-describedby` is wired automatically.
733
+ */
734
+ class ForToastDescription {
735
+ #ctx = injectToastContext('ForToastDescription');
736
+ #host = inject(ElementRef);
737
+ id = resolveHostId(this.#host.nativeElement, 'for-toast-description');
738
+ #text = signal('', /* @ts-ignore */
739
+ ...(ngDevMode ? [{ debugName: "#text" }] : /* istanbul ignore next */ []));
740
+ constructor() {
741
+ const handle = { id: this.id, text: this.#text.asReadonly() };
742
+ registerHandle(handle, (h) => this.#ctx.registerDescription(h), (h) => this.#ctx.unregisterDescription(h));
743
+ afterEveryRender(() => {
744
+ const next = (this.#host.nativeElement.textContent ?? '').trim();
745
+ if (next !== this.#text()) {
746
+ this.#text.set(next);
747
+ }
748
+ });
749
+ }
750
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToastDescription, deps: [], target: i0.ɵɵFactoryTarget.Directive });
751
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForToastDescription, isStandalone: true, selector: "[forToastDescription]", host: { properties: { "id": "id" } }, exportAs: ["forToastDescription"], ngImport: i0 });
752
+ }
753
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToastDescription, decorators: [{
754
+ type: Directive,
755
+ args: [{
756
+ selector: '[forToastDescription]',
757
+ exportAs: 'forToastDescription',
758
+ host: {
759
+ '[id]': 'id',
760
+ },
761
+ }]
762
+ }], ctorParameters: () => [] });
763
+
764
+ /**
765
+ * @internal Exposes the element injector at a child of the row's `[forToast]`
766
+ * element so a consumer-supplied toast `template` can be rendered with the
767
+ * `[forToast]` injection context in scope.
768
+ *
769
+ * Angular's `ngTemplateOutlet` renders a template against its *declaration*
770
+ * injector (the consumer's component, where `<ng-template>` was written), not
771
+ * the insertion point — so `[forToastTitle]` / `[forToastDescription]` /
772
+ * `[forToastAction]` / `[forToastClose]`, which `inject(FOR_TOAST_CONTEXT)`,
773
+ * would fail to resolve the row's `ForToast`.
774
+ *
775
+ * This directive sits on the same `<ng-container>` as the `ngTemplateOutlet`,
776
+ * inside the row's `[forToast]` element, so its own element injector already
777
+ * resolves `FOR_TOAST_CONTEXT`. The viewport feeds that injector to
778
+ * `[ngTemplateOutletInjector]`, making the rendered template inherit it and
779
+ * restoring the automatic `aria-labelledby` / `aria-describedby` / close-reason
780
+ * wiring for the helper directives inside the custom template — without adding
781
+ * a wrapper element to the consumer's markup.
782
+ */
783
+ class ForToastOutlet {
784
+ /**
785
+ * Element injector resolving `FOR_TOAST_CONTEXT` for the enclosing toast
786
+ * row. Passed to `[ngTemplateOutletInjector]` so helper directives inside a
787
+ * custom template wire a11y exactly as in the default shape.
788
+ */
789
+ injector = inject(Injector);
790
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToastOutlet, deps: [], target: i0.ɵɵFactoryTarget.Directive });
791
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForToastOutlet, isStandalone: true, selector: "[forToastOutlet]", exportAs: ["forToastOutlet"], ngImport: i0 });
792
+ }
793
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToastOutlet, decorators: [{
794
+ type: Directive,
795
+ args: [{
796
+ selector: '[forToastOutlet]',
797
+ exportAs: 'forToastOutlet',
798
+ }]
799
+ }] });
800
+
801
+ /**
802
+ * Element holding the toast's accessible name. Registers its generated id
803
+ * with the parent `[forToast]` so `aria-labelledby` is wired automatically.
804
+ * Apply on whatever heading element fits (`<strong>`, `<h2>`, `<div>`).
805
+ */
806
+ class ForToastTitle {
807
+ #ctx = injectToastContext('ForToastTitle');
808
+ #host = inject(ElementRef);
809
+ id = resolveHostId(this.#host.nativeElement, 'for-toast-title');
810
+ #text = signal('', /* @ts-ignore */
811
+ ...(ngDevMode ? [{ debugName: "#text" }] : /* istanbul ignore next */ []));
812
+ constructor() {
813
+ const handle = { id: this.id, text: this.#text.asReadonly() };
814
+ registerHandle(handle, (h) => this.#ctx.registerLabel(h), (h) => this.#ctx.unregisterLabel(h));
815
+ afterEveryRender(() => {
816
+ const next = (this.#host.nativeElement.textContent ?? '').trim();
817
+ if (next !== this.#text()) {
818
+ this.#text.set(next);
819
+ }
820
+ });
821
+ }
822
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToastTitle, deps: [], target: i0.ɵɵFactoryTarget.Directive });
823
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForToastTitle, isStandalone: true, selector: "[forToastTitle]", host: { properties: { "id": "id" } }, exportAs: ["forToastTitle"], ngImport: i0 });
824
+ }
825
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToastTitle, decorators: [{
826
+ type: Directive,
827
+ args: [{
828
+ selector: '[forToastTitle]',
829
+ exportAs: 'forToastTitle',
830
+ host: {
831
+ '[id]': 'id',
832
+ },
833
+ }]
834
+ }], ctorParameters: () => [] });
835
+
836
+ /**
837
+ * Mounted once near the root of the app — typically inside `app.html`.
838
+ * Listens to `ForToastManager.toasts()` and renders each programmatic
839
+ * toast inline. Position the viewport from CSS (`position: fixed`,
840
+ * `top|bottom`, `left|right`) — the directive doesn't impose any.
841
+ *
842
+ * Custom rendering: if a toast's config has `template`, that
843
+ * `TemplateRef<ForToastTemplateContext>` is used instead of the default
844
+ * title / description / action / close shape. The template receives
845
+ * `$implicit: instance` and `data: instance.config.data`, and is rendered
846
+ * with the `[forToast]` injection context in scope (via `ForToastOutlet`),
847
+ * so `[forToastTitle]` / `[forToastDescription]` / `[forToastAction]` /
848
+ * `[forToastClose]` keep their automatic a11y / close wiring inside it.
849
+ *
850
+ * Per-toast classes: a config's `class` / `classList` is applied to the
851
+ * rendered toast root, merged with the directive's own host attributes.
852
+ *
853
+ * Enter / exit animations: a toast renders inside this viewport's `@for`, so a
854
+ * `[animate.leave]` on the row defers the unmount natively — the toast stays
855
+ * mounted until its exit animation settles. Supply the class per-toast with
856
+ * `show({ animateLeave })` or set a viewport-wide default with `[animateLeave]`
857
+ * (per-toast wins); `[animateEnter]` mirrors it for class-applied entrances.
858
+ *
859
+ * Hotkey: pressing the configured `hotkey` (default `F6`) anywhere in the
860
+ * document focuses the first toast. Override per-viewport with `[hotkey]`
861
+ * or globally with `provideForToastDefaults({ hotkey: '…' })`. The listener
862
+ * is owned once by `ForToastManager`, so the hotkey never double-fires when
863
+ * several viewports are mounted.
864
+ *
865
+ * Regions: a viewport renders only toasts whose `region` matches its
866
+ * `[region]` input (default {@link DEFAULT_TOAST_REGION}), so independent
867
+ * viewports — e.g. top-right system notifications and bottom-center action
868
+ * confirmations — are a first-class feature. If two viewports share a region,
869
+ * only the first one mounted renders it (the rest stay dormant and warn in dev
870
+ * mode), so a single `show()` always produces exactly one toast node.
871
+ *
872
+ * Accessibility: the host carries `role="region"` and an `aria-label`
873
+ * (default `Notifications`). Toast nodes themselves carry their own
874
+ * `role` (`status` / `alert`) and `aria-live`, so screen readers
875
+ * announce updates without forcing focus.
876
+ */
877
+ class ForToastViewport {
878
+ #manager = inject(ForToastManager);
879
+ #host = inject(ElementRef);
880
+ /** Accessible name for the viewport region. Default `'Notifications'`. */
881
+ label = input('Notifications', /* @ts-ignore */
882
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
883
+ /**
884
+ * The toast region this viewport renders. Only toasts whose `region` matches
885
+ * appear here; toasts opened without a `region` use
886
+ * {@link DEFAULT_TOAST_REGION}, so the common single-viewport setup needs no
887
+ * `[region]` at all. Mount viewports with distinct `[region]` values to drive
888
+ * independent toast regions from one global `ForToastManager`.
889
+ */
890
+ region = input(DEFAULT_TOAST_REGION, /* @ts-ignore */
891
+ ...(ngDevMode ? [{ debugName: "region" }] : /* istanbul ignore next */ []));
892
+ /** Hotkey to focus the viewport. Default reads from `provideForToastDefaults`, falling back to `F6`. */
893
+ hotkey = input('', /* @ts-ignore */
894
+ ...(ngDevMode ? [{ debugName: "hotkey" }] : /* istanbul ignore next */ []));
895
+ /**
896
+ * Maximum number of toasts rendered at once. Older toasts collapse out of
897
+ * the visible stack but stay in `manager.toasts()` until dismissed. When
898
+ * unset (`null`, the default), the viewport falls back to the global
899
+ * `provideForToastDefaults({ maxVisible })` value (itself `Infinity` unless
900
+ * configured, which renders all live toasts). Setting `[maxVisible]`
901
+ * per-viewport overrides the global default.
902
+ *
903
+ * **Overflow is parked, not auto-expired.** A toast pushed out of the
904
+ * visible window is unmounted, so its auto-dismiss timer is not running
905
+ * while it waits. It re-enters the window when a newer toast is dismissed,
906
+ * and its `duration` countdown then restarts from full (a fresh `[forToast]`
907
+ * mounts). Cap with `maxVisible` for layout, but dismiss explicitly — via
908
+ * `ForToastRef.dismiss()`, `dismissAll()`, or the action / close button —
909
+ * if you need overflow toasts to clear on a deadline.
910
+ */
911
+ maxVisible = input(null, { ...(ngDevMode ? { debugName: "maxVisible" } : /* istanbul ignore next */ {}), transform: (v) => (v == null ? null : numberAttribute(v)) });
912
+ /**
913
+ * Default swipe direction(s) applied to every programmatic toast that
914
+ * doesn't override `swipeDirection` in its own config. `null` (default)
915
+ * keeps swipe disabled unless explicitly opted into per-toast.
916
+ */
917
+ swipeDirection = input(null, /* @ts-ignore */
918
+ ...(ngDevMode ? [{ debugName: "swipeDirection" }] : /* istanbul ignore next */ []));
919
+ /**
920
+ * Default dismiss-distance in pixels applied to every programmatic toast
921
+ * that doesn't override `swipeThreshold` in its own config.
922
+ */
923
+ swipeThreshold = input(50, { ...(ngDevMode ? { debugName: "swipeThreshold" } : /* istanbul ignore next */ {}), transform: numberAttribute });
924
+ /**
925
+ * Default `animate.enter` class applied to every programmatic toast that
926
+ * doesn't set `animateEnter` in its own config. Empty (default) plays no
927
+ * class-applied enter animation — a plain CSS `@keyframes` on `[forToast]`
928
+ * already runs on mount without it.
929
+ */
930
+ animateEnter = input('', /* @ts-ignore */
931
+ ...(ngDevMode ? [{ debugName: "animateEnter" }] : /* istanbul ignore next */ []));
932
+ /**
933
+ * Default `animate.leave` class applied to every programmatic toast that
934
+ * doesn't set `animateLeave` in its own config. Empty (default) unmounts the
935
+ * toast synchronously on dismiss; set it (or per-toast `animateLeave`) to
936
+ * keep the toast mounted until its exit animation settles before it leaves
937
+ * the DOM.
938
+ */
939
+ animateLeave = input('', /* @ts-ignore */
940
+ ...(ngDevMode ? [{ debugName: "animateLeave" }] : /* istanbul ignore next */ []));
941
+ defaultDuration = computed(() => this.#manager.defaultDuration(), /* @ts-ignore */
942
+ ...(ngDevMode ? [{ debugName: "defaultDuration" }] : /* istanbul ignore next */ []));
943
+ /**
944
+ * Resolved `maxVisible` limit: the per-viewport `[maxVisible]` when set,
945
+ * otherwise the global `provideForToastDefaults({ maxVisible })` value.
946
+ */
947
+ resolvedMaxVisible = computed(() => {
948
+ const own = this.maxVisible();
949
+ return own ?? this.#manager.defaultMaxVisible();
950
+ }, /* @ts-ignore */
951
+ ...(ngDevMode ? [{ debugName: "resolvedMaxVisible" }] : /* istanbul ignore next */ []));
952
+ /**
953
+ * Whether this viewport is the active renderer for its region. The manager
954
+ * grants this to the first viewport mounted per region; dormant viewports
955
+ * render nothing so a single `show()` yields exactly one toast node.
956
+ */
957
+ #active;
958
+ visible = computed(() => {
959
+ if (!this.#active()) {
960
+ return [];
961
+ }
962
+ const region = this.region();
963
+ const all = this.#manager.toasts().filter((toast) => toast.config.region === region);
964
+ const resolved = this.resolvedMaxVisible();
965
+ const limit = Number.isFinite(resolved) ? resolved : all.length;
966
+ if (all.length <= limit) {
967
+ return all;
968
+ }
969
+ // Newest toasts win; older ones drop out of the visible window.
970
+ return all.slice(all.length - limit);
971
+ }, /* @ts-ignore */
972
+ ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
973
+ constructor() {
974
+ const registration = {
975
+ region: this.region,
976
+ hotkey: () => this.hotkey() || this.#manager.hotkey(),
977
+ focusFirst: () => this.#focusFirst(),
978
+ };
979
+ this.#active = computed(() => this.#manager.isActiveViewport(registration), /* @ts-ignore */
980
+ ...(ngDevMode ? [{ debugName: "#active" }] : /* istanbul ignore next */ []));
981
+ const unregister = this.#manager.registerViewport(registration);
982
+ inject(DestroyRef).onDestroy(unregister);
983
+ if (isDevMode()) {
984
+ // Warn once each time this viewport becomes dormant because another
985
+ // viewport already owns its region — the silent-duplication footgun the
986
+ // region API exists to remove.
987
+ let warned = false;
988
+ effect(() => {
989
+ if (this.#active()) {
990
+ warned = false;
991
+ }
992
+ else if (!warned) {
993
+ warned = true;
994
+ console.warn(`[forty-cdk/toast] A <for-toast-viewport> for region "${this.region()}" is already mounted; ` +
995
+ `this one stays inactive to avoid duplicate toasts. Give it a distinct [region] for an independent toast region.`);
996
+ }
997
+ });
998
+ }
999
+ }
1000
+ /** Focus the first rendered toast host. Returns `true` when focus moved. */
1001
+ #focusFirst() {
1002
+ const first = this.#host.nativeElement.querySelector('[forToast]');
1003
+ if (first) {
1004
+ first.focus();
1005
+ return true;
1006
+ }
1007
+ return false;
1008
+ }
1009
+ onClose(toast, reason) {
1010
+ toast.dismiss(reason);
1011
+ }
1012
+ /**
1013
+ * Consumer class(es) for a toast row, resolved from its config's `class` /
1014
+ * `classList`. Returns `''` when neither is set so the `[class]` host
1015
+ * binding emits no extra tokens and leaves the directive's own host
1016
+ * attributes untouched.
1017
+ */
1018
+ toastClass(toast) {
1019
+ return resolveConfigClass(toast.config) ?? '';
1020
+ }
1021
+ /**
1022
+ * `animate.enter` class for a toast row: its per-toast `animateEnter` config
1023
+ * when set, otherwise the viewport-level `[animateEnter]`. Empty when neither
1024
+ * is set.
1025
+ */
1026
+ toastAnimateEnter(toast) {
1027
+ return toast.config.animateEnter ?? this.animateEnter();
1028
+ }
1029
+ /**
1030
+ * `animate.leave` class for a toast row: its per-toast `animateLeave` config
1031
+ * when set, otherwise the viewport-level `[animateLeave]`. Empty when neither
1032
+ * is set, which keeps the toast's unmount synchronous on dismiss.
1033
+ */
1034
+ toastAnimateLeave(toast) {
1035
+ return toast.config.animateLeave ?? this.animateLeave();
1036
+ }
1037
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToastViewport, deps: [], target: i0.ɵɵFactoryTarget.Component });
1038
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ForToastViewport, isStandalone: true, selector: "for-toast-viewport, [forToastViewport]", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, region: { classPropertyName: "region", publicName: "region", isSignal: true, isRequired: false, transformFunction: null }, hotkey: { classPropertyName: "hotkey", publicName: "hotkey", isSignal: true, isRequired: false, transformFunction: null }, maxVisible: { classPropertyName: "maxVisible", publicName: "maxVisible", isSignal: true, isRequired: false, transformFunction: null }, swipeDirection: { classPropertyName: "swipeDirection", publicName: "swipeDirection", isSignal: true, isRequired: false, transformFunction: null }, swipeThreshold: { classPropertyName: "swipeThreshold", publicName: "swipeThreshold", isSignal: true, isRequired: false, transformFunction: null }, animateEnter: { classPropertyName: "animateEnter", publicName: "animateEnter", isSignal: true, isRequired: false, transformFunction: null }, animateLeave: { classPropertyName: "animateLeave", publicName: "animateLeave", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "region", "tabindex": "-1" }, properties: { "attr.aria-label": "label()", "attr.data-region": "region()", "attr.data-toast-count": "visible().length" } }, exportAs: ["forToastViewport"], ngImport: i0, template: `
1039
+ @for (toast of visible(); track toast.id; let i = $index) {
1040
+ <div
1041
+ forToast
1042
+ [class]="toastClass(toast)"
1043
+ [animate.enter]="toastAnimateEnter(toast)"
1044
+ [animate.leave]="toastAnimateLeave(toast)"
1045
+ [variant]="toast.config.variant ?? 'info'"
1046
+ [duration]="toast.config.duration ?? defaultDuration()"
1047
+ [closable]="toast.config.closable !== false"
1048
+ [swipeDirection]="toast.config.swipeDirection ?? swipeDirection()"
1049
+ [swipeThreshold]="toast.config.swipeThreshold ?? swipeThreshold()"
1050
+ [attr.data-front-stack-index]="i"
1051
+ (dismiss)="onClose(toast, $event)"
1052
+ >
1053
+ @if (toast.config.template; as template) {
1054
+ <ng-container
1055
+ forToastOutlet
1056
+ #outlet="forToastOutlet"
1057
+ [ngTemplateOutlet]="template"
1058
+ [ngTemplateOutletContext]="{ $implicit: toast, data: toast.config.data }"
1059
+ [ngTemplateOutletInjector]="outlet.injector"
1060
+ />
1061
+ } @else {
1062
+ @if (toast.config.title) {
1063
+ <div forToastTitle>{{ toast.config.title }}</div>
1064
+ }
1065
+ @if (toast.config.description) {
1066
+ <div forToastDescription>{{ toast.config.description }}</div>
1067
+ }
1068
+ @if (toast.config.action; as action) {
1069
+ <button forToastAction (click)="action.onClick()">
1070
+ {{ action.label }}
1071
+ </button>
1072
+ }
1073
+ @if (toast.config.closable !== false) {
1074
+ <button forToastClose>×</button>
1075
+ }
1076
+ }
1077
+ </div>
1078
+ }
1079
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: ForToast, selector: "[forToast]", inputs: ["variant", "duration", "closable", "swipeDirection", "swipeThreshold"], outputs: ["dismiss", "swipeStart", "swipeMove", "swipeEnd", "swipeCancel"], exportAs: ["forToast"] }, { kind: "directive", type: ForToastOutlet, selector: "[forToastOutlet]", exportAs: ["forToastOutlet"] }, { kind: "directive", type: ForToastTitle, selector: "[forToastTitle]", exportAs: ["forToastTitle"] }, { kind: "directive", type: ForToastDescription, selector: "[forToastDescription]", exportAs: ["forToastDescription"] }, { kind: "directive", type: ForToastAction, selector: "[forToastAction]", inputs: ["altText"], exportAs: ["forToastAction"] }, { kind: "directive", type: ForToastClose, selector: "[forToastClose]", exportAs: ["forToastClose"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1080
+ }
1081
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForToastViewport, decorators: [{
1082
+ type: Component,
1083
+ args: [{
1084
+ selector: 'for-toast-viewport, [forToastViewport]',
1085
+ exportAs: 'forToastViewport',
1086
+ imports: [
1087
+ NgTemplateOutlet,
1088
+ ForToast,
1089
+ ForToastOutlet,
1090
+ ForToastTitle,
1091
+ ForToastDescription,
1092
+ ForToastAction,
1093
+ ForToastClose,
1094
+ ],
1095
+ changeDetection: ChangeDetectionStrategy.OnPush,
1096
+ host: {
1097
+ role: 'region',
1098
+ '[attr.aria-label]': 'label()',
1099
+ '[attr.data-region]': 'region()',
1100
+ tabindex: '-1',
1101
+ '[attr.data-toast-count]': 'visible().length',
1102
+ },
1103
+ template: `
1104
+ @for (toast of visible(); track toast.id; let i = $index) {
1105
+ <div
1106
+ forToast
1107
+ [class]="toastClass(toast)"
1108
+ [animate.enter]="toastAnimateEnter(toast)"
1109
+ [animate.leave]="toastAnimateLeave(toast)"
1110
+ [variant]="toast.config.variant ?? 'info'"
1111
+ [duration]="toast.config.duration ?? defaultDuration()"
1112
+ [closable]="toast.config.closable !== false"
1113
+ [swipeDirection]="toast.config.swipeDirection ?? swipeDirection()"
1114
+ [swipeThreshold]="toast.config.swipeThreshold ?? swipeThreshold()"
1115
+ [attr.data-front-stack-index]="i"
1116
+ (dismiss)="onClose(toast, $event)"
1117
+ >
1118
+ @if (toast.config.template; as template) {
1119
+ <ng-container
1120
+ forToastOutlet
1121
+ #outlet="forToastOutlet"
1122
+ [ngTemplateOutlet]="template"
1123
+ [ngTemplateOutletContext]="{ $implicit: toast, data: toast.config.data }"
1124
+ [ngTemplateOutletInjector]="outlet.injector"
1125
+ />
1126
+ } @else {
1127
+ @if (toast.config.title) {
1128
+ <div forToastTitle>{{ toast.config.title }}</div>
1129
+ }
1130
+ @if (toast.config.description) {
1131
+ <div forToastDescription>{{ toast.config.description }}</div>
1132
+ }
1133
+ @if (toast.config.action; as action) {
1134
+ <button forToastAction (click)="action.onClick()">
1135
+ {{ action.label }}
1136
+ </button>
1137
+ }
1138
+ @if (toast.config.closable !== false) {
1139
+ <button forToastClose>×</button>
1140
+ }
1141
+ }
1142
+ </div>
1143
+ }
1144
+ `,
1145
+ }]
1146
+ }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], region: [{ type: i0.Input, args: [{ isSignal: true, alias: "region", required: false }] }], hotkey: [{ type: i0.Input, args: [{ isSignal: true, alias: "hotkey", required: false }] }], maxVisible: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxVisible", required: false }] }], swipeDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "swipeDirection", required: false }] }], swipeThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "swipeThreshold", required: false }] }], animateEnter: [{ type: i0.Input, args: [{ isSignal: true, alias: "animateEnter", required: false }] }], animateLeave: [{ type: i0.Input, args: [{ isSignal: true, alias: "animateLeave", required: false }] }] } });
1147
+
1148
+ /**
1149
+ * Generated bundle index. Do not edit.
1150
+ */
1151
+
1152
+ export { DEFAULT_TOAST_REGION, FOR_TOAST_CONTEXT, FOR_TOAST_DEFAULTS, ForToast, ForToastAction, ForToastClose, ForToastDescription, ForToastManager, ForToastRef, ForToastTitle, ForToastViewport, provideForToastDefaults };
1153
+ //# sourceMappingURL=forty-cdk-toast.mjs.map