@cdevhub/ngx-tw 0.4.0 → 0.6.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.
- package/README.md +78 -15
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs +16 -5
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs +26 -15
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-badge.mjs +17 -5
- package/fesm2022/cdevhub-ngx-tw-badge.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs +27 -6
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-button.mjs +23 -11
- package/fesm2022/cdevhub-ngx-tw-button.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs +5 -5
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs +3 -3
- package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs +427 -122
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-card.mjs +21 -11
- package/fesm2022/cdevhub-ngx-tw-card.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs +215 -96
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs +108 -64
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs +18 -6
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs +55 -30
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-combobox-testing.mjs +218 -0
- package/fesm2022/cdevhub-ngx-tw-combobox-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs +236 -211
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-command-palette-testing.mjs +200 -0
- package/fesm2022/cdevhub-ngx-tw-command-palette-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs +34 -7
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-core.mjs +135 -15
- package/fesm2022/cdevhub-ngx-tw-core.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-date-picker-testing.mjs +203 -0
- package/fesm2022/cdevhub-ngx-tw-date-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs +160 -102
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-date-range-picker-testing.mjs +209 -0
- package/fesm2022/cdevhub-ngx-tw-date-range-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs +182 -107
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-dialog-dialog-renderer-DoIhoV3d.mjs +198 -0
- package/fesm2022/cdevhub-ngx-tw-dialog-dialog-renderer-DoIhoV3d.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-dialog-testing.mjs +122 -0
- package/fesm2022/cdevhub-ngx-tw-dialog-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs +199 -220
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs +1 -1
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-file-upload-testing.mjs +149 -0
- package/fesm2022/cdevhub-ngx-tw-file-upload-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-file-upload.mjs +57 -67
- package/fesm2022/cdevhub-ngx-tw-file-upload.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs +42 -24
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs +114 -21
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-icon.mjs +16 -6
- package/fesm2022/cdevhub-ngx-tw-icon.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-input.mjs +52 -61
- package/fesm2022/cdevhub-ngx-tw-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-item.mjs +18 -3
- package/fesm2022/cdevhub-ngx-tw-item.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-menu-testing.mjs +168 -0
- package/fesm2022/cdevhub-ngx-tw-menu-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-menu.mjs +58 -21
- package/fesm2022/cdevhub-ngx-tw-menu.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-number-input.mjs +34 -1
- package/fesm2022/cdevhub-ngx-tw-number-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs +95 -45
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-popover.mjs +81 -32
- package/fesm2022/cdevhub-ngx-tw-popover.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs +1 -1
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-radio.mjs +131 -98
- package/fesm2022/cdevhub-ngx-tw-radio.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs +207 -42
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-select-testing.mjs +165 -0
- package/fesm2022/cdevhub-ngx-tw-select-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-select.mjs +386 -210
- package/fesm2022/cdevhub-ngx-tw-select.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-CqVJwgmp.mjs +256 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-CqVJwgmp.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-testing.mjs +129 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs +197 -278
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-slider.mjs +89 -54
- package/fesm2022/cdevhub-ngx-tw-slider.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-sort.mjs +122 -17
- package/fesm2022/cdevhub-ngx-tw-sort.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-split.mjs +72 -19
- package/fesm2022/cdevhub-ngx-tw-split.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-stat.mjs +63 -25
- package/fesm2022/cdevhub-ngx-tw-stat.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs +35 -8
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-switch.mjs +54 -48
- package/fesm2022/cdevhub-ngx-tw-switch.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs +12 -15
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-table.mjs +74 -13
- package/fesm2022/cdevhub-ngx-tw-table.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs +84 -30
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tags-input-testing.mjs +172 -0
- package/fesm2022/cdevhub-ngx-tw-tags-input-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-tags-input.mjs +107 -71
- package/fesm2022/cdevhub-ngx-tw-tags-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs +41 -9
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-theme.mjs +257 -45
- package/fesm2022/cdevhub-ngx-tw-theme.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-time-picker-testing.mjs +201 -0
- package/fesm2022/cdevhub-ngx-tw-time-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs +184 -77
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs +110 -27
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-BlDxemsU.mjs +616 -0
- package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-BlDxemsU.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-toast.mjs +556 -945
- package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs +102 -15
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-transfer-testing.mjs +219 -0
- package/fesm2022/cdevhub-ngx-tw-transfer-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-transfer.mjs +88 -69
- package/fesm2022/cdevhub-ngx-tw-transfer.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tree.mjs +68 -19
- package/fesm2022/cdevhub-ngx-tw-tree.mjs.map +1 -1
- package/index.json +1 -1
- package/package.json +63 -2
- package/theme/_base.css +13 -0
- package/theme/_dark.css +110 -36
- package/theme/_high-contrast-dark.css +367 -0
- package/theme/_high-contrast.css +13 -2
- package/theme/_light.css +276 -0
- package/theme/_semantic.css +47 -12
- package/theme/_typography.css +17 -0
- package/theme/index.css +86 -7
- package/types/cdevhub-ngx-tw-accordion.d.ts +12 -4
- package/types/cdevhub-ngx-tw-avatar.d.ts +10 -3
- package/types/cdevhub-ngx-tw-button.d.ts +1 -0
- package/types/cdevhub-ngx-tw-calendar-luxon.d.ts +2 -2
- package/types/cdevhub-ngx-tw-calendar-testing.d.ts +1 -1
- package/types/cdevhub-ngx-tw-calendar.d.ts +179 -73
- package/types/cdevhub-ngx-tw-card.d.ts +11 -3
- package/types/cdevhub-ngx-tw-carousel.d.ts +89 -20
- package/types/cdevhub-ngx-tw-checkbox.d.ts +40 -12
- package/types/cdevhub-ngx-tw-code-block.d.ts +11 -2
- package/types/cdevhub-ngx-tw-collapsible.d.ts +32 -5
- package/types/cdevhub-ngx-tw-combobox-testing.d.ts +157 -0
- package/types/cdevhub-ngx-tw-combobox.d.ts +94 -53
- package/types/cdevhub-ngx-tw-command-palette-testing.d.ts +136 -0
- package/types/cdevhub-ngx-tw-command-palette.d.ts +33 -5
- package/types/cdevhub-ngx-tw-core.d.ts +152 -21
- package/types/cdevhub-ngx-tw-date-picker-testing.d.ts +141 -0
- package/types/cdevhub-ngx-tw-date-picker.d.ts +37 -29
- package/types/cdevhub-ngx-tw-date-range-picker-testing.d.ts +148 -0
- package/types/cdevhub-ngx-tw-date-range-picker.d.ts +36 -29
- package/types/cdevhub-ngx-tw-dialog-testing.d.ts +98 -0
- package/types/cdevhub-ngx-tw-dialog.d.ts +89 -19
- package/types/cdevhub-ngx-tw-file-upload-testing.d.ts +120 -0
- package/types/cdevhub-ngx-tw-file-upload.d.ts +8 -10
- package/types/cdevhub-ngx-tw-flip-card.d.ts +18 -11
- package/types/cdevhub-ngx-tw-form-field.d.ts +19 -2
- package/types/cdevhub-ngx-tw-icon.d.ts +3 -3
- package/types/cdevhub-ngx-tw-input.d.ts +5 -8
- package/types/cdevhub-ngx-tw-item.d.ts +2 -0
- package/types/cdevhub-ngx-tw-menu-testing.d.ts +131 -0
- package/types/cdevhub-ngx-tw-menu.d.ts +2 -2
- package/types/cdevhub-ngx-tw-number-input.d.ts +20 -0
- package/types/cdevhub-ngx-tw-paginator.d.ts +58 -24
- package/types/cdevhub-ngx-tw-popover.d.ts +41 -10
- package/types/cdevhub-ngx-tw-progress-bar.d.ts +1 -1
- package/types/cdevhub-ngx-tw-radio.d.ts +68 -25
- package/types/cdevhub-ngx-tw-segmented-control.d.ts +79 -16
- package/types/cdevhub-ngx-tw-select-testing.d.ts +114 -0
- package/types/cdevhub-ngx-tw-select.d.ts +104 -33
- package/types/cdevhub-ngx-tw-sheet-testing.d.ts +104 -0
- package/types/cdevhub-ngx-tw-sheet.d.ts +96 -21
- package/types/cdevhub-ngx-tw-slider.d.ts +79 -25
- package/types/cdevhub-ngx-tw-sort.d.ts +45 -4
- package/types/cdevhub-ngx-tw-split.d.ts +32 -2
- package/types/cdevhub-ngx-tw-stat.d.ts +14 -4
- package/types/cdevhub-ngx-tw-stepper.d.ts +16 -2
- package/types/cdevhub-ngx-tw-switch.d.ts +35 -13
- package/types/cdevhub-ngx-tw-tab-nav.d.ts +7 -6
- package/types/cdevhub-ngx-tw-table.d.ts +53 -10
- package/types/cdevhub-ngx-tw-tabs.d.ts +41 -10
- package/types/cdevhub-ngx-tw-tags-input-testing.d.ts +135 -0
- package/types/cdevhub-ngx-tw-tags-input.d.ts +26 -11
- package/types/cdevhub-ngx-tw-textarea.d.ts +3 -2
- package/types/cdevhub-ngx-tw-theme.d.ts +245 -39
- package/types/cdevhub-ngx-tw-time-picker-testing.d.ts +129 -0
- package/types/cdevhub-ngx-tw-time-picker.d.ts +76 -19
- package/types/cdevhub-ngx-tw-timeline.d.ts +36 -12
- package/types/cdevhub-ngx-tw-toast.d.ts +61 -12
- package/types/cdevhub-ngx-tw-tooltip.d.ts +28 -6
- package/types/cdevhub-ngx-tw-transfer-testing.d.ts +152 -0
- package/types/cdevhub-ngx-tw-transfer.d.ts +21 -8
- package/types/cdevhub-ngx-tw-tree.d.ts +6 -1
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, Directive, input, output, contentChild, computed, ChangeDetectionStrategy, Component
|
|
3
|
-
import { Overlay, createGlobalPositionStrategy } from '@angular/cdk/overlay';
|
|
4
|
-
import { CdkPortalOutlet, ComponentPortal } from '@angular/cdk/portal';
|
|
2
|
+
import { InjectionToken, signal, inject, Injector, Injectable, makeEnvironmentProviders, Directive, input, output, contentChild, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
5
3
|
import { ReplaySubject, Subject, defer, startWith } from 'rxjs';
|
|
6
|
-
import { NgTemplateOutlet } from '@angular/common';
|
|
7
|
-
import { LiveAnnouncer } from '@angular/cdk/a11y';
|
|
8
4
|
import { tv } from 'tailwind-variants';
|
|
9
5
|
|
|
10
6
|
/**
|
|
@@ -19,7 +15,16 @@ class ToastConfig {
|
|
|
19
15
|
severity = 'info';
|
|
20
16
|
/** Screen position of the toast stack. Defaults to `'bottom-right'`. */
|
|
21
17
|
position = 'bottom-right';
|
|
22
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Auto-dismiss duration in ms. `0` disables auto-dismiss. Defaults to `5000`.
|
|
20
|
+
*
|
|
21
|
+
* The duration alone would fail SC 2.2.1 (Timing Adjustable) — a time limit the
|
|
22
|
+
* user cannot influence. What makes 5s conformant is `pauseOnInteraction`
|
|
23
|
+
* (default `true`): the clock stops while the pointer is over the toast or
|
|
24
|
+
* focus is anywhere inside it, and resumes on leave / blur. Setting
|
|
25
|
+
* `pauseOnInteraction: false` alongside a non-zero duration re-introduces the
|
|
26
|
+
* failure, so pair it with `duration: 0`.
|
|
27
|
+
*/
|
|
23
28
|
duration = 5000;
|
|
24
29
|
// Default true: toasts must offer an escape hatch — sticky / long-duration toasts
|
|
25
30
|
// otherwise trap users; opt-out only for ephemeral progress markers paired with `duration`.
|
|
@@ -46,9 +51,11 @@ class ToastConfig {
|
|
|
46
51
|
* primitives for the string-content path.
|
|
47
52
|
*/
|
|
48
53
|
icon;
|
|
49
|
-
// Default true:
|
|
50
|
-
//
|
|
51
|
-
|
|
54
|
+
// Default true: this is what keeps a non-zero `duration` conformant with
|
|
55
|
+
// SC 2.2.1 (Timing Adjustable) — it is the user's control over the time limit,
|
|
56
|
+
// not a nicety. Also matches Material/Radix/Sonner. Opt out only together with
|
|
57
|
+
// `duration: 0`, for fully ambient progress indicators.
|
|
58
|
+
/** When true, pause the auto-dismiss timer while the toast is hovered or holds focus anywhere inside it, resuming on leave / blur. Defaults to `true`. */
|
|
52
59
|
pauseOnInteraction = true;
|
|
53
60
|
// Default true: matches mobile-platform expectations and gives non-precise pointers
|
|
54
61
|
// (touch) an easy dismiss gesture; opt-out only if the gesture conflicts with content.
|
|
@@ -73,886 +80,194 @@ const TW_TOAST_REF = new InjectionToken('TW_TOAST_REF');
|
|
|
73
80
|
/** Injection token carrying application-wide toast defaults. Set via `provideToast(defaults)`. */
|
|
74
81
|
const TW_TOAST_DEFAULT_OPTIONS = new InjectionToken('TW_TOAST_DEFAULT_OPTIONS');
|
|
75
82
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
title: 'text-sm font-semibold',
|
|
81
|
-
description: 'text-sm',
|
|
82
|
-
content: 'flex-1 min-w-0',
|
|
83
|
-
action: 'inline-flex items-center justify-center px-3 py-1.5 rounded-md text-sm font-medium cursor-pointer transition-colors duration-normal motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',
|
|
84
|
-
// Dismiss container is `size-6` (24px — xs square-interactive-target
|
|
85
|
-
// scale per CLAUDE.md icon sub-scale). The inner SVG is `size-4` (16px
|
|
86
|
-
// glyph scale), centred via flex; the larger container gives the
|
|
87
|
-
// touch/click affordance without enlarging the visual glyph.
|
|
88
|
-
dismiss: 'absolute top-3 right-3 inline-flex items-center justify-center size-6 rounded-md cursor-pointer transition-colors duration-normal motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',
|
|
89
|
-
},
|
|
90
|
-
variants: {
|
|
91
|
-
severity: {
|
|
92
|
-
info: {},
|
|
93
|
-
success: {},
|
|
94
|
-
warning: {},
|
|
95
|
-
error: {},
|
|
96
|
-
neutral: {},
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
// Color slot tokens (`{role}-soft`, `-soft-fg`, `-icon`, …) own light/dark
|
|
100
|
-
// contrast — components consume the slot only. See `_semantic.css`.
|
|
101
|
-
// Slot-token safelist: see `projects/ngx-tw/theme/index.css` `@source inline(...)`
|
|
102
|
-
// — Tailwind v4's JIT cannot scan template-literal class names, so every
|
|
103
|
-
// `bg-${severity}-soft` / `text-${severity}-icon` permutation below must be
|
|
104
|
-
// enumerated there.
|
|
105
|
-
compoundVariants: ['info', 'success', 'warning', 'error', 'neutral'].map((severity) => ({
|
|
106
|
-
severity,
|
|
107
|
-
class: {
|
|
108
|
-
root: `bg-${severity}-soft text-${severity}-soft-fg-muted border-${severity}-border`,
|
|
109
|
-
icon: `text-${severity}-icon`,
|
|
110
|
-
title: `text-${severity}-soft-fg`,
|
|
111
|
-
description: `text-${severity}-soft-fg-muted`,
|
|
112
|
-
action: `text-${severity}-soft-fg hover:bg-${severity}-soft-hover`,
|
|
113
|
-
dismiss: `text-${severity}-icon hover:bg-${severity}-soft-hover`,
|
|
114
|
-
},
|
|
115
|
-
})),
|
|
116
|
-
defaultVariants: {
|
|
117
|
-
severity: 'info',
|
|
118
|
-
},
|
|
119
|
-
}, { twMerge: true });
|
|
120
|
-
/** Slot directive for the icon. When projected, overrides the severity-default icon. */
|
|
121
|
-
class ToastIconDirective {
|
|
122
|
-
toast = inject(ToastComponent);
|
|
123
|
-
/** @internal */
|
|
124
|
-
classes = this.toast.iconClasses;
|
|
125
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastIconDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
126
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: ToastIconDirective, isStandalone: true, selector: "[twToastIcon]", host: { attributes: { "aria-hidden": "true" }, properties: { "class": "classes()" } }, ngImport: i0 });
|
|
127
|
-
}
|
|
128
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastIconDirective, decorators: [{
|
|
129
|
-
type: Directive,
|
|
130
|
-
args: [{
|
|
131
|
-
selector: '[twToastIcon]',
|
|
132
|
-
host: {
|
|
133
|
-
'[class]': 'classes()',
|
|
134
|
-
'aria-hidden': 'true',
|
|
135
|
-
},
|
|
136
|
-
}]
|
|
137
|
-
}] });
|
|
138
|
-
/** Slot directive for the bold title line inside a toast. */
|
|
139
|
-
class ToastTitleDirective {
|
|
140
|
-
toast = inject(ToastComponent);
|
|
141
|
-
/** @internal */
|
|
142
|
-
classes = this.toast.titleClasses;
|
|
143
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastTitleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
144
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: ToastTitleDirective, isStandalone: true, selector: "[twToastTitle]", host: { properties: { "class": "classes()" } }, ngImport: i0 });
|
|
145
|
-
}
|
|
146
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastTitleDirective, decorators: [{
|
|
147
|
-
type: Directive,
|
|
148
|
-
args: [{
|
|
149
|
-
selector: '[twToastTitle]',
|
|
150
|
-
host: {
|
|
151
|
-
'[class]': 'classes()',
|
|
152
|
-
},
|
|
153
|
-
}]
|
|
154
|
-
}] });
|
|
155
|
-
/** Slot directive for a secondary description line inside a toast. */
|
|
156
|
-
class ToastDescriptionDirective {
|
|
157
|
-
toast = inject(ToastComponent);
|
|
158
|
-
/** @internal */
|
|
159
|
-
classes = this.toast.descriptionClasses;
|
|
160
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastDescriptionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
161
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: ToastDescriptionDirective, isStandalone: true, selector: "[twToastDescription]", host: { properties: { "class": "classes()" } }, ngImport: i0 });
|
|
162
|
-
}
|
|
163
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastDescriptionDirective, decorators: [{
|
|
164
|
-
type: Directive,
|
|
165
|
-
args: [{
|
|
166
|
-
selector: '[twToastDescription]',
|
|
167
|
-
host: {
|
|
168
|
-
'[class]': 'classes()',
|
|
169
|
-
},
|
|
170
|
-
}]
|
|
171
|
-
}] });
|
|
172
|
-
/** Slot directive for the action button. Apply to a native `<button>` to inherit the severity-aware styling. */
|
|
173
|
-
class ToastActionDirective {
|
|
174
|
-
toast = inject(ToastComponent);
|
|
175
|
-
/** @internal */
|
|
176
|
-
classes = this.toast.actionClasses;
|
|
177
|
-
/** @internal */
|
|
178
|
-
onClick() {
|
|
179
|
-
this.toast.actionClicked.emit();
|
|
180
|
-
}
|
|
181
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastActionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
182
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: ToastActionDirective, isStandalone: true, selector: "[twToastAction]", host: { attributes: { "type": "button" }, listeners: { "click": "onClick()" }, properties: { "class": "classes()" } }, ngImport: i0 });
|
|
183
|
-
}
|
|
184
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastActionDirective, decorators: [{
|
|
185
|
-
type: Directive,
|
|
186
|
-
args: [{
|
|
187
|
-
selector: '[twToastAction]',
|
|
188
|
-
host: {
|
|
189
|
-
type: 'button',
|
|
190
|
-
'[class]': 'classes()',
|
|
191
|
-
'(click)': 'onClick()',
|
|
192
|
-
},
|
|
193
|
-
}]
|
|
194
|
-
}] });
|
|
83
|
+
/** Duration (ms) of the toast enter / leave animation. Must match `_base.css` toast-slide keyframes. */
|
|
84
|
+
const TOAST_ANIMATION_DURATION = 150;
|
|
85
|
+
/** Padding added to animation fallback timers to guarantee cleanup if the CSS event is swallowed. */
|
|
86
|
+
const TOAST_ANIMATION_FALLBACK_PADDING = 50;
|
|
195
87
|
/**
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
88
|
+
* Reference to a toast opened via {@link ToastService}. Use to dismiss the
|
|
89
|
+
* toast, pause / resume its auto-dismiss timer, or observe its lifecycle.
|
|
90
|
+
*
|
|
91
|
+
* The service returns a ref synchronously from every open method; the toast
|
|
92
|
+
* itself is attached asynchronously inside the CDK overlay.
|
|
199
93
|
*/
|
|
200
|
-
class
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
|
|
208
|
-
|
|
94
|
+
class ToastRef {
|
|
95
|
+
dismissCallback;
|
|
96
|
+
/** Unique id. Generated if `config.id` was not provided. */
|
|
97
|
+
id;
|
|
98
|
+
/** Instance of the component rendered inside the toast (only for component-class content). */
|
|
99
|
+
componentInstance = null;
|
|
100
|
+
/** Resolved static config for this toast (defaults merged, severity shorthand applied). */
|
|
101
|
+
config;
|
|
102
|
+
/** Reactive lifecycle state. */
|
|
103
|
+
state;
|
|
104
|
+
/** Reactive severity — may change after `update()` (used by the `promise()` helper). */
|
|
105
|
+
severity;
|
|
106
|
+
/** Reactive dismissibility. */
|
|
107
|
+
dismissible;
|
|
108
|
+
/** Reactive icon override. */
|
|
109
|
+
icon;
|
|
110
|
+
/** Reactive content (the source of truth for what gets rendered). */
|
|
111
|
+
content;
|
|
112
|
+
/** Reactive action button configuration. */
|
|
113
|
+
action;
|
|
114
|
+
/** Reactive accessible label. */
|
|
115
|
+
ariaLabel;
|
|
116
|
+
/** Reactive data payload — re-read by the container to update `TW_TOAST_DATA`. */
|
|
117
|
+
data;
|
|
118
|
+
/** @internal Reactive "transient" class applied by swipe gestures (e.g. `translate3d(...)`). */
|
|
119
|
+
swipeTransform = signal(null, /* @ts-ignore */
|
|
120
|
+
...(ngDevMode ? [{ debugName: "swipeTransform" }] : /* istanbul ignore next */ []));
|
|
121
|
+
/** @internal Reactive opacity applied during swipe gestures. */
|
|
122
|
+
swipeOpacity = signal(null, /* @ts-ignore */
|
|
123
|
+
...(ngDevMode ? [{ debugName: "swipeOpacity" }] : /* istanbul ignore next */ []));
|
|
124
|
+
/** @internal Leave-animation class override. When non-null, container uses this instead of the position default. */
|
|
125
|
+
leaveAnimationOverride = signal(null, /* @ts-ignore */
|
|
126
|
+
...(ngDevMode ? [{ debugName: "leaveAnimationOverride" }] : /* istanbul ignore next */ []));
|
|
127
|
+
/** @internal Portal for component-class content. Set by the service; null for string / template content. */
|
|
128
|
+
_portal = null;
|
|
129
|
+
/** @internal True while the pointer hovers this toast. */
|
|
130
|
+
_hovered = false;
|
|
131
|
+
/** @internal True while focus is within this toast. */
|
|
132
|
+
_focused = false;
|
|
133
|
+
/** @internal Set by the service so the ref can reach the CDK-owned OverlayRef if it needs to. */
|
|
134
|
+
_overlayPanelElement = null;
|
|
135
|
+
stateSignal = signal('entering', /* @ts-ignore */
|
|
136
|
+
...(ngDevMode ? [{ debugName: "stateSignal" }] : /* istanbul ignore next */ []));
|
|
137
|
+
severitySignal;
|
|
138
|
+
dismissibleSignal;
|
|
139
|
+
iconSignal;
|
|
140
|
+
contentSignal;
|
|
141
|
+
actionSignal;
|
|
142
|
+
ariaLabelSignal;
|
|
143
|
+
dataSignal;
|
|
144
|
+
afterOpenedSubject = new ReplaySubject(1);
|
|
145
|
+
beforeDismissedSubject = new ReplaySubject(1);
|
|
146
|
+
afterDismissedSubject = new ReplaySubject(1);
|
|
147
|
+
updatedSubject = new Subject();
|
|
148
|
+
dismissReason = 'programmatic';
|
|
149
|
+
dismissResult;
|
|
150
|
+
autoDismissTimer = null;
|
|
151
|
+
autoDismissStartedAt = 0;
|
|
152
|
+
autoDismissRemaining = 0;
|
|
153
|
+
paused = false;
|
|
154
|
+
enterTimer = null;
|
|
155
|
+
leaveTimer = null;
|
|
156
|
+
constructor(id, content, config, dismissCallback) {
|
|
157
|
+
this.dismissCallback = dismissCallback;
|
|
158
|
+
this.id = id;
|
|
159
|
+
this.config = config;
|
|
160
|
+
this.severitySignal = signal(config.severity ?? 'info', /* @ts-ignore */
|
|
161
|
+
...(ngDevMode ? [{ debugName: "severitySignal" }] : /* istanbul ignore next */ []));
|
|
162
|
+
this.dismissibleSignal = signal(config.dismissible ?? true, /* @ts-ignore */
|
|
163
|
+
...(ngDevMode ? [{ debugName: "dismissibleSignal" }] : /* istanbul ignore next */ []));
|
|
164
|
+
this.iconSignal = signal(typeof config.icon === 'string' || config.icon === false ? config.icon : undefined, /* @ts-ignore */
|
|
165
|
+
...(ngDevMode ? [{ debugName: "iconSignal" }] : /* istanbul ignore next */ []));
|
|
166
|
+
this.contentSignal = signal(content, /* @ts-ignore */
|
|
167
|
+
...(ngDevMode ? [{ debugName: "contentSignal" }] : /* istanbul ignore next */ []));
|
|
168
|
+
this.actionSignal = signal(config.action, /* @ts-ignore */
|
|
169
|
+
...(ngDevMode ? [{ debugName: "actionSignal" }] : /* istanbul ignore next */ []));
|
|
170
|
+
this.ariaLabelSignal = signal(config.ariaLabel, /* @ts-ignore */
|
|
171
|
+
...(ngDevMode ? [{ debugName: "ariaLabelSignal" }] : /* istanbul ignore next */ []));
|
|
172
|
+
this.dataSignal = signal(config.data ?? null, /* @ts-ignore */
|
|
173
|
+
...(ngDevMode ? [{ debugName: "dataSignal" }] : /* istanbul ignore next */ []));
|
|
174
|
+
this.state = this.stateSignal.asReadonly();
|
|
175
|
+
this.severity = this.severitySignal.asReadonly();
|
|
176
|
+
this.dismissible = this.dismissibleSignal.asReadonly();
|
|
177
|
+
this.icon = this.iconSignal.asReadonly();
|
|
178
|
+
this.content = this.contentSignal.asReadonly();
|
|
179
|
+
this.action = this.actionSignal.asReadonly();
|
|
180
|
+
this.ariaLabel = this.ariaLabelSignal.asReadonly();
|
|
181
|
+
this.data = this.dataSignal.asReadonly();
|
|
182
|
+
this.autoDismissRemaining = config.duration ?? 0;
|
|
183
|
+
}
|
|
209
184
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* child is projected. When omitted, the severity-default icon renders.
|
|
185
|
+
* @internal Start the enter animation clock. Called by the service once the
|
|
186
|
+
* toast has actually been attached to its container.
|
|
213
187
|
*
|
|
214
|
-
*
|
|
188
|
+
* This deliberately does NOT run from the constructor. The renderer is loaded
|
|
189
|
+
* through a dynamic `import()`, so a ref can exist for an arbitrary stretch
|
|
190
|
+
* before anything is on screen — starting the clock at construction would let
|
|
191
|
+
* a toast burn part (or all) of its auto-dismiss duration while still
|
|
192
|
+
* invisible on a slow connection. Idempotent and safe to call after dismissal.
|
|
215
193
|
*/
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
dismissed = output();
|
|
223
|
-
/** Fires when a `[twToastAction]` button is clicked. */
|
|
224
|
-
actionClicked = output();
|
|
225
|
-
/** @internal */
|
|
226
|
-
projectedIcon = contentChild(ToastIconDirective, /* @ts-ignore */
|
|
227
|
-
...(ngDevMode ? [{ debugName: "projectedIcon" }] : /* istanbul ignore next */ []));
|
|
228
|
-
/** @internal */
|
|
229
|
-
hasProjectedIcon = computed(() => !!this.projectedIcon(), /* @ts-ignore */
|
|
230
|
-
...(ngDevMode ? [{ debugName: "hasProjectedIcon" }] : /* istanbul ignore next */ []));
|
|
231
|
-
variantResult = computed(() => toastVariants({ severity: this.severity() }), /* @ts-ignore */
|
|
232
|
-
...(ngDevMode ? [{ debugName: "variantResult" }] : /* istanbul ignore next */ []));
|
|
233
|
-
/** @internal */
|
|
234
|
-
rootClasses = computed(() => {
|
|
235
|
-
const base = this.variantResult().root();
|
|
236
|
-
return this.dismissible() ? `${base} pr-10` : base;
|
|
237
|
-
}, /* @ts-ignore */
|
|
238
|
-
...(ngDevMode ? [{ debugName: "rootClasses" }] : /* istanbul ignore next */ []));
|
|
239
|
-
/** @internal */
|
|
240
|
-
iconClasses = computed(() => this.variantResult().icon(), /* @ts-ignore */
|
|
241
|
-
...(ngDevMode ? [{ debugName: "iconClasses" }] : /* istanbul ignore next */ []));
|
|
242
|
-
/** @internal */
|
|
243
|
-
titleClasses = computed(() => this.variantResult().title(), /* @ts-ignore */
|
|
244
|
-
...(ngDevMode ? [{ debugName: "titleClasses" }] : /* istanbul ignore next */ []));
|
|
245
|
-
/** @internal */
|
|
246
|
-
descriptionClasses = computed(() => this.variantResult().description(), /* @ts-ignore */
|
|
247
|
-
...(ngDevMode ? [{ debugName: "descriptionClasses" }] : /* istanbul ignore next */ []));
|
|
248
|
-
/** @internal */
|
|
249
|
-
contentClasses = computed(() => this.variantResult().content(), /* @ts-ignore */
|
|
250
|
-
...(ngDevMode ? [{ debugName: "contentClasses" }] : /* istanbul ignore next */ []));
|
|
251
|
-
/** @internal */
|
|
252
|
-
actionClasses = computed(() => this.variantResult().action(), /* @ts-ignore */
|
|
253
|
-
...(ngDevMode ? [{ debugName: "actionClasses" }] : /* istanbul ignore next */ []));
|
|
254
|
-
/** @internal */
|
|
255
|
-
dismissClasses = computed(() => this.variantResult().dismiss(), /* @ts-ignore */
|
|
256
|
-
...(ngDevMode ? [{ debugName: "dismissClasses" }] : /* istanbul ignore next */ []));
|
|
257
|
-
/** @internal */
|
|
258
|
-
roleAttr = computed(() => this.severity() === 'error' ? 'alert' : 'status', /* @ts-ignore */
|
|
259
|
-
...(ngDevMode ? [{ debugName: "roleAttr" }] : /* istanbul ignore next */ []));
|
|
260
|
-
/** @internal */
|
|
261
|
-
ariaLiveAttr = computed(() => this.severity() === 'error' ? 'assertive' : 'polite', /* @ts-ignore */
|
|
262
|
-
...(ngDevMode ? [{ debugName: "ariaLiveAttr" }] : /* istanbul ignore next */ []));
|
|
263
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
264
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: ToastComponent, isStandalone: true, selector: "tw-toast", inputs: { severity: { classPropertyName: "severity", publicName: "severity", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dismissed: "dismissed", actionClicked: "actionClicked" }, host: { attributes: { "aria-atomic": "true" }, properties: { "class": "rootClasses()", "attr.role": "roleAttr()", "attr.aria-live": "ariaLiveAttr()", "attr.aria-label": "ariaLabel() || null", "attr.data-severity": "severity()" } }, queries: [{ propertyName: "projectedIcon", first: true, predicate: ToastIconDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
265
|
-
@if (hasProjectedIcon()) {
|
|
266
|
-
<ng-content select="[twToastIcon]" />
|
|
267
|
-
} @else if (icon() !== false) {
|
|
268
|
-
<span [class]="iconClasses()" aria-hidden="true">
|
|
269
|
-
@switch (severity()) {
|
|
270
|
-
@case ('info') {
|
|
271
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
272
|
-
<path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm0-11a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm-.75 2.5a.75.75 0 0 0 0 1.5h.25v3h-.25a.75.75 0 0 0 0 1.5h2a.75.75 0 0 0 0-1.5h-.25v-3.75a.75.75 0 0 0-.75-.75h-1Z" clip-rule="evenodd"/>
|
|
273
|
-
</svg>
|
|
274
|
-
}
|
|
275
|
-
@case ('success') {
|
|
276
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
277
|
-
<path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5Z" clip-rule="evenodd"/>
|
|
278
|
-
</svg>
|
|
279
|
-
}
|
|
280
|
-
@case ('warning') {
|
|
281
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
282
|
-
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd"/>
|
|
283
|
-
</svg>
|
|
284
|
-
}
|
|
285
|
-
@case ('error') {
|
|
286
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
287
|
-
<path fill-rule="evenodd" d="M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0Zm-8-5a.75.75 0 0 1 .75.75v4.5a.75.75 0 0 1-1.5 0v-4.5A.75.75 0 0 1 10 5Zm0 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd"/>
|
|
288
|
-
</svg>
|
|
289
|
-
}
|
|
290
|
-
@default {
|
|
291
|
-
@if (icon() && icon() !== false) {
|
|
292
|
-
{{ icon() }}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
</span>
|
|
297
|
-
}
|
|
298
|
-
<div [class]="contentClasses()">
|
|
299
|
-
<ng-content select="[twToastTitle]" />
|
|
300
|
-
<ng-content select="[twToastDescription]" />
|
|
301
|
-
<ng-content />
|
|
302
|
-
<ng-content select="[twToastAction]" />
|
|
303
|
-
</div>
|
|
304
|
-
@if (dismissible()) {
|
|
305
|
-
<button
|
|
306
|
-
type="button"
|
|
307
|
-
aria-label="Dismiss"
|
|
308
|
-
[class]="dismissClasses()"
|
|
309
|
-
(click)="dismissed.emit()"
|
|
310
|
-
>
|
|
311
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
|
|
312
|
-
<path d="M5.28 4.22a.75.75 0 0 0-1.06 1.06L6.94 8l-2.72 2.72a.75.75 0 1 0 1.06 1.06L8 9.06l2.72 2.72a.75.75 0 1 0 1.06-1.06L9.06 8l2.72-2.72a.75.75 0 0 0-1.06-1.06L8 6.94 5.28 4.22Z"/>
|
|
313
|
-
</svg>
|
|
314
|
-
</button>
|
|
315
|
-
}
|
|
316
|
-
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
317
|
-
}
|
|
318
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastComponent, decorators: [{
|
|
319
|
-
type: Component,
|
|
320
|
-
args: [{
|
|
321
|
-
selector: 'tw-toast',
|
|
322
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
323
|
-
host: {
|
|
324
|
-
'[class]': 'rootClasses()',
|
|
325
|
-
'[attr.role]': 'roleAttr()',
|
|
326
|
-
'[attr.aria-live]': 'ariaLiveAttr()',
|
|
327
|
-
'aria-atomic': 'true',
|
|
328
|
-
'[attr.aria-label]': 'ariaLabel() || null',
|
|
329
|
-
'[attr.data-severity]': 'severity()',
|
|
330
|
-
},
|
|
331
|
-
template: `
|
|
332
|
-
@if (hasProjectedIcon()) {
|
|
333
|
-
<ng-content select="[twToastIcon]" />
|
|
334
|
-
} @else if (icon() !== false) {
|
|
335
|
-
<span [class]="iconClasses()" aria-hidden="true">
|
|
336
|
-
@switch (severity()) {
|
|
337
|
-
@case ('info') {
|
|
338
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
339
|
-
<path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm0-11a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm-.75 2.5a.75.75 0 0 0 0 1.5h.25v3h-.25a.75.75 0 0 0 0 1.5h2a.75.75 0 0 0 0-1.5h-.25v-3.75a.75.75 0 0 0-.75-.75h-1Z" clip-rule="evenodd"/>
|
|
340
|
-
</svg>
|
|
341
|
-
}
|
|
342
|
-
@case ('success') {
|
|
343
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
344
|
-
<path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5Z" clip-rule="evenodd"/>
|
|
345
|
-
</svg>
|
|
346
|
-
}
|
|
347
|
-
@case ('warning') {
|
|
348
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
349
|
-
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd"/>
|
|
350
|
-
</svg>
|
|
351
|
-
}
|
|
352
|
-
@case ('error') {
|
|
353
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
354
|
-
<path fill-rule="evenodd" d="M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0Zm-8-5a.75.75 0 0 1 .75.75v4.5a.75.75 0 0 1-1.5 0v-4.5A.75.75 0 0 1 10 5Zm0 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd"/>
|
|
355
|
-
</svg>
|
|
356
|
-
}
|
|
357
|
-
@default {
|
|
358
|
-
@if (icon() && icon() !== false) {
|
|
359
|
-
{{ icon() }}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
</span>
|
|
364
|
-
}
|
|
365
|
-
<div [class]="contentClasses()">
|
|
366
|
-
<ng-content select="[twToastTitle]" />
|
|
367
|
-
<ng-content select="[twToastDescription]" />
|
|
368
|
-
<ng-content />
|
|
369
|
-
<ng-content select="[twToastAction]" />
|
|
370
|
-
</div>
|
|
371
|
-
@if (dismissible()) {
|
|
372
|
-
<button
|
|
373
|
-
type="button"
|
|
374
|
-
aria-label="Dismiss"
|
|
375
|
-
[class]="dismissClasses()"
|
|
376
|
-
(click)="dismissed.emit()"
|
|
377
|
-
>
|
|
378
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
|
|
379
|
-
<path d="M5.28 4.22a.75.75 0 0 0-1.06 1.06L6.94 8l-2.72 2.72a.75.75 0 1 0 1.06 1.06L8 9.06l2.72 2.72a.75.75 0 1 0 1.06-1.06L9.06 8l2.72-2.72a.75.75 0 0 0-1.06-1.06L8 6.94 5.28 4.22Z"/>
|
|
380
|
-
</svg>
|
|
381
|
-
</button>
|
|
194
|
+
_startEnterSequence() {
|
|
195
|
+
if (this.enterTimer !== null)
|
|
196
|
+
return;
|
|
197
|
+
if (this.stateSignal() !== 'entering')
|
|
198
|
+
return;
|
|
199
|
+
this.enterTimer = setTimeout(() => this._markVisible(), TOAST_ANIMATION_DURATION + TOAST_ANIMATION_FALLBACK_PADDING);
|
|
382
200
|
}
|
|
383
|
-
`,
|
|
384
|
-
}]
|
|
385
|
-
}], propDecorators: { severity: [{ type: i0.Input, args: [{ isSignal: true, alias: "severity", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], dismissed: [{ type: i0.Output, args: ["dismissed"] }], actionClicked: [{ type: i0.Output, args: ["actionClicked"] }], projectedIcon: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ToastIconDirective), { isSignal: true }] }] } });
|
|
386
|
-
|
|
387
|
-
const POSITION_AXIS = {
|
|
388
|
-
'top-right': 'right',
|
|
389
|
-
'bottom-right': 'right',
|
|
390
|
-
'top-left': 'left',
|
|
391
|
-
'bottom-left': 'left',
|
|
392
|
-
'top-center': 'top',
|
|
393
|
-
'bottom-center': 'bottom',
|
|
394
|
-
};
|
|
395
|
-
const POSITION_HOST_CLASSES = {
|
|
396
|
-
'top-right': 'items-end',
|
|
397
|
-
'bottom-right': 'items-end',
|
|
398
|
-
'top-left': 'items-start',
|
|
399
|
-
'bottom-left': 'items-start',
|
|
400
|
-
'top-center': 'items-center',
|
|
401
|
-
'bottom-center': 'items-center',
|
|
402
|
-
};
|
|
403
|
-
const POSITION_ORDER_REVERSED = {
|
|
404
|
-
'top-right': false,
|
|
405
|
-
'top-left': false,
|
|
406
|
-
'top-center': false,
|
|
407
|
-
'bottom-right': true,
|
|
408
|
-
'bottom-left': true,
|
|
409
|
-
'bottom-center': true,
|
|
410
|
-
};
|
|
411
|
-
const SWIPE_DISMISS_FRACTION = 0.4;
|
|
412
|
-
const SWIPE_MAX_OPACITY_FADE = 0.6;
|
|
413
|
-
/**
|
|
414
|
-
* Internal flex-column host rendered inside each per-position CDK overlay.
|
|
415
|
-
* Stacks visible toasts via `@for`, wires pause-on-interaction, Escape
|
|
416
|
-
* dismissal, swipe gestures, and `LiveAnnouncer` announcements.
|
|
417
|
-
*
|
|
418
|
-
* @docs-private
|
|
419
|
-
*/
|
|
420
|
-
class ToastContainerComponent {
|
|
421
|
-
/** Per-position stacking anchor. Mutated from {@link ToastService} via `.instance.position.set(...)`. */
|
|
422
|
-
position = signal('bottom-right', /* @ts-ignore */
|
|
423
|
-
...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
|
|
424
|
-
/** Accessible label applied to the `role="region"` host. Mutated from the service on init. */
|
|
425
|
-
regionLabel = signal('Notifications', /* @ts-ignore */
|
|
426
|
-
...(ngDevMode ? [{ debugName: "regionLabel" }] : /* istanbul ignore next */ []));
|
|
427
201
|
/**
|
|
428
|
-
*
|
|
429
|
-
*
|
|
202
|
+
* Dismiss the toast programmatically. The leave animation plays before the
|
|
203
|
+
* element is removed and subscribers are notified. Safe to call multiple times.
|
|
430
204
|
*/
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
injector = inject(Injector);
|
|
434
|
-
liveAnnouncer = inject(LiveAnnouncer);
|
|
435
|
-
host = inject((ElementRef));
|
|
436
|
-
swipeSessions = new WeakMap();
|
|
437
|
-
entries = computed(() => {
|
|
438
|
-
const pos = this.position();
|
|
439
|
-
const axis = POSITION_AXIS[pos];
|
|
440
|
-
const enter = `toast-enter-${axis}`;
|
|
441
|
-
const leave = `toast-leave-${axis}`;
|
|
442
|
-
const refs = this.visibleRefs().filter((ref) => {
|
|
443
|
-
const s = ref.state();
|
|
444
|
-
return s === 'entering' || s === 'visible' || s === 'paused';
|
|
445
|
-
});
|
|
446
|
-
return refs.map((ref) => ({
|
|
447
|
-
ref,
|
|
448
|
-
kind: resolveKind(ref.content()),
|
|
449
|
-
enterClass: enter,
|
|
450
|
-
leaveClass: leave,
|
|
451
|
-
}));
|
|
452
|
-
}, /* @ts-ignore */
|
|
453
|
-
...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
|
|
454
|
-
orderedEntries = computed(() => {
|
|
455
|
-
const list = this.entries();
|
|
456
|
-
return POSITION_ORDER_REVERSED[this.position()] ? [...list].reverse() : list;
|
|
457
|
-
}, /* @ts-ignore */
|
|
458
|
-
...(ngDevMode ? [{ debugName: "orderedEntries" }] : /* istanbul ignore next */ []));
|
|
459
|
-
hostClasses = computed(() => {
|
|
460
|
-
const base = 'flex flex-col gap-2 w-full max-w-sm';
|
|
461
|
-
const align = POSITION_HOST_CLASSES[this.position()];
|
|
462
|
-
return `${base} ${align}`;
|
|
463
|
-
}, /* @ts-ignore */
|
|
464
|
-
...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
|
|
465
|
-
/** @internal Called by the service right after attaching a new toast. Runs `LiveAnnouncer`. */
|
|
466
|
-
_announceOpen(ref) {
|
|
467
|
-
const politeness = this.resolvePoliteness(ref);
|
|
468
|
-
if (politeness === 'off')
|
|
469
|
-
return;
|
|
470
|
-
const msg = this.resolveAnnouncementText(ref);
|
|
471
|
-
if (msg)
|
|
472
|
-
this.liveAnnouncer.announce(msg, politeness);
|
|
473
|
-
}
|
|
474
|
-
/** @internal Called by the service when `ref.update()` fires — re-announces with the new severity / content. */
|
|
475
|
-
_announceUpdate(ref) {
|
|
476
|
-
this._announceOpen(ref);
|
|
205
|
+
dismiss(result) {
|
|
206
|
+
this._dismissWith('programmatic', result);
|
|
477
207
|
}
|
|
478
|
-
/**
|
|
479
|
-
|
|
480
|
-
if (
|
|
481
|
-
|
|
208
|
+
/** Pause the auto-dismiss timer (if any). Called automatically on hover / focus when `pauseOnInteraction` is true. */
|
|
209
|
+
pause() {
|
|
210
|
+
if (this.paused)
|
|
211
|
+
return;
|
|
212
|
+
this.paused = true;
|
|
213
|
+
if (this.autoDismissTimer !== null) {
|
|
214
|
+
const elapsed = Date.now() - this.autoDismissStartedAt;
|
|
215
|
+
this.autoDismissRemaining = Math.max(0, this.autoDismissRemaining - elapsed);
|
|
216
|
+
clearTimeout(this.autoDismissTimer);
|
|
217
|
+
this.autoDismissTimer = null;
|
|
482
218
|
}
|
|
219
|
+
const current = this.stateSignal();
|
|
220
|
+
if (current === 'visible')
|
|
221
|
+
this.stateSignal.set('paused');
|
|
483
222
|
}
|
|
484
|
-
/**
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
parent: this.injector,
|
|
488
|
-
providers: [
|
|
489
|
-
{ provide: TW_TOAST_REF, useValue: ref },
|
|
490
|
-
{ provide: TW_TOAST_DATA, useValue: ref.data() },
|
|
491
|
-
],
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
// ── Template helpers ──
|
|
495
|
-
asString(content) {
|
|
496
|
-
return typeof content === 'string' ? content : '';
|
|
497
|
-
}
|
|
498
|
-
asTemplate(content) {
|
|
499
|
-
return content instanceof TemplateRef ? content : null;
|
|
500
|
-
}
|
|
501
|
-
templateContext(ref) {
|
|
502
|
-
return { $implicit: ref.data(), ref };
|
|
503
|
-
}
|
|
504
|
-
panelClassFor(ref) {
|
|
505
|
-
const raw = ref.config.panelClass;
|
|
506
|
-
if (!raw)
|
|
507
|
-
return '';
|
|
508
|
-
return Array.isArray(raw) ? raw.join(' ') : raw;
|
|
509
|
-
}
|
|
510
|
-
// ── Interaction handlers ──
|
|
511
|
-
onPointerEnter(ref) {
|
|
512
|
-
ref._setHovered(true);
|
|
513
|
-
}
|
|
514
|
-
onPointerLeave(ref) {
|
|
515
|
-
ref._setHovered(false);
|
|
516
|
-
}
|
|
517
|
-
onFocusIn(ref) {
|
|
518
|
-
ref._setFocused(true);
|
|
519
|
-
}
|
|
520
|
-
onFocusOut(ref, event) {
|
|
521
|
-
const related = event.relatedTarget;
|
|
522
|
-
const target = event.currentTarget;
|
|
523
|
-
if (related && target && target.contains(related))
|
|
223
|
+
/** Resume the auto-dismiss timer with the remaining time. */
|
|
224
|
+
resume() {
|
|
225
|
+
if (!this.paused)
|
|
524
226
|
return;
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
227
|
+
this.paused = false;
|
|
228
|
+
if (this.stateSignal() === 'paused')
|
|
229
|
+
this.stateSignal.set('visible');
|
|
230
|
+
if (this.config.duration && this.config.duration > 0) {
|
|
231
|
+
if (this.autoDismissRemaining <= 0) {
|
|
232
|
+
this._dismissWith('timeout');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
this._startAutoDismissTimer();
|
|
236
|
+
}
|
|
531
237
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
return;
|
|
540
|
-
const width = el.getBoundingClientRect().width;
|
|
541
|
-
this.swipeSessions.set(ref, {
|
|
542
|
-
pointerId: event.pointerId,
|
|
543
|
-
startX: event.clientX,
|
|
544
|
-
width,
|
|
545
|
-
active: false,
|
|
546
|
-
});
|
|
547
|
-
el.setPointerCapture(event.pointerId);
|
|
548
|
-
const onMove = (e) => this.onSwipeMove(ref, e, el);
|
|
549
|
-
const onUp = (e) => this.onSwipeEnd(ref, e, el, onMove, onUp);
|
|
550
|
-
el.addEventListener('pointermove', onMove);
|
|
551
|
-
el.addEventListener('pointerup', onUp);
|
|
552
|
-
el.addEventListener('pointercancel', onUp);
|
|
238
|
+
/**
|
|
239
|
+
* @internal Update the hover state. When `pauseOnInteraction` is set and
|
|
240
|
+
* either hover or focus is active, the auto-dismiss timer is paused.
|
|
241
|
+
*/
|
|
242
|
+
_setHovered(hovered) {
|
|
243
|
+
this._hovered = hovered;
|
|
244
|
+
this._syncInteractionPause();
|
|
553
245
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
const dx = event.clientX - session.startX;
|
|
559
|
-
if (!session.active && Math.abs(dx) < 6)
|
|
560
|
-
return;
|
|
561
|
-
session.active = true;
|
|
562
|
-
ref.swipeTransform.set(`translate3d(${dx}px, 0, 0)`);
|
|
563
|
-
const fade = 1 - Math.min(Math.abs(dx) / session.width, 1) * SWIPE_MAX_OPACITY_FADE;
|
|
564
|
-
ref.swipeOpacity.set(fade);
|
|
246
|
+
/** @internal Update the focus-within state. See `_setHovered`. */
|
|
247
|
+
_setFocused(focused) {
|
|
248
|
+
this._focused = focused;
|
|
249
|
+
this._syncInteractionPause();
|
|
565
250
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
el.removeEventListener('pointermove', onMove);
|
|
569
|
-
el.removeEventListener('pointerup', onUp);
|
|
570
|
-
el.removeEventListener('pointercancel', onUp);
|
|
571
|
-
if (!session || event.pointerId !== session.pointerId)
|
|
572
|
-
return;
|
|
573
|
-
try {
|
|
574
|
-
el.releasePointerCapture(session.pointerId);
|
|
575
|
-
}
|
|
576
|
-
catch {
|
|
577
|
-
/* no-op */
|
|
578
|
-
}
|
|
579
|
-
this.swipeSessions.delete(ref);
|
|
580
|
-
if (!session.active)
|
|
251
|
+
_syncInteractionPause() {
|
|
252
|
+
if (!this.config.pauseOnInteraction)
|
|
581
253
|
return;
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
const allowed = this.swipeDirectionAllowed(dx);
|
|
585
|
-
if (Math.abs(dx) >= threshold && allowed) {
|
|
586
|
-
untracked(() => {
|
|
587
|
-
ref.swipeTransform.set(`translate3d(${Math.sign(dx) * session.width * 1.2}px, 0, 0)`);
|
|
588
|
-
ref.swipeOpacity.set(0);
|
|
589
|
-
ref.leaveAnimationOverride.set('fade-out');
|
|
590
|
-
});
|
|
591
|
-
ref._dismissWith('swipe');
|
|
254
|
+
if (this._hovered || this._focused) {
|
|
255
|
+
this.pause();
|
|
592
256
|
}
|
|
593
257
|
else {
|
|
594
|
-
|
|
595
|
-
ref.swipeOpacity.set(null);
|
|
258
|
+
this.resume();
|
|
596
259
|
}
|
|
597
260
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
}
|
|
606
|
-
resolvePoliteness(ref) {
|
|
607
|
-
if (ref.config.politeness)
|
|
608
|
-
return ref.config.politeness;
|
|
609
|
-
return ref.severity() === 'error' ? 'assertive' : 'polite';
|
|
610
|
-
}
|
|
611
|
-
resolveAnnouncementText(ref) {
|
|
612
|
-
const explicit = ref.ariaLabel();
|
|
613
|
-
if (explicit)
|
|
614
|
-
return explicit;
|
|
615
|
-
const content = ref.content();
|
|
616
|
-
if (typeof content === 'string')
|
|
617
|
-
return content;
|
|
618
|
-
const host = this.host.nativeElement.querySelector(`[data-toast-id="${ref.id}"]`);
|
|
619
|
-
if (host)
|
|
620
|
-
return host.textContent?.trim() ?? '';
|
|
621
|
-
return this.host.nativeElement.textContent?.trim() ?? '';
|
|
622
|
-
}
|
|
623
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
624
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: ToastContainerComponent, isStandalone: true, selector: "tw-toast-container", host: { attributes: { "role": "region" }, properties: { "class": "hostClasses()", "attr.aria-label": "regionLabel()", "attr.data-position": "position()" } }, ngImport: i0, template: `
|
|
625
|
-
@for (entry of orderedEntries(); track entry.ref.id) {
|
|
626
|
-
<!-- Toast entry wrapper; the projected <tw-toast> is the focusable affordance.
|
|
627
|
-
This element only forwards pointer/focus events so the container can
|
|
628
|
-
pause auto-dismiss while the user is interacting. -->
|
|
629
|
-
<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->
|
|
630
|
-
<div
|
|
631
|
-
class="pointer-events-auto w-full max-w-sm"
|
|
632
|
-
[attr.data-toast-id]="entry.ref.id"
|
|
633
|
-
[style.transform]="entry.ref.swipeTransform() || null"
|
|
634
|
-
[style.opacity]="entry.ref.swipeOpacity() ?? null"
|
|
635
|
-
[style.touch-action]="'pan-y'"
|
|
636
|
-
[animate.enter]="entry.enterClass"
|
|
637
|
-
[animate.leave]="entry.ref.leaveAnimationOverride() ?? entry.leaveClass"
|
|
638
|
-
(pointerenter)="onPointerEnter(entry.ref)"
|
|
639
|
-
(pointerleave)="onPointerLeave(entry.ref)"
|
|
640
|
-
(focusin)="onFocusIn(entry.ref)"
|
|
641
|
-
(focusout)="onFocusOut(entry.ref, $event)"
|
|
642
|
-
(keydown.escape)="onEscape(entry.ref, $event)"
|
|
643
|
-
(pointerdown)="onSwipeStart(entry.ref, $event, swipeEl)"
|
|
644
|
-
#swipeEl
|
|
645
|
-
>
|
|
646
|
-
@switch (entry.kind) {
|
|
647
|
-
@case ('string') {
|
|
648
|
-
<tw-toast
|
|
649
|
-
[severity]="entry.ref.severity()"
|
|
650
|
-
[dismissible]="entry.ref.dismissible()"
|
|
651
|
-
[icon]="entry.ref.icon()"
|
|
652
|
-
[ariaLabel]="entry.ref.ariaLabel()"
|
|
653
|
-
[class]="panelClassFor(entry.ref)"
|
|
654
|
-
(dismissed)="entry.ref._dismissWith('manual')"
|
|
655
|
-
(actionClicked)="entry.ref.triggerAction()"
|
|
656
|
-
>
|
|
657
|
-
{{ asString(entry.ref.content()) }}
|
|
658
|
-
@if (entry.ref.action(); as action) {
|
|
659
|
-
<button twToastAction>{{ action.label }}</button>
|
|
660
|
-
}
|
|
661
|
-
</tw-toast>
|
|
662
|
-
}
|
|
663
|
-
@case ('template') {
|
|
664
|
-
<tw-toast
|
|
665
|
-
[severity]="entry.ref.severity()"
|
|
666
|
-
[dismissible]="entry.ref.dismissible()"
|
|
667
|
-
[icon]="entry.ref.icon()"
|
|
668
|
-
[ariaLabel]="entry.ref.ariaLabel()"
|
|
669
|
-
[class]="panelClassFor(entry.ref)"
|
|
670
|
-
(dismissed)="entry.ref._dismissWith('manual')"
|
|
671
|
-
(actionClicked)="entry.ref.triggerAction()"
|
|
672
|
-
>
|
|
673
|
-
<ng-container
|
|
674
|
-
[ngTemplateOutlet]="asTemplate(entry.ref.content())"
|
|
675
|
-
[ngTemplateOutletContext]="templateContext(entry.ref)"
|
|
676
|
-
/>
|
|
677
|
-
@if (entry.ref.action(); as action) {
|
|
678
|
-
<button twToastAction>{{ action.label }}</button>
|
|
679
|
-
}
|
|
680
|
-
</tw-toast>
|
|
681
|
-
}
|
|
682
|
-
@case ('component') {
|
|
683
|
-
<ng-template
|
|
684
|
-
[cdkPortalOutlet]="entry.ref._portal"
|
|
685
|
-
(attached)="onPortalAttached(entry.ref, $event)"
|
|
686
|
-
/>
|
|
687
|
-
}
|
|
261
|
+
/** Invoke the configured action handler (or dismiss with reason `'action'` if none is set). */
|
|
262
|
+
triggerAction() {
|
|
263
|
+
const action = this.actionSignal();
|
|
264
|
+
if (!action)
|
|
265
|
+
return;
|
|
266
|
+
if (action.handler) {
|
|
267
|
+
action.handler(this);
|
|
688
268
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { kind: "component", type: ToastComponent, selector: "tw-toast", inputs: ["severity", "dismissible", "icon", "ariaLabel"], outputs: ["dismissed", "actionClicked"] }, { kind: "directive", type: ToastActionDirective, selector: "[twToastAction]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
692
|
-
}
|
|
693
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastContainerComponent, decorators: [{
|
|
694
|
-
type: Component,
|
|
695
|
-
args: [{
|
|
696
|
-
selector: 'tw-toast-container',
|
|
697
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
698
|
-
encapsulation: ViewEncapsulation.None,
|
|
699
|
-
imports: [NgTemplateOutlet, CdkPortalOutlet, ToastComponent, ToastActionDirective],
|
|
700
|
-
host: {
|
|
701
|
-
role: 'region',
|
|
702
|
-
'[class]': 'hostClasses()',
|
|
703
|
-
'[attr.aria-label]': 'regionLabel()',
|
|
704
|
-
'[attr.data-position]': 'position()',
|
|
705
|
-
},
|
|
706
|
-
template: `
|
|
707
|
-
@for (entry of orderedEntries(); track entry.ref.id) {
|
|
708
|
-
<!-- Toast entry wrapper; the projected <tw-toast> is the focusable affordance.
|
|
709
|
-
This element only forwards pointer/focus events so the container can
|
|
710
|
-
pause auto-dismiss while the user is interacting. -->
|
|
711
|
-
<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->
|
|
712
|
-
<div
|
|
713
|
-
class="pointer-events-auto w-full max-w-sm"
|
|
714
|
-
[attr.data-toast-id]="entry.ref.id"
|
|
715
|
-
[style.transform]="entry.ref.swipeTransform() || null"
|
|
716
|
-
[style.opacity]="entry.ref.swipeOpacity() ?? null"
|
|
717
|
-
[style.touch-action]="'pan-y'"
|
|
718
|
-
[animate.enter]="entry.enterClass"
|
|
719
|
-
[animate.leave]="entry.ref.leaveAnimationOverride() ?? entry.leaveClass"
|
|
720
|
-
(pointerenter)="onPointerEnter(entry.ref)"
|
|
721
|
-
(pointerleave)="onPointerLeave(entry.ref)"
|
|
722
|
-
(focusin)="onFocusIn(entry.ref)"
|
|
723
|
-
(focusout)="onFocusOut(entry.ref, $event)"
|
|
724
|
-
(keydown.escape)="onEscape(entry.ref, $event)"
|
|
725
|
-
(pointerdown)="onSwipeStart(entry.ref, $event, swipeEl)"
|
|
726
|
-
#swipeEl
|
|
727
|
-
>
|
|
728
|
-
@switch (entry.kind) {
|
|
729
|
-
@case ('string') {
|
|
730
|
-
<tw-toast
|
|
731
|
-
[severity]="entry.ref.severity()"
|
|
732
|
-
[dismissible]="entry.ref.dismissible()"
|
|
733
|
-
[icon]="entry.ref.icon()"
|
|
734
|
-
[ariaLabel]="entry.ref.ariaLabel()"
|
|
735
|
-
[class]="panelClassFor(entry.ref)"
|
|
736
|
-
(dismissed)="entry.ref._dismissWith('manual')"
|
|
737
|
-
(actionClicked)="entry.ref.triggerAction()"
|
|
738
|
-
>
|
|
739
|
-
{{ asString(entry.ref.content()) }}
|
|
740
|
-
@if (entry.ref.action(); as action) {
|
|
741
|
-
<button twToastAction>{{ action.label }}</button>
|
|
742
|
-
}
|
|
743
|
-
</tw-toast>
|
|
744
|
-
}
|
|
745
|
-
@case ('template') {
|
|
746
|
-
<tw-toast
|
|
747
|
-
[severity]="entry.ref.severity()"
|
|
748
|
-
[dismissible]="entry.ref.dismissible()"
|
|
749
|
-
[icon]="entry.ref.icon()"
|
|
750
|
-
[ariaLabel]="entry.ref.ariaLabel()"
|
|
751
|
-
[class]="panelClassFor(entry.ref)"
|
|
752
|
-
(dismissed)="entry.ref._dismissWith('manual')"
|
|
753
|
-
(actionClicked)="entry.ref.triggerAction()"
|
|
754
|
-
>
|
|
755
|
-
<ng-container
|
|
756
|
-
[ngTemplateOutlet]="asTemplate(entry.ref.content())"
|
|
757
|
-
[ngTemplateOutletContext]="templateContext(entry.ref)"
|
|
758
|
-
/>
|
|
759
|
-
@if (entry.ref.action(); as action) {
|
|
760
|
-
<button twToastAction>{{ action.label }}</button>
|
|
761
|
-
}
|
|
762
|
-
</tw-toast>
|
|
763
|
-
}
|
|
764
|
-
@case ('component') {
|
|
765
|
-
<ng-template
|
|
766
|
-
[cdkPortalOutlet]="entry.ref._portal"
|
|
767
|
-
(attached)="onPortalAttached(entry.ref, $event)"
|
|
768
|
-
/>
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
</div>
|
|
772
|
-
}
|
|
773
|
-
`,
|
|
774
|
-
}]
|
|
775
|
-
}] });
|
|
776
|
-
function resolveKind(content) {
|
|
777
|
-
if (typeof content === 'string')
|
|
778
|
-
return 'string';
|
|
779
|
-
if (content instanceof TemplateRef)
|
|
780
|
-
return 'template';
|
|
781
|
-
return 'component';
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
/** Duration (ms) of the toast enter / leave animation. Must match `_base.css` toast-slide keyframes. */
|
|
785
|
-
const TOAST_ANIMATION_DURATION = 150;
|
|
786
|
-
/** Padding added to animation fallback timers to guarantee cleanup if the CSS event is swallowed. */
|
|
787
|
-
const TOAST_ANIMATION_FALLBACK_PADDING = 50;
|
|
788
|
-
/**
|
|
789
|
-
* Reference to a toast opened via {@link ToastService}. Use to dismiss the
|
|
790
|
-
* toast, pause / resume its auto-dismiss timer, or observe its lifecycle.
|
|
791
|
-
*
|
|
792
|
-
* The service returns a ref synchronously from every open method; the toast
|
|
793
|
-
* itself is attached asynchronously inside the CDK overlay.
|
|
794
|
-
*/
|
|
795
|
-
class ToastRef {
|
|
796
|
-
dismissCallback;
|
|
797
|
-
/** Unique id. Generated if `config.id` was not provided. */
|
|
798
|
-
id;
|
|
799
|
-
/** Instance of the component rendered inside the toast (only for component-class content). */
|
|
800
|
-
componentInstance = null;
|
|
801
|
-
/** Resolved static config for this toast (defaults merged, severity shorthand applied). */
|
|
802
|
-
config;
|
|
803
|
-
/** Reactive lifecycle state. */
|
|
804
|
-
state;
|
|
805
|
-
/** Reactive severity — may change after `update()` (used by the `promise()` helper). */
|
|
806
|
-
severity;
|
|
807
|
-
/** Reactive dismissibility. */
|
|
808
|
-
dismissible;
|
|
809
|
-
/** Reactive icon override. */
|
|
810
|
-
icon;
|
|
811
|
-
/** Reactive content (the source of truth for what gets rendered). */
|
|
812
|
-
content;
|
|
813
|
-
/** Reactive action button configuration. */
|
|
814
|
-
action;
|
|
815
|
-
/** Reactive accessible label. */
|
|
816
|
-
ariaLabel;
|
|
817
|
-
/** Reactive data payload — re-read by the container to update `TW_TOAST_DATA`. */
|
|
818
|
-
data;
|
|
819
|
-
/** @internal Reactive "transient" class applied by swipe gestures (e.g. `translate3d(...)`). */
|
|
820
|
-
swipeTransform = signal(null, /* @ts-ignore */
|
|
821
|
-
...(ngDevMode ? [{ debugName: "swipeTransform" }] : /* istanbul ignore next */ []));
|
|
822
|
-
/** @internal Reactive opacity applied during swipe gestures. */
|
|
823
|
-
swipeOpacity = signal(null, /* @ts-ignore */
|
|
824
|
-
...(ngDevMode ? [{ debugName: "swipeOpacity" }] : /* istanbul ignore next */ []));
|
|
825
|
-
/** @internal Leave-animation class override. When non-null, container uses this instead of the position default. */
|
|
826
|
-
leaveAnimationOverride = signal(null, /* @ts-ignore */
|
|
827
|
-
...(ngDevMode ? [{ debugName: "leaveAnimationOverride" }] : /* istanbul ignore next */ []));
|
|
828
|
-
/** @internal Portal for component-class content. Set by the service; null for string / template content. */
|
|
829
|
-
_portal = null;
|
|
830
|
-
/** @internal True while the pointer hovers this toast. */
|
|
831
|
-
_hovered = false;
|
|
832
|
-
/** @internal True while focus is within this toast. */
|
|
833
|
-
_focused = false;
|
|
834
|
-
/** @internal Set by the service so the ref can reach the CDK-owned OverlayRef if it needs to. */
|
|
835
|
-
_overlayPanelElement = null;
|
|
836
|
-
stateSignal = signal('entering', /* @ts-ignore */
|
|
837
|
-
...(ngDevMode ? [{ debugName: "stateSignal" }] : /* istanbul ignore next */ []));
|
|
838
|
-
severitySignal;
|
|
839
|
-
dismissibleSignal;
|
|
840
|
-
iconSignal;
|
|
841
|
-
contentSignal;
|
|
842
|
-
actionSignal;
|
|
843
|
-
ariaLabelSignal;
|
|
844
|
-
dataSignal;
|
|
845
|
-
afterOpenedSubject = new ReplaySubject(1);
|
|
846
|
-
beforeDismissedSubject = new ReplaySubject(1);
|
|
847
|
-
afterDismissedSubject = new ReplaySubject(1);
|
|
848
|
-
updatedSubject = new Subject();
|
|
849
|
-
dismissReason = 'programmatic';
|
|
850
|
-
dismissResult;
|
|
851
|
-
autoDismissTimer = null;
|
|
852
|
-
autoDismissStartedAt = 0;
|
|
853
|
-
autoDismissRemaining = 0;
|
|
854
|
-
paused = false;
|
|
855
|
-
enterTimer = null;
|
|
856
|
-
leaveTimer = null;
|
|
857
|
-
constructor(id, content, config, dismissCallback) {
|
|
858
|
-
this.dismissCallback = dismissCallback;
|
|
859
|
-
this.id = id;
|
|
860
|
-
this.config = config;
|
|
861
|
-
this.severitySignal = signal(config.severity ?? 'info', /* @ts-ignore */
|
|
862
|
-
...(ngDevMode ? [{ debugName: "severitySignal" }] : /* istanbul ignore next */ []));
|
|
863
|
-
this.dismissibleSignal = signal(config.dismissible ?? true, /* @ts-ignore */
|
|
864
|
-
...(ngDevMode ? [{ debugName: "dismissibleSignal" }] : /* istanbul ignore next */ []));
|
|
865
|
-
this.iconSignal = signal(typeof config.icon === 'string' || config.icon === false ? config.icon : undefined, /* @ts-ignore */
|
|
866
|
-
...(ngDevMode ? [{ debugName: "iconSignal" }] : /* istanbul ignore next */ []));
|
|
867
|
-
this.contentSignal = signal(content, /* @ts-ignore */
|
|
868
|
-
...(ngDevMode ? [{ debugName: "contentSignal" }] : /* istanbul ignore next */ []));
|
|
869
|
-
this.actionSignal = signal(config.action, /* @ts-ignore */
|
|
870
|
-
...(ngDevMode ? [{ debugName: "actionSignal" }] : /* istanbul ignore next */ []));
|
|
871
|
-
this.ariaLabelSignal = signal(config.ariaLabel, /* @ts-ignore */
|
|
872
|
-
...(ngDevMode ? [{ debugName: "ariaLabelSignal" }] : /* istanbul ignore next */ []));
|
|
873
|
-
this.dataSignal = signal(config.data ?? null, /* @ts-ignore */
|
|
874
|
-
...(ngDevMode ? [{ debugName: "dataSignal" }] : /* istanbul ignore next */ []));
|
|
875
|
-
this.state = this.stateSignal.asReadonly();
|
|
876
|
-
this.severity = this.severitySignal.asReadonly();
|
|
877
|
-
this.dismissible = this.dismissibleSignal.asReadonly();
|
|
878
|
-
this.icon = this.iconSignal.asReadonly();
|
|
879
|
-
this.content = this.contentSignal.asReadonly();
|
|
880
|
-
this.action = this.actionSignal.asReadonly();
|
|
881
|
-
this.ariaLabel = this.ariaLabelSignal.asReadonly();
|
|
882
|
-
this.data = this.dataSignal.asReadonly();
|
|
883
|
-
this.autoDismissRemaining = config.duration ?? 0;
|
|
884
|
-
this.enterTimer = setTimeout(() => this._markVisible(), TOAST_ANIMATION_DURATION + TOAST_ANIMATION_FALLBACK_PADDING);
|
|
885
|
-
}
|
|
886
|
-
/**
|
|
887
|
-
* Dismiss the toast programmatically. The leave animation plays before the
|
|
888
|
-
* element is removed and subscribers are notified. Safe to call multiple times.
|
|
889
|
-
*/
|
|
890
|
-
dismiss(result) {
|
|
891
|
-
this._dismissWith('programmatic', result);
|
|
892
|
-
}
|
|
893
|
-
/** Pause the auto-dismiss timer (if any). Called automatically on hover / focus when `pauseOnInteraction` is true. */
|
|
894
|
-
pause() {
|
|
895
|
-
if (this.paused)
|
|
896
|
-
return;
|
|
897
|
-
this.paused = true;
|
|
898
|
-
if (this.autoDismissTimer !== null) {
|
|
899
|
-
const elapsed = Date.now() - this.autoDismissStartedAt;
|
|
900
|
-
this.autoDismissRemaining = Math.max(0, this.autoDismissRemaining - elapsed);
|
|
901
|
-
clearTimeout(this.autoDismissTimer);
|
|
902
|
-
this.autoDismissTimer = null;
|
|
903
|
-
}
|
|
904
|
-
const current = this.stateSignal();
|
|
905
|
-
if (current === 'visible')
|
|
906
|
-
this.stateSignal.set('paused');
|
|
907
|
-
}
|
|
908
|
-
/** Resume the auto-dismiss timer with the remaining time. */
|
|
909
|
-
resume() {
|
|
910
|
-
if (!this.paused)
|
|
911
|
-
return;
|
|
912
|
-
this.paused = false;
|
|
913
|
-
if (this.stateSignal() === 'paused')
|
|
914
|
-
this.stateSignal.set('visible');
|
|
915
|
-
if (this.config.duration && this.config.duration > 0) {
|
|
916
|
-
if (this.autoDismissRemaining <= 0) {
|
|
917
|
-
this._dismissWith('timeout');
|
|
918
|
-
return;
|
|
919
|
-
}
|
|
920
|
-
this._startAutoDismissTimer();
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
/**
|
|
924
|
-
* @internal Update the hover state. When `pauseOnInteraction` is set and
|
|
925
|
-
* either hover or focus is active, the auto-dismiss timer is paused.
|
|
926
|
-
*/
|
|
927
|
-
_setHovered(hovered) {
|
|
928
|
-
this._hovered = hovered;
|
|
929
|
-
this._syncInteractionPause();
|
|
930
|
-
}
|
|
931
|
-
/** @internal Update the focus-within state. See `_setHovered`. */
|
|
932
|
-
_setFocused(focused) {
|
|
933
|
-
this._focused = focused;
|
|
934
|
-
this._syncInteractionPause();
|
|
935
|
-
}
|
|
936
|
-
_syncInteractionPause() {
|
|
937
|
-
if (!this.config.pauseOnInteraction)
|
|
938
|
-
return;
|
|
939
|
-
if (this._hovered || this._focused) {
|
|
940
|
-
this.pause();
|
|
941
|
-
}
|
|
942
|
-
else {
|
|
943
|
-
this.resume();
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
/** Invoke the configured action handler (or dismiss with reason `'action'` if none is set). */
|
|
947
|
-
triggerAction() {
|
|
948
|
-
const action = this.actionSignal();
|
|
949
|
-
if (!action)
|
|
950
|
-
return;
|
|
951
|
-
if (action.handler) {
|
|
952
|
-
action.handler(this);
|
|
953
|
-
}
|
|
954
|
-
else {
|
|
955
|
-
this._dismissWith('action');
|
|
269
|
+
else {
|
|
270
|
+
this._dismissWith('action');
|
|
956
271
|
}
|
|
957
272
|
}
|
|
958
273
|
/** Mutate the live toast in place. Used by the `promise()` helper to swap loading → success / error. */
|
|
@@ -1070,7 +385,6 @@ class ToastRef {
|
|
|
1070
385
|
}
|
|
1071
386
|
}
|
|
1072
387
|
|
|
1073
|
-
const OVERLAY_EDGE_OFFSET = '1rem';
|
|
1074
388
|
let nextToastId = 0;
|
|
1075
389
|
function generateId() {
|
|
1076
390
|
return `tw-toast-${++nextToastId}`;
|
|
@@ -1080,14 +394,22 @@ function generateId() {
|
|
|
1080
394
|
* created per `ToastPosition` on first use and reused for the lifetime of the
|
|
1081
395
|
* service; toasts stack vertically inside their position container.
|
|
1082
396
|
*
|
|
397
|
+
* The rendering layer (CDK overlay + the toast components) is loaded through a
|
|
398
|
+
* dynamic `import()` on the first `show()` call, so merely registering this
|
|
399
|
+
* service costs nothing in the initial bundle. Every open method still returns
|
|
400
|
+
* its {@link ToastRef} synchronously — the toast is attached once the chunk
|
|
401
|
+
* lands, and `update()` / `dismiss()` called in the meantime are honoured.
|
|
402
|
+
*
|
|
1083
403
|
* Not `providedIn: 'root'` — register via {@link provideToast}.
|
|
1084
404
|
*/
|
|
1085
405
|
class ToastService {
|
|
1086
|
-
overlay = inject(Overlay);
|
|
1087
406
|
injector = inject(Injector);
|
|
1088
407
|
defaultOptions = inject(TW_TOAST_DEFAULT_OPTIONS, { optional: true }) ?? {};
|
|
1089
408
|
parent = inject(ToastService, { optional: true, skipSelf: true });
|
|
1090
|
-
|
|
409
|
+
/** Cached renderer chunk. Non-null once the first `show()` has kicked off the import. */
|
|
410
|
+
rendererPromise = null;
|
|
411
|
+
renderer = null;
|
|
412
|
+
destroyed = false;
|
|
1091
413
|
activeRefsAtThisLevel = signal([], /* @ts-ignore */
|
|
1092
414
|
...(ngDevMode ? [{ debugName: "activeRefsAtThisLevel" }] : /* istanbul ignore next */ []));
|
|
1093
415
|
afterOpenedSubject = new Subject();
|
|
@@ -1186,13 +508,14 @@ class ToastService {
|
|
|
1186
508
|
return this.activeToasts().find((ref) => ref.id === id);
|
|
1187
509
|
}
|
|
1188
510
|
ngOnDestroy() {
|
|
511
|
+
// Set first: an in-flight renderer import resolves after this and must not
|
|
512
|
+
// instantiate overlays for a service that is already gone.
|
|
513
|
+
this.destroyed = true;
|
|
1189
514
|
const refs = [...this.activeRefsAtThisLevel()];
|
|
1190
515
|
for (let i = refs.length - 1; i >= 0; i--)
|
|
1191
516
|
refs[i]._finishDismiss();
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
}
|
|
1195
|
-
this.positionOverlays.clear();
|
|
517
|
+
this.renderer?.dispose();
|
|
518
|
+
this.renderer = null;
|
|
1196
519
|
this.afterOpenedSubject.complete();
|
|
1197
520
|
this.afterAllDismissedSubject.complete();
|
|
1198
521
|
}
|
|
@@ -1207,16 +530,53 @@ class ToastService {
|
|
|
1207
530
|
const id = config.id ?? generateId();
|
|
1208
531
|
config.id = id;
|
|
1209
532
|
const ref = new ToastRef(id, content, config, (dismissed) => this.handleDismissed(dismissed));
|
|
1210
|
-
if (isComponentConstructor(content)) {
|
|
1211
|
-
const injector = this.getContainerForPosition(config.position).containerRef.instance._createContentInjector(ref);
|
|
1212
|
-
ref._portal = new ComponentPortal(content, null, injector);
|
|
1213
|
-
}
|
|
1214
533
|
this.enforceMaxVisible(config.position, config.maxVisible ?? 5);
|
|
1215
534
|
this.registerOpen(ref);
|
|
1216
|
-
this.
|
|
1217
|
-
this.announceOpen(ref);
|
|
535
|
+
void this.attachWhenRendererReady(ref, config.position);
|
|
1218
536
|
return ref;
|
|
1219
537
|
}
|
|
538
|
+
/**
|
|
539
|
+
* Attach a toast once the renderer chunk has loaded. The ref was already
|
|
540
|
+
* returned to the caller, so it may have been dismissed (or the service
|
|
541
|
+
* destroyed) while the import was in flight — both are checked before
|
|
542
|
+
* anything is put on screen.
|
|
543
|
+
*/
|
|
544
|
+
async attachWhenRendererReady(ref, position) {
|
|
545
|
+
const renderer = await this.loadRenderer();
|
|
546
|
+
if (!renderer || this.destroyed)
|
|
547
|
+
return;
|
|
548
|
+
const state = ref.state();
|
|
549
|
+
if (state === 'dismissing' || state === 'dismissed')
|
|
550
|
+
return;
|
|
551
|
+
if (!renderer.attach(ref, position))
|
|
552
|
+
return;
|
|
553
|
+
// Only now does the enter animation — and with it the auto-dismiss
|
|
554
|
+
// countdown — begin. See `ToastRef._startEnterSequence`.
|
|
555
|
+
ref._startEnterSequence();
|
|
556
|
+
renderer.announceOpen(ref);
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* @internal Resolves once the renderer chunk has loaded and pending toasts
|
|
560
|
+
* have been attached. Exposed for tests, which must await the dynamic import
|
|
561
|
+
* before asserting on rendered DOM.
|
|
562
|
+
*/
|
|
563
|
+
async _whenRendered() {
|
|
564
|
+
await this.loadRenderer();
|
|
565
|
+
// Let the per-toast `attachWhenRendererReady` continuations run.
|
|
566
|
+
await Promise.resolve();
|
|
567
|
+
}
|
|
568
|
+
/** Load (once) and instantiate the rendering layer. Resolves to `null` if the service was destroyed mid-import. */
|
|
569
|
+
loadRenderer() {
|
|
570
|
+
if (!this.rendererPromise) {
|
|
571
|
+
this.rendererPromise = import('./cdevhub-ngx-tw-toast-toast-renderer-BlDxemsU.mjs').then(({ ToastRenderer }) => {
|
|
572
|
+
if (this.destroyed)
|
|
573
|
+
return null;
|
|
574
|
+
this.renderer = new ToastRenderer(this.injector, this.defaultOptions.regionAriaLabel ?? 'Notifications');
|
|
575
|
+
return this.renderer;
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
return this.rendererPromise;
|
|
579
|
+
}
|
|
1220
580
|
enforceMaxVisible(position, max) {
|
|
1221
581
|
if (max <= 0)
|
|
1222
582
|
return;
|
|
@@ -1239,72 +599,18 @@ class ToastService {
|
|
|
1239
599
|
handleDismissed(ref) {
|
|
1240
600
|
const scope = this.parent ?? this;
|
|
1241
601
|
scope.activeRefsAtThisLevel.update((list) => list.filter((r) => r !== ref));
|
|
1242
|
-
|
|
1243
|
-
const entry = this.positionOverlays.get(position);
|
|
1244
|
-
if (entry) {
|
|
1245
|
-
const instance = entry.containerRef.instance;
|
|
1246
|
-
instance.visibleRefs.update((list) => list.filter((r) => r !== ref));
|
|
1247
|
-
}
|
|
602
|
+
this.renderer?.detach(ref);
|
|
1248
603
|
if (scope.activeRefsAtThisLevel().length === 0) {
|
|
1249
604
|
scope.afterAllDismissedSubject.next();
|
|
1250
605
|
}
|
|
1251
606
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
ref._overlayPanelElement = entry.overlayRef.overlayElement;
|
|
1258
|
-
}
|
|
1259
|
-
announceOpen(ref) {
|
|
1260
|
-
const entry = this.positionOverlays.get(ref.config.position ?? 'bottom-right');
|
|
1261
|
-
entry?.containerRef.instance._announceOpen(ref);
|
|
1262
|
-
}
|
|
607
|
+
/**
|
|
608
|
+
* Re-announce an updated toast. If the renderer has not landed yet there is
|
|
609
|
+
* nothing on screen to announce — the announcement that fires on attach
|
|
610
|
+
* reads the ref's current (already updated) content, so nothing is lost.
|
|
611
|
+
*/
|
|
1263
612
|
announceUpdate(ref) {
|
|
1264
|
-
|
|
1265
|
-
entry?.containerRef.instance._announceUpdate(ref);
|
|
1266
|
-
}
|
|
1267
|
-
getContainerForPosition(position) {
|
|
1268
|
-
const existing = this.positionOverlays.get(position);
|
|
1269
|
-
if (existing)
|
|
1270
|
-
return existing;
|
|
1271
|
-
const overlayRef = this.overlay.create({
|
|
1272
|
-
positionStrategy: this.buildPositionStrategy(position),
|
|
1273
|
-
scrollStrategy: this.overlay.scrollStrategies.noop(),
|
|
1274
|
-
hasBackdrop: false,
|
|
1275
|
-
panelClass: ['tw-toast-overlay', `tw-toast-overlay-${position}`],
|
|
1276
|
-
});
|
|
1277
|
-
const containerPortal = new ComponentPortal(ToastContainerComponent, null, this.injector);
|
|
1278
|
-
const containerRef = overlayRef.attach(containerPortal);
|
|
1279
|
-
containerRef.instance.position.set(position);
|
|
1280
|
-
containerRef.instance.regionLabel.set(this.defaultOptions.regionAriaLabel ?? 'Notifications');
|
|
1281
|
-
const entry = { overlayRef, containerRef };
|
|
1282
|
-
this.positionOverlays.set(position, entry);
|
|
1283
|
-
return entry;
|
|
1284
|
-
}
|
|
1285
|
-
buildPositionStrategy(position) {
|
|
1286
|
-
const strategy = createGlobalPositionStrategy(this.injector);
|
|
1287
|
-
switch (position) {
|
|
1288
|
-
case 'top-right':
|
|
1289
|
-
strategy.top(OVERLAY_EDGE_OFFSET).right(OVERLAY_EDGE_OFFSET);
|
|
1290
|
-
break;
|
|
1291
|
-
case 'top-left':
|
|
1292
|
-
strategy.top(OVERLAY_EDGE_OFFSET).left(OVERLAY_EDGE_OFFSET);
|
|
1293
|
-
break;
|
|
1294
|
-
case 'top-center':
|
|
1295
|
-
strategy.top(OVERLAY_EDGE_OFFSET).centerHorizontally();
|
|
1296
|
-
break;
|
|
1297
|
-
case 'bottom-right':
|
|
1298
|
-
strategy.bottom(OVERLAY_EDGE_OFFSET).right(OVERLAY_EDGE_OFFSET);
|
|
1299
|
-
break;
|
|
1300
|
-
case 'bottom-left':
|
|
1301
|
-
strategy.bottom(OVERLAY_EDGE_OFFSET).left(OVERLAY_EDGE_OFFSET);
|
|
1302
|
-
break;
|
|
1303
|
-
case 'bottom-center':
|
|
1304
|
-
strategy.bottom(OVERLAY_EDGE_OFFSET).centerHorizontally();
|
|
1305
|
-
break;
|
|
1306
|
-
}
|
|
1307
|
-
return strategy;
|
|
613
|
+
this.renderer?.announceUpdate(ref);
|
|
1308
614
|
}
|
|
1309
615
|
resolveConfig(config) {
|
|
1310
616
|
const merged = new ToastConfig();
|
|
@@ -1320,9 +626,6 @@ class ToastService {
|
|
|
1320
626
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastService, decorators: [{
|
|
1321
627
|
type: Injectable
|
|
1322
628
|
}] });
|
|
1323
|
-
function isComponentConstructor(value) {
|
|
1324
|
-
return typeof value === 'function' && !(value instanceof TemplateRef);
|
|
1325
|
-
}
|
|
1326
629
|
/**
|
|
1327
630
|
* Registers {@link ToastService} for dependency injection and installs optional
|
|
1328
631
|
* application-wide defaults.
|
|
@@ -1348,6 +651,314 @@ const ToastInternals = {
|
|
|
1348
651
|
},
|
|
1349
652
|
};
|
|
1350
653
|
|
|
654
|
+
const toastVariants = tv({
|
|
655
|
+
slots: {
|
|
656
|
+
root: 'pointer-events-auto relative flex items-start gap-3 w-full max-w-sm p-4 rounded-lg border shadow-md text-sm will-change-transform transition-colors duration-normal motion-reduce:transition-none',
|
|
657
|
+
icon: 'size-5 shrink-0 mt-0.5',
|
|
658
|
+
title: 'text-sm font-semibold',
|
|
659
|
+
description: 'text-sm',
|
|
660
|
+
content: 'flex-1 min-w-0',
|
|
661
|
+
action: 'inline-flex items-center justify-center px-3 py-1.5 rounded-md text-sm font-medium cursor-pointer transition-colors duration-normal motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',
|
|
662
|
+
// Dismiss container is `size-6` (24px — xs square-interactive-target
|
|
663
|
+
// scale per CLAUDE.md icon sub-scale). The inner SVG is `size-4` (16px
|
|
664
|
+
// glyph scale), centred via flex; the larger container gives the
|
|
665
|
+
// touch/click affordance without enlarging the visual glyph.
|
|
666
|
+
dismiss: 'absolute top-3 right-3 inline-flex items-center justify-center size-6 rounded-md cursor-pointer transition-colors duration-normal motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',
|
|
667
|
+
},
|
|
668
|
+
variants: {
|
|
669
|
+
severity: {
|
|
670
|
+
info: {},
|
|
671
|
+
success: {},
|
|
672
|
+
warning: {},
|
|
673
|
+
error: {},
|
|
674
|
+
neutral: {},
|
|
675
|
+
},
|
|
676
|
+
},
|
|
677
|
+
// Color slot tokens (`{role}-soft`, `-soft-fg`, `-icon`, …) own light/dark
|
|
678
|
+
// contrast — components consume the slot only. See `_semantic.css`.
|
|
679
|
+
// Slot-token safelist: see `projects/ngx-tw/theme/index.css` `@source inline(...)`
|
|
680
|
+
// — Tailwind v4's JIT cannot scan template-literal class names, so every
|
|
681
|
+
// `bg-${severity}-soft` / `text-${severity}-icon` permutation below must be
|
|
682
|
+
// enumerated there.
|
|
683
|
+
compoundVariants: ['info', 'success', 'warning', 'error', 'neutral'].map((severity) => ({
|
|
684
|
+
severity,
|
|
685
|
+
class: {
|
|
686
|
+
root: `bg-${severity}-soft text-${severity}-soft-fg-muted border-${severity}-border`,
|
|
687
|
+
icon: `text-${severity}-icon`,
|
|
688
|
+
title: `text-${severity}-soft-fg`,
|
|
689
|
+
description: `text-${severity}-soft-fg-muted`,
|
|
690
|
+
action: `text-${severity}-soft-fg hover:bg-${severity}-soft-hover`,
|
|
691
|
+
dismiss: `text-${severity}-icon hover:bg-${severity}-soft-hover`,
|
|
692
|
+
},
|
|
693
|
+
})),
|
|
694
|
+
defaultVariants: {
|
|
695
|
+
severity: 'info',
|
|
696
|
+
},
|
|
697
|
+
}, { twMerge: true });
|
|
698
|
+
/** Slot directive for the icon. When projected, overrides the severity-default icon. */
|
|
699
|
+
class ToastIconDirective {
|
|
700
|
+
toast = inject(ToastComponent);
|
|
701
|
+
/** @internal */
|
|
702
|
+
classes = this.toast.iconClasses;
|
|
703
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastIconDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
704
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: ToastIconDirective, isStandalone: true, selector: "[twToastIcon]", host: { attributes: { "aria-hidden": "true" }, properties: { "class": "classes()" } }, ngImport: i0 });
|
|
705
|
+
}
|
|
706
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastIconDirective, decorators: [{
|
|
707
|
+
type: Directive,
|
|
708
|
+
args: [{
|
|
709
|
+
selector: '[twToastIcon]',
|
|
710
|
+
host: {
|
|
711
|
+
'[class]': 'classes()',
|
|
712
|
+
'aria-hidden': 'true',
|
|
713
|
+
},
|
|
714
|
+
}]
|
|
715
|
+
}] });
|
|
716
|
+
/** Slot directive for the bold title line inside a toast. */
|
|
717
|
+
class ToastTitleDirective {
|
|
718
|
+
toast = inject(ToastComponent);
|
|
719
|
+
/** @internal */
|
|
720
|
+
classes = this.toast.titleClasses;
|
|
721
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastTitleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
722
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: ToastTitleDirective, isStandalone: true, selector: "[twToastTitle]", host: { properties: { "class": "classes()" } }, ngImport: i0 });
|
|
723
|
+
}
|
|
724
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastTitleDirective, decorators: [{
|
|
725
|
+
type: Directive,
|
|
726
|
+
args: [{
|
|
727
|
+
selector: '[twToastTitle]',
|
|
728
|
+
host: {
|
|
729
|
+
'[class]': 'classes()',
|
|
730
|
+
},
|
|
731
|
+
}]
|
|
732
|
+
}] });
|
|
733
|
+
/** Slot directive for a secondary description line inside a toast. */
|
|
734
|
+
class ToastDescriptionDirective {
|
|
735
|
+
toast = inject(ToastComponent);
|
|
736
|
+
/** @internal */
|
|
737
|
+
classes = this.toast.descriptionClasses;
|
|
738
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastDescriptionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
739
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: ToastDescriptionDirective, isStandalone: true, selector: "[twToastDescription]", host: { properties: { "class": "classes()" } }, ngImport: i0 });
|
|
740
|
+
}
|
|
741
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastDescriptionDirective, decorators: [{
|
|
742
|
+
type: Directive,
|
|
743
|
+
args: [{
|
|
744
|
+
selector: '[twToastDescription]',
|
|
745
|
+
host: {
|
|
746
|
+
'[class]': 'classes()',
|
|
747
|
+
},
|
|
748
|
+
}]
|
|
749
|
+
}] });
|
|
750
|
+
/** Slot directive for the action button. Apply to a native `<button>` to inherit the severity-aware styling. */
|
|
751
|
+
class ToastActionDirective {
|
|
752
|
+
toast = inject(ToastComponent);
|
|
753
|
+
/** @internal */
|
|
754
|
+
classes = this.toast.actionClasses;
|
|
755
|
+
/** @internal */
|
|
756
|
+
onClick() {
|
|
757
|
+
this.toast.actionClicked.emit();
|
|
758
|
+
}
|
|
759
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastActionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
760
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: ToastActionDirective, isStandalone: true, selector: "[twToastAction]", host: { attributes: { "type": "button" }, listeners: { "click": "onClick()" }, properties: { "class": "classes()" } }, ngImport: i0 });
|
|
761
|
+
}
|
|
762
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastActionDirective, decorators: [{
|
|
763
|
+
type: Directive,
|
|
764
|
+
args: [{
|
|
765
|
+
selector: '[twToastAction]',
|
|
766
|
+
host: {
|
|
767
|
+
type: 'button',
|
|
768
|
+
'[class]': 'classes()',
|
|
769
|
+
'(click)': 'onClick()',
|
|
770
|
+
},
|
|
771
|
+
}]
|
|
772
|
+
}] });
|
|
773
|
+
/**
|
|
774
|
+
* Visual toast / snackbar panel. Rendered internally by {@link ToastService} for
|
|
775
|
+
* string content, and exported for consumers who want to compose the same
|
|
776
|
+
* visual inside a custom `TemplateRef` or component class passed to `show()`.
|
|
777
|
+
*/
|
|
778
|
+
class ToastComponent {
|
|
779
|
+
/** Severity variant. Drives color palette, default icon, and ARIA role / live politeness. Defaults to `'info'`. */
|
|
780
|
+
severity = input('info', /* @ts-ignore */
|
|
781
|
+
...(ngDevMode ? [{ debugName: "severity" }] : /* istanbul ignore next */ []));
|
|
782
|
+
/** Whether to render the close button. Defaults to `true` — mirroring `ToastConfig.dismissible`, toasts must offer an escape hatch; the special case is an ephemeral progress marker paired with `duration`. */
|
|
783
|
+
dismissible = input(true, /* @ts-ignore */
|
|
784
|
+
...(ngDevMode ? [{ debugName: "dismissible" }] : /* istanbul ignore next */ []));
|
|
785
|
+
/**
|
|
786
|
+
* Icon override. Pass a string to render as text inside the icon slot, or
|
|
787
|
+
* `false` to hide the built-in severity icon. Ignored when a `[twToastIcon]`
|
|
788
|
+
* child is projected. When omitted, the severity-default icon renders.
|
|
789
|
+
*
|
|
790
|
+
* For arbitrary icon markup, project a `[twToastIcon]` child instead.
|
|
791
|
+
*/
|
|
792
|
+
icon = input(undefined, /* @ts-ignore */
|
|
793
|
+
...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
794
|
+
/** Explicit aria-label for the toast wrapper. Mirrored to `aria-label`. When omitted, text content is used by assistive tech. Defaults to `undefined`. */
|
|
795
|
+
ariaLabel = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
|
|
796
|
+
/** Fires when the close button is clicked. */
|
|
797
|
+
dismissed = output();
|
|
798
|
+
/** Fires when a `[twToastAction]` button is clicked. */
|
|
799
|
+
actionClicked = output();
|
|
800
|
+
/** @internal */
|
|
801
|
+
projectedIcon = contentChild(ToastIconDirective, /* @ts-ignore */
|
|
802
|
+
...(ngDevMode ? [{ debugName: "projectedIcon" }] : /* istanbul ignore next */ []));
|
|
803
|
+
/** @internal */
|
|
804
|
+
hasProjectedIcon = computed(() => !!this.projectedIcon(), /* @ts-ignore */
|
|
805
|
+
...(ngDevMode ? [{ debugName: "hasProjectedIcon" }] : /* istanbul ignore next */ []));
|
|
806
|
+
variantResult = computed(() => toastVariants({ severity: this.severity() }), /* @ts-ignore */
|
|
807
|
+
...(ngDevMode ? [{ debugName: "variantResult" }] : /* istanbul ignore next */ []));
|
|
808
|
+
/** @internal */
|
|
809
|
+
rootClasses = computed(() => {
|
|
810
|
+
const base = this.variantResult().root();
|
|
811
|
+
return this.dismissible() ? `${base} pr-10` : base;
|
|
812
|
+
}, /* @ts-ignore */
|
|
813
|
+
...(ngDevMode ? [{ debugName: "rootClasses" }] : /* istanbul ignore next */ []));
|
|
814
|
+
/** @internal */
|
|
815
|
+
iconClasses = computed(() => this.variantResult().icon(), /* @ts-ignore */
|
|
816
|
+
...(ngDevMode ? [{ debugName: "iconClasses" }] : /* istanbul ignore next */ []));
|
|
817
|
+
/** @internal */
|
|
818
|
+
titleClasses = computed(() => this.variantResult().title(), /* @ts-ignore */
|
|
819
|
+
...(ngDevMode ? [{ debugName: "titleClasses" }] : /* istanbul ignore next */ []));
|
|
820
|
+
/** @internal */
|
|
821
|
+
descriptionClasses = computed(() => this.variantResult().description(), /* @ts-ignore */
|
|
822
|
+
...(ngDevMode ? [{ debugName: "descriptionClasses" }] : /* istanbul ignore next */ []));
|
|
823
|
+
/** @internal */
|
|
824
|
+
contentClasses = computed(() => this.variantResult().content(), /* @ts-ignore */
|
|
825
|
+
...(ngDevMode ? [{ debugName: "contentClasses" }] : /* istanbul ignore next */ []));
|
|
826
|
+
/** @internal */
|
|
827
|
+
actionClasses = computed(() => this.variantResult().action(), /* @ts-ignore */
|
|
828
|
+
...(ngDevMode ? [{ debugName: "actionClasses" }] : /* istanbul ignore next */ []));
|
|
829
|
+
/** @internal */
|
|
830
|
+
dismissClasses = computed(() => this.variantResult().dismiss(), /* @ts-ignore */
|
|
831
|
+
...(ngDevMode ? [{ debugName: "dismissClasses" }] : /* istanbul ignore next */ []));
|
|
832
|
+
/** @internal */
|
|
833
|
+
roleAttr = computed(() => this.severity() === 'error' ? 'alert' : 'status', /* @ts-ignore */
|
|
834
|
+
...(ngDevMode ? [{ debugName: "roleAttr" }] : /* istanbul ignore next */ []));
|
|
835
|
+
/** @internal */
|
|
836
|
+
ariaLiveAttr = computed(() => this.severity() === 'error' ? 'assertive' : 'polite', /* @ts-ignore */
|
|
837
|
+
...(ngDevMode ? [{ debugName: "ariaLiveAttr" }] : /* istanbul ignore next */ []));
|
|
838
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
839
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: ToastComponent, isStandalone: true, selector: "tw-toast", inputs: { severity: { classPropertyName: "severity", publicName: "severity", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dismissed: "dismissed", actionClicked: "actionClicked" }, host: { attributes: { "aria-atomic": "true" }, properties: { "class": "rootClasses()", "attr.role": "roleAttr()", "attr.aria-live": "ariaLiveAttr()", "attr.aria-label": "ariaLabel() || null", "attr.data-severity": "severity()" } }, queries: [{ propertyName: "projectedIcon", first: true, predicate: ToastIconDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
840
|
+
@if (hasProjectedIcon()) {
|
|
841
|
+
<ng-content select="[twToastIcon]" />
|
|
842
|
+
} @else if (icon() !== false) {
|
|
843
|
+
<span [class]="iconClasses()" aria-hidden="true">
|
|
844
|
+
@switch (severity()) {
|
|
845
|
+
@case ('info') {
|
|
846
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
847
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm0-11a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm-.75 2.5a.75.75 0 0 0 0 1.5h.25v3h-.25a.75.75 0 0 0 0 1.5h2a.75.75 0 0 0 0-1.5h-.25v-3.75a.75.75 0 0 0-.75-.75h-1Z" clip-rule="evenodd"/>
|
|
848
|
+
</svg>
|
|
849
|
+
}
|
|
850
|
+
@case ('success') {
|
|
851
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
852
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5Z" clip-rule="evenodd"/>
|
|
853
|
+
</svg>
|
|
854
|
+
}
|
|
855
|
+
@case ('warning') {
|
|
856
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
857
|
+
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd"/>
|
|
858
|
+
</svg>
|
|
859
|
+
}
|
|
860
|
+
@case ('error') {
|
|
861
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
862
|
+
<path fill-rule="evenodd" d="M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0Zm-8-5a.75.75 0 0 1 .75.75v4.5a.75.75 0 0 1-1.5 0v-4.5A.75.75 0 0 1 10 5Zm0 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd"/>
|
|
863
|
+
</svg>
|
|
864
|
+
}
|
|
865
|
+
@default {
|
|
866
|
+
@if (icon() && icon() !== false) {
|
|
867
|
+
{{ icon() }}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
</span>
|
|
872
|
+
}
|
|
873
|
+
<div [class]="contentClasses()">
|
|
874
|
+
<ng-content select="[twToastTitle]" />
|
|
875
|
+
<ng-content select="[twToastDescription]" />
|
|
876
|
+
<ng-content />
|
|
877
|
+
<ng-content select="[twToastAction]" />
|
|
878
|
+
</div>
|
|
879
|
+
@if (dismissible()) {
|
|
880
|
+
<button
|
|
881
|
+
type="button"
|
|
882
|
+
aria-label="Dismiss"
|
|
883
|
+
[class]="dismissClasses()"
|
|
884
|
+
(click)="dismissed.emit()"
|
|
885
|
+
>
|
|
886
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
|
|
887
|
+
<path d="M5.28 4.22a.75.75 0 0 0-1.06 1.06L6.94 8l-2.72 2.72a.75.75 0 1 0 1.06 1.06L8 9.06l2.72 2.72a.75.75 0 1 0 1.06-1.06L9.06 8l2.72-2.72a.75.75 0 0 0-1.06-1.06L8 6.94 5.28 4.22Z"/>
|
|
888
|
+
</svg>
|
|
889
|
+
</button>
|
|
890
|
+
}
|
|
891
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
892
|
+
}
|
|
893
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastComponent, decorators: [{
|
|
894
|
+
type: Component,
|
|
895
|
+
args: [{
|
|
896
|
+
selector: 'tw-toast',
|
|
897
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
898
|
+
host: {
|
|
899
|
+
'[class]': 'rootClasses()',
|
|
900
|
+
'[attr.role]': 'roleAttr()',
|
|
901
|
+
'[attr.aria-live]': 'ariaLiveAttr()',
|
|
902
|
+
'aria-atomic': 'true',
|
|
903
|
+
'[attr.aria-label]': 'ariaLabel() || null',
|
|
904
|
+
'[attr.data-severity]': 'severity()',
|
|
905
|
+
},
|
|
906
|
+
template: `
|
|
907
|
+
@if (hasProjectedIcon()) {
|
|
908
|
+
<ng-content select="[twToastIcon]" />
|
|
909
|
+
} @else if (icon() !== false) {
|
|
910
|
+
<span [class]="iconClasses()" aria-hidden="true">
|
|
911
|
+
@switch (severity()) {
|
|
912
|
+
@case ('info') {
|
|
913
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
914
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm0-11a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm-.75 2.5a.75.75 0 0 0 0 1.5h.25v3h-.25a.75.75 0 0 0 0 1.5h2a.75.75 0 0 0 0-1.5h-.25v-3.75a.75.75 0 0 0-.75-.75h-1Z" clip-rule="evenodd"/>
|
|
915
|
+
</svg>
|
|
916
|
+
}
|
|
917
|
+
@case ('success') {
|
|
918
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
919
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5Z" clip-rule="evenodd"/>
|
|
920
|
+
</svg>
|
|
921
|
+
}
|
|
922
|
+
@case ('warning') {
|
|
923
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
924
|
+
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd"/>
|
|
925
|
+
</svg>
|
|
926
|
+
}
|
|
927
|
+
@case ('error') {
|
|
928
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
929
|
+
<path fill-rule="evenodd" d="M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0Zm-8-5a.75.75 0 0 1 .75.75v4.5a.75.75 0 0 1-1.5 0v-4.5A.75.75 0 0 1 10 5Zm0 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd"/>
|
|
930
|
+
</svg>
|
|
931
|
+
}
|
|
932
|
+
@default {
|
|
933
|
+
@if (icon() && icon() !== false) {
|
|
934
|
+
{{ icon() }}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
</span>
|
|
939
|
+
}
|
|
940
|
+
<div [class]="contentClasses()">
|
|
941
|
+
<ng-content select="[twToastTitle]" />
|
|
942
|
+
<ng-content select="[twToastDescription]" />
|
|
943
|
+
<ng-content />
|
|
944
|
+
<ng-content select="[twToastAction]" />
|
|
945
|
+
</div>
|
|
946
|
+
@if (dismissible()) {
|
|
947
|
+
<button
|
|
948
|
+
type="button"
|
|
949
|
+
aria-label="Dismiss"
|
|
950
|
+
[class]="dismissClasses()"
|
|
951
|
+
(click)="dismissed.emit()"
|
|
952
|
+
>
|
|
953
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
|
|
954
|
+
<path d="M5.28 4.22a.75.75 0 0 0-1.06 1.06L6.94 8l-2.72 2.72a.75.75 0 1 0 1.06 1.06L8 9.06l2.72 2.72a.75.75 0 1 0 1.06-1.06L9.06 8l2.72-2.72a.75.75 0 0 0-1.06-1.06L8 6.94 5.28 4.22Z"/>
|
|
955
|
+
</svg>
|
|
956
|
+
</button>
|
|
957
|
+
}
|
|
958
|
+
`,
|
|
959
|
+
}]
|
|
960
|
+
}], propDecorators: { severity: [{ type: i0.Input, args: [{ isSignal: true, alias: "severity", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], dismissed: [{ type: i0.Output, args: ["dismissed"] }], actionClicked: [{ type: i0.Output, args: ["actionClicked"] }], projectedIcon: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ToastIconDirective), { isSignal: true }] }] } });
|
|
961
|
+
|
|
1351
962
|
/**
|
|
1352
963
|
* Generated bundle index. Do not edit.
|
|
1353
964
|
*/
|