@rivinia/toast 1.0.0 → 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 +4 -0
- package/README.md +34 -345
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -3,372 +3,61 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@rivinia/toast)
|
|
4
4
|
[](https://www.npmjs.com/package/@rivinia/toast)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
<img src="https://raw.githubusercontent.com/abdelfattahqandil21-oss/Revinia/toast/v1.0.0/assets/rivinia-abstract-mark-light.svg" alt="Rivinia" width="340" />
|
|
8
|
-
</p>
|
|
6
|
+
A signal-based Angular toast queue with an optional ready-made UI.
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
overflow handling. Core is headless; an optional standalone UI is available at
|
|
12
|
-
`@rivinia/toast/ui`.
|
|
13
|
-
|
|
14
|
-
- Package version: **1.0.0**
|
|
15
|
-
- Peer dependency: **`@angular/core` `^22.0.0`**
|
|
16
|
-
- No `tslib`, router, or HTTP peer dependency.
|
|
17
|
-
|
|
18
|
-
## Install and configure
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
npm install @rivinia/toast
|
|
22
|
-
ng add @rivinia/toast
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
`ng add` adds `provideRiviniaToast()` to a standalone app's `app.config.ts`; it
|
|
26
|
-
does not add the optional UI component. You can also configure the service
|
|
27
|
-
manually:
|
|
8
|
+
Package version: **1.0.1**
|
|
28
9
|
|
|
29
10
|
```ts
|
|
30
|
-
// src/app/app.config.ts
|
|
31
|
-
import type { ApplicationConfig } from '@angular/core';
|
|
32
|
-
import { provideRiviniaToast } from '@rivinia/toast';
|
|
33
|
-
|
|
34
|
-
export const appConfig: ApplicationConfig = {
|
|
35
|
-
providers: [
|
|
36
|
-
provideRiviniaToast({
|
|
37
|
-
duration: 5000,
|
|
38
|
-
durations: { error: 8000 },
|
|
39
|
-
maxVisible: 5,
|
|
40
|
-
overflow: 'dismiss-oldest',
|
|
41
|
-
}),
|
|
42
|
-
],
|
|
43
|
-
};
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
All provider fields are optional. Defaults are 5000 ms, five active toasts,
|
|
47
|
-
an unbounded queue, and `dismiss-oldest` overflow. `ng add` also accepts
|
|
48
|
-
`--duration` and `--max-visible`.
|
|
49
|
-
|
|
50
|
-
## Use with the optional UI
|
|
51
|
-
|
|
52
|
-
The UI is a separate secondary entry point; import its component explicitly.
|
|
53
|
-
This standalone example keeps the injected manager in a class field (injection
|
|
54
|
-
must not be called from the template):
|
|
55
|
-
|
|
56
|
-
```ts
|
|
57
|
-
// src/app/notifications.component.ts
|
|
58
11
|
import { Component } from '@angular/core';
|
|
59
12
|
import { injectRiviniaToast } from '@rivinia/toast';
|
|
60
13
|
import { RiviniaToastList } from '@rivinia/toast/ui';
|
|
61
|
-
|
|
62
|
-
@Component({
|
|
63
|
-
selector: 'app-notifications',
|
|
64
|
-
imports: [RiviniaToastList],
|
|
65
|
-
templateUrl: './notifications.component.html',
|
|
66
|
-
})
|
|
14
|
+
@Component({ imports: [RiviniaToastList], template: '<rv-toast-list />' })
|
|
67
15
|
export class NotificationsComponent {
|
|
68
16
|
readonly toast = injectRiviniaToast();
|
|
69
|
-
|
|
70
|
-
save(): void {
|
|
71
|
-
this.toast.show({
|
|
72
|
-
message: 'Changes saved',
|
|
73
|
-
kind: 'success',
|
|
74
|
-
dedupeKey: 'save',
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
saveWithRetry(retry: () => void): void {
|
|
79
|
-
this.toast.show({
|
|
80
|
-
message: 'Could not save',
|
|
81
|
-
kind: 'error',
|
|
82
|
-
duration: 0,
|
|
83
|
-
action: { label: 'Retry', onPress: retry },
|
|
84
|
-
});
|
|
85
|
-
}
|
|
17
|
+
save() { this.toast.show({ message: 'Changes saved', kind: 'success' }); }
|
|
86
18
|
}
|
|
87
19
|
```
|
|
88
20
|
|
|
89
|
-
|
|
90
|
-
<!-- src/app/notifications.component.html -->
|
|
91
|
-
<button type="button" (click)="save()">Save</button>
|
|
92
|
-
<rv-toast-list [manager]="toast" position="bottom-end" />
|
|
93
|
-
```
|
|
21
|
+
## Why @rivinia/toast
|
|
94
22
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
`animation="slide"`, `richColors=false`, and `showCloseButton=true`.
|
|
99
|
-
`position` supports `top`/`bottom` × `start`/`center`/`end` (logical start/end
|
|
100
|
-
work in RTL); `animation` supports `slide`, `fade`, and `none`. Errors use an
|
|
101
|
-
assertive live region; other kinds use polite announcements.
|
|
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.
|
|
102
26
|
|
|
103
|
-
|
|
104
|
-
yourself:
|
|
27
|
+
## Install
|
|
105
28
|
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
import type { Toast } from '@rivinia/toast';
|
|
110
|
-
import { RiviniaToastList } from '@rivinia/toast/ui';
|
|
111
|
-
|
|
112
|
-
@Component({
|
|
113
|
-
selector: 'app-toast-snapshots',
|
|
114
|
-
imports: [RiviniaToastList],
|
|
115
|
-
templateUrl: './toast-snapshots.component.html',
|
|
116
|
-
})
|
|
117
|
-
export class ToastSnapshotsComponent {
|
|
118
|
-
items: readonly Toast[] = [
|
|
119
|
-
{
|
|
120
|
-
id: 1,
|
|
121
|
-
kind: 'info',
|
|
122
|
-
message: 'A background task is ready',
|
|
123
|
-
duration: 0,
|
|
124
|
-
remaining: 0,
|
|
125
|
-
paused: false,
|
|
126
|
-
action: { label: 'Open', onPress: () => console.log('Open task') },
|
|
127
|
-
},
|
|
128
|
-
];
|
|
129
|
-
|
|
130
|
-
remove(id: number): void {
|
|
131
|
-
this.items = this.items.filter((item) => item.id !== id);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
run(id: number): void {
|
|
135
|
-
const item = this.items.find((candidate) => candidate.id === id);
|
|
136
|
-
if (!item?.action) return;
|
|
137
|
-
item.action.onPress();
|
|
138
|
-
if (item.action.dismissAfterPress ?? true) this.remove(id);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
```html
|
|
144
|
-
<!-- src/app/toast-snapshots.component.html -->
|
|
145
|
-
<rv-toast-list [toasts]="items" (dismissed)="remove($event)" (actionPressed)="run($event)" />
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
In this presentational mode your app owns timers, pause behavior, and action
|
|
149
|
-
handling. If both `manager` and `toasts` are supplied, the manager takes
|
|
150
|
-
precedence.
|
|
151
|
-
|
|
152
|
-
## Use the headless service
|
|
153
|
-
|
|
154
|
-
Core can be used without the UI. `active()` and `queue()` are readonly signals;
|
|
155
|
-
the active list is oldest-first. Timers begin only when a toast becomes active.
|
|
156
|
-
|
|
157
|
-
```ts
|
|
158
|
-
// src/app/save-status.component.ts
|
|
159
|
-
import { Component, inject } from '@angular/core';
|
|
160
|
-
import { resolveToastText, RiviniaToast } from '@rivinia/toast';
|
|
161
|
-
|
|
162
|
-
@Component({
|
|
163
|
-
selector: 'app-save-status',
|
|
164
|
-
templateUrl: './save-status.component.html',
|
|
165
|
-
})
|
|
166
|
-
export class SaveStatusComponent {
|
|
167
|
-
readonly toast = inject(RiviniaToast);
|
|
168
|
-
readonly resolveText = resolveToastText;
|
|
169
|
-
|
|
170
|
-
save(): void {
|
|
171
|
-
const id: number | null = this.toast.show({ message: 'Saving…' });
|
|
172
|
-
if (id === null) return; // possible with overflow: 'reject'
|
|
173
|
-
|
|
174
|
-
// A matching key updates the existing active or queued item.
|
|
175
|
-
this.toast.show({
|
|
176
|
-
message: 'Saved',
|
|
177
|
-
kind: 'success',
|
|
178
|
-
dedupeKey: 'save',
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
```html
|
|
185
|
-
<!-- src/app/save-status.component.html -->
|
|
186
|
-
<button type="button" (click)="save()">Save</button>
|
|
187
|
-
@for (item of toast.active(); track item.id) {
|
|
188
|
-
<p [attr.data-kind]="item.kind">{{ resolveText(item.message) }}</p>
|
|
189
|
-
}
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
`message` and action `label` accept either a string or a reactive string
|
|
193
|
-
signal, so they can reflect language changes. For a custom outlet, use the exported
|
|
194
|
-
`resolveToastText(message)` utility to read either shape.
|
|
195
|
-
|
|
196
|
-
## Theming
|
|
197
|
-
|
|
198
|
-
Every component reads **its own token → the shared semantic layer → its
|
|
199
|
-
built-in default**:
|
|
200
|
-
|
|
201
|
-
```css
|
|
202
|
-
--rv-toast-bg → --rv-bg → #ffffff
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
| Level | Token | Scope |
|
|
206
|
-
| -------------- | --------------- | ----------------------------------- |
|
|
207
|
-
| 1. per package | `--rv-toast-bg` | that toast only |
|
|
208
|
-
| 2. global | `--rv-bg` | every Rivinia component, everywhere |
|
|
209
|
-
| 3. default | — | what you get if you set nothing |
|
|
210
|
-
|
|
211
|
-
```css
|
|
212
|
-
@import '@rivinia/theme';
|
|
213
|
-
|
|
214
|
-
:root {
|
|
215
|
-
--rv-bg: #ffffff;
|
|
216
|
-
--rv-fg: #111111;
|
|
217
|
-
--rv-border: #e0e0e0;
|
|
218
|
-
}
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
Its defaults sit in `@layer rivinia-theme`, so your unlayered `:root` rules win
|
|
222
|
-
wherever they appear. **Map it on `:root`, never on a component element** — an
|
|
223
|
-
element-level declaration shadows the root value and the override silently
|
|
224
|
-
stops working.
|
|
225
|
-
|
|
226
|
-
### The library's own tokens
|
|
227
|
-
|
|
228
|
-
| Token | Default | Applies to |
|
|
229
|
-
| ------------------- | ----------------------------- | -------------- |
|
|
230
|
-
| `--rv-toast-bg` | `#ffffff` | the toast |
|
|
231
|
-
| `--rv-toast-fg` | `#111111` | the toast |
|
|
232
|
-
| `--rv-toast-muted` | `#555555` | the kind rail |
|
|
233
|
-
| `--rv-toast-border` | `#e0e0e0` | the toast |
|
|
234
|
-
| `--rv-toast-radius` | `12px` | the toast |
|
|
235
|
-
| `--rv-toast-shadow` | `0 12px 32px rgba(0,0,0,.32)` | the toast |
|
|
236
|
-
| `--rv-toast-gap` | `8px` | between toasts |
|
|
237
|
-
| `--rv-toast-width` | `22rem` | the list |
|
|
238
|
-
| `--rv-toast-z` | `1000` | the list |
|
|
239
|
-
|
|
240
|
-
The kind colours chain to the semantic layer too:
|
|
241
|
-
|
|
242
|
-
| Token | Semantic token |
|
|
243
|
-
| -------------------- | -------------- |
|
|
244
|
-
| `--rv-toast-success` | `--rv-ok` |
|
|
245
|
-
| `--rv-toast-error` | `--rv-danger` |
|
|
246
|
-
| `--rv-toast-info` | `--rv-info` |
|
|
247
|
-
| `--rv-toast-warning` | `--rv-warning` |
|
|
248
|
-
|
|
249
|
-
**No forced light mode** — there is no `prefers-color-scheme` block in this
|
|
250
|
-
package, so a toast never overrides your explicit choice.
|
|
251
|
-
|
|
252
|
-
### Which styling stack?
|
|
253
|
-
|
|
254
|
-
| Stack | Import |
|
|
255
|
-
| --------------- | -------------------------------------------- |
|
|
256
|
-
| **Plain CSS** | `@import '@rivinia/theme';` |
|
|
257
|
-
| **Tailwind v4** | `@import '@rivinia/theme/tailwind.css';` |
|
|
258
|
-
| **Bootstrap** | `@import '@rivinia/theme/bootstrap.css';` |
|
|
259
|
-
| **Sass / SCSS** | `@use '@rivinia/theme/scss';` |
|
|
260
|
-
| **Less** | `@import (less) '@rivinia/theme/theme.css';` |
|
|
261
|
-
|
|
262
|
-
## The list is in the top layer
|
|
263
|
-
|
|
264
|
-
A `position: fixed` list with a `z-index` is still in the DOCUMENT, so it is
|
|
265
|
-
clipped by an `overflow: hidden` ancestor and loses to any stacking context
|
|
266
|
-
above it — including the one a modal `<dialog>` creates.
|
|
267
|
-
|
|
268
|
-
The list is therefore a **manual popover**, which puts it in the browser's top
|
|
269
|
-
layer: the same layer a modal `<dialog>` uses. The top layer stacks in the
|
|
270
|
-
order elements are shown, and the list is re-shown whenever its contents
|
|
271
|
-
change, so a toast raised while a dialog is open lands above it. Measured: the
|
|
272
|
-
element at the toast's position is the toast, not the dialog.
|
|
273
|
-
|
|
274
|
-
A manual popover is not modal: it does not trap focus, does not make the page
|
|
275
|
-
inert, and does not close on an outside click — so it behaves exactly like
|
|
276
|
-
the list it replaces.
|
|
277
|
-
|
|
278
|
-
**The popover's user-agent styles are reset.** A popover gets a background, a
|
|
279
|
-
border, padding and `overflow: auto` from the UA, which puts a shared canvas
|
|
280
|
-
and a scrollbar around the stack — so the toasts read as one block instead of
|
|
281
|
-
separate cards. The list is a transparent, borderless column.
|
|
282
|
-
|
|
283
|
-
Where the browser has no `showPopover` the list stays in the document and a
|
|
284
|
-
`z-index` decides its order. The feature is detected, not assumed.
|
|
285
|
-
|
|
286
|
-
## The list must be MANAGED
|
|
287
|
-
|
|
288
|
-
`rv-toast-list` renders what it is given. Without a manager it has an empty
|
|
289
|
-
list and **no toast ever appears**:
|
|
290
|
-
|
|
291
|
-
```html
|
|
292
|
-
<!-- nothing shows: the list is presentational and empty -->
|
|
293
|
-
<rv-toast-list position="bottom-end" />
|
|
294
|
-
|
|
295
|
-
<!-- the manager drives it -->
|
|
296
|
-
<rv-toast-list [manager]="toast" position="bottom-end" />
|
|
29
|
+
```bash
|
|
30
|
+
npm install @rivinia/toast
|
|
31
|
+
ng add @rivinia/toast
|
|
297
32
|
```
|
|
298
33
|
|
|
299
|
-
##
|
|
300
|
-
|
|
301
|
-
| Export | Kind | Purpose |
|
|
302
|
-
| -------------------------------------------------------------------------------------------------------------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
303
|
-
| `provideRiviniaToast(config?)` | Provider | Configure defaults; see `RiviniaToastConfig`. |
|
|
304
|
-
| `RiviniaToast` | Service | `show`, `dismiss`, `dismissAll`, `update`, `pause`, `resume`, `runAction`; `active` and `queue` signals. |
|
|
305
|
-
| `injectRiviniaToast()` | Function | Shorthand for `inject(RiviniaToast)`. |
|
|
306
|
-
| UI secondary entry point | `@rivinia/toast/ui` | Optional managed or presentational standalone list and its UI types. |
|
|
307
|
-
| `Toast`, `ToastKind`, `ToastMessage`, `ToastAction`, `ToastOptions`, `ToastChanges`, `ToastOverflowPolicy`, `RiviniaToastConfig` | Types | Public service, configuration, and UI input types. |
|
|
308
|
-
| `resolveToastText`, `clampDuration` | Utilities | Resolve signal/string text; normalize a duration. |
|
|
309
|
-
|
|
310
|
-
Important details:
|
|
311
|
-
|
|
312
|
-
- `show()` returns a stable `number` ID, or `null` when rejected by the
|
|
313
|
-
`reject` policy or when a finite queue is full. `dismiss()` and `update()`
|
|
314
|
-
return `false` for unknown or dismissed IDs; `pause()`/`resume()` also return
|
|
315
|
-
`false` when the timer cannot be changed.
|
|
316
|
-
- A repeated `dedupeKey` reuses the active or queued toast ID and updates it
|
|
317
|
-
rather than stacking another toast. A matching `show()` without an `action`
|
|
318
|
-
clears the previous action; include it again if it should remain.
|
|
319
|
-
- A toast-specific `duration` overrides per-kind and global defaults.
|
|
320
|
-
`duration: 0`, negative values, not-a-number, and infinities mean persistent (no
|
|
321
|
-
timer). Per-kind defaults are set with `durations`, for example
|
|
322
|
-
`{ error: 8000 }`.
|
|
323
|
-
- `pause()` preserves the remaining time on an active timed toast. Persistent
|
|
324
|
-
or queued toasts cannot be paused. `resume()` restarts the saved time; if it
|
|
325
|
-
has already elapsed, the toast is dismissed.
|
|
326
|
-
- With `overflow: 'queue'`, new toasts wait without timers and are promoted
|
|
327
|
-
when a visible slot opens. Set `maxQueue` to bound that queue. With
|
|
328
|
-
`overflow: 'reject'`, `show()` returns `null`. `dismiss-oldest` replaces an
|
|
329
|
-
active toast, preferring timed, actionless toasts for eviction.
|
|
330
|
-
- `update(id, changes)` restarts an active timer; a queued item's timer starts
|
|
331
|
-
only on promotion. Changing `kind` re-applies that kind's default duration
|
|
332
|
-
unless the toast was given an explicit duration. `action: null` removes an
|
|
333
|
-
action.
|
|
334
|
-
- `runAction(id)` runs the callback, then dismisses by default.
|
|
335
|
-
`dismissAfterPress: false` keeps the toast visible. Callback errors propagate
|
|
336
|
-
to the caller (and, in managed UI use, Angular's `ErrorHandler`).
|
|
337
|
-
- No routing policy is built in. To clear timed notifications on navigation,
|
|
338
|
-
subscribe to your router in app code and call `dismiss(id)` as appropriate.
|
|
34
|
+
## Quick start
|
|
339
35
|
|
|
340
|
-
|
|
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.
|
|
341
39
|
|
|
342
|
-
|
|
40
|
+
## Two ways to use it
|
|
343
41
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
import { provideRiviniaToast, RiviniaToast } from '@rivinia/toast';
|
|
348
|
-
|
|
349
|
-
beforeEach(() => {
|
|
350
|
-
TestBed.configureTestingModule({ providers: [provideRiviniaToast()] });
|
|
351
|
-
vi.useFakeTimers();
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
afterEach(() => {
|
|
355
|
-
TestBed.resetTestingModule();
|
|
356
|
-
vi.useRealTimers();
|
|
357
|
-
});
|
|
42
|
+
| Ready-made UI | Headless service |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| `@rivinia/toast/ui` | `@rivinia/toast` |
|
|
358
45
|
|
|
359
|
-
|
|
360
|
-
const toast = TestBed.inject(RiviniaToast);
|
|
361
|
-
const id = toast.show({ message: 'Saved', duration: 1000 });
|
|
362
|
-
|
|
363
|
-
vi.advanceTimersByTime(1000);
|
|
364
|
-
expect(toast.active().some((item) => item.id === id)).toBe(false);
|
|
365
|
-
});
|
|
366
|
-
```
|
|
46
|
+
The UI and controller are independent; use either or combine them.
|
|
367
47
|
|
|
368
|
-
##
|
|
48
|
+
## Compatibility
|
|
369
49
|
|
|
370
|
-
|
|
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 |
|
|
371
53
|
|
|
372
|
-
##
|
|
54
|
+
## Go further
|
|
373
55
|
|
|
374
|
-
|
|
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) |
|