@rivinia/toast 0.5.1 → 1.0.1
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/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +34 -337
- package/fesm2022/rivinia-toast-ui.mjs +5 -92
- package/fesm2022/rivinia-toast-ui.mjs.map +1 -1
- package/fesm2022/rivinia-toast.mjs +3 -3
- package/fesm2022/rivinia-toast.mjs.map +1 -1
- package/package.json +13 -1
- package/types/rivinia-toast.d.ts +1 -8
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rivinia contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -3,364 +3,61 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@rivinia/toast)
|
|
4
4
|
[](https://www.npmjs.com/package/@rivinia/toast)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
overflow handling. Core is headless; an optional standalone UI is available at
|
|
8
|
-
`@rivinia/toast/ui`.
|
|
6
|
+
A signal-based Angular toast queue with an optional ready-made UI.
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
- Peer dependency: **`@angular/core` `^22.0.0`**
|
|
12
|
-
- No `tslib`, router, or HTTP peer dependency.
|
|
13
|
-
|
|
14
|
-
## Install and configure
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
npm install @rivinia/toast
|
|
18
|
-
ng add @rivinia/toast
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
`ng add` adds `provideRiviniaToast()` to a standalone app's `app.config.ts`; it
|
|
22
|
-
does not add the optional UI component. You can also configure the service
|
|
23
|
-
manually:
|
|
24
|
-
|
|
25
|
-
```ts
|
|
26
|
-
// src/app/app.config.ts
|
|
27
|
-
import type { ApplicationConfig } from '@angular/core';
|
|
28
|
-
import { provideRiviniaToast } from '@rivinia/toast';
|
|
29
|
-
|
|
30
|
-
export const appConfig: ApplicationConfig = {
|
|
31
|
-
providers: [
|
|
32
|
-
provideRiviniaToast({
|
|
33
|
-
duration: 5000,
|
|
34
|
-
durations: { error: 8000 },
|
|
35
|
-
maxVisible: 5,
|
|
36
|
-
overflow: 'dismiss-oldest',
|
|
37
|
-
}),
|
|
38
|
-
],
|
|
39
|
-
};
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
All provider fields are optional. Defaults are 5000 ms, five active toasts,
|
|
43
|
-
an unbounded queue, and `dismiss-oldest` overflow. `ng add` also accepts
|
|
44
|
-
`--duration` and `--max-visible`.
|
|
45
|
-
|
|
46
|
-
## Use with the optional UI
|
|
47
|
-
|
|
48
|
-
The UI is a separate secondary entry point; import its component explicitly.
|
|
49
|
-
This standalone example keeps the injected manager in a class field (injection
|
|
50
|
-
must not be called from the template):
|
|
8
|
+
Package version: **1.0.1**
|
|
51
9
|
|
|
52
10
|
```ts
|
|
53
|
-
// src/app/notifications.component.ts
|
|
54
11
|
import { Component } from '@angular/core';
|
|
55
12
|
import { injectRiviniaToast } from '@rivinia/toast';
|
|
56
13
|
import { RiviniaToastList } from '@rivinia/toast/ui';
|
|
57
|
-
|
|
58
|
-
@Component({
|
|
59
|
-
selector: 'app-notifications',
|
|
60
|
-
imports: [RiviniaToastList],
|
|
61
|
-
templateUrl: './notifications.component.html',
|
|
62
|
-
})
|
|
14
|
+
@Component({ imports: [RiviniaToastList], template: '<rv-toast-list />' })
|
|
63
15
|
export class NotificationsComponent {
|
|
64
16
|
readonly toast = injectRiviniaToast();
|
|
65
|
-
|
|
66
|
-
save(): void {
|
|
67
|
-
this.toast.show({
|
|
68
|
-
message: 'Changes saved',
|
|
69
|
-
kind: 'success',
|
|
70
|
-
dedupeKey: 'save',
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
saveWithRetry(retry: () => void): void {
|
|
75
|
-
this.toast.show({
|
|
76
|
-
message: 'Could not save',
|
|
77
|
-
kind: 'error',
|
|
78
|
-
duration: 0,
|
|
79
|
-
action: { label: 'Retry', onPress: retry },
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
```
|
|
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
|
-
|
|
91
|
-
The standalone list renders the manager's `active()` signal, pauses timed
|
|
92
|
-
notifications on hover and keyboard focus, runs actions, and provides a
|
|
93
|
-
dismiss button by default. Its defaults are `position="bottom-end"`,
|
|
94
|
-
`animation="slide"`, `richColors=false`, and `showCloseButton=true`.
|
|
95
|
-
`position` supports `top`/`bottom` × `start`/`center`/`end` (logical start/end
|
|
96
|
-
work in RTL); `animation` supports `slide`, `fade`, and `none`. Errors use an
|
|
97
|
-
assertive live region; other kinds use polite announcements.
|
|
98
|
-
|
|
99
|
-
For snapshot-driven UIs, provide the snapshots and handle the emitted IDs
|
|
100
|
-
yourself:
|
|
101
|
-
|
|
102
|
-
```ts
|
|
103
|
-
// src/app/toast-snapshots.component.ts
|
|
104
|
-
import { Component } from '@angular/core';
|
|
105
|
-
import type { Toast } from '@rivinia/toast';
|
|
106
|
-
import { RiviniaToastList } from '@rivinia/toast/ui';
|
|
107
|
-
|
|
108
|
-
@Component({
|
|
109
|
-
selector: 'app-toast-snapshots',
|
|
110
|
-
imports: [RiviniaToastList],
|
|
111
|
-
templateUrl: './toast-snapshots.component.html',
|
|
112
|
-
})
|
|
113
|
-
export class ToastSnapshotsComponent {
|
|
114
|
-
items: readonly Toast[] = [
|
|
115
|
-
{
|
|
116
|
-
id: 1,
|
|
117
|
-
kind: 'info',
|
|
118
|
-
message: 'A background task is ready',
|
|
119
|
-
duration: 0,
|
|
120
|
-
remaining: 0,
|
|
121
|
-
paused: false,
|
|
122
|
-
action: { label: 'Open', onPress: () => console.log('Open task') },
|
|
123
|
-
},
|
|
124
|
-
];
|
|
125
|
-
|
|
126
|
-
remove(id: number): void {
|
|
127
|
-
this.items = this.items.filter((item) => item.id !== id);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
run(id: number): void {
|
|
131
|
-
const item = this.items.find((candidate) => candidate.id === id);
|
|
132
|
-
if (!item?.action) return;
|
|
133
|
-
item.action.onPress();
|
|
134
|
-
if (item.action.dismissAfterPress ?? true) this.remove(id);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
```html
|
|
140
|
-
<!-- src/app/toast-snapshots.component.html -->
|
|
141
|
-
<rv-toast-list [toasts]="items" (dismissed)="remove($event)" (actionPressed)="run($event)" />
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
In this presentational mode your app owns timers, pause behavior, and action
|
|
145
|
-
handling. If both `manager` and `toasts` are supplied, the manager takes
|
|
146
|
-
precedence.
|
|
147
|
-
|
|
148
|
-
## Use the headless service
|
|
149
|
-
|
|
150
|
-
Core can be used without the UI. `active()` and `queue()` are readonly signals;
|
|
151
|
-
the active list is oldest-first. Timers begin only when a toast becomes active.
|
|
152
|
-
|
|
153
|
-
```ts
|
|
154
|
-
// src/app/save-status.component.ts
|
|
155
|
-
import { Component, inject } from '@angular/core';
|
|
156
|
-
import { resolveToastText, RiviniaToast } from '@rivinia/toast';
|
|
157
|
-
|
|
158
|
-
@Component({
|
|
159
|
-
selector: 'app-save-status',
|
|
160
|
-
templateUrl: './save-status.component.html',
|
|
161
|
-
})
|
|
162
|
-
export class SaveStatusComponent {
|
|
163
|
-
readonly toast = inject(RiviniaToast);
|
|
164
|
-
readonly resolveText = resolveToastText;
|
|
165
|
-
|
|
166
|
-
save(): void {
|
|
167
|
-
const id: number | null = this.toast.show({ message: 'Saving…' });
|
|
168
|
-
if (id === null) return; // possible with overflow: 'reject'
|
|
169
|
-
|
|
170
|
-
// A matching key updates the existing active or queued item.
|
|
171
|
-
this.toast.show({
|
|
172
|
-
message: 'Saved',
|
|
173
|
-
kind: 'success',
|
|
174
|
-
dedupeKey: 'save',
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
```
|
|
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
|
-
|
|
188
|
-
`message` and action `label` accept either a string or a reactive string
|
|
189
|
-
signal, so they can reflect language changes. For a custom outlet, use the exported
|
|
190
|
-
`resolveToastText(message)` utility to read either shape.
|
|
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;
|
|
17
|
+
save() { this.toast.show({ message: 'Changes saved', kind: 'success' }); }
|
|
214
18
|
}
|
|
215
19
|
```
|
|
216
20
|
|
|
217
|
-
|
|
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` |
|
|
21
|
+
## Why @rivinia/toast
|
|
244
22
|
|
|
245
|
-
|
|
246
|
-
|
|
23
|
+
- A signal-based queue handles timers, actions, deduplication, and overflow.
|
|
24
|
+
- The service can be used without rendering the optional list component.
|
|
25
|
+
- Package-specific tokens and shared theme tokens can both style the UI.
|
|
247
26
|
|
|
248
|
-
|
|
27
|
+
## Install
|
|
249
28
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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" />
|
|
29
|
+
```bash
|
|
30
|
+
npm install @rivinia/toast
|
|
31
|
+
ng add @rivinia/toast
|
|
293
32
|
```
|
|
294
33
|
|
|
295
|
-
##
|
|
34
|
+
## Quick start
|
|
296
35
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
| `RiviniaToast` | Service | `show`, `dismiss`, `dismissAll`, `update`, `pause`, `resume`, `runAction`; `active` and `queue` signals. |
|
|
301
|
-
| `injectRiviniaToast()` | Function | Shorthand for `inject(RiviniaToast)`. |
|
|
302
|
-
| UI secondary entry point | `@rivinia/toast/ui` | Optional managed or presentational standalone list and its UI types. |
|
|
303
|
-
| `Toast`, `ToastKind`, `ToastMessage`, `ToastAction`, `ToastOptions`, `ToastChanges`, `ToastOverflowPolicy`, `RiviniaToastConfig` | Types | Public service, configuration, and UI input types. |
|
|
304
|
-
| `resolveToastText`, `clampDuration` | Utilities | Resolve signal/string text; normalize a duration. |
|
|
36
|
+
1. Register `provideRiviniaToast()` and import `RiviniaToastList`.
|
|
37
|
+
2. Inject the toast service in a component.
|
|
38
|
+
3. Render the list and show a notification.
|
|
305
39
|
|
|
306
|
-
|
|
40
|
+
## Two ways to use it
|
|
307
41
|
|
|
308
|
-
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
`false` when the timer cannot be changed.
|
|
312
|
-
- A repeated `dedupeKey` reuses the active or queued toast ID and updates it
|
|
313
|
-
rather than stacking another toast. A matching `show()` without an `action`
|
|
314
|
-
clears the previous action; include it again if it should remain.
|
|
315
|
-
- A toast-specific `duration` overrides per-kind and global defaults.
|
|
316
|
-
`duration: 0`, negative values, not-a-number, and infinities mean persistent (no
|
|
317
|
-
timer). Per-kind defaults are set with `durations`, for example
|
|
318
|
-
`{ error: 8000 }`.
|
|
319
|
-
- `pause()` preserves the remaining time on an active timed toast. Persistent
|
|
320
|
-
or queued toasts cannot be paused. `resume()` restarts the saved time; if it
|
|
321
|
-
has already elapsed, the toast is dismissed.
|
|
322
|
-
- With `overflow: 'queue'`, new toasts wait without timers and are promoted
|
|
323
|
-
when a visible slot opens. Set `maxQueue` to bound that queue. With
|
|
324
|
-
`overflow: 'reject'`, `show()` returns `null`. `dismiss-oldest` replaces an
|
|
325
|
-
active toast, preferring timed, actionless toasts for eviction.
|
|
326
|
-
- `update(id, changes)` restarts an active timer; a queued item's timer starts
|
|
327
|
-
only on promotion. Changing `kind` re-applies that kind's default duration
|
|
328
|
-
unless the toast was given an explicit duration. `action: null` removes an
|
|
329
|
-
action.
|
|
330
|
-
- `runAction(id)` runs the callback, then dismisses by default.
|
|
331
|
-
`dismissAfterPress: false` keeps the toast visible. Callback errors propagate
|
|
332
|
-
to the caller (and, in managed UI use, Angular's `ErrorHandler`).
|
|
333
|
-
- No routing policy is built in. To clear timed notifications on navigation,
|
|
334
|
-
subscribe to your router in app code and call `dismiss(id)` as appropriate.
|
|
42
|
+
| Ready-made UI | Headless service |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| `@rivinia/toast/ui` | `@rivinia/toast` |
|
|
335
45
|
|
|
336
|
-
|
|
46
|
+
The UI and controller are independent; use either or combine them.
|
|
337
47
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
```ts
|
|
341
|
-
import { TestBed } from '@angular/core/testing';
|
|
342
|
-
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
|
|
343
|
-
import { provideRiviniaToast, RiviniaToast } from '@rivinia/toast';
|
|
48
|
+
## Compatibility
|
|
344
49
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
afterEach(() => {
|
|
351
|
-
TestBed.resetTestingModule();
|
|
352
|
-
vi.useRealTimers();
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
it('auto-dismisses after the configured duration', () => {
|
|
356
|
-
const toast = TestBed.inject(RiviniaToast);
|
|
357
|
-
const id = toast.show({ message: 'Saved', duration: 1000 });
|
|
358
|
-
|
|
359
|
-
vi.advanceTimersByTime(1000);
|
|
360
|
-
expect(toast.active().some((item) => item.id === id)).toBe(false);
|
|
361
|
-
});
|
|
362
|
-
```
|
|
50
|
+
| Angular | Node | Browser verification |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| `^22.0.0` | `^22.22.3 or ^24.15.0 or >=26.0.0` | Chromium and Firefox; native Safari not verified |
|
|
363
53
|
|
|
364
|
-
##
|
|
54
|
+
## Go further
|
|
365
55
|
|
|
366
|
-
|
|
56
|
+
| Topic | Read |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| Ready-made UI | [Ready-made UI](https://github.com/abdelfattahqandil21-oss/Revinia/blob/toast/v1.0.1/docs/toast/ready-made-ui.md) |
|
|
59
|
+
| Headless usage | [Headless usage](https://github.com/abdelfattahqandil21-oss/Revinia/blob/toast/v1.0.1/docs/toast/headless.md) |
|
|
60
|
+
| Theming | [Theming](https://github.com/abdelfattahqandil21-oss/Revinia/blob/toast/v1.0.1/docs/toast/theming.md) |
|
|
61
|
+
| Top layer and managed list | [Top layer and managed list](https://github.com/abdelfattahqandil21-oss/Revinia/blob/toast/v1.0.1/docs/toast/top-layer-and-managed-list.md) |
|
|
62
|
+
| API reference | [API reference](https://github.com/abdelfattahqandil21-oss/Revinia/blob/toast/v1.0.1/docs/toast/api.md) |
|
|
63
|
+
| Testing | [Testing](https://github.com/abdelfattahqandil21-oss/Revinia/blob/toast/v1.0.1/docs/toast/testing.md) |
|
|
@@ -109,7 +109,7 @@ var RiviniaToastList = class RiviniaToastList {
|
|
|
109
109
|
}
|
|
110
110
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
111
111
|
minVersion: "12.0.0",
|
|
112
|
-
version: "22.2.
|
|
112
|
+
version: "22.2.3",
|
|
113
113
|
ngImport: i0,
|
|
114
114
|
type: RiviniaToastList,
|
|
115
115
|
deps: [],
|
|
@@ -117,7 +117,7 @@ var RiviniaToastList = class RiviniaToastList {
|
|
|
117
117
|
});
|
|
118
118
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
119
119
|
minVersion: "17.0.0",
|
|
120
|
-
version: "22.2.
|
|
120
|
+
version: "22.2.3",
|
|
121
121
|
type: RiviniaToastList,
|
|
122
122
|
isStandalone: true,
|
|
123
123
|
selector: "rv-toast-list",
|
|
@@ -177,107 +177,20 @@ var RiviniaToastList = class RiviniaToastList {
|
|
|
177
177
|
isSignal: true
|
|
178
178
|
}],
|
|
179
179
|
ngImport: i0,
|
|
180
|
-
template:
|
|
181
|
-
<div
|
|
182
|
-
#panel
|
|
183
|
-
class="rv-toasts"
|
|
184
|
-
role="region"
|
|
185
|
-
aria-label="Notifications"
|
|
186
|
-
[attr.popover]="topLayerSupported ? 'manual' : null"
|
|
187
|
-
[attr.data-position]="position()"
|
|
188
|
-
[attr.data-animation]="animation()"
|
|
189
|
-
[attr.data-rich-colors]="richColors()"
|
|
190
|
-
>
|
|
191
|
-
@for (item of rendered(); track item.toast.id) {
|
|
192
|
-
<div
|
|
193
|
-
class="rv-toast"
|
|
194
|
-
[attr.data-kind]="item.toast.kind"
|
|
195
|
-
[attr.data-state]="item.leaving ? 'leaving' : item.toast.paused ? 'paused' : 'active'"
|
|
196
|
-
role="status"
|
|
197
|
-
[attr.aria-live]="item.toast.kind === 'error' ? 'assertive' : 'polite'"
|
|
198
|
-
(mouseenter)="pause(item.toast.id)"
|
|
199
|
-
(mouseleave)="resume(item.toast.id)"
|
|
200
|
-
(focusin)="pause(item.toast.id)"
|
|
201
|
-
(focusout)="resume(item.toast.id)"
|
|
202
|
-
>
|
|
203
|
-
<span class="rv-toast-icon" aria-hidden="true">{{ iconFor(item.toast.kind) }}</span>
|
|
204
|
-
<p class="rv-toast-message">{{ textOf(item.toast) }}</p>
|
|
205
|
-
@if (item.toast.action) {
|
|
206
|
-
<button type="button" class="rv-toast-action" (click)="act(item.toast.id)">
|
|
207
|
-
{{ textOfAction(item.toast) }}
|
|
208
|
-
</button>
|
|
209
|
-
}
|
|
210
|
-
@if (showCloseButton()) {
|
|
211
|
-
<button
|
|
212
|
-
type="button"
|
|
213
|
-
class="rv-toast-close"
|
|
214
|
-
(click)="close(item.toast.id)"
|
|
215
|
-
aria-label="Dismiss notification"
|
|
216
|
-
>
|
|
217
|
-
<span aria-hidden="true">×</span>
|
|
218
|
-
</button>
|
|
219
|
-
}
|
|
220
|
-
</div>
|
|
221
|
-
}
|
|
222
|
-
</div>
|
|
223
|
-
`,
|
|
224
|
-
isInline: true,
|
|
180
|
+
template: "<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",
|
|
225
181
|
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"]
|
|
226
182
|
});
|
|
227
183
|
};
|
|
228
184
|
i0.ɵɵngDeclareClassMetadata({
|
|
229
185
|
minVersion: "12.0.0",
|
|
230
|
-
version: "22.2.
|
|
186
|
+
version: "22.2.3",
|
|
231
187
|
ngImport: i0,
|
|
232
188
|
type: RiviniaToastList,
|
|
233
189
|
decorators: [{
|
|
234
190
|
type: Component,
|
|
235
191
|
args: [{
|
|
236
192
|
selector: "rv-toast-list",
|
|
237
|
-
template:
|
|
238
|
-
<div
|
|
239
|
-
#panel
|
|
240
|
-
class="rv-toasts"
|
|
241
|
-
role="region"
|
|
242
|
-
aria-label="Notifications"
|
|
243
|
-
[attr.popover]="topLayerSupported ? 'manual' : null"
|
|
244
|
-
[attr.data-position]="position()"
|
|
245
|
-
[attr.data-animation]="animation()"
|
|
246
|
-
[attr.data-rich-colors]="richColors()"
|
|
247
|
-
>
|
|
248
|
-
@for (item of rendered(); track item.toast.id) {
|
|
249
|
-
<div
|
|
250
|
-
class="rv-toast"
|
|
251
|
-
[attr.data-kind]="item.toast.kind"
|
|
252
|
-
[attr.data-state]="item.leaving ? 'leaving' : item.toast.paused ? 'paused' : 'active'"
|
|
253
|
-
role="status"
|
|
254
|
-
[attr.aria-live]="item.toast.kind === 'error' ? 'assertive' : 'polite'"
|
|
255
|
-
(mouseenter)="pause(item.toast.id)"
|
|
256
|
-
(mouseleave)="resume(item.toast.id)"
|
|
257
|
-
(focusin)="pause(item.toast.id)"
|
|
258
|
-
(focusout)="resume(item.toast.id)"
|
|
259
|
-
>
|
|
260
|
-
<span class="rv-toast-icon" aria-hidden="true">{{ iconFor(item.toast.kind) }}</span>
|
|
261
|
-
<p class="rv-toast-message">{{ textOf(item.toast) }}</p>
|
|
262
|
-
@if (item.toast.action) {
|
|
263
|
-
<button type="button" class="rv-toast-action" (click)="act(item.toast.id)">
|
|
264
|
-
{{ textOfAction(item.toast) }}
|
|
265
|
-
</button>
|
|
266
|
-
}
|
|
267
|
-
@if (showCloseButton()) {
|
|
268
|
-
<button
|
|
269
|
-
type="button"
|
|
270
|
-
class="rv-toast-close"
|
|
271
|
-
(click)="close(item.toast.id)"
|
|
272
|
-
aria-label="Dismiss notification"
|
|
273
|
-
>
|
|
274
|
-
<span aria-hidden="true">×</span>
|
|
275
|
-
</button>
|
|
276
|
-
}
|
|
277
|
-
</div>
|
|
278
|
-
}
|
|
279
|
-
</div>
|
|
280
|
-
`,
|
|
193
|
+
template: "<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",
|
|
281
194
|
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"]
|
|
282
195
|
}]
|
|
283
196
|
}],
|
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
1
|
+
{"version":3,"file":"rivinia-toast-ui.mjs","names":[],"sources":["../../../projects/toast/ui/src/lib/components/toast-list/toast-list.ts","../../../projects/toast/ui/src/lib/components/toast-list/toast-list.html"],"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 '../../models/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 templateUrl: './toast-list.html',\n styleUrl: './toast-list.css',\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","<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"],"mappings":"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"}
|
|
@@ -258,7 +258,7 @@ var RiviniaToast = class RiviniaToast {
|
|
|
258
258
|
}
|
|
259
259
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
260
260
|
minVersion: "12.0.0",
|
|
261
|
-
version: "22.2.
|
|
261
|
+
version: "22.2.3",
|
|
262
262
|
ngImport: i0,
|
|
263
263
|
type: RiviniaToast,
|
|
264
264
|
deps: [],
|
|
@@ -266,14 +266,14 @@ var RiviniaToast = class RiviniaToast {
|
|
|
266
266
|
});
|
|
267
267
|
static ɵprov = i0.ɵɵngDeclareService({
|
|
268
268
|
minVersion: "22.0.0",
|
|
269
|
-
version: "22.2.
|
|
269
|
+
version: "22.2.3",
|
|
270
270
|
ngImport: i0,
|
|
271
271
|
type: RiviniaToast
|
|
272
272
|
});
|
|
273
273
|
};
|
|
274
274
|
i0.ɵɵngDeclareClassMetadata({
|
|
275
275
|
minVersion: "12.0.0",
|
|
276
|
-
version: "22.2.
|
|
276
|
+
version: "22.2.3",
|
|
277
277
|
ngImport: i0,
|
|
278
278
|
type: RiviniaToast,
|
|
279
279
|
decorators: [{ type: Service }],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rivinia-toast.mjs","names":[],"sources":["../../../projects/toast/src/lib/toast-utils.ts","../../../projects/toast/src/lib/toast-config.ts","../../../projects/toast/src/lib/rivinia-toast.ts","../../../projects/toast/src/lib/provide-toast.ts"],"sourcesContent":["import type { Signal } from '@angular/core';\nimport type { ToastMessage } from './toast-types';\n\n/**\n * Reads a toast message regardless of shape. Future UI entry points call this\n * in templates — one place to support `string | Signal<string>`.\n */\nexport function resolveToastText(message: ToastMessage): string {\n return typeof message === 'string' ? message : (message as Signal<string>)();\n}\n\n/**\n * Normalizes a duration into a safe non-negative integer.\n * `0`, negatives, `NaN` and non-finite values all mean persistent (no timer).\n * Rationale: a `setTimeout(Infinity)` silently overflows to ~1ms in browsers —\n * converting to persistent is the only non-surprising mapping.\n */\nexport function clampDuration(value: number | undefined, fallback: number): number {\n const candidate = value === undefined ? fallback : value;\n if (typeof candidate !== 'number' || !Number.isFinite(candidate) || candidate <= 0) {\n return 0;\n }\n return Math.floor(candidate);\n}\n","import { InjectionToken } from '@angular/core';\nimport type { ToastKind, ToastOverflowPolicy } from './toast-types';\n\n/**\n * Global configuration for `@rivinia/toast`.\n *\n * Every field is optional. Per-toast options always win over globals.\n * URLs, icons, positions, styles: intentionally absent — presentation is\n * app-owned (a future `@rivinia/toast/ui` entry point will consume this Core).\n */\nexport interface RiviniaToastConfig {\n /**\n * Default auto-dismiss delay in milliseconds.\n * @default 5000\n */\n duration?: number;\n /**\n * Per-kind default delays. A per-toast `duration` beats both levels.\n * @default {} (every kind falls back to `duration`)\n */\n durations?: Partial<Record<ToastKind, number>>;\n /**\n * Maximum simultaneously visible (active) toasts. Clamped to ≥ 1.\n * @default 5 (industry standard: shadcn `TOAST_LIMIT = 3`, BRT `MAX_VISIBLE = 5`)\n */\n maxVisible?: number;\n /**\n * Maximum queued toasts when `overflow: 'queue'`. Further shows are rejected.\n * @default Infinity (bounded only if you bound it — an unbounded queue is a\n * documented leak risk for hot paths; prefer `'dismiss-oldest'` there)\n */\n maxQueue?: number;\n /**\n * What happens when a new toast arrives while `maxVisible` are active.\n * - `'dismiss-oldest'`: dismiss the oldest active toast, show the new one.\n * - `'queue'`: hold the new toast (no timer runs) until room frees up.\n * - `'reject'`: `show()` returns `null`; nothing changes.\n * @default 'dismiss-oldest'\n */\n overflow?: ToastOverflowPolicy;\n}\n\n/**\n * Internal configuration token. Consumers must use `provideRiviniaToast()`.\n * @internal\n */\nexport const RIVINIA_TOAST_CONFIG = new InjectionToken<RiviniaToastConfig>(\n 'RIVINIA_TOAST_CONFIG',\n);\n\nexport const DEFAULT_DURATION = 5000;\nexport const DEFAULT_MAX_VISIBLE = 5;\n","import { DestroyRef, inject, Service, signal } from '@angular/core';\nimport {\n DEFAULT_DURATION,\n DEFAULT_MAX_VISIBLE,\n RIVINIA_TOAST_CONFIG,\n type RiviniaToastConfig,\n} from './toast-config';\nimport type { Toast, ToastChanges, ToastKind, ToastOptions } from './toast-types';\nimport { clampDuration } from './toast-utils';\n\ninterface TimerRecord {\n handle: ReturnType<typeof setTimeout>;\n startedAt: number;\n generation: number;\n}\n\n/**\n * Headless toast queue owner.\n *\n * State: two readonly signals — `active` (visible, capped) and `queue`\n * (overflow waiting, timerless). Who changes it: only the methods below.\n * Timers: one `setTimeout` per active timed toast, guarded by a per-toast\n * generation so a stale fire can never dismiss an updated toast. No DOM,\n * no styles, no router — presentation and navigation policy are app-owned\n * (see README recipes).\n */\n@Service()\nexport class RiviniaToast {\n private readonly config = inject<RiviniaToastConfig>(RIVINIA_TOAST_CONFIG, {\n optional: true,\n });\n\n private readonly _active = signal<Toast[]>([]);\n private readonly _queue = signal<Toast[]>([]);\n\n /** Visible toasts, oldest first. Never exceeds effective `maxVisible`. */\n readonly active = this._active.asReadonly();\n /** Overflow waiting for room. No timers run while queued. */\n readonly queue = this._queue.asReadonly();\n\n private readonly _timers = new Map<number, TimerRecord>();\n private readonly _paused = new Set<number>();\n /** Ids whose duration was set explicitly (per-toast override wins forever). */\n private readonly _explicitDuration = new Set<number>();\n private readonly _generations = new Map<number, number>();\n private readonly _dedupeByKey = new Map<string, number>();\n private readonly _dedupeKeyById = new Map<number, string>();\n private _nextId = 0;\n\n constructor() {\n inject(DestroyRef).onDestroy(() => this.clearTimers());\n }\n\n /**\n * Shows a toast. Returns its stable id, or `null` when rejected by the\n * `'reject'` overflow policy (or a full finite queue).\n */\n show(options: ToastOptions): number | null {\n if (options.dedupeKey !== undefined) {\n const existing = this.findByDedupeKey(options.dedupeKey);\n if (existing !== null) {\n this.update(existing, {\n message: options.message,\n kind: options.kind,\n duration: options.duration,\n action: options.action ?? null,\n });\n return existing;\n }\n }\n\n const id = ++this._nextId;\n const kind = options.kind ?? 'info';\n const duration = this.resolveDuration(kind, options.duration);\n if (options.duration !== undefined) this._explicitDuration.add(id);\n const toast: Toast = {\n id,\n kind,\n message: options.message,\n duration,\n remaining: duration,\n paused: false,\n action: options.action ?? null,\n };\n if (options.dedupeKey !== undefined) {\n this._dedupeByKey.set(options.dedupeKey, id);\n this._dedupeKeyById.set(id, options.dedupeKey);\n }\n\n if (this._active().length < this.maxVisible()) {\n this._active.update((list) => [...list, toast]);\n this.arm(id, duration);\n return id;\n }\n\n switch (this.config?.overflow ?? 'dismiss-oldest') {\n case 'queue': {\n if (this._queue().length >= this.maxQueue()) {\n this.forgetDedupe(id);\n return null;\n }\n this._queue.update((list) => [...list, toast]);\n return id;\n }\n case 'reject':\n this.forgetDedupe(id);\n return null;\n case 'dismiss-oldest':\n default: {\n // Prefer evicting the oldest dismissible toast: timed and actionless\n // first (a persistent error with a Retry action is never collateral).\n // Only when everything is persistent/with-action does the oldest go.\n const list = this._active();\n const victim = list.find((t) => t.duration > 0 && !t.action) ?? list[0];\n if (victim) this.dismiss(victim.id);\n this._active.update((next) => [...next, toast]);\n this.arm(id, duration);\n return id;\n }\n }\n }\n\n /**\n * Merges changes into an active or queued toast and restarts its timer\n * with the new duration (unchanged when `changes.duration` is absent).\n * Unknown ids (never shown or already dismissed): no-op, returns `false`.\n */\n update(id: number, changes: ToastChanges): boolean {\n const inActive = this._active().some((t) => t.id === id);\n // Duration resolution on update: an explicit new duration always wins and\n // becomes sticky; otherwise a kind change re-resolves the per-kind default\n // (unless the toast already carries an explicit duration).\n let nextDuration = 0;\n const apply = (t: Toast): Toast => {\n if (changes.duration !== undefined) {\n nextDuration = clampDuration(changes.duration, t.duration);\n this._explicitDuration.add(id);\n } else if (changes.kind !== undefined && !this._explicitDuration.has(id)) {\n nextDuration = this.fallbackDuration(changes.kind);\n } else {\n nextDuration = t.duration;\n }\n return {\n ...t,\n message: changes.message ?? t.message,\n kind: changes.kind ?? t.kind,\n duration: nextDuration,\n remaining: nextDuration,\n action: changes.action !== undefined ? changes.action : t.action,\n };\n };\n if (inActive) {\n this.disarm(id);\n let next: Toast | undefined;\n this._active.update((list) =>\n list.map((t) => {\n if (t.id !== id) return t;\n next = apply(t);\n return next;\n }),\n );\n if (next && !this._paused.has(id)) this.arm(id, next.duration);\n return true;\n }\n let found = false;\n this._queue.update((list) =>\n list.map((t) => {\n if (t.id !== id) return t;\n found = true;\n return apply(t);\n }),\n );\n return found;\n }\n\n /**\n * Removes a toast wherever it lives, clearing its timer. Unknown ids and\n * repeated calls: no-op, returns `false`. Removing an active toast promotes\n * the queue head (if any), whose timer starts at promotion.\n */\n dismiss(id: number): boolean {\n this.disarm(id);\n this._paused.delete(id);\n this._explicitDuration.delete(id);\n let removed = false;\n this._active.update((list) => {\n const next = list.filter((t) => t.id !== id);\n removed = next.length !== list.length;\n return next;\n });\n if (!removed) {\n this._queue.update((list) => {\n const next = list.filter((t) => t.id !== id);\n removed = next.length !== list.length;\n return next;\n });\n }\n if (removed) {\n this.forgetDedupe(id);\n this._generations.delete(id);\n this.promote();\n }\n return removed;\n }\n\n /** Clears timers, active and queue. Ids keep incrementing (never reused). */\n dismissAll(): void {\n this.clearTimers();\n this._paused.clear();\n this._explicitDuration.clear();\n this._generations.clear();\n this._active.set([]);\n this._queue.set([]);\n this._dedupeByKey.clear();\n this._dedupeKeyById.clear();\n }\n\n /**\n * Suspends the active timer, preserving `remaining` and marking `paused`.\n * UI binds this to hover/focus (WCAG 2.2.1 timing control). Returns `false`\n * for unknown ids, queued/persistent toasts, or already-paused timers —\n * a persistent toast can never be paused, so hover-leave cannot dismiss it.\n */\n pause(id: number): boolean {\n const record = this._timers.get(id);\n if (!record) return false;\n clearTimeout(record.handle);\n this._timers.delete(id);\n this._paused.add(id);\n const elapsed = Date.now() - record.startedAt;\n this._active.update((list) =>\n list.map((t) =>\n t.id === id ? { ...t, remaining: Math.max(0, t.remaining - elapsed), paused: true } : t,\n ),\n );\n return true;\n }\n\n /**\n * Restarts a paused timer with the preserved `remaining`. No-op (`false`) on\n * anything that was never paused — including persistent toasts. A paused\n * toast whose remaining reached zero is dismissed instead (it logically\n * already expired).\n */\n resume(id: number): boolean {\n if (!this._paused.has(id)) return false;\n this._paused.delete(id);\n const toast = this._active().find((t) => t.id === id);\n if (!toast) return false;\n if (toast.remaining <= 0) {\n this.dismiss(id);\n return true;\n }\n this._active.update((list) => list.map((t) => (t.id === id ? { ...t, paused: false } : t)));\n this.arm(id, toast.remaining);\n return true;\n }\n\n /**\n * Runs the toast action callback, then dismisses unless the action opts out\n * via `dismissAfterPress: false`. Callback exceptions propagate to the caller.\n * Returns `false` when the toast or its action is missing.\n */\n runAction(id: number): boolean {\n const toast = this._active().find((t) => t.id === id) ?? this._queue().find((t) => t.id === id);\n if (!toast?.action) return false;\n toast.action.onPress();\n if (toast.action.dismissAfterPress ?? true) this.dismiss(id);\n return true;\n }\n\n private maxVisible(): number {\n const value = this.config?.maxVisible ?? DEFAULT_MAX_VISIBLE;\n return Math.max(1, Math.floor(value));\n }\n\n private maxQueue(): number {\n const value = this.config?.maxQueue ?? Number.POSITIVE_INFINITY;\n return value < 0 ? 0 : value;\n }\n\n private resolveDuration(kind: ToastKind, override?: number): number {\n if (override !== undefined) return clampDuration(override, this.fallbackDuration(kind));\n return this.fallbackDuration(kind);\n }\n\n private fallbackDuration(kind: ToastKind): number {\n const perKind = this.config?.durations?.[kind];\n if (perKind !== undefined) return clampDuration(perKind, DEFAULT_DURATION);\n return clampDuration(this.config?.duration, DEFAULT_DURATION);\n }\n\n private arm(id: number, delay: number): void {\n if (delay <= 0) return;\n const generation = (this._generations.get(id) ?? 0) + 1;\n this._generations.set(id, generation);\n this._timers.set(id, {\n handle: setTimeout(() => {\n // Stale-guard: only the latest generation may dismiss.\n if (this._timers.get(id)?.generation === generation) this.dismiss(id);\n }, delay),\n startedAt: Date.now(),\n generation,\n });\n }\n\n private disarm(id: number): void {\n const record = this._timers.get(id);\n if (record) {\n clearTimeout(record.handle);\n this._timers.delete(id);\n }\n }\n\n private clearTimers(): void {\n for (const record of this._timers.values()) clearTimeout(record.handle);\n this._timers.clear();\n }\n\n private promote(): void {\n while (this._active().length < this.maxVisible() && this._queue().length > 0) {\n const head = this._queue()[0];\n this._queue.update((list) => list.slice(1));\n const fresh: Toast = { ...head, remaining: head.duration };\n this._active.update((list) => [...list, fresh]);\n this.arm(fresh.id, fresh.duration);\n }\n }\n\n private findByDedupeKey(key: string): number | null {\n const id = this._dedupeByKey.get(key);\n if (id === undefined) return null;\n const present =\n this._active().some((t) => t.id === id) || this._queue().some((t) => t.id === id);\n if (!present) {\n this.forgetDedupe(id);\n return null;\n }\n return id;\n }\n\n private forgetDedupe(id: number): void {\n const key = this._dedupeKeyById.get(id);\n if (key !== undefined) {\n this._dedupeKeyById.delete(id);\n if (this._dedupeByKey.get(key) === id) this._dedupeByKey.delete(key);\n }\n }\n}\n","import { EnvironmentProviders, inject, makeEnvironmentProviders } from '@angular/core';\nimport { RIVINIA_TOAST_CONFIG, type RiviniaToastConfig } from './toast-config';\nimport { RiviniaToast } from './rivinia-toast';\n\n/**\n * Configures `@rivinia/toast`. The queue is a root singleton; call once at\n * bootstrap. Presentation (outlet component, position, styles, icons,\n * translations) stays app-owned — a future `@rivinia/toast/ui` entry point\n * will consume the same `RiviniaToast` state.\n *\n * @example\n * ```ts\n * provideRiviniaToast({ duration: 5000, maxVisible: 5 })\n * ```\n */\nexport function provideRiviniaToast(config: RiviniaToastConfig = {}): EnvironmentProviders {\n return makeEnvironmentProviders([{ provide: RIVINIA_TOAST_CONFIG, useValue: config }]);\n}\n\n/** Shorthand for `inject(RiviniaToast)`. */\nexport function injectRiviniaToast(): RiviniaToast {\n return inject(RiviniaToast);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rivinia-toast.mjs","names":[],"sources":["../../../projects/toast/src/lib/utils/toast-utils.ts","../../../projects/toast/src/lib/models/toast-config.ts","../../../projects/toast/src/lib/services/rivinia-toast.ts","../../../projects/toast/src/lib/providers/provide-toast.ts"],"sourcesContent":["import type { Signal } from '@angular/core';\nimport type { ToastMessage } from '../models/toast-types';\n\n/**\n * Reads a toast message regardless of shape. Future UI entry points call this\n * in templates — one place to support `string | Signal<string>`.\n */\nexport function resolveToastText(message: ToastMessage): string {\n return typeof message === 'string' ? message : (message as Signal<string>)();\n}\n\n/**\n * Normalizes a duration into a safe non-negative integer.\n * `0`, negatives, `NaN` and non-finite values all mean persistent (no timer).\n * Rationale: a `setTimeout(Infinity)` silently overflows to ~1ms in browsers —\n * converting to persistent is the only non-surprising mapping.\n */\nexport function clampDuration(value: number | undefined, fallback: number): number {\n const candidate = value === undefined ? fallback : value;\n if (typeof candidate !== 'number' || !Number.isFinite(candidate) || candidate <= 0) {\n return 0;\n }\n return Math.floor(candidate);\n}\n","import { InjectionToken } from '@angular/core';\nimport type { ToastKind, ToastOverflowPolicy } from './toast-types';\n\n/**\n * Global configuration for `@rivinia/toast`.\n *\n * Every field is optional. Per-toast options always win over globals.\n * URLs, icons, positions, styles: intentionally absent — presentation is\n * app-owned (a future `@rivinia/toast/ui` entry point will consume this Core).\n */\nexport interface RiviniaToastConfig {\n /**\n * Default auto-dismiss delay in milliseconds.\n * @default 5000\n */\n duration?: number;\n /**\n * Per-kind default delays. A per-toast `duration` beats both levels.\n * @default {} (every kind falls back to `duration`)\n */\n durations?: Partial<Record<ToastKind, number>>;\n /**\n * Maximum simultaneously visible (active) toasts. Clamped to ≥ 1.\n * @default 5 (industry standard: shadcn `TOAST_LIMIT = 3`, BRT `MAX_VISIBLE = 5`)\n */\n maxVisible?: number;\n /**\n * Maximum queued toasts when `overflow: 'queue'`. Further shows are rejected.\n * @default Infinity (bounded only if you bound it — an unbounded queue is a\n * documented leak risk for hot paths; prefer `'dismiss-oldest'` there)\n */\n maxQueue?: number;\n /**\n * What happens when a new toast arrives while `maxVisible` are active.\n * - `'dismiss-oldest'`: dismiss the oldest active toast, show the new one.\n * - `'queue'`: hold the new toast (no timer runs) until room frees up.\n * - `'reject'`: `show()` returns `null`; nothing changes.\n * @default 'dismiss-oldest'\n */\n overflow?: ToastOverflowPolicy;\n}\n\n/**\n * Internal configuration token. Consumers must use `provideRiviniaToast()`.\n * @internal\n */\nexport const RIVINIA_TOAST_CONFIG = new InjectionToken<RiviniaToastConfig>(\n 'RIVINIA_TOAST_CONFIG',\n);\n\nexport const DEFAULT_DURATION = 5000;\nexport const DEFAULT_MAX_VISIBLE = 5;\n","import { DestroyRef, inject, Service, signal } from '@angular/core';\nimport {\n DEFAULT_DURATION,\n DEFAULT_MAX_VISIBLE,\n RIVINIA_TOAST_CONFIG,\n type RiviniaToastConfig,\n} from '../models/toast-config';\nimport type { Toast, ToastChanges, ToastKind, ToastOptions } from '../models/toast-types';\nimport { clampDuration } from '../utils/toast-utils';\n\ninterface TimerRecord {\n handle: ReturnType<typeof setTimeout>;\n startedAt: number;\n generation: number;\n}\n\n/**\n * Headless toast queue owner.\n *\n * State: two readonly signals — `active` (visible, capped) and `queue`\n * (overflow waiting, timerless). Who changes it: only the methods below.\n * Timers: one `setTimeout` per active timed toast, guarded by a per-toast\n * generation so a stale fire can never dismiss an updated toast. No DOM,\n * no styles, no router — presentation and navigation policy are app-owned\n * (see README recipes).\n */\n@Service()\nexport class RiviniaToast {\n private readonly config = inject<RiviniaToastConfig>(RIVINIA_TOAST_CONFIG, {\n optional: true,\n });\n\n private readonly _active = signal<Toast[]>([]);\n private readonly _queue = signal<Toast[]>([]);\n\n /** Visible toasts, oldest first. Never exceeds effective `maxVisible`. */\n readonly active = this._active.asReadonly();\n /** Overflow waiting for room. No timers run while queued. */\n readonly queue = this._queue.asReadonly();\n\n private readonly _timers = new Map<number, TimerRecord>();\n private readonly _paused = new Set<number>();\n /** Ids whose duration was set explicitly (per-toast override wins forever). */\n private readonly _explicitDuration = new Set<number>();\n private readonly _generations = new Map<number, number>();\n private readonly _dedupeByKey = new Map<string, number>();\n private readonly _dedupeKeyById = new Map<number, string>();\n private _nextId = 0;\n\n constructor() {\n inject(DestroyRef).onDestroy(() => this.clearTimers());\n }\n\n /**\n * Shows a toast. Returns its stable id, or `null` when rejected by the\n * `'reject'` overflow policy (or a full finite queue).\n */\n show(options: ToastOptions): number | null {\n if (options.dedupeKey !== undefined) {\n const existing = this.findByDedupeKey(options.dedupeKey);\n if (existing !== null) {\n this.update(existing, {\n message: options.message,\n kind: options.kind,\n duration: options.duration,\n action: options.action ?? null,\n });\n return existing;\n }\n }\n\n const id = ++this._nextId;\n const kind = options.kind ?? 'info';\n const duration = this.resolveDuration(kind, options.duration);\n if (options.duration !== undefined) this._explicitDuration.add(id);\n const toast: Toast = {\n id,\n kind,\n message: options.message,\n duration,\n remaining: duration,\n paused: false,\n action: options.action ?? null,\n };\n if (options.dedupeKey !== undefined) {\n this._dedupeByKey.set(options.dedupeKey, id);\n this._dedupeKeyById.set(id, options.dedupeKey);\n }\n\n if (this._active().length < this.maxVisible()) {\n this._active.update((list) => [...list, toast]);\n this.arm(id, duration);\n return id;\n }\n\n switch (this.config?.overflow ?? 'dismiss-oldest') {\n case 'queue': {\n if (this._queue().length >= this.maxQueue()) {\n this.forgetDedupe(id);\n return null;\n }\n this._queue.update((list) => [...list, toast]);\n return id;\n }\n case 'reject':\n this.forgetDedupe(id);\n return null;\n case 'dismiss-oldest':\n default: {\n // Prefer evicting the oldest dismissible toast: timed and actionless\n // first (a persistent error with a Retry action is never collateral).\n // Only when everything is persistent/with-action does the oldest go.\n const list = this._active();\n const victim = list.find((t) => t.duration > 0 && !t.action) ?? list[0];\n if (victim) this.dismiss(victim.id);\n this._active.update((next) => [...next, toast]);\n this.arm(id, duration);\n return id;\n }\n }\n }\n\n /**\n * Merges changes into an active or queued toast and restarts its timer\n * with the new duration (unchanged when `changes.duration` is absent).\n * Unknown ids (never shown or already dismissed): no-op, returns `false`.\n */\n update(id: number, changes: ToastChanges): boolean {\n const inActive = this._active().some((t) => t.id === id);\n // Duration resolution on update: an explicit new duration always wins and\n // becomes sticky; otherwise a kind change re-resolves the per-kind default\n // (unless the toast already carries an explicit duration).\n let nextDuration = 0;\n const apply = (t: Toast): Toast => {\n if (changes.duration !== undefined) {\n nextDuration = clampDuration(changes.duration, t.duration);\n this._explicitDuration.add(id);\n } else if (changes.kind !== undefined && !this._explicitDuration.has(id)) {\n nextDuration = this.fallbackDuration(changes.kind);\n } else {\n nextDuration = t.duration;\n }\n return {\n ...t,\n message: changes.message ?? t.message,\n kind: changes.kind ?? t.kind,\n duration: nextDuration,\n remaining: nextDuration,\n action: changes.action !== undefined ? changes.action : t.action,\n };\n };\n if (inActive) {\n this.disarm(id);\n let next: Toast | undefined;\n this._active.update((list) =>\n list.map((t) => {\n if (t.id !== id) return t;\n next = apply(t);\n return next;\n }),\n );\n if (next && !this._paused.has(id)) this.arm(id, next.duration);\n return true;\n }\n let found = false;\n this._queue.update((list) =>\n list.map((t) => {\n if (t.id !== id) return t;\n found = true;\n return apply(t);\n }),\n );\n return found;\n }\n\n /**\n * Removes a toast wherever it lives, clearing its timer. Unknown ids and\n * repeated calls: no-op, returns `false`. Removing an active toast promotes\n * the queue head (if any), whose timer starts at promotion.\n */\n dismiss(id: number): boolean {\n this.disarm(id);\n this._paused.delete(id);\n this._explicitDuration.delete(id);\n let removed = false;\n this._active.update((list) => {\n const next = list.filter((t) => t.id !== id);\n removed = next.length !== list.length;\n return next;\n });\n if (!removed) {\n this._queue.update((list) => {\n const next = list.filter((t) => t.id !== id);\n removed = next.length !== list.length;\n return next;\n });\n }\n if (removed) {\n this.forgetDedupe(id);\n this._generations.delete(id);\n this.promote();\n }\n return removed;\n }\n\n /** Clears timers, active and queue. Ids keep incrementing (never reused). */\n dismissAll(): void {\n this.clearTimers();\n this._paused.clear();\n this._explicitDuration.clear();\n this._generations.clear();\n this._active.set([]);\n this._queue.set([]);\n this._dedupeByKey.clear();\n this._dedupeKeyById.clear();\n }\n\n /**\n * Suspends the active timer, preserving `remaining` and marking `paused`.\n * UI binds this to hover/focus (WCAG 2.2.1 timing control). Returns `false`\n * for unknown ids, queued/persistent toasts, or already-paused timers —\n * a persistent toast can never be paused, so hover-leave cannot dismiss it.\n */\n pause(id: number): boolean {\n const record = this._timers.get(id);\n if (!record) return false;\n clearTimeout(record.handle);\n this._timers.delete(id);\n this._paused.add(id);\n const elapsed = Date.now() - record.startedAt;\n this._active.update((list) =>\n list.map((t) =>\n t.id === id ? { ...t, remaining: Math.max(0, t.remaining - elapsed), paused: true } : t,\n ),\n );\n return true;\n }\n\n /**\n * Restarts a paused timer with the preserved `remaining`. No-op (`false`) on\n * anything that was never paused — including persistent toasts. A paused\n * toast whose remaining reached zero is dismissed instead (it logically\n * already expired).\n */\n resume(id: number): boolean {\n if (!this._paused.has(id)) return false;\n this._paused.delete(id);\n const toast = this._active().find((t) => t.id === id);\n if (!toast) return false;\n if (toast.remaining <= 0) {\n this.dismiss(id);\n return true;\n }\n this._active.update((list) => list.map((t) => (t.id === id ? { ...t, paused: false } : t)));\n this.arm(id, toast.remaining);\n return true;\n }\n\n /**\n * Runs the toast action callback, then dismisses unless the action opts out\n * via `dismissAfterPress: false`. Callback exceptions propagate to the caller.\n * Returns `false` when the toast or its action is missing.\n */\n runAction(id: number): boolean {\n const toast = this._active().find((t) => t.id === id) ?? this._queue().find((t) => t.id === id);\n if (!toast?.action) return false;\n toast.action.onPress();\n if (toast.action.dismissAfterPress ?? true) this.dismiss(id);\n return true;\n }\n\n private maxVisible(): number {\n const value = this.config?.maxVisible ?? DEFAULT_MAX_VISIBLE;\n return Math.max(1, Math.floor(value));\n }\n\n private maxQueue(): number {\n const value = this.config?.maxQueue ?? Number.POSITIVE_INFINITY;\n return value < 0 ? 0 : value;\n }\n\n private resolveDuration(kind: ToastKind, override?: number): number {\n if (override !== undefined) return clampDuration(override, this.fallbackDuration(kind));\n return this.fallbackDuration(kind);\n }\n\n private fallbackDuration(kind: ToastKind): number {\n const perKind = this.config?.durations?.[kind];\n if (perKind !== undefined) return clampDuration(perKind, DEFAULT_DURATION);\n return clampDuration(this.config?.duration, DEFAULT_DURATION);\n }\n\n private arm(id: number, delay: number): void {\n if (delay <= 0) return;\n const generation = (this._generations.get(id) ?? 0) + 1;\n this._generations.set(id, generation);\n this._timers.set(id, {\n handle: setTimeout(() => {\n // Stale-guard: only the latest generation may dismiss.\n if (this._timers.get(id)?.generation === generation) this.dismiss(id);\n }, delay),\n startedAt: Date.now(),\n generation,\n });\n }\n\n private disarm(id: number): void {\n const record = this._timers.get(id);\n if (record) {\n clearTimeout(record.handle);\n this._timers.delete(id);\n }\n }\n\n private clearTimers(): void {\n for (const record of this._timers.values()) clearTimeout(record.handle);\n this._timers.clear();\n }\n\n private promote(): void {\n while (this._active().length < this.maxVisible() && this._queue().length > 0) {\n const head = this._queue()[0];\n this._queue.update((list) => list.slice(1));\n const fresh: Toast = { ...head, remaining: head.duration };\n this._active.update((list) => [...list, fresh]);\n this.arm(fresh.id, fresh.duration);\n }\n }\n\n private findByDedupeKey(key: string): number | null {\n const id = this._dedupeByKey.get(key);\n if (id === undefined) return null;\n const present =\n this._active().some((t) => t.id === id) || this._queue().some((t) => t.id === id);\n if (!present) {\n this.forgetDedupe(id);\n return null;\n }\n return id;\n }\n\n private forgetDedupe(id: number): void {\n const key = this._dedupeKeyById.get(id);\n if (key !== undefined) {\n this._dedupeKeyById.delete(id);\n if (this._dedupeByKey.get(key) === id) this._dedupeByKey.delete(key);\n }\n }\n}\n","import { EnvironmentProviders, inject, makeEnvironmentProviders } from '@angular/core';\nimport { RIVINIA_TOAST_CONFIG, type RiviniaToastConfig } from '../models/toast-config';\nimport { RiviniaToast } from '../services/rivinia-toast';\n\n/**\n * Configures `@rivinia/toast`. The queue is a root singleton; call once at\n * bootstrap. Presentation (outlet component, position, styles, icons,\n * translations) stays app-owned — a future `@rivinia/toast/ui` entry point\n * will consume the same `RiviniaToast` state.\n *\n * @example\n * ```ts\n * provideRiviniaToast({ duration: 5000, maxVisible: 5 })\n * ```\n */\nexport function provideRiviniaToast(config: RiviniaToastConfig = {}): EnvironmentProviders {\n return makeEnvironmentProviders([{ provide: RIVINIA_TOAST_CONFIG, useValue: config }]);\n}\n\n/** Shorthand for `inject(RiviniaToast)`. */\nexport function injectRiviniaToast(): RiviniaToast {\n return inject(RiviniaToast);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rivinia/toast",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Headless toast queue for Angular: signals, pause-on-hover timers, actions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -16,6 +16,18 @@
|
|
|
16
16
|
},
|
|
17
17
|
"sideEffects": false,
|
|
18
18
|
"schematics": "./schematics/collection.json",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/abdelfattahqandil21-oss/Revinia.git",
|
|
22
|
+
"directory": "projects/toast"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/abdelfattahqandil21-oss/Revinia/tree/toast/v1.0.0/projects/toast#readme",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/abdelfattahqandil21-oss/Revinia/issues"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": "^22.22.3 || ^24.15.0 || >=26.0.0"
|
|
30
|
+
},
|
|
19
31
|
"module": "fesm2022/rivinia-toast.mjs",
|
|
20
32
|
"typings": "types/rivinia-toast.d.ts",
|
|
21
33
|
"exports": {
|
package/types/rivinia-toast.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
|
-
import { EnvironmentProviders,
|
|
2
|
+
import { EnvironmentProviders, Signal } from "@angular/core";
|
|
3
3
|
/**
|
|
4
4
|
* Semantic severity. Core treats this as data only (default durations,
|
|
5
5
|
* UI icon/role mapping) — never as styling.
|
|
@@ -110,13 +110,6 @@ interface RiviniaToastConfig {
|
|
|
110
110
|
*/
|
|
111
111
|
overflow?: ToastOverflowPolicy;
|
|
112
112
|
}
|
|
113
|
-
/**
|
|
114
|
-
* Internal configuration token. Consumers must use `provideRiviniaToast()`.
|
|
115
|
-
* @internal
|
|
116
|
-
*/
|
|
117
|
-
declare const RIVINIA_TOAST_CONFIG: InjectionToken<RiviniaToastConfig>;
|
|
118
|
-
declare const DEFAULT_DURATION = 5000;
|
|
119
|
-
declare const DEFAULT_MAX_VISIBLE = 5;
|
|
120
113
|
/**
|
|
121
114
|
* Reads a toast message regardless of shape. Future UI entry points call this
|
|
122
115
|
* in templates — one place to support `string | Signal<string>`.
|