@cdevhub/ngx-tw 0.4.0 → 0.5.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/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.mjs +189 -219
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-ByyNluUd.mjs +256 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-ByyNluUd.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs +178 -275
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-DSu4YoTy.mjs +528 -0
- package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-DSu4YoTy.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-toast.mjs +414 -811
- package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -1
- package/index.json +1 -1
- package/package.json +1 -1
- package/types/cdevhub-ngx-tw-dialog.d.ts +80 -19
- package/types/cdevhub-ngx-tw-sheet.d.ts +78 -19
- package/types/cdevhub-ngx-tw-toast.d.ts +41 -6
|
@@ -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
|
/**
|
|
@@ -73,714 +69,6 @@ const TW_TOAST_REF = new InjectionToken('TW_TOAST_REF');
|
|
|
73
69
|
/** Injection token carrying application-wide toast defaults. Set via `provideToast(defaults)`. */
|
|
74
70
|
const TW_TOAST_DEFAULT_OPTIONS = new InjectionToken('TW_TOAST_DEFAULT_OPTIONS');
|
|
75
71
|
|
|
76
|
-
const toastVariants = tv({
|
|
77
|
-
slots: {
|
|
78
|
-
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',
|
|
79
|
-
icon: 'size-5 shrink-0 mt-0.5',
|
|
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
|
-
}] });
|
|
195
|
-
/**
|
|
196
|
-
* Visual toast / snackbar panel. Rendered internally by {@link ToastService} for
|
|
197
|
-
* string content, and exported for consumers who want to compose the same
|
|
198
|
-
* visual inside a custom `TemplateRef` or component class passed to `show()`.
|
|
199
|
-
*/
|
|
200
|
-
class ToastComponent {
|
|
201
|
-
/** Severity variant. Drives color palette, default icon, and ARIA role / live politeness. Defaults to `'info'`. */
|
|
202
|
-
severity = input('info', /* @ts-ignore */
|
|
203
|
-
...(ngDevMode ? [{ debugName: "severity" }] : /* istanbul ignore next */ []));
|
|
204
|
-
// Default true: mirrors `ToastConfig.dismissible` — toasts must offer an escape
|
|
205
|
-
// hatch by default; opt-out only for ephemeral progress markers paired with `duration`.
|
|
206
|
-
/** Whether to render the close button. Defaults to `true`. */
|
|
207
|
-
dismissible = input(true, /* @ts-ignore */
|
|
208
|
-
...(ngDevMode ? [{ debugName: "dismissible" }] : /* istanbul ignore next */ []));
|
|
209
|
-
/**
|
|
210
|
-
* Icon override. Pass a string to render as text inside the icon slot, or
|
|
211
|
-
* `false` to hide the built-in severity icon. Ignored when a `[twToastIcon]`
|
|
212
|
-
* child is projected. When omitted, the severity-default icon renders.
|
|
213
|
-
*
|
|
214
|
-
* For arbitrary icon markup, project a `[twToastIcon]` child instead.
|
|
215
|
-
*/
|
|
216
|
-
icon = input(undefined, /* @ts-ignore */
|
|
217
|
-
...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
218
|
-
/** Explicit aria-label for the toast wrapper. When omitted, text content is used by assistive tech. */
|
|
219
|
-
ariaLabel = input(undefined, /* @ts-ignore */
|
|
220
|
-
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
221
|
-
/** Fires when the close button is clicked. */
|
|
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>
|
|
382
|
-
}
|
|
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
|
-
/**
|
|
428
|
-
* Toasts currently assigned to this container. The service pushes toasts
|
|
429
|
-
* into this signal; the container filters by state to drive `animate.leave`.
|
|
430
|
-
*/
|
|
431
|
-
visibleRefs = signal([], /* @ts-ignore */
|
|
432
|
-
...(ngDevMode ? [{ debugName: "visibleRefs" }] : /* istanbul ignore next */ []));
|
|
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);
|
|
477
|
-
}
|
|
478
|
-
/** @internal Captures the component instance after `cdkPortalOutlet` attaches. */
|
|
479
|
-
onPortalAttached(ref, attached) {
|
|
480
|
-
if (attached && 'instance' in attached) {
|
|
481
|
-
ref.componentInstance = attached.instance;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
/** @internal Injector factory for component-class content. Provides `TW_TOAST_DATA` + `TW_TOAST_REF`. */
|
|
485
|
-
_createContentInjector(ref) {
|
|
486
|
-
return Injector.create({
|
|
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))
|
|
524
|
-
return;
|
|
525
|
-
ref._setFocused(false);
|
|
526
|
-
}
|
|
527
|
-
onEscape(ref, event) {
|
|
528
|
-
event.stopPropagation();
|
|
529
|
-
event.preventDefault();
|
|
530
|
-
ref._dismissWith('manual');
|
|
531
|
-
}
|
|
532
|
-
onSwipeStart(ref, event, el) {
|
|
533
|
-
if (!ref.config.swipeToDismiss)
|
|
534
|
-
return;
|
|
535
|
-
if (event.button !== 0)
|
|
536
|
-
return;
|
|
537
|
-
const target = event.target;
|
|
538
|
-
if (target?.closest('button, a, input, select, textarea, [role="button"]'))
|
|
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);
|
|
553
|
-
}
|
|
554
|
-
onSwipeMove(ref, event, _el) {
|
|
555
|
-
const session = this.swipeSessions.get(ref);
|
|
556
|
-
if (!session || event.pointerId !== session.pointerId)
|
|
557
|
-
return;
|
|
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);
|
|
565
|
-
}
|
|
566
|
-
onSwipeEnd(ref, event, el, onMove, onUp) {
|
|
567
|
-
const session = this.swipeSessions.get(ref);
|
|
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)
|
|
581
|
-
return;
|
|
582
|
-
const dx = event.clientX - session.startX;
|
|
583
|
-
const threshold = session.width * SWIPE_DISMISS_FRACTION;
|
|
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');
|
|
592
|
-
}
|
|
593
|
-
else {
|
|
594
|
-
ref.swipeTransform.set(null);
|
|
595
|
-
ref.swipeOpacity.set(null);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
swipeDirectionAllowed(dx) {
|
|
599
|
-
const pos = this.position();
|
|
600
|
-
if (pos === 'top-right' || pos === 'bottom-right')
|
|
601
|
-
return dx > 0;
|
|
602
|
-
if (pos === 'top-left' || pos === 'bottom-left')
|
|
603
|
-
return dx < 0;
|
|
604
|
-
return true;
|
|
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
|
-
}
|
|
688
|
-
}
|
|
689
|
-
</div>
|
|
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
72
|
/** Duration (ms) of the toast enter / leave animation. Must match `_base.css` toast-slide keyframes. */
|
|
785
73
|
const TOAST_ANIMATION_DURATION = 150;
|
|
786
74
|
/** Padding added to animation fallback timers to guarantee cleanup if the CSS event is swallowed. */
|
|
@@ -881,6 +169,22 @@ class ToastRef {
|
|
|
881
169
|
this.ariaLabel = this.ariaLabelSignal.asReadonly();
|
|
882
170
|
this.data = this.dataSignal.asReadonly();
|
|
883
171
|
this.autoDismissRemaining = config.duration ?? 0;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* @internal Start the enter animation clock. Called by the service once the
|
|
175
|
+
* toast has actually been attached to its container.
|
|
176
|
+
*
|
|
177
|
+
* This deliberately does NOT run from the constructor. The renderer is loaded
|
|
178
|
+
* through a dynamic `import()`, so a ref can exist for an arbitrary stretch
|
|
179
|
+
* before anything is on screen — starting the clock at construction would let
|
|
180
|
+
* a toast burn part (or all) of its auto-dismiss duration while still
|
|
181
|
+
* invisible on a slow connection. Idempotent and safe to call after dismissal.
|
|
182
|
+
*/
|
|
183
|
+
_startEnterSequence() {
|
|
184
|
+
if (this.enterTimer !== null)
|
|
185
|
+
return;
|
|
186
|
+
if (this.stateSignal() !== 'entering')
|
|
187
|
+
return;
|
|
884
188
|
this.enterTimer = setTimeout(() => this._markVisible(), TOAST_ANIMATION_DURATION + TOAST_ANIMATION_FALLBACK_PADDING);
|
|
885
189
|
}
|
|
886
190
|
/**
|
|
@@ -1070,7 +374,6 @@ class ToastRef {
|
|
|
1070
374
|
}
|
|
1071
375
|
}
|
|
1072
376
|
|
|
1073
|
-
const OVERLAY_EDGE_OFFSET = '1rem';
|
|
1074
377
|
let nextToastId = 0;
|
|
1075
378
|
function generateId() {
|
|
1076
379
|
return `tw-toast-${++nextToastId}`;
|
|
@@ -1080,14 +383,22 @@ function generateId() {
|
|
|
1080
383
|
* created per `ToastPosition` on first use and reused for the lifetime of the
|
|
1081
384
|
* service; toasts stack vertically inside their position container.
|
|
1082
385
|
*
|
|
386
|
+
* The rendering layer (CDK overlay + the toast components) is loaded through a
|
|
387
|
+
* dynamic `import()` on the first `show()` call, so merely registering this
|
|
388
|
+
* service costs nothing in the initial bundle. Every open method still returns
|
|
389
|
+
* its {@link ToastRef} synchronously — the toast is attached once the chunk
|
|
390
|
+
* lands, and `update()` / `dismiss()` called in the meantime are honoured.
|
|
391
|
+
*
|
|
1083
392
|
* Not `providedIn: 'root'` — register via {@link provideToast}.
|
|
1084
393
|
*/
|
|
1085
394
|
class ToastService {
|
|
1086
|
-
overlay = inject(Overlay);
|
|
1087
395
|
injector = inject(Injector);
|
|
1088
396
|
defaultOptions = inject(TW_TOAST_DEFAULT_OPTIONS, { optional: true }) ?? {};
|
|
1089
397
|
parent = inject(ToastService, { optional: true, skipSelf: true });
|
|
1090
|
-
|
|
398
|
+
/** Cached renderer chunk. Non-null once the first `show()` has kicked off the import. */
|
|
399
|
+
rendererPromise = null;
|
|
400
|
+
renderer = null;
|
|
401
|
+
destroyed = false;
|
|
1091
402
|
activeRefsAtThisLevel = signal([], /* @ts-ignore */
|
|
1092
403
|
...(ngDevMode ? [{ debugName: "activeRefsAtThisLevel" }] : /* istanbul ignore next */ []));
|
|
1093
404
|
afterOpenedSubject = new Subject();
|
|
@@ -1186,13 +497,14 @@ class ToastService {
|
|
|
1186
497
|
return this.activeToasts().find((ref) => ref.id === id);
|
|
1187
498
|
}
|
|
1188
499
|
ngOnDestroy() {
|
|
500
|
+
// Set first: an in-flight renderer import resolves after this and must not
|
|
501
|
+
// instantiate overlays for a service that is already gone.
|
|
502
|
+
this.destroyed = true;
|
|
1189
503
|
const refs = [...this.activeRefsAtThisLevel()];
|
|
1190
504
|
for (let i = refs.length - 1; i >= 0; i--)
|
|
1191
505
|
refs[i]._finishDismiss();
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
}
|
|
1195
|
-
this.positionOverlays.clear();
|
|
506
|
+
this.renderer?.dispose();
|
|
507
|
+
this.renderer = null;
|
|
1196
508
|
this.afterOpenedSubject.complete();
|
|
1197
509
|
this.afterAllDismissedSubject.complete();
|
|
1198
510
|
}
|
|
@@ -1207,16 +519,53 @@ class ToastService {
|
|
|
1207
519
|
const id = config.id ?? generateId();
|
|
1208
520
|
config.id = id;
|
|
1209
521
|
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
522
|
this.enforceMaxVisible(config.position, config.maxVisible ?? 5);
|
|
1215
523
|
this.registerOpen(ref);
|
|
1216
|
-
this.
|
|
1217
|
-
this.announceOpen(ref);
|
|
524
|
+
void this.attachWhenRendererReady(ref, config.position);
|
|
1218
525
|
return ref;
|
|
1219
526
|
}
|
|
527
|
+
/**
|
|
528
|
+
* Attach a toast once the renderer chunk has loaded. The ref was already
|
|
529
|
+
* returned to the caller, so it may have been dismissed (or the service
|
|
530
|
+
* destroyed) while the import was in flight — both are checked before
|
|
531
|
+
* anything is put on screen.
|
|
532
|
+
*/
|
|
533
|
+
async attachWhenRendererReady(ref, position) {
|
|
534
|
+
const renderer = await this.loadRenderer();
|
|
535
|
+
if (!renderer || this.destroyed)
|
|
536
|
+
return;
|
|
537
|
+
const state = ref.state();
|
|
538
|
+
if (state === 'dismissing' || state === 'dismissed')
|
|
539
|
+
return;
|
|
540
|
+
if (!renderer.attach(ref, position))
|
|
541
|
+
return;
|
|
542
|
+
// Only now does the enter animation — and with it the auto-dismiss
|
|
543
|
+
// countdown — begin. See `ToastRef._startEnterSequence`.
|
|
544
|
+
ref._startEnterSequence();
|
|
545
|
+
renderer.announceOpen(ref);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* @internal Resolves once the renderer chunk has loaded and pending toasts
|
|
549
|
+
* have been attached. Exposed for tests, which must await the dynamic import
|
|
550
|
+
* before asserting on rendered DOM.
|
|
551
|
+
*/
|
|
552
|
+
async _whenRendered() {
|
|
553
|
+
await this.loadRenderer();
|
|
554
|
+
// Let the per-toast `attachWhenRendererReady` continuations run.
|
|
555
|
+
await Promise.resolve();
|
|
556
|
+
}
|
|
557
|
+
/** Load (once) and instantiate the rendering layer. Resolves to `null` if the service was destroyed mid-import. */
|
|
558
|
+
loadRenderer() {
|
|
559
|
+
if (!this.rendererPromise) {
|
|
560
|
+
this.rendererPromise = import('./cdevhub-ngx-tw-toast-toast-renderer-DSu4YoTy.mjs').then(({ ToastRenderer }) => {
|
|
561
|
+
if (this.destroyed)
|
|
562
|
+
return null;
|
|
563
|
+
this.renderer = new ToastRenderer(this.injector, this.defaultOptions.regionAriaLabel ?? 'Notifications');
|
|
564
|
+
return this.renderer;
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
return this.rendererPromise;
|
|
568
|
+
}
|
|
1220
569
|
enforceMaxVisible(position, max) {
|
|
1221
570
|
if (max <= 0)
|
|
1222
571
|
return;
|
|
@@ -1239,72 +588,18 @@ class ToastService {
|
|
|
1239
588
|
handleDismissed(ref) {
|
|
1240
589
|
const scope = this.parent ?? this;
|
|
1241
590
|
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
|
-
}
|
|
591
|
+
this.renderer?.detach(ref);
|
|
1248
592
|
if (scope.activeRefsAtThisLevel().length === 0) {
|
|
1249
593
|
scope.afterAllDismissedSubject.next();
|
|
1250
594
|
}
|
|
1251
595
|
}
|
|
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
|
-
}
|
|
596
|
+
/**
|
|
597
|
+
* Re-announce an updated toast. If the renderer has not landed yet there is
|
|
598
|
+
* nothing on screen to announce — the announcement that fires on attach
|
|
599
|
+
* reads the ref's current (already updated) content, so nothing is lost.
|
|
600
|
+
*/
|
|
1263
601
|
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;
|
|
602
|
+
this.renderer?.announceUpdate(ref);
|
|
1308
603
|
}
|
|
1309
604
|
resolveConfig(config) {
|
|
1310
605
|
const merged = new ToastConfig();
|
|
@@ -1317,36 +612,344 @@ class ToastService {
|
|
|
1317
612
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1318
613
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastService });
|
|
1319
614
|
}
|
|
1320
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastService, decorators: [{
|
|
1321
|
-
type: Injectable
|
|
615
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastService, decorators: [{
|
|
616
|
+
type: Injectable
|
|
617
|
+
}] });
|
|
618
|
+
/**
|
|
619
|
+
* Registers {@link ToastService} for dependency injection and installs optional
|
|
620
|
+
* application-wide defaults.
|
|
621
|
+
*
|
|
622
|
+
* @example
|
|
623
|
+
* ```ts
|
|
624
|
+
* bootstrapApplication(AppComponent, {
|
|
625
|
+
* providers: [provideToast({ position: 'top-right', duration: 4000 })]
|
|
626
|
+
* });
|
|
627
|
+
* ```
|
|
628
|
+
*/
|
|
629
|
+
function provideToast(defaultOptions) {
|
|
630
|
+
const providers = [ToastService];
|
|
631
|
+
if (defaultOptions) {
|
|
632
|
+
providers.push({ provide: TW_TOAST_DEFAULT_OPTIONS, useValue: defaultOptions });
|
|
633
|
+
}
|
|
634
|
+
return makeEnvironmentProviders(providers);
|
|
635
|
+
}
|
|
636
|
+
const ToastInternals = {
|
|
637
|
+
/** @internal Reset id counter for tests. */
|
|
638
|
+
_resetIdForTesting() {
|
|
639
|
+
nextToastId = 0;
|
|
640
|
+
},
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
const toastVariants = tv({
|
|
644
|
+
slots: {
|
|
645
|
+
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',
|
|
646
|
+
icon: 'size-5 shrink-0 mt-0.5',
|
|
647
|
+
title: 'text-sm font-semibold',
|
|
648
|
+
description: 'text-sm',
|
|
649
|
+
content: 'flex-1 min-w-0',
|
|
650
|
+
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',
|
|
651
|
+
// Dismiss container is `size-6` (24px — xs square-interactive-target
|
|
652
|
+
// scale per CLAUDE.md icon sub-scale). The inner SVG is `size-4` (16px
|
|
653
|
+
// glyph scale), centred via flex; the larger container gives the
|
|
654
|
+
// touch/click affordance without enlarging the visual glyph.
|
|
655
|
+
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',
|
|
656
|
+
},
|
|
657
|
+
variants: {
|
|
658
|
+
severity: {
|
|
659
|
+
info: {},
|
|
660
|
+
success: {},
|
|
661
|
+
warning: {},
|
|
662
|
+
error: {},
|
|
663
|
+
neutral: {},
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
// Color slot tokens (`{role}-soft`, `-soft-fg`, `-icon`, …) own light/dark
|
|
667
|
+
// contrast — components consume the slot only. See `_semantic.css`.
|
|
668
|
+
// Slot-token safelist: see `projects/ngx-tw/theme/index.css` `@source inline(...)`
|
|
669
|
+
// — Tailwind v4's JIT cannot scan template-literal class names, so every
|
|
670
|
+
// `bg-${severity}-soft` / `text-${severity}-icon` permutation below must be
|
|
671
|
+
// enumerated there.
|
|
672
|
+
compoundVariants: ['info', 'success', 'warning', 'error', 'neutral'].map((severity) => ({
|
|
673
|
+
severity,
|
|
674
|
+
class: {
|
|
675
|
+
root: `bg-${severity}-soft text-${severity}-soft-fg-muted border-${severity}-border`,
|
|
676
|
+
icon: `text-${severity}-icon`,
|
|
677
|
+
title: `text-${severity}-soft-fg`,
|
|
678
|
+
description: `text-${severity}-soft-fg-muted`,
|
|
679
|
+
action: `text-${severity}-soft-fg hover:bg-${severity}-soft-hover`,
|
|
680
|
+
dismiss: `text-${severity}-icon hover:bg-${severity}-soft-hover`,
|
|
681
|
+
},
|
|
682
|
+
})),
|
|
683
|
+
defaultVariants: {
|
|
684
|
+
severity: 'info',
|
|
685
|
+
},
|
|
686
|
+
}, { twMerge: true });
|
|
687
|
+
/** Slot directive for the icon. When projected, overrides the severity-default icon. */
|
|
688
|
+
class ToastIconDirective {
|
|
689
|
+
toast = inject(ToastComponent);
|
|
690
|
+
/** @internal */
|
|
691
|
+
classes = this.toast.iconClasses;
|
|
692
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastIconDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
693
|
+
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 });
|
|
694
|
+
}
|
|
695
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastIconDirective, decorators: [{
|
|
696
|
+
type: Directive,
|
|
697
|
+
args: [{
|
|
698
|
+
selector: '[twToastIcon]',
|
|
699
|
+
host: {
|
|
700
|
+
'[class]': 'classes()',
|
|
701
|
+
'aria-hidden': 'true',
|
|
702
|
+
},
|
|
703
|
+
}]
|
|
704
|
+
}] });
|
|
705
|
+
/** Slot directive for the bold title line inside a toast. */
|
|
706
|
+
class ToastTitleDirective {
|
|
707
|
+
toast = inject(ToastComponent);
|
|
708
|
+
/** @internal */
|
|
709
|
+
classes = this.toast.titleClasses;
|
|
710
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastTitleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
711
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: ToastTitleDirective, isStandalone: true, selector: "[twToastTitle]", host: { properties: { "class": "classes()" } }, ngImport: i0 });
|
|
712
|
+
}
|
|
713
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastTitleDirective, decorators: [{
|
|
714
|
+
type: Directive,
|
|
715
|
+
args: [{
|
|
716
|
+
selector: '[twToastTitle]',
|
|
717
|
+
host: {
|
|
718
|
+
'[class]': 'classes()',
|
|
719
|
+
},
|
|
720
|
+
}]
|
|
721
|
+
}] });
|
|
722
|
+
/** Slot directive for a secondary description line inside a toast. */
|
|
723
|
+
class ToastDescriptionDirective {
|
|
724
|
+
toast = inject(ToastComponent);
|
|
725
|
+
/** @internal */
|
|
726
|
+
classes = this.toast.descriptionClasses;
|
|
727
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastDescriptionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
728
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: ToastDescriptionDirective, isStandalone: true, selector: "[twToastDescription]", host: { properties: { "class": "classes()" } }, ngImport: i0 });
|
|
729
|
+
}
|
|
730
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastDescriptionDirective, decorators: [{
|
|
731
|
+
type: Directive,
|
|
732
|
+
args: [{
|
|
733
|
+
selector: '[twToastDescription]',
|
|
734
|
+
host: {
|
|
735
|
+
'[class]': 'classes()',
|
|
736
|
+
},
|
|
737
|
+
}]
|
|
1322
738
|
}] });
|
|
1323
|
-
|
|
1324
|
-
|
|
739
|
+
/** Slot directive for the action button. Apply to a native `<button>` to inherit the severity-aware styling. */
|
|
740
|
+
class ToastActionDirective {
|
|
741
|
+
toast = inject(ToastComponent);
|
|
742
|
+
/** @internal */
|
|
743
|
+
classes = this.toast.actionClasses;
|
|
744
|
+
/** @internal */
|
|
745
|
+
onClick() {
|
|
746
|
+
this.toast.actionClicked.emit();
|
|
747
|
+
}
|
|
748
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastActionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
749
|
+
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 });
|
|
1325
750
|
}
|
|
751
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastActionDirective, decorators: [{
|
|
752
|
+
type: Directive,
|
|
753
|
+
args: [{
|
|
754
|
+
selector: '[twToastAction]',
|
|
755
|
+
host: {
|
|
756
|
+
type: 'button',
|
|
757
|
+
'[class]': 'classes()',
|
|
758
|
+
'(click)': 'onClick()',
|
|
759
|
+
},
|
|
760
|
+
}]
|
|
761
|
+
}] });
|
|
1326
762
|
/**
|
|
1327
|
-
*
|
|
1328
|
-
*
|
|
1329
|
-
*
|
|
1330
|
-
* @example
|
|
1331
|
-
* ```ts
|
|
1332
|
-
* bootstrapApplication(AppComponent, {
|
|
1333
|
-
* providers: [provideToast({ position: 'top-right', duration: 4000 })]
|
|
1334
|
-
* });
|
|
1335
|
-
* ```
|
|
763
|
+
* Visual toast / snackbar panel. Rendered internally by {@link ToastService} for
|
|
764
|
+
* string content, and exported for consumers who want to compose the same
|
|
765
|
+
* visual inside a custom `TemplateRef` or component class passed to `show()`.
|
|
1336
766
|
*/
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
767
|
+
class ToastComponent {
|
|
768
|
+
/** Severity variant. Drives color palette, default icon, and ARIA role / live politeness. Defaults to `'info'`. */
|
|
769
|
+
severity = input('info', /* @ts-ignore */
|
|
770
|
+
...(ngDevMode ? [{ debugName: "severity" }] : /* istanbul ignore next */ []));
|
|
771
|
+
// Default true: mirrors `ToastConfig.dismissible` — toasts must offer an escape
|
|
772
|
+
// hatch by default; opt-out only for ephemeral progress markers paired with `duration`.
|
|
773
|
+
/** Whether to render the close button. Defaults to `true`. */
|
|
774
|
+
dismissible = input(true, /* @ts-ignore */
|
|
775
|
+
...(ngDevMode ? [{ debugName: "dismissible" }] : /* istanbul ignore next */ []));
|
|
776
|
+
/**
|
|
777
|
+
* Icon override. Pass a string to render as text inside the icon slot, or
|
|
778
|
+
* `false` to hide the built-in severity icon. Ignored when a `[twToastIcon]`
|
|
779
|
+
* child is projected. When omitted, the severity-default icon renders.
|
|
780
|
+
*
|
|
781
|
+
* For arbitrary icon markup, project a `[twToastIcon]` child instead.
|
|
782
|
+
*/
|
|
783
|
+
icon = input(undefined, /* @ts-ignore */
|
|
784
|
+
...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
785
|
+
/** Explicit aria-label for the toast wrapper. When omitted, text content is used by assistive tech. */
|
|
786
|
+
ariaLabel = input(undefined, /* @ts-ignore */
|
|
787
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
788
|
+
/** Fires when the close button is clicked. */
|
|
789
|
+
dismissed = output();
|
|
790
|
+
/** Fires when a `[twToastAction]` button is clicked. */
|
|
791
|
+
actionClicked = output();
|
|
792
|
+
/** @internal */
|
|
793
|
+
projectedIcon = contentChild(ToastIconDirective, /* @ts-ignore */
|
|
794
|
+
...(ngDevMode ? [{ debugName: "projectedIcon" }] : /* istanbul ignore next */ []));
|
|
795
|
+
/** @internal */
|
|
796
|
+
hasProjectedIcon = computed(() => !!this.projectedIcon(), /* @ts-ignore */
|
|
797
|
+
...(ngDevMode ? [{ debugName: "hasProjectedIcon" }] : /* istanbul ignore next */ []));
|
|
798
|
+
variantResult = computed(() => toastVariants({ severity: this.severity() }), /* @ts-ignore */
|
|
799
|
+
...(ngDevMode ? [{ debugName: "variantResult" }] : /* istanbul ignore next */ []));
|
|
800
|
+
/** @internal */
|
|
801
|
+
rootClasses = computed(() => {
|
|
802
|
+
const base = this.variantResult().root();
|
|
803
|
+
return this.dismissible() ? `${base} pr-10` : base;
|
|
804
|
+
}, /* @ts-ignore */
|
|
805
|
+
...(ngDevMode ? [{ debugName: "rootClasses" }] : /* istanbul ignore next */ []));
|
|
806
|
+
/** @internal */
|
|
807
|
+
iconClasses = computed(() => this.variantResult().icon(), /* @ts-ignore */
|
|
808
|
+
...(ngDevMode ? [{ debugName: "iconClasses" }] : /* istanbul ignore next */ []));
|
|
809
|
+
/** @internal */
|
|
810
|
+
titleClasses = computed(() => this.variantResult().title(), /* @ts-ignore */
|
|
811
|
+
...(ngDevMode ? [{ debugName: "titleClasses" }] : /* istanbul ignore next */ []));
|
|
812
|
+
/** @internal */
|
|
813
|
+
descriptionClasses = computed(() => this.variantResult().description(), /* @ts-ignore */
|
|
814
|
+
...(ngDevMode ? [{ debugName: "descriptionClasses" }] : /* istanbul ignore next */ []));
|
|
815
|
+
/** @internal */
|
|
816
|
+
contentClasses = computed(() => this.variantResult().content(), /* @ts-ignore */
|
|
817
|
+
...(ngDevMode ? [{ debugName: "contentClasses" }] : /* istanbul ignore next */ []));
|
|
818
|
+
/** @internal */
|
|
819
|
+
actionClasses = computed(() => this.variantResult().action(), /* @ts-ignore */
|
|
820
|
+
...(ngDevMode ? [{ debugName: "actionClasses" }] : /* istanbul ignore next */ []));
|
|
821
|
+
/** @internal */
|
|
822
|
+
dismissClasses = computed(() => this.variantResult().dismiss(), /* @ts-ignore */
|
|
823
|
+
...(ngDevMode ? [{ debugName: "dismissClasses" }] : /* istanbul ignore next */ []));
|
|
824
|
+
/** @internal */
|
|
825
|
+
roleAttr = computed(() => this.severity() === 'error' ? 'alert' : 'status', /* @ts-ignore */
|
|
826
|
+
...(ngDevMode ? [{ debugName: "roleAttr" }] : /* istanbul ignore next */ []));
|
|
827
|
+
/** @internal */
|
|
828
|
+
ariaLiveAttr = computed(() => this.severity() === 'error' ? 'assertive' : 'polite', /* @ts-ignore */
|
|
829
|
+
...(ngDevMode ? [{ debugName: "ariaLiveAttr" }] : /* istanbul ignore next */ []));
|
|
830
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
831
|
+
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: `
|
|
832
|
+
@if (hasProjectedIcon()) {
|
|
833
|
+
<ng-content select="[twToastIcon]" />
|
|
834
|
+
} @else if (icon() !== false) {
|
|
835
|
+
<span [class]="iconClasses()" aria-hidden="true">
|
|
836
|
+
@switch (severity()) {
|
|
837
|
+
@case ('info') {
|
|
838
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
839
|
+
<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"/>
|
|
840
|
+
</svg>
|
|
841
|
+
}
|
|
842
|
+
@case ('success') {
|
|
843
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
844
|
+
<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"/>
|
|
845
|
+
</svg>
|
|
846
|
+
}
|
|
847
|
+
@case ('warning') {
|
|
848
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
849
|
+
<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"/>
|
|
850
|
+
</svg>
|
|
851
|
+
}
|
|
852
|
+
@case ('error') {
|
|
853
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
854
|
+
<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"/>
|
|
855
|
+
</svg>
|
|
856
|
+
}
|
|
857
|
+
@default {
|
|
858
|
+
@if (icon() && icon() !== false) {
|
|
859
|
+
{{ icon() }}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
</span>
|
|
1341
864
|
}
|
|
1342
|
-
|
|
865
|
+
<div [class]="contentClasses()">
|
|
866
|
+
<ng-content select="[twToastTitle]" />
|
|
867
|
+
<ng-content select="[twToastDescription]" />
|
|
868
|
+
<ng-content />
|
|
869
|
+
<ng-content select="[twToastAction]" />
|
|
870
|
+
</div>
|
|
871
|
+
@if (dismissible()) {
|
|
872
|
+
<button
|
|
873
|
+
type="button"
|
|
874
|
+
aria-label="Dismiss"
|
|
875
|
+
[class]="dismissClasses()"
|
|
876
|
+
(click)="dismissed.emit()"
|
|
877
|
+
>
|
|
878
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
|
|
879
|
+
<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"/>
|
|
880
|
+
</svg>
|
|
881
|
+
</button>
|
|
882
|
+
}
|
|
883
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1343
884
|
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
885
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ToastComponent, decorators: [{
|
|
886
|
+
type: Component,
|
|
887
|
+
args: [{
|
|
888
|
+
selector: 'tw-toast',
|
|
889
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
890
|
+
host: {
|
|
891
|
+
'[class]': 'rootClasses()',
|
|
892
|
+
'[attr.role]': 'roleAttr()',
|
|
893
|
+
'[attr.aria-live]': 'ariaLiveAttr()',
|
|
894
|
+
'aria-atomic': 'true',
|
|
895
|
+
'[attr.aria-label]': 'ariaLabel() || null',
|
|
896
|
+
'[attr.data-severity]': 'severity()',
|
|
897
|
+
},
|
|
898
|
+
template: `
|
|
899
|
+
@if (hasProjectedIcon()) {
|
|
900
|
+
<ng-content select="[twToastIcon]" />
|
|
901
|
+
} @else if (icon() !== false) {
|
|
902
|
+
<span [class]="iconClasses()" aria-hidden="true">
|
|
903
|
+
@switch (severity()) {
|
|
904
|
+
@case ('info') {
|
|
905
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
906
|
+
<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"/>
|
|
907
|
+
</svg>
|
|
908
|
+
}
|
|
909
|
+
@case ('success') {
|
|
910
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
911
|
+
<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"/>
|
|
912
|
+
</svg>
|
|
913
|
+
}
|
|
914
|
+
@case ('warning') {
|
|
915
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
916
|
+
<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"/>
|
|
917
|
+
</svg>
|
|
918
|
+
}
|
|
919
|
+
@case ('error') {
|
|
920
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-full">
|
|
921
|
+
<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"/>
|
|
922
|
+
</svg>
|
|
923
|
+
}
|
|
924
|
+
@default {
|
|
925
|
+
@if (icon() && icon() !== false) {
|
|
926
|
+
{{ icon() }}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
</span>
|
|
931
|
+
}
|
|
932
|
+
<div [class]="contentClasses()">
|
|
933
|
+
<ng-content select="[twToastTitle]" />
|
|
934
|
+
<ng-content select="[twToastDescription]" />
|
|
935
|
+
<ng-content />
|
|
936
|
+
<ng-content select="[twToastAction]" />
|
|
937
|
+
</div>
|
|
938
|
+
@if (dismissible()) {
|
|
939
|
+
<button
|
|
940
|
+
type="button"
|
|
941
|
+
aria-label="Dismiss"
|
|
942
|
+
[class]="dismissClasses()"
|
|
943
|
+
(click)="dismissed.emit()"
|
|
944
|
+
>
|
|
945
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
|
|
946
|
+
<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"/>
|
|
947
|
+
</svg>
|
|
948
|
+
</button>
|
|
949
|
+
}
|
|
950
|
+
`,
|
|
951
|
+
}]
|
|
952
|
+
}], 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 }] }] } });
|
|
1350
953
|
|
|
1351
954
|
/**
|
|
1352
955
|
* Generated bundle index. Do not edit.
|