@rivinia/toast 0.4.1 → 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/README.md +128 -17
- package/fesm2022/rivinia-toast-ui.mjs +37 -4
- package/fesm2022/rivinia-toast-ui.mjs.map +1 -1
- package/package.json +1 -1
- package/types/rivinia-toast-ui.d.ts +51 -2
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ An Angular signal-based toast queue with timers, actions, deduplication, and
|
|
|
7
7
|
overflow handling. Core is headless; an optional standalone UI is available at
|
|
8
8
|
`@rivinia/toast/ui`.
|
|
9
9
|
|
|
10
|
-
-
|
|
10
|
+
- Package version: **0.5.0**
|
|
11
11
|
- Peer dependency: **`@angular/core` `^22.0.0`**
|
|
12
12
|
- No `tslib`, router, or HTTP peer dependency.
|
|
13
13
|
|
|
@@ -57,12 +57,8 @@ import { RiviniaToastList } from '@rivinia/toast/ui';
|
|
|
57
57
|
|
|
58
58
|
@Component({
|
|
59
59
|
selector: 'app-notifications',
|
|
60
|
-
standalone: true,
|
|
61
60
|
imports: [RiviniaToastList],
|
|
62
|
-
|
|
63
|
-
<button type="button" (click)="save()">Save</button>
|
|
64
|
-
<rv-toast-list [manager]="toast" position="bottom-end" />
|
|
65
|
-
`,
|
|
61
|
+
templateUrl: './notifications.component.html',
|
|
66
62
|
})
|
|
67
63
|
export class NotificationsComponent {
|
|
68
64
|
readonly toast = injectRiviniaToast();
|
|
@@ -86,6 +82,12 @@ export class NotificationsComponent {
|
|
|
86
82
|
}
|
|
87
83
|
```
|
|
88
84
|
|
|
85
|
+
```html
|
|
86
|
+
<!-- src/app/notifications.component.html -->
|
|
87
|
+
<button type="button" (click)="save()">Save</button>
|
|
88
|
+
<rv-toast-list [manager]="toast" position="bottom-end" />
|
|
89
|
+
```
|
|
90
|
+
|
|
89
91
|
The standalone list renders the manager's `active()` signal, pauses timed
|
|
90
92
|
notifications on hover and keyboard focus, runs actions, and provides a
|
|
91
93
|
dismiss button by default. Its defaults are `position="bottom-end"`,
|
|
@@ -98,17 +100,15 @@ For snapshot-driven UIs, provide the snapshots and handle the emitted IDs
|
|
|
98
100
|
yourself:
|
|
99
101
|
|
|
100
102
|
```ts
|
|
103
|
+
// src/app/toast-snapshots.component.ts
|
|
101
104
|
import { Component } from '@angular/core';
|
|
102
105
|
import type { Toast } from '@rivinia/toast';
|
|
103
106
|
import { RiviniaToastList } from '@rivinia/toast/ui';
|
|
104
107
|
|
|
105
108
|
@Component({
|
|
106
109
|
selector: 'app-toast-snapshots',
|
|
107
|
-
standalone: true,
|
|
108
110
|
imports: [RiviniaToastList],
|
|
109
|
-
|
|
110
|
-
<rv-toast-list [toasts]="items" (dismissed)="remove($event)" (actionPressed)="run($event)" />
|
|
111
|
-
`,
|
|
111
|
+
templateUrl: './toast-snapshots.component.html',
|
|
112
112
|
})
|
|
113
113
|
export class ToastSnapshotsComponent {
|
|
114
114
|
items: readonly Toast[] = [
|
|
@@ -136,6 +136,11 @@ export class ToastSnapshotsComponent {
|
|
|
136
136
|
}
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
+
```html
|
|
140
|
+
<!-- src/app/toast-snapshots.component.html -->
|
|
141
|
+
<rv-toast-list [toasts]="items" (dismissed)="remove($event)" (actionPressed)="run($event)" />
|
|
142
|
+
```
|
|
143
|
+
|
|
139
144
|
In this presentational mode your app owns timers, pause behavior, and action
|
|
140
145
|
handling. If both `manager` and `toasts` are supplied, the manager takes
|
|
141
146
|
precedence.
|
|
@@ -146,18 +151,13 @@ Core can be used without the UI. `active()` and `queue()` are readonly signals;
|
|
|
146
151
|
the active list is oldest-first. Timers begin only when a toast becomes active.
|
|
147
152
|
|
|
148
153
|
```ts
|
|
154
|
+
// src/app/save-status.component.ts
|
|
149
155
|
import { Component, inject } from '@angular/core';
|
|
150
156
|
import { resolveToastText, RiviniaToast } from '@rivinia/toast';
|
|
151
157
|
|
|
152
158
|
@Component({
|
|
153
159
|
selector: 'app-save-status',
|
|
154
|
-
|
|
155
|
-
template: `
|
|
156
|
-
<button type="button" (click)="save()">Save</button>
|
|
157
|
-
@for (item of toast.active(); track item.id) {
|
|
158
|
-
<p [attr.data-kind]="item.kind">{{ resolveText(item.message) }}</p>
|
|
159
|
-
}
|
|
160
|
-
`,
|
|
160
|
+
templateUrl: './save-status.component.html',
|
|
161
161
|
})
|
|
162
162
|
export class SaveStatusComponent {
|
|
163
163
|
readonly toast = inject(RiviniaToast);
|
|
@@ -177,10 +177,121 @@ export class SaveStatusComponent {
|
|
|
177
177
|
}
|
|
178
178
|
```
|
|
179
179
|
|
|
180
|
+
```html
|
|
181
|
+
<!-- src/app/save-status.component.html -->
|
|
182
|
+
<button type="button" (click)="save()">Save</button>
|
|
183
|
+
@for (item of toast.active(); track item.id) {
|
|
184
|
+
<p [attr.data-kind]="item.kind">{{ resolveText(item.message) }}</p>
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
180
188
|
`message` and action `label` accept either a string or a reactive string
|
|
181
189
|
signal, so they can reflect language changes. For a custom outlet, use the exported
|
|
182
190
|
`resolveToastText(message)` utility to read either shape.
|
|
183
191
|
|
|
192
|
+
## Theming
|
|
193
|
+
|
|
194
|
+
Every component reads **its own token → the shared semantic layer → its
|
|
195
|
+
built-in default**:
|
|
196
|
+
|
|
197
|
+
```css
|
|
198
|
+
--rv-toast-bg → --rv-bg → #ffffff
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
| Level | Token | Scope |
|
|
202
|
+
| -------------- | --------------- | ----------------------------------- |
|
|
203
|
+
| 1. per package | `--rv-toast-bg` | that toast only |
|
|
204
|
+
| 2. global | `--rv-bg` | every Rivinia component, everywhere |
|
|
205
|
+
| 3. default | — | what you get if you set nothing |
|
|
206
|
+
|
|
207
|
+
```css
|
|
208
|
+
@import '@rivinia/theme';
|
|
209
|
+
|
|
210
|
+
:root {
|
|
211
|
+
--rv-bg: #ffffff;
|
|
212
|
+
--rv-fg: #111111;
|
|
213
|
+
--rv-border: #e0e0e0;
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Its defaults sit in `@layer rivinia-theme`, so your unlayered `:root` rules win
|
|
218
|
+
wherever they appear. **Map it on `:root`, never on a component element** — an
|
|
219
|
+
element-level declaration shadows the root value and the override silently
|
|
220
|
+
stops working.
|
|
221
|
+
|
|
222
|
+
### The library's own tokens
|
|
223
|
+
|
|
224
|
+
| Token | Default | Applies to |
|
|
225
|
+
| ------------------- | ----------------------------- | -------------- |
|
|
226
|
+
| `--rv-toast-bg` | `#ffffff` | the toast |
|
|
227
|
+
| `--rv-toast-fg` | `#111111` | the toast |
|
|
228
|
+
| `--rv-toast-muted` | `#555555` | the kind rail |
|
|
229
|
+
| `--rv-toast-border` | `#e0e0e0` | the toast |
|
|
230
|
+
| `--rv-toast-radius` | `12px` | the toast |
|
|
231
|
+
| `--rv-toast-shadow` | `0 12px 32px rgba(0,0,0,.32)` | the toast |
|
|
232
|
+
| `--rv-toast-gap` | `8px` | between toasts |
|
|
233
|
+
| `--rv-toast-width` | `22rem` | the list |
|
|
234
|
+
| `--rv-toast-z` | `1000` | the list |
|
|
235
|
+
|
|
236
|
+
The kind colours chain to the semantic layer too:
|
|
237
|
+
|
|
238
|
+
| Token | Semantic token |
|
|
239
|
+
| -------------------- | -------------- |
|
|
240
|
+
| `--rv-toast-success` | `--rv-ok` |
|
|
241
|
+
| `--rv-toast-error` | `--rv-danger` |
|
|
242
|
+
| `--rv-toast-info` | `--rv-info` |
|
|
243
|
+
| `--rv-toast-warning` | `--rv-warning` |
|
|
244
|
+
|
|
245
|
+
**No forced light mode** — there is no `prefers-color-scheme` block in this
|
|
246
|
+
package, so a toast never overrides your explicit choice.
|
|
247
|
+
|
|
248
|
+
### Which styling stack?
|
|
249
|
+
|
|
250
|
+
| Stack | Import |
|
|
251
|
+
| --------------- | -------------------------------------------- |
|
|
252
|
+
| **Plain CSS** | `@import '@rivinia/theme';` |
|
|
253
|
+
| **Tailwind v4** | `@import '@rivinia/theme/tailwind.css';` |
|
|
254
|
+
| **Bootstrap** | `@import '@rivinia/theme/bootstrap.css';` |
|
|
255
|
+
| **Sass / SCSS** | `@use '@rivinia/theme/scss';` |
|
|
256
|
+
| **Less** | `@import (less) '@rivinia/theme/theme.css';` |
|
|
257
|
+
|
|
258
|
+
## The list is in the top layer
|
|
259
|
+
|
|
260
|
+
A `position: fixed` list with a `z-index` is still in the DOCUMENT, so it is
|
|
261
|
+
clipped by an `overflow: hidden` ancestor and loses to any stacking context
|
|
262
|
+
above it — including the one a modal `<dialog>` creates.
|
|
263
|
+
|
|
264
|
+
The list is therefore a **manual popover**, which puts it in the browser's top
|
|
265
|
+
layer: the same layer a modal `<dialog>` uses. The top layer stacks in the
|
|
266
|
+
order elements are shown, and the list is re-shown whenever its contents
|
|
267
|
+
change, so a toast raised while a dialog is open lands above it. Measured: the
|
|
268
|
+
element at the toast's position is the toast, not the dialog.
|
|
269
|
+
|
|
270
|
+
A manual popover is not modal: it does not trap focus, does not make the page
|
|
271
|
+
inert, and does not close on an outside click — so it behaves exactly like
|
|
272
|
+
the list it replaces.
|
|
273
|
+
|
|
274
|
+
**The popover's user-agent styles are reset.** A popover gets a background, a
|
|
275
|
+
border, padding and `overflow: auto` from the UA, which puts a shared canvas
|
|
276
|
+
and a scrollbar around the stack — so the toasts read as one block instead of
|
|
277
|
+
separate cards. The list is a transparent, borderless column.
|
|
278
|
+
|
|
279
|
+
Where the browser has no `showPopover` the list stays in the document and a
|
|
280
|
+
`z-index` decides its order. The feature is detected, not assumed.
|
|
281
|
+
|
|
282
|
+
## The list must be MANAGED
|
|
283
|
+
|
|
284
|
+
`rv-toast-list` renders what it is given. Without a manager it has an empty
|
|
285
|
+
list and **no toast ever appears**:
|
|
286
|
+
|
|
287
|
+
```html
|
|
288
|
+
<!-- nothing shows: the list is presentational and empty -->
|
|
289
|
+
<rv-toast-list position="bottom-end" />
|
|
290
|
+
|
|
291
|
+
<!-- the manager drives it -->
|
|
292
|
+
<rv-toast-list [manager]="toast" position="bottom-end" />
|
|
293
|
+
```
|
|
294
|
+
|
|
184
295
|
## API and behavior
|
|
185
296
|
|
|
186
297
|
| Export | Kind | Purpose |
|
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
|
-
import { Component, computed, effect, input, output, signal } from "@angular/core";
|
|
2
|
+
import { Component, computed, effect, input, output, signal, viewChild } from "@angular/core";
|
|
3
3
|
import { resolveToastText } from "@rivinia/toast";
|
|
4
4
|
const LEAVE_MS = 150;
|
|
5
5
|
var RiviniaToastList = class RiviniaToastList {
|
|
6
|
+
topLayerSupported = typeof HTMLElement !== "undefined" && "showPopover" in HTMLElement.prototype;
|
|
7
|
+
panel = viewChild("panel", ...ngDevMode ? [{ debugName: "panel" }] : /* istanbul ignore next */ []);
|
|
8
|
+
ngAfterViewInit() {
|
|
9
|
+
this.promote();
|
|
10
|
+
}
|
|
11
|
+
promote() {
|
|
12
|
+
if (!this.topLayerSupported) return;
|
|
13
|
+
const el = this.panel()?.nativeElement;
|
|
14
|
+
if (!el || typeof el.showPopover !== "function") return;
|
|
15
|
+
el.setAttribute("popover", "manual");
|
|
16
|
+
try {
|
|
17
|
+
if (el.matches(":popover-open")) el.hidePopover?.();
|
|
18
|
+
el.showPopover();
|
|
19
|
+
} catch {}
|
|
20
|
+
}
|
|
21
|
+
keepOnTop = effect(() => {
|
|
22
|
+
this.rendered();
|
|
23
|
+
this.promote();
|
|
24
|
+
}, ...ngDevMode ? [{ debugName: "keepOnTop" }] : /* istanbul ignore next */ []);
|
|
6
25
|
manager = input(null, ...ngDevMode ? [{ debugName: "manager" }] : /* istanbul ignore next */ []);
|
|
7
26
|
toasts = input([], ...ngDevMode ? [{ debugName: "toasts" }] : /* istanbul ignore next */ []);
|
|
8
27
|
position = input("bottom-end", ...ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []);
|
|
@@ -150,12 +169,21 @@ var RiviniaToastList = class RiviniaToastList {
|
|
|
150
169
|
dismissed: "dismissed",
|
|
151
170
|
actionPressed: "actionPressed"
|
|
152
171
|
},
|
|
172
|
+
viewQueries: [{
|
|
173
|
+
propertyName: "panel",
|
|
174
|
+
first: true,
|
|
175
|
+
predicate: ["panel"],
|
|
176
|
+
descendants: true,
|
|
177
|
+
isSignal: true
|
|
178
|
+
}],
|
|
153
179
|
ngImport: i0,
|
|
154
180
|
template: `
|
|
155
181
|
<div
|
|
182
|
+
#panel
|
|
156
183
|
class="rv-toasts"
|
|
157
184
|
role="region"
|
|
158
185
|
aria-label="Notifications"
|
|
186
|
+
[attr.popover]="topLayerSupported ? 'manual' : null"
|
|
159
187
|
[attr.data-position]="position()"
|
|
160
188
|
[attr.data-animation]="animation()"
|
|
161
189
|
[attr.data-rich-colors]="richColors()"
|
|
@@ -194,7 +222,7 @@ var RiviniaToastList = class RiviniaToastList {
|
|
|
194
222
|
</div>
|
|
195
223
|
`,
|
|
196
224
|
isInline: true,
|
|
197
|
-
styles: [".rv-toasts{--rv-toast-
|
|
225
|
+
styles: [".rv-toasts{--rv-toast-gap: .5rem;--rv-toast-width: 22rem;--rv-toast-duration: .18s;--rv-toast-easing: cubic-bezier(.21, 1.02, .73, 1);--rv-toast-success: #34c759;--rv-toast-error: #ff453a;--rv-toast-info: #0a84ff;--rv-toast-warning: #ff9f0a;position:fixed;z-index:var(--rv-toast-z, 1000);background:transparent;border:0;padding:0;color:inherit;overflow:visible;height:auto;inset:auto;margin:0;display:flex;flex-direction:column;gap:var(--rv-toast-gap);width:var(--rv-toast-width);max-width:calc(100dvw - 2rem);pointer-events:none}.rv-toasts>*{pointer-events:auto}.rv-toasts[data-position=top-start],.rv-toasts[data-position=bottom-start]{inset-inline-start:1rem}.rv-toasts[data-position=top-end],.rv-toasts[data-position=bottom-end]{inset-inline-end:1rem}.rv-toasts[data-position=top-center],.rv-toasts[data-position=bottom-center]{inset-inline:0;margin-inline:auto}.rv-toasts[data-position^=top]{inset-block-start:1rem}.rv-toasts[data-position^=bottom]{inset-block-end:1rem}.rv-toast{display:flex;align-items:flex-start;gap:.625rem;background:var(--rv-toast-bg, var(--rv-bg, #ffffff));color:var(--rv-toast-fg, var(--rv-fg, #111111));border:1px solid var(--rv-toast-border, var(--rv-border, #e0e0e0));border-inline-start:3px solid var(--rv-toast-muted, var(--rv-muted, #555555));border-radius:var(--rv-toast-radius, var(--rv-radius, 12px));box-shadow:var(--rv-toast-shadow, var(--rv-shadow, 0 12px 32px rgba(0, 0, 0, .32)));padding:12px 14px}.rv-toast[data-kind=success]{border-inline-start-color:var(--rv-toast-success, var(--rv-ok, #16a34a))}.rv-toast[data-kind=error]{border-inline-start-color:var(--rv-toast-error, var(--rv-danger, #dc2626))}.rv-toast[data-kind=info]{border-inline-start-color:var(--rv-toast-info, var(--rv-info, #2563eb))}.rv-toast[data-kind=warning]{border-inline-start-color:var(--rv-toast-warning, var(--rv-warning, #d97706))}.rv-toasts[data-rich-colors=true] .rv-toast[data-kind=success]{background:var(--rv-toast-success, var(--rv-ok, #16a34a));color:#06130a}.rv-toasts[data-rich-colors=true] .rv-toast[data-kind=error]{background:var(--rv-toast-error, var(--rv-danger, #dc2626));color:#1c0605}.rv-toasts[data-rich-colors=true] .rv-toast[data-kind=info]{background:var(--rv-toast-info, var(--rv-info, #2563eb));color:#04121f}.rv-toasts[data-rich-colors=true] .rv-toast[data-kind=warning]{background:var(--rv-toast-warning, var(--rv-warning, #d97706));color:#1d1204}.rv-toast-icon{flex:none;font-weight:700}.rv-toast[data-kind=success] .rv-toast-icon:before{content:\"\\2713\"}.rv-toast[data-kind=error] .rv-toast-icon:before{content:\"!\"}.rv-toast[data-kind=info] .rv-toast-icon:before{content:\"i\"}.rv-toast[data-kind=warning] .rv-toast-icon:before{content:\"\\26a0\"}.rv-toast-message{margin:0;flex:1;font-size:.875rem;line-height:1.45}.rv-toast-action,.rv-toast-close{flex:none;cursor:pointer;border-radius:.5rem;border:1px solid var(--rv-toast-border, var(--rv-border, #e0e0e0));background:transparent;color:inherit;padding:.25rem .625rem;font-size:.8125rem}.rv-toast-action:focus-visible,.rv-toast-close:focus-visible{outline:2px solid currentColor;outline-offset:2px}.rv-toasts[data-animation=slide] .rv-toast[data-state=active]{animation:rv-toast-in var(--rv-toast-duration) var(--rv-toast-easing)}.rv-toasts[data-animation=fade] .rv-toast[data-state=active]{animation:rv-toast-in-fade var(--rv-toast-duration) ease-out}.rv-toast[data-state=leaving]{animation:rv-toast-out .15s ease-in forwards}@keyframes rv-toast-in{0%{opacity:0;transform:translateY(12px)}}@keyframes rv-toast-in-fade{0%{opacity:0}}@keyframes rv-toast-out{to{opacity:0;transform:translateY(8px)}}@media(prefers-reduced-motion:reduce){.rv-toasts .rv-toast{animation:none}}\n"]
|
|
198
226
|
});
|
|
199
227
|
};
|
|
200
228
|
i0.ɵɵngDeclareClassMetadata({
|
|
@@ -206,12 +234,13 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
206
234
|
type: Component,
|
|
207
235
|
args: [{
|
|
208
236
|
selector: "rv-toast-list",
|
|
209
|
-
standalone: true,
|
|
210
237
|
template: `
|
|
211
238
|
<div
|
|
239
|
+
#panel
|
|
212
240
|
class="rv-toasts"
|
|
213
241
|
role="region"
|
|
214
242
|
aria-label="Notifications"
|
|
243
|
+
[attr.popover]="topLayerSupported ? 'manual' : null"
|
|
215
244
|
[attr.data-position]="position()"
|
|
216
245
|
[attr.data-animation]="animation()"
|
|
217
246
|
[attr.data-rich-colors]="richColors()"
|
|
@@ -249,11 +278,15 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
249
278
|
}
|
|
250
279
|
</div>
|
|
251
280
|
`,
|
|
252
|
-
styles: [".rv-toasts{--rv-toast-
|
|
281
|
+
styles: [".rv-toasts{--rv-toast-gap: .5rem;--rv-toast-width: 22rem;--rv-toast-duration: .18s;--rv-toast-easing: cubic-bezier(.21, 1.02, .73, 1);--rv-toast-success: #34c759;--rv-toast-error: #ff453a;--rv-toast-info: #0a84ff;--rv-toast-warning: #ff9f0a;position:fixed;z-index:var(--rv-toast-z, 1000);background:transparent;border:0;padding:0;color:inherit;overflow:visible;height:auto;inset:auto;margin:0;display:flex;flex-direction:column;gap:var(--rv-toast-gap);width:var(--rv-toast-width);max-width:calc(100dvw - 2rem);pointer-events:none}.rv-toasts>*{pointer-events:auto}.rv-toasts[data-position=top-start],.rv-toasts[data-position=bottom-start]{inset-inline-start:1rem}.rv-toasts[data-position=top-end],.rv-toasts[data-position=bottom-end]{inset-inline-end:1rem}.rv-toasts[data-position=top-center],.rv-toasts[data-position=bottom-center]{inset-inline:0;margin-inline:auto}.rv-toasts[data-position^=top]{inset-block-start:1rem}.rv-toasts[data-position^=bottom]{inset-block-end:1rem}.rv-toast{display:flex;align-items:flex-start;gap:.625rem;background:var(--rv-toast-bg, var(--rv-bg, #ffffff));color:var(--rv-toast-fg, var(--rv-fg, #111111));border:1px solid var(--rv-toast-border, var(--rv-border, #e0e0e0));border-inline-start:3px solid var(--rv-toast-muted, var(--rv-muted, #555555));border-radius:var(--rv-toast-radius, var(--rv-radius, 12px));box-shadow:var(--rv-toast-shadow, var(--rv-shadow, 0 12px 32px rgba(0, 0, 0, .32)));padding:12px 14px}.rv-toast[data-kind=success]{border-inline-start-color:var(--rv-toast-success, var(--rv-ok, #16a34a))}.rv-toast[data-kind=error]{border-inline-start-color:var(--rv-toast-error, var(--rv-danger, #dc2626))}.rv-toast[data-kind=info]{border-inline-start-color:var(--rv-toast-info, var(--rv-info, #2563eb))}.rv-toast[data-kind=warning]{border-inline-start-color:var(--rv-toast-warning, var(--rv-warning, #d97706))}.rv-toasts[data-rich-colors=true] .rv-toast[data-kind=success]{background:var(--rv-toast-success, var(--rv-ok, #16a34a));color:#06130a}.rv-toasts[data-rich-colors=true] .rv-toast[data-kind=error]{background:var(--rv-toast-error, var(--rv-danger, #dc2626));color:#1c0605}.rv-toasts[data-rich-colors=true] .rv-toast[data-kind=info]{background:var(--rv-toast-info, var(--rv-info, #2563eb));color:#04121f}.rv-toasts[data-rich-colors=true] .rv-toast[data-kind=warning]{background:var(--rv-toast-warning, var(--rv-warning, #d97706));color:#1d1204}.rv-toast-icon{flex:none;font-weight:700}.rv-toast[data-kind=success] .rv-toast-icon:before{content:\"\\2713\"}.rv-toast[data-kind=error] .rv-toast-icon:before{content:\"!\"}.rv-toast[data-kind=info] .rv-toast-icon:before{content:\"i\"}.rv-toast[data-kind=warning] .rv-toast-icon:before{content:\"\\26a0\"}.rv-toast-message{margin:0;flex:1;font-size:.875rem;line-height:1.45}.rv-toast-action,.rv-toast-close{flex:none;cursor:pointer;border-radius:.5rem;border:1px solid var(--rv-toast-border, var(--rv-border, #e0e0e0));background:transparent;color:inherit;padding:.25rem .625rem;font-size:.8125rem}.rv-toast-action:focus-visible,.rv-toast-close:focus-visible{outline:2px solid currentColor;outline-offset:2px}.rv-toasts[data-animation=slide] .rv-toast[data-state=active]{animation:rv-toast-in var(--rv-toast-duration) var(--rv-toast-easing)}.rv-toasts[data-animation=fade] .rv-toast[data-state=active]{animation:rv-toast-in-fade var(--rv-toast-duration) ease-out}.rv-toast[data-state=leaving]{animation:rv-toast-out .15s ease-in forwards}@keyframes rv-toast-in{0%{opacity:0;transform:translateY(12px)}}@keyframes rv-toast-in-fade{0%{opacity:0}}@keyframes rv-toast-out{to{opacity:0;transform:translateY(8px)}}@media(prefers-reduced-motion:reduce){.rv-toasts .rv-toast{animation:none}}\n"]
|
|
253
282
|
}]
|
|
254
283
|
}],
|
|
255
284
|
ctorParameters: () => [],
|
|
256
285
|
propDecorators: {
|
|
286
|
+
panel: [{
|
|
287
|
+
type: i0.ViewChild,
|
|
288
|
+
args: ["panel", { isSignal: true }]
|
|
289
|
+
}],
|
|
257
290
|
manager: [{
|
|
258
291
|
type: i0.Input,
|
|
259
292
|
args: [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rivinia-toast-ui.mjs","names":[],"sources":["../../../projects/toast/ui/src/lib/toast-list.ts"],"sourcesContent":["import { Component, computed, effect, input, output, signal } from '@angular/core';\nimport type { Toast, ToastKind, ToastOptions } from '@rivinia/toast';\nimport { resolveToastText } from '@rivinia/toast';\nimport type { ToastAnimation, ToastPosition } from './ui-toast-types';\n\nconst LEAVE_MS = 150;\n\n/**\n * The structural slice of `RiviniaToast` the list actually drives. The full\n * manager satisfies it from any module realm; test doubles need five methods.\n * When both `manager` and `toasts` are given, the manager wins (single\n * source of truth — the UI never races Core).\n */\nexport interface RiviniaToastListManager {\n readonly active: () => readonly Toast[];\n show(options: ToastOptions): number | null;\n dismiss(id: number): boolean;\n pause(id: number): boolean;\n resume(id: number): boolean;\n runAction(id: number): boolean;\n}\n\n/** A toast snapshot kept rendering while its 150 ms exit plays. */\ninterface LeavingToast {\n readonly toast: Toast;\n}\n\n/**\n * Ready-made toast stack. Two modes (manager wins when both are given):\n *\n * - Managed: `[manager]=\"injectRiviniaToast()\"` — renders `active()`,\n * hover pauses, action button runs `runAction` (throws propagate to the\n * app's `ErrorHandler`), X dismisses. Dismissed items play a 150 ms exit\n * from their last snapshot, then vanish (browser only — SSR drops them).\n * - Presentational: `[toasts]=\"[...]\"` + `(dismissed)/(actionPressed)` —\n * no manager needed. Timer/pause state is yours.\n *\n * Each item is `role=\"status\"` (polite by default; `error` kind asserts).\n * Stack order is `active()` order (oldest first), anchored at `position`.\n */\n@Component({\n selector: 'rv-toast-list',\n standalone: true,\n template: `\n <div\n class=\"rv-toasts\"\n role=\"region\"\n aria-label=\"Notifications\"\n [attr.data-position]=\"position()\"\n [attr.data-animation]=\"animation()\"\n [attr.data-rich-colors]=\"richColors()\"\n >\n @for (item of rendered(); track item.toast.id) {\n <div\n class=\"rv-toast\"\n [attr.data-kind]=\"item.toast.kind\"\n [attr.data-state]=\"item.leaving ? 'leaving' : item.toast.paused ? 'paused' : 'active'\"\n role=\"status\"\n [attr.aria-live]=\"item.toast.kind === 'error' ? 'assertive' : 'polite'\"\n (mouseenter)=\"pause(item.toast.id)\"\n (mouseleave)=\"resume(item.toast.id)\"\n (focusin)=\"pause(item.toast.id)\"\n (focusout)=\"resume(item.toast.id)\"\n >\n <span class=\"rv-toast-icon\" aria-hidden=\"true\">{{ iconFor(item.toast.kind) }}</span>\n <p class=\"rv-toast-message\">{{ textOf(item.toast) }}</p>\n @if (item.toast.action) {\n <button type=\"button\" class=\"rv-toast-action\" (click)=\"act(item.toast.id)\">\n {{ textOfAction(item.toast) }}\n </button>\n }\n @if (showCloseButton()) {\n <button\n type=\"button\"\n class=\"rv-toast-close\"\n (click)=\"close(item.toast.id)\"\n aria-label=\"Dismiss notification\"\n >\n <span aria-hidden=\"true\">×</span>\n </button>\n }\n </div>\n }\n </div>\n `,\n styles: [\n `\n .rv-toasts {\n --rv-toast-bg: #1c1c1e;\n --rv-toast-fg: #f5f5f7;\n --rv-toast-muted: #a8a8a8;\n --rv-toast-border: #38383a;\n --rv-toast-radius: 12px;\n --rv-toast-shadow: 0 12px 32px rgba(0, 0, 0, 0.32);\n --rv-toast-gap: 8px;\n --rv-toast-width: 22rem;\n --rv-toast-duration: 180ms;\n --rv-toast-easing: cubic-bezier(0.21, 1.02, 0.73, 1);\n --rv-toast-success: #34c759;\n --rv-toast-error: #ff453a;\n --rv-toast-info: #0a84ff;\n --rv-toast-warning: #ff9f0a;\n position: fixed;\n z-index: var(--rv-toast-z, 1000);\n display: flex;\n flex-direction: column;\n gap: var(--rv-toast-gap);\n width: var(--rv-toast-width);\n max-width: calc(100dvw - 2rem);\n pointer-events: none;\n }\n .rv-toasts > * {\n pointer-events: auto;\n }\n @media (prefers-color-scheme: light) {\n .rv-toasts {\n --rv-toast-bg: #ffffff;\n --rv-toast-fg: #111111;\n --rv-toast-muted: #555555;\n --rv-toast-border: #e0e0e0;\n --rv-toast-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);\n }\n }\n .rv-toasts[data-position='top-start'],\n .rv-toasts[data-position='bottom-start'] {\n inset-inline-start: 1rem;\n }\n .rv-toasts[data-position='top-end'],\n .rv-toasts[data-position='bottom-end'] {\n inset-inline-end: 1rem;\n }\n .rv-toasts[data-position='top-center'],\n .rv-toasts[data-position='bottom-center'] {\n inset-inline: 0;\n margin-inline: auto;\n }\n .rv-toasts[data-position^='top'] {\n inset-block-start: 1rem;\n }\n .rv-toasts[data-position^='bottom'] {\n inset-block-end: 1rem;\n }\n .rv-toast {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n background: var(--rv-toast-bg);\n color: var(--rv-toast-fg);\n border: 1px solid var(--rv-toast-border);\n border-inline-start: 3px solid var(--rv-toast-muted);\n border-radius: var(--rv-toast-radius);\n box-shadow: var(--rv-toast-shadow);\n padding: 12px 14px;\n }\n .rv-toast[data-kind='success'] {\n border-inline-start-color: var(--rv-toast-success);\n }\n .rv-toast[data-kind='error'] {\n border-inline-start-color: var(--rv-toast-error);\n }\n .rv-toast[data-kind='info'] {\n border-inline-start-color: var(--rv-toast-info);\n }\n .rv-toast[data-kind='warning'] {\n border-inline-start-color: var(--rv-toast-warning);\n }\n .rv-toasts[data-rich-colors='true'] .rv-toast[data-kind='success'] {\n background: var(--rv-toast-success);\n color: #06130a;\n }\n .rv-toasts[data-rich-colors='true'] .rv-toast[data-kind='error'] {\n background: var(--rv-toast-error);\n color: #1c0605;\n }\n .rv-toasts[data-rich-colors='true'] .rv-toast[data-kind='info'] {\n background: var(--rv-toast-info);\n color: #04121f;\n }\n .rv-toasts[data-rich-colors='true'] .rv-toast[data-kind='warning'] {\n background: var(--rv-toast-warning);\n color: #1d1204;\n }\n .rv-toast-icon {\n flex: none;\n font-weight: 700;\n }\n .rv-toast[data-kind='success'] .rv-toast-icon::before {\n content: '✓';\n }\n .rv-toast[data-kind='error'] .rv-toast-icon::before {\n content: '!';\n }\n .rv-toast[data-kind='info'] .rv-toast-icon::before {\n content: 'i';\n }\n .rv-toast[data-kind='warning'] .rv-toast-icon::before {\n content: '⚠';\n }\n .rv-toast-message {\n margin: 0;\n flex: 1;\n font-size: 14px;\n line-height: 1.45;\n }\n .rv-toast-action,\n .rv-toast-close {\n flex: none;\n cursor: pointer;\n border-radius: 8px;\n border: 1px solid var(--rv-toast-border);\n background: transparent;\n color: inherit;\n padding: 4px 10px;\n font-size: 13px;\n }\n .rv-toast-action:focus-visible,\n .rv-toast-close:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n .rv-toasts[data-animation='slide'] .rv-toast[data-state='active'] {\n animation: rv-toast-in var(--rv-toast-duration) var(--rv-toast-easing);\n }\n .rv-toasts[data-animation='fade'] .rv-toast[data-state='active'] {\n animation: rv-toast-in-fade var(--rv-toast-duration) ease-out;\n }\n .rv-toast[data-state='leaving'] {\n animation: rv-toast-out 150ms ease-in forwards;\n }\n @keyframes rv-toast-in {\n from {\n opacity: 0;\n transform: translateY(12px);\n }\n }\n @keyframes rv-toast-in-fade {\n from {\n opacity: 0;\n }\n }\n @keyframes rv-toast-out {\n to {\n opacity: 0;\n transform: translateY(8px);\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .rv-toasts .rv-toast {\n animation: none;\n }\n }\n `,\n ],\n})\nexport class RiviniaToastList {\n /** Managed mode: the service (or a structural test double). Wins over `toasts`. */\n readonly manager = input<RiviniaToastListManager | null>(null);\n /** Presentational mode: snapshots you own (timers/pause are yours). */\n readonly toasts = input<readonly Toast[]>([]);\n /** Stack anchor. @default 'bottom-end' */\n readonly position = input<ToastPosition>('bottom-end');\n /** Entrance animation. @default 'slide' */\n readonly animation = input<ToastAnimation>('slide');\n /** Kind-tinted rail (default) vs full-bleed kind colors. @default false */\n readonly richColors = input<boolean>(false);\n /** Render the X button. @default true */\n readonly showCloseButton = input<boolean>(true);\n\n /** Presentational-mode dismiss (id). Managed mode settles via the manager. */\n readonly dismissed = output<number>();\n /** Presentational-mode action press (id). */\n readonly actionPressed = output<number>();\n\n private readonly leaving = signal<readonly LeavingToast[]>([]);\n private seen = new Set<number>();\n private readonly known = new Map<number, Toast>();\n\n /** Rendered rows: live items plus exit-playing snapshots (browser only). */\n protected readonly rendered = computed(() => {\n const manager = this.manager();\n const live: readonly Toast[] = manager ? manager.active() : this.toasts();\n const liveIds = new Set(live.map((t) => t.id));\n const gone = this.leaving().filter((l) => !liveIds.has(l.toast.id));\n return [\n ...live.map((toast) => ({ toast, leaving: false })),\n ...gone.map((l) => ({ ...l, leaving: true })),\n ];\n });\n\n constructor() {\n effect(() => {\n // Track removals: anything that was live and isn't anymore plays exit.\n // Every run also refreshes the last-known snapshots the exit needs.\n const manager = this.manager();\n const live = manager ? manager.active() : this.toasts();\n const ids = new Set<number>();\n for (const toast of live) {\n ids.add(toast.id);\n this.known.set(toast.id, toast);\n }\n for (const id of this.seen) {\n if (!ids.has(id)) this.beginLeave(id);\n }\n this.seen = ids;\n });\n }\n\n protected textOf(toast: Toast): string {\n try {\n return resolveToastText(toast.message);\n } catch {\n return '';\n }\n }\n\n protected textOfAction(toast: Toast): string {\n const action = toast.action;\n if (!action) return '';\n try {\n return resolveToastText(action.label);\n } catch {\n return '';\n }\n }\n\n protected iconFor(_kind: ToastKind): string {\n // Glyph comes from CSS ::before per kind (the span stays empty for AT).\n return '';\n }\n\n protected pause(id: number): void {\n this.manager()?.pause(id);\n }\n\n protected resume(id: number): void {\n this.manager()?.resume(id);\n }\n\n protected close(id: number): void {\n const manager = this.manager();\n if (manager) manager.dismiss(id);\n else this.dismissed.emit(id);\n }\n\n protected act(id: number): void {\n const manager = this.manager();\n if (manager) manager.runAction(id);\n else this.actionPressed.emit(id);\n }\n\n private beginLeave(id: number): void {\n if (typeof window === 'undefined') return; // SSR: drop, never hang render\n const current = this.snapshot(id);\n if (!current) return;\n const entry: LeavingToast = { toast: current };\n this.leaving.update((list) => [...list.filter((l) => l.toast.id !== id), entry]);\n setTimeout(() => {\n this.leaving.update((list) => list.filter((l) => l.toast.id !== id));\n }, LEAVE_MS);\n }\n\n private snapshot(id: number): Toast | null {\n return this.known.get(id) ?? null;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rivinia-toast-ui.mjs","names":[],"sources":["../../../projects/toast/ui/src/lib/toast-list.ts"],"sourcesContent":["import {\n AfterViewInit,\n Component,\n computed,\n effect,\n ElementRef,\n viewChild,\n input,\n output,\n signal,\n} from '@angular/core';\nimport type { Toast, ToastKind, ToastOptions } from '@rivinia/toast';\nimport { resolveToastText } from '@rivinia/toast';\nimport type { ToastAnimation, ToastPosition } from './ui-toast-types';\n\nconst LEAVE_MS = 150;\n\n/**\n * The structural slice of `RiviniaToast` the list actually drives. The full\n * manager satisfies it from any module realm; test doubles need five methods.\n * When both `manager` and `toasts` are given, the manager wins (single\n * source of truth — the UI never races Core).\n */\nexport interface RiviniaToastListManager {\n readonly active: () => readonly Toast[];\n show(options: ToastOptions): number | null;\n dismiss(id: number): boolean;\n pause(id: number): boolean;\n resume(id: number): boolean;\n runAction(id: number): boolean;\n}\n\n/** A toast snapshot kept rendering while its 150 ms exit plays. */\ninterface LeavingToast {\n readonly toast: Toast;\n}\n\n/**\n * Ready-made toast stack. Two modes (manager wins when both are given):\n *\n * - Managed: `[manager]=\"injectRiviniaToast()\"` — renders `active()`,\n * hover pauses, action button runs `runAction` (throws propagate to the\n * app's `ErrorHandler`), X dismisses. Dismissed items play a 150 ms exit\n * from their last snapshot, then vanish (browser only — SSR drops them).\n * - Presentational: `[toasts]=\"[...]\"` + `(dismissed)/(actionPressed)` —\n * no manager needed. Timer/pause state is yours.\n *\n * Each item is `role=\"status\"` (polite by default; `error` kind asserts).\n * Stack order is `active()` order (oldest first), anchored at `position`.\n */\n@Component({\n selector: 'rv-toast-list',\n template: `\n <div\n #panel\n class=\"rv-toasts\"\n role=\"region\"\n aria-label=\"Notifications\"\n [attr.popover]=\"topLayerSupported ? 'manual' : null\"\n [attr.data-position]=\"position()\"\n [attr.data-animation]=\"animation()\"\n [attr.data-rich-colors]=\"richColors()\"\n >\n @for (item of rendered(); track item.toast.id) {\n <div\n class=\"rv-toast\"\n [attr.data-kind]=\"item.toast.kind\"\n [attr.data-state]=\"item.leaving ? 'leaving' : item.toast.paused ? 'paused' : 'active'\"\n role=\"status\"\n [attr.aria-live]=\"item.toast.kind === 'error' ? 'assertive' : 'polite'\"\n (mouseenter)=\"pause(item.toast.id)\"\n (mouseleave)=\"resume(item.toast.id)\"\n (focusin)=\"pause(item.toast.id)\"\n (focusout)=\"resume(item.toast.id)\"\n >\n <span class=\"rv-toast-icon\" aria-hidden=\"true\">{{ iconFor(item.toast.kind) }}</span>\n <p class=\"rv-toast-message\">{{ textOf(item.toast) }}</p>\n @if (item.toast.action) {\n <button type=\"button\" class=\"rv-toast-action\" (click)=\"act(item.toast.id)\">\n {{ textOfAction(item.toast) }}\n </button>\n }\n @if (showCloseButton()) {\n <button\n type=\"button\"\n class=\"rv-toast-close\"\n (click)=\"close(item.toast.id)\"\n aria-label=\"Dismiss notification\"\n >\n <span aria-hidden=\"true\">×</span>\n </button>\n }\n </div>\n }\n </div>\n `,\n styles: [\n `\n .rv-toasts {\n /*\n * The PALETTE tokens are NOT declared here — see the same note in\n * @rivinia/modal. A declaration on the element out-ranks an\n * ancestor's value, so the shared semantic layer would never reach\n * the toast. They are read as\n * var(--rv-toast-*, var(--rv-<semantic>, <default>)) instead.\n *\n * The LAYOUT tokens (gap, width, duration) stay here: they are\n * component-specific and have no semantic counterpart.\n */\n --rv-toast-gap: 0.5rem;\n --rv-toast-width: 22rem;\n --rv-toast-duration: 180ms;\n --rv-toast-easing: cubic-bezier(0.21, 1.02, 0.73, 1);\n --rv-toast-success: #34c759;\n --rv-toast-error: #ff453a;\n --rv-toast-info: #0a84ff;\n --rv-toast-warning: #ff9f0a;\n position: fixed;\n z-index: var(--rv-toast-z, 1000);\n /*\n * Reset the POPOVER's UA styles.\n *\n * A popover gets a background, a border, padding and overflow: auto\n * from the user agent — which puts a shared canvas and a scrollbar\n * around the stack, so the toasts read as one block instead of\n * separate cards. The list is a transparent, borderless column.\n */\n background: transparent;\n border: 0;\n padding: 0;\n color: inherit;\n overflow: visible;\n height: auto;\n\n /*\n * The list is a manual popover, so it lives in the browser's TOP\n * LAYER — the same layer a modal <dialog> uses. A top layer stacks\n * in the order elements are shown, so a toast shown while a dialog is\n * open lands ABOVE it. A z-index cannot do this: a dialog in the top\n * layer is above every z-index in the document.\n *\n * The popover UA style sets inset: 0 and margin: auto, which would\n * over-constrain the box. Reset it, so the per-position rules below\n * have the last word.\n */\n inset: auto;\n margin: 0;\n display: flex;\n flex-direction: column;\n gap: var(--rv-toast-gap);\n width: var(--rv-toast-width);\n max-width: calc(100dvw - 2rem);\n pointer-events: none;\n }\n .rv-toasts > * {\n pointer-events: auto;\n }\n .rv-toasts[data-position='top-start'],\n .rv-toasts[data-position='bottom-start'] {\n inset-inline-start: 1rem;\n }\n .rv-toasts[data-position='top-end'],\n .rv-toasts[data-position='bottom-end'] {\n inset-inline-end: 1rem;\n }\n .rv-toasts[data-position='top-center'],\n .rv-toasts[data-position='bottom-center'] {\n inset-inline: 0;\n margin-inline: auto;\n }\n .rv-toasts[data-position^='top'] {\n inset-block-start: 1rem;\n }\n .rv-toasts[data-position^='bottom'] {\n inset-block-end: 1rem;\n }\n .rv-toast {\n display: flex;\n align-items: flex-start;\n gap: 0.625rem;\n background: var(--rv-toast-bg, var(--rv-bg, #ffffff));\n color: var(--rv-toast-fg, var(--rv-fg, #111111));\n border: 1px solid var(--rv-toast-border, var(--rv-border, #e0e0e0));\n border-inline-start: 3px solid var(--rv-toast-muted, var(--rv-muted, #555555));\n border-radius: var(--rv-toast-radius, var(--rv-radius, 12px));\n box-shadow: var(--rv-toast-shadow, var(--rv-shadow, 0 12px 32px rgba(0, 0, 0, 0.32)));\n padding: 12px 14px;\n }\n .rv-toast[data-kind='success'] {\n border-inline-start-color: var(--rv-toast-success, var(--rv-ok, #16a34a));\n }\n .rv-toast[data-kind='error'] {\n border-inline-start-color: var(--rv-toast-error, var(--rv-danger, #dc2626));\n }\n .rv-toast[data-kind='info'] {\n border-inline-start-color: var(--rv-toast-info, var(--rv-info, #2563eb));\n }\n .rv-toast[data-kind='warning'] {\n border-inline-start-color: var(--rv-toast-warning, var(--rv-warning, #d97706));\n }\n .rv-toasts[data-rich-colors='true'] .rv-toast[data-kind='success'] {\n background: var(--rv-toast-success, var(--rv-ok, #16a34a));\n color: #06130a;\n }\n .rv-toasts[data-rich-colors='true'] .rv-toast[data-kind='error'] {\n background: var(--rv-toast-error, var(--rv-danger, #dc2626));\n color: #1c0605;\n }\n .rv-toasts[data-rich-colors='true'] .rv-toast[data-kind='info'] {\n background: var(--rv-toast-info, var(--rv-info, #2563eb));\n color: #04121f;\n }\n .rv-toasts[data-rich-colors='true'] .rv-toast[data-kind='warning'] {\n background: var(--rv-toast-warning, var(--rv-warning, #d97706));\n color: #1d1204;\n }\n .rv-toast-icon {\n flex: none;\n font-weight: 700;\n }\n .rv-toast[data-kind='success'] .rv-toast-icon::before {\n content: '✓';\n }\n .rv-toast[data-kind='error'] .rv-toast-icon::before {\n content: '!';\n }\n .rv-toast[data-kind='info'] .rv-toast-icon::before {\n content: 'i';\n }\n .rv-toast[data-kind='warning'] .rv-toast-icon::before {\n content: '⚠';\n }\n .rv-toast-message {\n margin: 0;\n flex: 1;\n font-size: 0.875rem;\n line-height: 1.45;\n }\n .rv-toast-action,\n .rv-toast-close {\n flex: none;\n cursor: pointer;\n border-radius: 0.5rem;\n border: 1px solid var(--rv-toast-border, var(--rv-border, #e0e0e0));\n background: transparent;\n color: inherit;\n padding: 0.25rem 0.625rem;\n font-size: 0.8125rem;\n }\n .rv-toast-action:focus-visible,\n .rv-toast-close:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n .rv-toasts[data-animation='slide'] .rv-toast[data-state='active'] {\n animation: rv-toast-in var(--rv-toast-duration) var(--rv-toast-easing);\n }\n .rv-toasts[data-animation='fade'] .rv-toast[data-state='active'] {\n animation: rv-toast-in-fade var(--rv-toast-duration) ease-out;\n }\n .rv-toast[data-state='leaving'] {\n animation: rv-toast-out 150ms ease-in forwards;\n }\n @keyframes rv-toast-in {\n from {\n opacity: 0;\n transform: translateY(12px);\n }\n }\n @keyframes rv-toast-in-fade {\n from {\n opacity: 0;\n }\n }\n @keyframes rv-toast-out {\n to {\n opacity: 0;\n transform: translateY(8px);\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .rv-toasts .rv-toast {\n animation: none;\n }\n }\n `,\n ],\n})\nexport class RiviniaToastList implements AfterViewInit {\n /**\n * Whether the browser can put the list in the top layer.\n *\n * Where it cannot, the list stays in the document and a `z-index` decides\n * its order — which puts it BELOW a modal dialog, because a dialog in the\n * top layer is above every z-index. The limitation is documented.\n */\n protected readonly topLayerSupported =\n typeof HTMLElement !== 'undefined' && 'showPopover' in HTMLElement.prototype;\n\n /**\n * Put the list in the top layer.\n *\n * The top layer stacks in the order elements are shown, so a toast shown\n * while a dialog is open lands above it. A `z-index` cannot do this.\n *\n * A manual popover is not modal: it does not trap focus, does not make the\n * page inert, and does not close on an outside click — so it behaves like\n * the list it replaces.\n */\n /** The panel — the element that becomes the popover. */\n private readonly panel = viewChild<ElementRef<HTMLElement>>('panel');\n\n /**\n * Put the list in the top layer.\n *\n * `ngAfterViewInit` rather than `afterNextRender`: the callback has to run\n * with the view in the DOM and the `popover` attribute already applied, and\n * `showPopover()` on an element that is not a popover yet throws\n * NotSupportedError — measured, not assumed.\n */\n ngAfterViewInit(): void {\n this.promote();\n }\n\n /**\n * Put the list at the TOP of the top layer.\n *\n * The top layer stacks in the order elements are SHOWN, so a list that was\n * shown when the app started sits BELOW a dialog opened later. Re-showing it\n * moves it back to the top — which is what \"a toast appears above the\n * dialog\" means.\n *\n * `showPopover()` on an open popover throws, so it is hidden first.\n */\n private promote(): void {\n if (!this.topLayerSupported) return;\n // The POPOVER is the panel, not the component host: the host is the\n // `<rv-toast-list>` element, and showing THAT would put the wrong box in\n // the top layer.\n const el = this.panel()?.nativeElement as\n | (HTMLElement & { showPopover?: () => void; hidePopover?: () => void })\n | undefined;\n if (!el || typeof el.showPopover !== 'function') return;\n // Set imperatively as well as by the template binding: the binding can\n // land after this hook, and the imperative set makes the order irrelevant.\n el.setAttribute('popover', 'manual');\n try {\n if (el.matches(':popover-open')) el.hidePopover?.();\n el.showPopover();\n } catch {\n // The browser refused. The list still works — it is just not in the top\n // layer.\n }\n }\n\n /**\n * Re-promote the list whenever its contents change.\n *\n * A toast that appears while a dialog is open must be ABOVE it, and a\n * dialog opened after the list was shown pushes the list down in the top\n * layer. Promoting on every change puts the list back on top each time.\n */\n private readonly keepOnTop = effect(() => {\n // Track the rendered items, so the effect runs when the list changes.\n this.rendered();\n this.promote();\n });\n\n /** Managed mode: the service (or a structural test double). Wins over `toasts`. */\n readonly manager = input<RiviniaToastListManager | null>(null);\n /** Presentational mode: snapshots you own (timers/pause are yours). */\n readonly toasts = input<readonly Toast[]>([]);\n /** Stack anchor. @default 'bottom-end' */\n readonly position = input<ToastPosition>('bottom-end');\n /** Entrance animation. @default 'slide' */\n readonly animation = input<ToastAnimation>('slide');\n /** Kind-tinted rail (default) vs full-bleed kind colors. @default false */\n readonly richColors = input<boolean>(false);\n /** Render the X button. @default true */\n readonly showCloseButton = input<boolean>(true);\n\n /** Presentational-mode dismiss (id). Managed mode settles via the manager. */\n readonly dismissed = output<number>();\n /** Presentational-mode action press (id). */\n readonly actionPressed = output<number>();\n\n private readonly leaving = signal<readonly LeavingToast[]>([]);\n private seen = new Set<number>();\n private readonly known = new Map<number, Toast>();\n\n /** Rendered rows: live items plus exit-playing snapshots (browser only). */\n protected readonly rendered = computed(() => {\n const manager = this.manager();\n const live: readonly Toast[] = manager ? manager.active() : this.toasts();\n const liveIds = new Set(live.map((t) => t.id));\n const gone = this.leaving().filter((l) => !liveIds.has(l.toast.id));\n return [\n ...live.map((toast) => ({ toast, leaving: false })),\n ...gone.map((l) => ({ ...l, leaving: true })),\n ];\n });\n\n constructor() {\n effect(() => {\n // Track removals: anything that was live and isn't anymore plays exit.\n // Every run also refreshes the last-known snapshots the exit needs.\n const manager = this.manager();\n const live = manager ? manager.active() : this.toasts();\n const ids = new Set<number>();\n for (const toast of live) {\n ids.add(toast.id);\n this.known.set(toast.id, toast);\n }\n for (const id of this.seen) {\n if (!ids.has(id)) this.beginLeave(id);\n }\n this.seen = ids;\n });\n }\n\n protected textOf(toast: Toast): string {\n try {\n return resolveToastText(toast.message);\n } catch {\n return '';\n }\n }\n\n protected textOfAction(toast: Toast): string {\n const action = toast.action;\n if (!action) return '';\n try {\n return resolveToastText(action.label);\n } catch {\n return '';\n }\n }\n\n protected iconFor(_kind: ToastKind): string {\n // Glyph comes from CSS ::before per kind (the span stays empty for AT).\n return '';\n }\n\n protected pause(id: number): void {\n this.manager()?.pause(id);\n }\n\n protected resume(id: number): void {\n this.manager()?.resume(id);\n }\n\n protected close(id: number): void {\n const manager = this.manager();\n if (manager) manager.dismiss(id);\n else this.dismissed.emit(id);\n }\n\n protected act(id: number): void {\n const manager = this.manager();\n if (manager) manager.runAction(id);\n else this.actionPressed.emit(id);\n }\n\n private beginLeave(id: number): void {\n if (typeof window === 'undefined') return; // SSR: drop, never hang render\n const current = this.snapshot(id);\n if (!current) return;\n const entry: LeavingToast = { toast: current };\n this.leaving.update((list) => [...list.filter((l) => l.toast.id !== id), entry]);\n setTimeout(() => {\n this.leaving.update((list) => list.filter((l) => l.toast.id !== id));\n }, LEAVE_MS);\n }\n\n private snapshot(id: number): Toast | null {\n return this.known.get(id) ?? null;\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Toast, ToastKind, ToastOptions } from "@rivinia/toast";
|
|
2
1
|
import * as i0 from "@angular/core";
|
|
2
|
+
import { AfterViewInit } from "@angular/core";
|
|
3
|
+
import { Toast, ToastKind, ToastOptions } from "@rivinia/toast";
|
|
3
4
|
/**
|
|
4
5
|
* Stack anchor. Logical (`start`/`end`) variants are RTL-aware. The user
|
|
5
6
|
* picks exactly one — default `'bottom-end'` (sonner-compatible default).
|
|
@@ -36,7 +37,55 @@ interface RiviniaToastListManager {
|
|
|
36
37
|
* Each item is `role="status"` (polite by default; `error` kind asserts).
|
|
37
38
|
* Stack order is `active()` order (oldest first), anchored at `position`.
|
|
38
39
|
*/
|
|
39
|
-
export declare class RiviniaToastList {
|
|
40
|
+
export declare class RiviniaToastList implements AfterViewInit {
|
|
41
|
+
/**
|
|
42
|
+
* Whether the browser can put the list in the top layer.
|
|
43
|
+
*
|
|
44
|
+
* Where it cannot, the list stays in the document and a `z-index` decides
|
|
45
|
+
* its order — which puts it BELOW a modal dialog, because a dialog in the
|
|
46
|
+
* top layer is above every z-index. The limitation is documented.
|
|
47
|
+
*/
|
|
48
|
+
protected readonly topLayerSupported: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Put the list in the top layer.
|
|
51
|
+
*
|
|
52
|
+
* The top layer stacks in the order elements are shown, so a toast shown
|
|
53
|
+
* while a dialog is open lands above it. A `z-index` cannot do this.
|
|
54
|
+
*
|
|
55
|
+
* A manual popover is not modal: it does not trap focus, does not make the
|
|
56
|
+
* page inert, and does not close on an outside click — so it behaves like
|
|
57
|
+
* the list it replaces.
|
|
58
|
+
*/
|
|
59
|
+
/** The panel — the element that becomes the popover. */
|
|
60
|
+
private readonly panel;
|
|
61
|
+
/**
|
|
62
|
+
* Put the list in the top layer.
|
|
63
|
+
*
|
|
64
|
+
* `ngAfterViewInit` rather than `afterNextRender`: the callback has to run
|
|
65
|
+
* with the view in the DOM and the `popover` attribute already applied, and
|
|
66
|
+
* `showPopover()` on an element that is not a popover yet throws
|
|
67
|
+
* NotSupportedError — measured, not assumed.
|
|
68
|
+
*/
|
|
69
|
+
ngAfterViewInit(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Put the list at the TOP of the top layer.
|
|
72
|
+
*
|
|
73
|
+
* The top layer stacks in the order elements are SHOWN, so a list that was
|
|
74
|
+
* shown when the app started sits BELOW a dialog opened later. Re-showing it
|
|
75
|
+
* moves it back to the top — which is what "a toast appears above the
|
|
76
|
+
* dialog" means.
|
|
77
|
+
*
|
|
78
|
+
* `showPopover()` on an open popover throws, so it is hidden first.
|
|
79
|
+
*/
|
|
80
|
+
private promote;
|
|
81
|
+
/**
|
|
82
|
+
* Re-promote the list whenever its contents change.
|
|
83
|
+
*
|
|
84
|
+
* A toast that appears while a dialog is open must be ABOVE it, and a
|
|
85
|
+
* dialog opened after the list was shown pushes the list down in the top
|
|
86
|
+
* layer. Promoting on every change puts the list back on top each time.
|
|
87
|
+
*/
|
|
88
|
+
private readonly keepOnTop;
|
|
40
89
|
/** Managed mode: the service (or a structural test double). Wins over `toasts`. */
|
|
41
90
|
readonly manager: import("@angular/core").InputSignal<RiviniaToastListManager | null>;
|
|
42
91
|
/** Presentational mode: snapshots you own (timers/pause are yours). */
|