@radix-ng/primitives 1.0.0-beta.0 → 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/radix-ng-primitives-accordion.mjs +2 -2
- package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-calendar.mjs +109 -84
- package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-checkbox.mjs +2 -2
- package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-collapsible.mjs +1 -1
- package/fesm2022/radix-ng-primitives-collapsible.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-combobox.mjs +1923 -0
- package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-context-menu.mjs +1 -1
- package/fesm2022/radix-ng-primitives-context-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-core.mjs +591 -470
- package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-cropper.mjs +287 -308
- package/fesm2022/radix-ng-primitives-cropper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-date-field.mjs +66 -15
- package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-dialog.mjs +1 -1
- package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-drawer.mjs +7 -106
- package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-editable.mjs +305 -24
- package/fesm2022/radix-ng-primitives-editable.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-field.mjs +86 -6
- package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-fieldset.mjs +1 -1
- package/fesm2022/radix-ng-primitives-fieldset.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-focus-scope.mjs +1 -1
- package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-form.mjs +207 -0
- package/fesm2022/radix-ng-primitives-form.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-input.mjs +85 -4
- package/fesm2022/radix-ng-primitives-input.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menu.mjs +413 -5
- package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menubar.mjs +1 -1
- package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-meter.mjs +1 -1
- package/fesm2022/radix-ng-primitives-meter.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs +1 -1
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-number-field.mjs +2 -2
- package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popover.mjs +1 -1
- package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popper.mjs +22 -5
- package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-portal.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-preview-card.mjs +1 -1
- package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-progress.mjs +1 -1
- package/fesm2022/radix-ng-primitives-progress.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-roving-focus.mjs +1 -1
- package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-scroll-area.mjs +923 -0
- package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-select.mjs +421 -224
- package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-slider.mjs +1 -1
- package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-switch.mjs +3 -2
- package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-tabs.mjs +12 -3
- package/fesm2022/radix-ng-primitives-tabs.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-time-field.mjs +27 -3
- package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toast.mjs +839 -0
- package/fesm2022/radix-ng-primitives-toast.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-toggle-group.mjs +1 -1
- package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toolbar.mjs +2 -2
- package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-tooltip.mjs +11 -3
- package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
- package/package.json +18 -2
- package/schematics/ng-add/index.js +57 -0
- package/schematics/ng-add/index.js.map +1 -1
- package/schematics/ng-add/schema.d.ts +1 -0
- package/schematics/ng-add/schema.json +6 -0
- package/types/radix-ng-primitives-accordion.d.ts +3 -2
- package/types/radix-ng-primitives-calendar.d.ts +38 -18
- package/types/radix-ng-primitives-checkbox.d.ts +5 -5
- package/types/radix-ng-primitives-collapsible.d.ts +2 -1
- package/types/radix-ng-primitives-combobox.d.ts +1265 -0
- package/types/radix-ng-primitives-context-menu.d.ts +3 -2
- package/types/radix-ng-primitives-core.d.ts +187 -56
- package/types/radix-ng-primitives-cropper.d.ts +89 -56
- package/types/radix-ng-primitives-date-field.d.ts +11 -5
- package/types/radix-ng-primitives-dialog.d.ts +2 -1
- package/types/radix-ng-primitives-drawer.d.ts +5 -27
- package/types/radix-ng-primitives-editable.d.ts +90 -13
- package/types/radix-ng-primitives-field.d.ts +74 -4
- package/types/radix-ng-primitives-fieldset.d.ts +3 -2
- package/types/radix-ng-primitives-focus-scope.d.ts +2 -1
- package/types/radix-ng-primitives-form.d.ts +124 -0
- package/types/radix-ng-primitives-input.d.ts +75 -5
- package/types/radix-ng-primitives-menu.d.ts +16 -4
- package/types/radix-ng-primitives-menubar.d.ts +2 -1
- package/types/radix-ng-primitives-meter.d.ts +3 -2
- package/types/radix-ng-primitives-navigation-menu.d.ts +1 -1
- package/types/radix-ng-primitives-number-field.d.ts +6 -6
- package/types/radix-ng-primitives-popover.d.ts +2 -1
- package/types/radix-ng-primitives-popper.d.ts +19 -2
- package/types/radix-ng-primitives-preview-card.d.ts +1 -1
- package/types/radix-ng-primitives-progress.d.ts +3 -2
- package/types/radix-ng-primitives-roving-focus.d.ts +4 -3
- package/types/radix-ng-primitives-scroll-area.d.ts +253 -0
- package/types/radix-ng-primitives-select.d.ts +296 -136
- package/types/radix-ng-primitives-slider.d.ts +1 -1
- package/types/radix-ng-primitives-switch.d.ts +1 -1
- package/types/radix-ng-primitives-tabs.d.ts +1 -1
- package/types/radix-ng-primitives-toast.d.ts +378 -0
- package/types/radix-ng-primitives-toggle-group.d.ts +2 -1
- package/types/radix-ng-primitives-toolbar.d.ts +3 -2
- package/types/radix-ng-primitives-tooltip.d.ts +3 -2
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, signal, computed, Injectable, input, effect, Directive, assertInInjectionContext, DestroyRef, ElementRef, Injector, NgModule } from '@angular/core';
|
|
3
|
+
import { RdxIdGenerator, usePointerDrag, createContext, injectId, elementSize } from '@radix-ng/primitives/core';
|
|
4
|
+
import * as i1 from '@radix-ng/primitives/portal';
|
|
5
|
+
import { RdxPortal } from '@radix-ng/primitives/portal';
|
|
6
|
+
import * as i1$1 from '@radix-ng/primitives/popper';
|
|
7
|
+
import { RdxPopperContentWrapper, provideRdxPopperContentConfig, RdxPopper } from '@radix-ng/primitives/popper';
|
|
8
|
+
|
|
9
|
+
/** Default auto-dismiss delay (ms) applied when a toast omits `timeout`. */
|
|
10
|
+
const DEFAULT_TIMEOUT = 5000;
|
|
11
|
+
/** Max number of toasts kept in the queue; the oldest is dropped past this. */
|
|
12
|
+
const DEFAULT_LIMIT = 3;
|
|
13
|
+
/**
|
|
14
|
+
* App-level coordinator and imperative API for toasts — the Angular counterpart of Base UI's
|
|
15
|
+
* `<Toast.Provider>` plus `useToastManager()`. Holds the queue as a signal that `RdxToastViewport`
|
|
16
|
+
* renders, owns each toast's auto-dismiss timer (pausable while the viewport is hovered/focused),
|
|
17
|
+
* and exposes `add` / `update` / `close` / `promise`.
|
|
18
|
+
*
|
|
19
|
+
* Provide it once near the app root with {@link provideRdxToastManager} or the `[rdxToastProvider]`
|
|
20
|
+
* directive, then inject `RdxToastManager` anywhere to push toasts.
|
|
21
|
+
*/
|
|
22
|
+
class RdxToastManager {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.idGenerator = inject(RdxIdGenerator);
|
|
25
|
+
this._toasts = signal([], ...(ngDevMode ? [{ debugName: "_toasts" }] : /* istanbul ignore next */ []));
|
|
26
|
+
/** The live queue, oldest first. Render it in `RdxToastViewport` (e.g. via `@for`). */
|
|
27
|
+
this.toasts = this._toasts.asReadonly();
|
|
28
|
+
/** Whether any toast is currently in the queue. */
|
|
29
|
+
this.hasToasts = computed(() => this._toasts().length > 0, ...(ngDevMode ? [{ debugName: "hasToasts" }] : /* istanbul ignore next */ []));
|
|
30
|
+
this._expanded = signal(false, ...(ngDevMode ? [{ debugName: "_expanded" }] : /* istanbul ignore next */ []));
|
|
31
|
+
/** Whether the stack is expanded (viewport hovered or focused) — drives `data-expanded`. */
|
|
32
|
+
this.expanded = this._expanded.asReadonly();
|
|
33
|
+
this._heights = signal({}, ...(ngDevMode ? [{ debugName: "_heights" }] : /* istanbul ignore next */ []));
|
|
34
|
+
/** Measured heights (px) per toast id, reported by each `RdxToastRoot` for expanded-stack offsets. */
|
|
35
|
+
this.heights = this._heights.asReadonly();
|
|
36
|
+
/**
|
|
37
|
+
* Per-toast stacking metrics, computed once for the whole queue (front = newest, index `0`):
|
|
38
|
+
* `index` is the distance from the front and `offsetY` is the combined height of the toasts
|
|
39
|
+
* stacked in front. Roots read this by id instead of each rescanning the queue (O(n) vs O(n²)).
|
|
40
|
+
*/
|
|
41
|
+
this.layout = computed(() => {
|
|
42
|
+
const list = this._toasts();
|
|
43
|
+
const heights = this._heights();
|
|
44
|
+
const result = {};
|
|
45
|
+
let offsetInFront = 0;
|
|
46
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
47
|
+
const toast = list[i];
|
|
48
|
+
result[toast.id] = { index: list.length - 1 - i, offsetY: offsetInFront };
|
|
49
|
+
offsetInFront += heights[toast.id] ?? 0;
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}, ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
|
|
53
|
+
/** Max number of simultaneously visible toasts. Configurable via `[rdxToastProvider]`. */
|
|
54
|
+
this.limit = DEFAULT_LIMIT;
|
|
55
|
+
/** Default auto-dismiss delay (ms) for toasts that omit `timeout`. Set via `[rdxToastProvider]`. */
|
|
56
|
+
this.defaultTimeout = DEFAULT_TIMEOUT;
|
|
57
|
+
this.timers = new Map();
|
|
58
|
+
/** Nested-pause depth so overlapping holds (hover + swipe) resume only when all release. */
|
|
59
|
+
this.pauseDepth = 0;
|
|
60
|
+
}
|
|
61
|
+
/** Queue a new toast. Returns its id (generated when not supplied). */
|
|
62
|
+
add(options) {
|
|
63
|
+
const id = options.id ?? this.idGenerator.getId('rdx-toast-');
|
|
64
|
+
const toast = {
|
|
65
|
+
...options,
|
|
66
|
+
id,
|
|
67
|
+
priority: options.priority ?? 'low',
|
|
68
|
+
transitionStatus: 'starting'
|
|
69
|
+
};
|
|
70
|
+
// Re-adding an id that is mid-dismissal resurrects it rather than updating it: finish the
|
|
71
|
+
// abandoned dismissal so its leave handler can't later remove the replacement, and its
|
|
72
|
+
// `onRemove` still fires. (A same-id toast that is still open is a normal update — left alone.)
|
|
73
|
+
const existing = this._toasts().find((t) => t.id === id);
|
|
74
|
+
if (existing?.transitionStatus === 'ending') {
|
|
75
|
+
this.clearTimer(id);
|
|
76
|
+
existing.onRemove?.();
|
|
77
|
+
}
|
|
78
|
+
let evicted = [];
|
|
79
|
+
this._toasts.update((toasts) => {
|
|
80
|
+
const next = [...toasts.filter((t) => t.id !== id), toast];
|
|
81
|
+
// Enforce the limit by dropping the oldest entries.
|
|
82
|
+
const overflow = next.length - this.limit;
|
|
83
|
+
if (overflow > 0) {
|
|
84
|
+
evicted = next.slice(0, overflow);
|
|
85
|
+
return next.slice(overflow);
|
|
86
|
+
}
|
|
87
|
+
return next;
|
|
88
|
+
});
|
|
89
|
+
// Fully retire evicted toasts (timer + height + callback), not just their timers.
|
|
90
|
+
evicted.forEach((dropped) => {
|
|
91
|
+
this.clearTimer(dropped.id);
|
|
92
|
+
this.clearHeight(dropped.id);
|
|
93
|
+
dropped.onRemove?.();
|
|
94
|
+
});
|
|
95
|
+
this.scheduleTimeout(toast);
|
|
96
|
+
return id;
|
|
97
|
+
}
|
|
98
|
+
/** Merge new options into an existing toast (no-op if the id is unknown). */
|
|
99
|
+
update(id, options) {
|
|
100
|
+
this._toasts.update((toasts) => toasts.map((toast) => (toast.id === id ? { ...toast, ...options, id } : toast)));
|
|
101
|
+
const toast = this._toasts().find((t) => t.id === id);
|
|
102
|
+
if (toast) {
|
|
103
|
+
this.scheduleTimeout(toast);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Begin dismissing a toast: fire `onClose`, then remove it after the leave animation.
|
|
108
|
+
* Omit `id` to dismiss every toast at once (mirrors Base UI's `close()`).
|
|
109
|
+
*/
|
|
110
|
+
close(id) {
|
|
111
|
+
if (id === undefined) {
|
|
112
|
+
this._toasts().forEach((toast) => this.close(toast.id));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const toast = this._toasts().find((t) => t.id === id);
|
|
116
|
+
if (!toast) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
this.clearTimer(id);
|
|
120
|
+
toast.onClose?.();
|
|
121
|
+
this._toasts.update((toasts) => toasts.map((t) => (t.id === id ? { ...t, transitionStatus: 'ending' } : t)));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Remove a toast from the queue immediately. `RdxToastRoot` calls this once its leave animation
|
|
125
|
+
* has finished; call it directly only when there is no exit animation.
|
|
126
|
+
*/
|
|
127
|
+
remove(id) {
|
|
128
|
+
const toast = this._toasts().find((t) => t.id === id);
|
|
129
|
+
this.clearTimer(id);
|
|
130
|
+
this._toasts.update((toasts) => toasts.filter((t) => t.id !== id));
|
|
131
|
+
this.clearHeight(id);
|
|
132
|
+
toast?.onRemove?.();
|
|
133
|
+
}
|
|
134
|
+
/** Toggle the expanded (hover/focus) state of the stack. Called by the viewport. */
|
|
135
|
+
setExpanded(expanded) {
|
|
136
|
+
this._expanded.set(expanded);
|
|
137
|
+
}
|
|
138
|
+
/** Record a toast's measured height (px) for expanded-stack offset math. Called by each root. */
|
|
139
|
+
setHeight(id, height) {
|
|
140
|
+
this._heights.update((heights) => (heights[id] === height ? heights : { ...heights, [id]: height }));
|
|
141
|
+
}
|
|
142
|
+
/** Pause every auto-dismiss timer (e.g. while the viewport is hovered, focused, or being swiped). */
|
|
143
|
+
pauseAll() {
|
|
144
|
+
if (this.pauseDepth++ > 0) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
this.timers.forEach((timer) => {
|
|
149
|
+
if (timer.handle !== null) {
|
|
150
|
+
clearTimeout(timer.handle);
|
|
151
|
+
timer.remaining -= now - timer.start;
|
|
152
|
+
timer.handle = null;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/** Release one pause; auto-dismiss timers resume only once every hold has released. */
|
|
157
|
+
resumeAll() {
|
|
158
|
+
if (this.pauseDepth === 0 || --this.pauseDepth > 0) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const now = Date.now();
|
|
162
|
+
this.timers.forEach((timer, id) => {
|
|
163
|
+
if (timer.handle === null && timer.remaining > 0) {
|
|
164
|
+
timer.start = now;
|
|
165
|
+
timer.handle = setTimeout(() => this.close(id), timer.remaining);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Drive a toast through a promise's lifecycle: show `loading`, then swap to `success` or
|
|
171
|
+
* `error` copy when it settles. Returns the original promise for chaining.
|
|
172
|
+
*/
|
|
173
|
+
async promise(promise, options) {
|
|
174
|
+
const id = this.add({ ...resolveCopy(options.loading), timeout: 0, loading: true });
|
|
175
|
+
try {
|
|
176
|
+
const value = await promise;
|
|
177
|
+
this.update(id, { ...resolveCopy(options.success, value), loading: false, timeout: DEFAULT_TIMEOUT });
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
this.update(id, { ...resolveCopy(options.error, error), loading: false, timeout: DEFAULT_TIMEOUT });
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
scheduleTimeout(toast) {
|
|
186
|
+
this.clearTimer(toast.id);
|
|
187
|
+
const timeout = toast.timeout ?? this.defaultTimeout;
|
|
188
|
+
if (timeout <= 0 || toast.loading) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const timer = { handle: null, remaining: timeout, start: Date.now() };
|
|
192
|
+
if (this.pauseDepth === 0) {
|
|
193
|
+
timer.handle = setTimeout(() => this.close(toast.id), timeout);
|
|
194
|
+
}
|
|
195
|
+
this.timers.set(toast.id, timer);
|
|
196
|
+
}
|
|
197
|
+
clearTimer(id) {
|
|
198
|
+
const timer = this.timers.get(id);
|
|
199
|
+
if (timer?.handle != null) {
|
|
200
|
+
clearTimeout(timer.handle);
|
|
201
|
+
}
|
|
202
|
+
this.timers.delete(id);
|
|
203
|
+
}
|
|
204
|
+
clearHeight(id) {
|
|
205
|
+
this._heights.update((heights) => {
|
|
206
|
+
if (!(id in heights)) {
|
|
207
|
+
return heights;
|
|
208
|
+
}
|
|
209
|
+
const next = { ...heights };
|
|
210
|
+
delete next[id];
|
|
211
|
+
return next;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
215
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastManager }); }
|
|
216
|
+
}
|
|
217
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastManager, decorators: [{
|
|
218
|
+
type: Injectable
|
|
219
|
+
}] });
|
|
220
|
+
/** Normalize a string-or-options promise-copy entry (optionally derived from the settled value). */
|
|
221
|
+
function resolveCopy(entry, value) {
|
|
222
|
+
const resolved = typeof entry === 'function' ? entry(value) : entry;
|
|
223
|
+
return typeof resolved === 'string' ? { title: resolved } : resolved;
|
|
224
|
+
}
|
|
225
|
+
/** Provide a {@link RdxToastManager} for an app (e.g. in `app.config.ts`). */
|
|
226
|
+
function provideRdxToastManager() {
|
|
227
|
+
return [RdxToastManager];
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Hosts a {@link RdxToastManager} for its subtree. Put it on a wrapping element (or the app root)
|
|
231
|
+
* so descendant viewports and components share one queue. `limit` caps simultaneously visible toasts.
|
|
232
|
+
*/
|
|
233
|
+
class RdxToastProvider {
|
|
234
|
+
constructor() {
|
|
235
|
+
this.manager = inject(RdxToastManager);
|
|
236
|
+
/** Max number of toasts shown at once. */
|
|
237
|
+
this.limit = input(DEFAULT_LIMIT, ...(ngDevMode ? [{ debugName: "limit" }] : /* istanbul ignore next */ []));
|
|
238
|
+
/** Default auto-dismiss delay (ms) for toasts that omit their own `timeout`. `0` disables it. */
|
|
239
|
+
this.timeout = input(DEFAULT_TIMEOUT, ...(ngDevMode ? [{ debugName: "timeout" }] : /* istanbul ignore next */ []));
|
|
240
|
+
// `limit` / `defaultTimeout` are plain fields on the manager; keep them in sync with inputs.
|
|
241
|
+
effect(() => {
|
|
242
|
+
this.manager.limit = this.limit();
|
|
243
|
+
this.manager.defaultTimeout = this.timeout();
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastProvider, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
247
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxToastProvider, isStandalone: true, selector: "[rdxToastProvider]", inputs: { limit: { classPropertyName: "limit", publicName: "limit", isSignal: true, isRequired: false, transformFunction: null }, timeout: { classPropertyName: "timeout", publicName: "timeout", isSignal: true, isRequired: false, transformFunction: null } }, providers: [RdxToastManager], exportAs: ["rdxToastProvider"], ngImport: i0 }); }
|
|
248
|
+
}
|
|
249
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastProvider, decorators: [{
|
|
250
|
+
type: Directive,
|
|
251
|
+
args: [{
|
|
252
|
+
selector: '[rdxToastProvider]',
|
|
253
|
+
exportAs: 'rdxToastProvider',
|
|
254
|
+
providers: [RdxToastManager]
|
|
255
|
+
}]
|
|
256
|
+
}], ctorParameters: () => [], propDecorators: { limit: [{ type: i0.Input, args: [{ isSignal: true, alias: "limit", required: false }] }], timeout: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeout", required: false }] }] } });
|
|
257
|
+
|
|
258
|
+
/** Unit vectors per dismiss direction (screen coordinates: +y is down). */
|
|
259
|
+
const UNIT = {
|
|
260
|
+
up: { x: 0, y: -1 },
|
|
261
|
+
down: { x: 0, y: 1 },
|
|
262
|
+
left: { x: -1, y: 0 },
|
|
263
|
+
right: { x: 1, y: 0 }
|
|
264
|
+
};
|
|
265
|
+
/** Default travel (px) past which a release dismisses. */
|
|
266
|
+
const DEFAULT_THRESHOLD = 45;
|
|
267
|
+
/** Signed velocity (px/ms) toward the dismiss direction that flicks a toast away. */
|
|
268
|
+
const FLICK_VELOCITY = 0.3;
|
|
269
|
+
/** iOS-style resistance when dragging opposite the dismiss direction. */
|
|
270
|
+
const RUBBER_BAND = 0.5;
|
|
271
|
+
/**
|
|
272
|
+
* Headless swipe-to-dismiss for a toast, built on the shared {@link usePointerDrag} lifecycle (which
|
|
273
|
+
* owns pointer capture, the start threshold, window listeners, and tap/cancel handling). This layer
|
|
274
|
+
* adds the toast-specific direction projection, rubber-banding, flick detection, and the styling
|
|
275
|
+
* contract — it applies no transform itself:
|
|
276
|
+
*
|
|
277
|
+
* - `--toast-swipe-movement-x` / `--toast-swipe-movement-y` — signed px offset along the active axis.
|
|
278
|
+
* - `[data-swiping]` — present while a gesture is active (drive `transition: none` off this).
|
|
279
|
+
* - `[data-swipe-direction]` — the active direction.
|
|
280
|
+
* - `[data-swipe-dismiss]` — present briefly when a release commits to dismissal.
|
|
281
|
+
*
|
|
282
|
+
* Mark inner interactive regions with `[data-toast-swipe-ignore]` to opt them out of the gesture.
|
|
283
|
+
* `RdxToastRoot` (the gesture host) sets `touch-action: none` so a touch-drag is not stolen by native
|
|
284
|
+
* scrolling — without it the gesture only works with a mouse, not on touch devices.
|
|
285
|
+
* Must be called from an injection context (a directive constructor).
|
|
286
|
+
*/
|
|
287
|
+
function useToastSwipe(config) {
|
|
288
|
+
assertInInjectionContext(useToastSwipe);
|
|
289
|
+
const el = () => config.element();
|
|
290
|
+
const axisSize = (direction) => direction === 'left' || direction === 'right' ? el().offsetWidth : el().offsetHeight;
|
|
291
|
+
const rubber = (distance, direction) => {
|
|
292
|
+
const size = axisSize(direction) || 1;
|
|
293
|
+
return (1 - 1 / ((Math.abs(distance) * RUBBER_BAND) / size + 1)) * size;
|
|
294
|
+
};
|
|
295
|
+
let active = false;
|
|
296
|
+
let startX = 0;
|
|
297
|
+
let startY = 0;
|
|
298
|
+
let direction = null;
|
|
299
|
+
let pending = 0;
|
|
300
|
+
let lastProjected = 0;
|
|
301
|
+
let lastTime = 0;
|
|
302
|
+
let velocity = 0;
|
|
303
|
+
const write = (offset, dir) => {
|
|
304
|
+
const unit = dir ? UNIT[dir] : { x: 0, y: 0 };
|
|
305
|
+
const node = el();
|
|
306
|
+
node.style.setProperty('--toast-swipe-movement-x', `${unit.x * offset}px`);
|
|
307
|
+
node.style.setProperty('--toast-swipe-movement-y', `${unit.y * offset}px`);
|
|
308
|
+
};
|
|
309
|
+
/** Pick the allowed direction the drag points toward most, with its signed projection (px). */
|
|
310
|
+
const project = (dx, dy) => {
|
|
311
|
+
let best = null;
|
|
312
|
+
let bestProjection = 0;
|
|
313
|
+
for (const dir of config.directions()) {
|
|
314
|
+
const unit = UNIT[dir];
|
|
315
|
+
const projection = dx * unit.x + dy * unit.y;
|
|
316
|
+
if (projection > bestProjection) {
|
|
317
|
+
bestProjection = projection;
|
|
318
|
+
best = dir;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return { direction: best, projection: bestProjection };
|
|
322
|
+
};
|
|
323
|
+
usePointerDrag({
|
|
324
|
+
canStart: (event) => {
|
|
325
|
+
if (!config.enabled()) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
return !event.target?.closest('[data-toast-swipe-ignore]');
|
|
329
|
+
},
|
|
330
|
+
onStart: (event) => {
|
|
331
|
+
active = true;
|
|
332
|
+
startX = event.clientX;
|
|
333
|
+
startY = event.clientY;
|
|
334
|
+
direction = null;
|
|
335
|
+
pending = 0;
|
|
336
|
+
lastProjected = 0;
|
|
337
|
+
lastTime = event.timeStamp;
|
|
338
|
+
velocity = 0;
|
|
339
|
+
el().setAttribute('data-swiping', '');
|
|
340
|
+
el().removeAttribute('data-swipe-dismiss');
|
|
341
|
+
config.onPress?.();
|
|
342
|
+
},
|
|
343
|
+
onMove: (event) => {
|
|
344
|
+
// Abort if the toast began leaving mid-drag; the end handler settles back.
|
|
345
|
+
if (!config.enabled()) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
const dx = event.clientX - startX;
|
|
349
|
+
const dy = event.clientY - startY;
|
|
350
|
+
const { direction: dir, projection } = project(dx, dy);
|
|
351
|
+
if (dir) {
|
|
352
|
+
direction = dir;
|
|
353
|
+
}
|
|
354
|
+
el().setAttribute('data-swipe-direction', direction ?? '');
|
|
355
|
+
// Move freely toward the dismiss direction; resist dragging the other way.
|
|
356
|
+
pending = projection >= 0 ? projection : -rubber(projection, direction);
|
|
357
|
+
const dt = event.timeStamp - lastTime;
|
|
358
|
+
if (dt > 0) {
|
|
359
|
+
velocity = (projection - lastProjected) / dt;
|
|
360
|
+
lastProjected = projection;
|
|
361
|
+
lastTime = event.timeStamp;
|
|
362
|
+
}
|
|
363
|
+
write(pending, direction);
|
|
364
|
+
return true;
|
|
365
|
+
},
|
|
366
|
+
onEnd: (event, committed) => {
|
|
367
|
+
active = false;
|
|
368
|
+
el().removeAttribute('data-swiping');
|
|
369
|
+
const threshold = config.threshold ?? DEFAULT_THRESHOLD;
|
|
370
|
+
const dismiss = committed && config.enabled() && (pending >= threshold || velocity >= FLICK_VELOCITY);
|
|
371
|
+
if (dismiss) {
|
|
372
|
+
el().setAttribute('data-swipe-dismiss', '');
|
|
373
|
+
config.onDismiss(event);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
// Settle home; the consumer's `transition` animates the snap-back.
|
|
377
|
+
write(0, direction);
|
|
378
|
+
}
|
|
379
|
+
config.onRelease?.();
|
|
380
|
+
direction = null;
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
// usePointerDrag does not call onEnd when the host is destroyed mid-gesture; balance the onPress
|
|
384
|
+
// that paused the timers so the manager doesn't stay paused forever.
|
|
385
|
+
inject(DestroyRef).onDestroy(() => {
|
|
386
|
+
if (active) {
|
|
387
|
+
active = false;
|
|
388
|
+
config.onRelease?.();
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const [injectRdxToastRootContext, provideRdxToastRootContext] = createContext('RdxToastRootContext', 'components/toast');
|
|
394
|
+
const rootContext = () => {
|
|
395
|
+
const instance = inject(RdxToastRoot);
|
|
396
|
+
return {
|
|
397
|
+
toast: () => instance.toast(),
|
|
398
|
+
titleId: instance.titleId,
|
|
399
|
+
descriptionId: instance.descriptionId,
|
|
400
|
+
setTitlePresent: (present) => instance.titlePresent.set(present),
|
|
401
|
+
setDescriptionPresent: (present) => instance.descriptionPresent.set(present),
|
|
402
|
+
close: () => instance.close()
|
|
403
|
+
};
|
|
404
|
+
};
|
|
405
|
+
/**
|
|
406
|
+
* A single toast — the Angular counterpart of `<Toast.Root>`. Bind the toast model from the
|
|
407
|
+
* viewport's `@for`; this directive owns the announcement `role`, the `data-state` enter/leave
|
|
408
|
+
* contract, swipe-to-dismiss, and the stacking variables consumers style against.
|
|
409
|
+
*
|
|
410
|
+
* Stacking / styling contract written to the host (no transform is applied for you):
|
|
411
|
+
* - `--toast-index` — position from the front (`0` = frontmost).
|
|
412
|
+
* - `--toast-height` — this toast's measured height (px).
|
|
413
|
+
* - `--toast-offset-y` — combined height (px) of the toasts stacked in front, for expanded layout.
|
|
414
|
+
* - `--toast-swipe-movement-x` / `--toast-swipe-movement-y` — live swipe offset (px).
|
|
415
|
+
* - `[data-front]` — present on the frontmost toast.
|
|
416
|
+
* - `[data-expanded]` — present while the viewport is hovered/focused.
|
|
417
|
+
* - `[data-state]` — `open` while visible, `closed` once dismissal begins.
|
|
418
|
+
* - `[data-swiping]` / `[data-swipe-direction]` / `[data-swipe-dismiss]` — gesture state.
|
|
419
|
+
*
|
|
420
|
+
* When the leave animation (driven by `data-state="closed"`) ends, the toast leaves the queue.
|
|
421
|
+
*/
|
|
422
|
+
class RdxToastRoot {
|
|
423
|
+
constructor() {
|
|
424
|
+
this.manager = inject(RdxToastManager);
|
|
425
|
+
this.elementRef = inject(ElementRef);
|
|
426
|
+
this.injector = inject(Injector);
|
|
427
|
+
/** The toast model to render — pass the item from the viewport's `@for`. */
|
|
428
|
+
this.toast = input.required(...(ngDevMode ? [{ debugName: "toast" }] : /* istanbul ignore next */ []));
|
|
429
|
+
/** Allowed swipe-to-dismiss directions. Accepts a single direction or a list. */
|
|
430
|
+
this.swipeDirection = input(['down', 'right'], ...(ngDevMode ? [{ debugName: "swipeDirection" }] : /* istanbul ignore next */ []));
|
|
431
|
+
this.titleId = injectId('rdx-toast-title-');
|
|
432
|
+
this.descriptionId = injectId('rdx-toast-description-');
|
|
433
|
+
/** Whether a title / description part has registered, gating the aria-* references. */
|
|
434
|
+
this.titlePresent = signal(false, ...(ngDevMode ? [{ debugName: "titlePresent" }] : /* istanbul ignore next */ []));
|
|
435
|
+
this.descriptionPresent = signal(false, ...(ngDevMode ? [{ debugName: "descriptionPresent" }] : /* istanbul ignore next */ []));
|
|
436
|
+
this.size = elementSize({ elementRef: this.elementRef, injector: this.injector });
|
|
437
|
+
/** This toast's measured height (px). */
|
|
438
|
+
this.height = computed(() => this.size().height, ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
|
|
439
|
+
/** Position from the front of the stack — `0` is the newest/frontmost toast. */
|
|
440
|
+
this.index = computed(() => this.manager.layout()[this.toast().id]?.index ?? 0, ...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
|
|
441
|
+
/** Combined height (px) of the toasts stacked in front of this one (for expanded layout). */
|
|
442
|
+
this.offsetY = computed(() => this.manager.layout()[this.toast().id]?.offsetY ?? 0, ...(ngDevMode ? [{ debugName: "offsetY" }] : /* istanbul ignore next */ []));
|
|
443
|
+
/** `alert` (assertive) for high-priority toasts, otherwise `status`. */
|
|
444
|
+
this.role = computed(() => (this.toast().priority === 'high' ? 'alert' : 'status'), ...(ngDevMode ? [{ debugName: "role" }] : /* istanbul ignore next */ []));
|
|
445
|
+
/** `open` while visible, `closed` once dismissal begins — drives enter/leave animations. */
|
|
446
|
+
this.dataState = computed(() => (this.toast().transitionStatus === 'ending' ? 'closed' : 'open'), ...(ngDevMode ? [{ debugName: "dataState" }] : /* istanbul ignore next */ []));
|
|
447
|
+
this.directions = computed(() => {
|
|
448
|
+
const value = this.swipeDirection();
|
|
449
|
+
return Array.isArray(value) ? value : [value];
|
|
450
|
+
}, ...(ngDevMode ? [{ debugName: "directions" }] : /* istanbul ignore next */ []));
|
|
451
|
+
const destroyRef = inject(DestroyRef);
|
|
452
|
+
// Mirror the measured height into the manager for the expanded-stack offset math.
|
|
453
|
+
effect(() => {
|
|
454
|
+
this.manager.setHeight(this.toast().id, this.size().height);
|
|
455
|
+
});
|
|
456
|
+
useToastSwipe({
|
|
457
|
+
element: () => this.elementRef.nativeElement,
|
|
458
|
+
directions: () => this.directions(),
|
|
459
|
+
enabled: () => this.toast().transitionStatus !== 'ending',
|
|
460
|
+
onDismiss: () => this.close(),
|
|
461
|
+
onPress: () => this.manager.pauseAll(),
|
|
462
|
+
onRelease: () => this.manager.resumeAll()
|
|
463
|
+
});
|
|
464
|
+
// Replay the enter animation on an upsert that bumps `updateKey`. The toast keeps the same
|
|
465
|
+
// DOM node (tracked by id), so restart the running animation by clearing and re-reading it.
|
|
466
|
+
let firstKey = true;
|
|
467
|
+
effect(() => {
|
|
468
|
+
this.toast().updateKey;
|
|
469
|
+
if (firstKey) {
|
|
470
|
+
firstKey = false;
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const node = this.elementRef.nativeElement;
|
|
474
|
+
node.style.animation = 'none';
|
|
475
|
+
void node.offsetWidth; // force reflow so the animation can restart
|
|
476
|
+
node.style.animation = '';
|
|
477
|
+
});
|
|
478
|
+
destroyRef.onDestroy(() => this.manager.setHeight(this.toast().id, 0));
|
|
479
|
+
}
|
|
480
|
+
close() {
|
|
481
|
+
this.manager.close(this.toast().id);
|
|
482
|
+
}
|
|
483
|
+
onAnimationEnd() {
|
|
484
|
+
// Remove from the queue only after the leave ("closed") animation completes.
|
|
485
|
+
if (this.toast().transitionStatus === 'ending') {
|
|
486
|
+
this.manager.remove(this.toast().id);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
490
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxToastRoot, isStandalone: true, selector: "[rdxToastRoot]", inputs: { toast: { classPropertyName: "toast", publicName: "toast", isSignal: true, isRequired: true, transformFunction: null }, swipeDirection: { classPropertyName: "swipeDirection", publicName: "swipeDirection", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "animationend": "onAnimationEnd()" }, properties: { "style.touch-action": "\"none\"", "style.user-select": "\"none\"", "style.-webkit-user-select": "\"none\"", "attr.role": "role()", "attr.aria-labelledby": "titlePresent() ? titleId : undefined", "attr.aria-describedby": "descriptionPresent() ? descriptionId : undefined", "attr.data-state": "dataState()", "attr.data-type": "toast().type ?? undefined", "attr.data-front": "index() === 0 ? \"\" : undefined", "attr.data-expanded": "manager.expanded() ? \"\" : undefined", "style.--toast-index": "index()", "style.--toast-height": "height() + \"px\"", "style.--toast-offset-y": "offsetY() + \"px\"" } }, providers: [provideRdxToastRootContext(rootContext)], exportAs: ["rdxToastRoot"], ngImport: i0 }); }
|
|
491
|
+
}
|
|
492
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastRoot, decorators: [{
|
|
493
|
+
type: Directive,
|
|
494
|
+
args: [{
|
|
495
|
+
selector: '[rdxToastRoot]',
|
|
496
|
+
exportAs: 'rdxToastRoot',
|
|
497
|
+
providers: [provideRdxToastRootContext(rootContext)],
|
|
498
|
+
host: {
|
|
499
|
+
// Own the swipe gesture's touch behavior on the element usePointerDrag binds to. Without
|
|
500
|
+
// touch-action:none a touch-drag is claimed by native scrolling (pointercancel fires before
|
|
501
|
+
// the gesture starts), so swipe-to-dismiss works with a mouse but not on touch devices. The
|
|
502
|
+
// toast surface never scrolls, so disabling pan/zoom here is safe; user-select stops text
|
|
503
|
+
// selection mid-swipe. Mirrors number-field's scrub area.
|
|
504
|
+
'[style.touch-action]': '"none"',
|
|
505
|
+
'[style.user-select]': '"none"',
|
|
506
|
+
'[style.-webkit-user-select]': '"none"',
|
|
507
|
+
'[attr.role]': 'role()',
|
|
508
|
+
'[attr.aria-labelledby]': 'titlePresent() ? titleId : undefined',
|
|
509
|
+
'[attr.aria-describedby]': 'descriptionPresent() ? descriptionId : undefined',
|
|
510
|
+
'[attr.data-state]': 'dataState()',
|
|
511
|
+
'[attr.data-type]': 'toast().type ?? undefined',
|
|
512
|
+
'[attr.data-front]': 'index() === 0 ? "" : undefined',
|
|
513
|
+
'[attr.data-expanded]': 'manager.expanded() ? "" : undefined',
|
|
514
|
+
'[style.--toast-index]': 'index()',
|
|
515
|
+
'[style.--toast-height]': 'height() + "px"',
|
|
516
|
+
'[style.--toast-offset-y]': 'offsetY() + "px"',
|
|
517
|
+
'(animationend)': 'onAnimationEnd()'
|
|
518
|
+
}
|
|
519
|
+
}]
|
|
520
|
+
}], ctorParameters: () => [], propDecorators: { toast: [{ type: i0.Input, args: [{ isSignal: true, alias: "toast", required: true }] }], swipeDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "swipeDirection", required: false }] }] } });
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* An action button inside a toast (e.g. "Undo"). Unlike {@link RdxToastClose} it does not dismiss
|
|
524
|
+
* on its own — wire your handler with `(click)` and call `close()` from the exposed context when
|
|
525
|
+
* the action should also close the toast.
|
|
526
|
+
*/
|
|
527
|
+
class RdxToastAction {
|
|
528
|
+
constructor() {
|
|
529
|
+
/** The toast root context, so handlers can read the toast or dismiss it after acting. */
|
|
530
|
+
this.rootContext = injectRdxToastRootContext();
|
|
531
|
+
}
|
|
532
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastAction, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
533
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxToastAction, isStandalone: true, selector: "[rdxToastAction]", host: { attributes: { "type": "button" } }, exportAs: ["rdxToastAction"], ngImport: i0 }); }
|
|
534
|
+
}
|
|
535
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastAction, decorators: [{
|
|
536
|
+
type: Directive,
|
|
537
|
+
args: [{
|
|
538
|
+
selector: '[rdxToastAction]',
|
|
539
|
+
exportAs: 'rdxToastAction',
|
|
540
|
+
host: {
|
|
541
|
+
type: 'button'
|
|
542
|
+
}
|
|
543
|
+
}]
|
|
544
|
+
}] });
|
|
545
|
+
|
|
546
|
+
/** Button that dismisses its toast. Put it on a native `<button>` for built-in semantics. */
|
|
547
|
+
class RdxToastClose {
|
|
548
|
+
constructor() {
|
|
549
|
+
this.rootContext = injectRdxToastRootContext();
|
|
550
|
+
}
|
|
551
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastClose, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
552
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxToastClose, isStandalone: true, selector: "[rdxToastClose]", host: { attributes: { "type": "button" }, listeners: { "click": "rootContext.close()" } }, exportAs: ["rdxToastClose"], ngImport: i0 }); }
|
|
553
|
+
}
|
|
554
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastClose, decorators: [{
|
|
555
|
+
type: Directive,
|
|
556
|
+
args: [{
|
|
557
|
+
selector: '[rdxToastClose]',
|
|
558
|
+
exportAs: 'rdxToastClose',
|
|
559
|
+
host: {
|
|
560
|
+
type: 'button',
|
|
561
|
+
'(click)': 'rootContext.close()'
|
|
562
|
+
}
|
|
563
|
+
}]
|
|
564
|
+
}] });
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Wraps a toast's inner parts (title, description, actions) — the Angular counterpart of
|
|
568
|
+
* `<Toast.Content>`. Headless: it carries no styles; consumers apply `overflow: hidden` to clip
|
|
569
|
+
* taller toasts during the stack/expand animation. It mirrors the root's `data-state` so content
|
|
570
|
+
* can transition together with the root.
|
|
571
|
+
*/
|
|
572
|
+
class RdxToastContent {
|
|
573
|
+
constructor() {
|
|
574
|
+
this.rootContext = injectRdxToastRootContext();
|
|
575
|
+
}
|
|
576
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastContent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
577
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxToastContent, isStandalone: true, selector: "[rdxToastContent]", host: { properties: { "attr.data-state": "rootContext.toast().transitionStatus === \"ending\" ? \"closed\" : \"open\"" } }, exportAs: ["rdxToastContent"], ngImport: i0 }); }
|
|
578
|
+
}
|
|
579
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastContent, decorators: [{
|
|
580
|
+
type: Directive,
|
|
581
|
+
args: [{
|
|
582
|
+
selector: '[rdxToastContent]',
|
|
583
|
+
exportAs: 'rdxToastContent',
|
|
584
|
+
host: {
|
|
585
|
+
'[attr.data-state]': 'rootContext.toast().transitionStatus === "ending" ? "closed" : "open"'
|
|
586
|
+
}
|
|
587
|
+
}]
|
|
588
|
+
}] });
|
|
589
|
+
|
|
590
|
+
/** Supporting body text for a toast; registers so `aria-describedby` only targets a real node. */
|
|
591
|
+
class RdxToastDescription {
|
|
592
|
+
constructor() {
|
|
593
|
+
this.rootContext = injectRdxToastRootContext();
|
|
594
|
+
this.rootContext.setDescriptionPresent(true);
|
|
595
|
+
inject(DestroyRef).onDestroy(() => this.rootContext.setDescriptionPresent(false));
|
|
596
|
+
}
|
|
597
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastDescription, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
598
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxToastDescription, isStandalone: true, selector: "[rdxToastDescription]", host: { properties: { "id": "rootContext.descriptionId" } }, exportAs: ["rdxToastDescription"], ngImport: i0 }); }
|
|
599
|
+
}
|
|
600
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastDescription, decorators: [{
|
|
601
|
+
type: Directive,
|
|
602
|
+
args: [{
|
|
603
|
+
selector: '[rdxToastDescription]',
|
|
604
|
+
exportAs: 'rdxToastDescription',
|
|
605
|
+
host: {
|
|
606
|
+
'[id]': 'rootContext.descriptionId'
|
|
607
|
+
}
|
|
608
|
+
}]
|
|
609
|
+
}], ctorParameters: () => [] });
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Moves the toast viewport to a different part of the DOM — the Angular counterpart of
|
|
613
|
+
* `<Toast.Portal>`. Defaults to `document.body`; pass `container` to target another element.
|
|
614
|
+
*/
|
|
615
|
+
class RdxToastPortal {
|
|
616
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
617
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxToastPortal, isStandalone: true, selector: "[rdxToastPortal]", exportAs: ["rdxToastPortal"], hostDirectives: [{ directive: i1.RdxPortal, inputs: ["container", "container"] }], ngImport: i0 }); }
|
|
618
|
+
}
|
|
619
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastPortal, decorators: [{
|
|
620
|
+
type: Directive,
|
|
621
|
+
args: [{
|
|
622
|
+
selector: '[rdxToastPortal]',
|
|
623
|
+
exportAs: 'rdxToastPortal',
|
|
624
|
+
hostDirectives: [
|
|
625
|
+
{
|
|
626
|
+
directive: RdxPortal,
|
|
627
|
+
inputs: ['container']
|
|
628
|
+
}
|
|
629
|
+
]
|
|
630
|
+
}]
|
|
631
|
+
}] });
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Positions an anchored toast against an element — the Angular counterpart of `<Toast.Positioner>`.
|
|
635
|
+
* Composes the popper machinery so a toast can point at a trigger instead of living in the stack.
|
|
636
|
+
*
|
|
637
|
+
* Bind `anchor` (and optionally `side`/`align`/offsets) — typically from a toast's `positionerProps`.
|
|
638
|
+
* It hosts its own `RdxPopper` so it is self-contained (no surrounding `rdxPopperRoot` needed), and
|
|
639
|
+
* mirrors the popper measurements as friendlier CSS variables plus `data-side` / `data-align`.
|
|
640
|
+
*/
|
|
641
|
+
class RdxToastPositioner {
|
|
642
|
+
constructor() {
|
|
643
|
+
this.wrapper = inject(RdxPopperContentWrapper);
|
|
644
|
+
}
|
|
645
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastPositioner, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
646
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxToastPositioner, isStandalone: true, selector: "[rdxToastPositioner]", host: { properties: { "attr.data-side": "wrapper.placedSide()", "attr.data-align": "wrapper.placedAlign()", "attr.data-anchor-hidden": "wrapper.anchorHidden() ? \"\" : undefined", "style.--toast-anchor-width": "\"var(--radix-popper-anchor-width)\"", "style.--toast-transform-origin": "\"var(--radix-popper-transform-origin)\"" } }, providers: [
|
|
647
|
+
provideRdxPopperContentConfig({
|
|
648
|
+
side: 'top',
|
|
649
|
+
sideOffset: 8,
|
|
650
|
+
align: 'center',
|
|
651
|
+
arrowPadding: 6,
|
|
652
|
+
collisionPadding: 8,
|
|
653
|
+
updatePositionStrategy: 'always'
|
|
654
|
+
})
|
|
655
|
+
], exportAs: ["rdxToastPositioner"], hostDirectives: [{ directive: i1$1.RdxPopper }, { directive: i1$1.RdxPopperContentWrapper, inputs: ["anchor", "anchor", "side", "side", "sideOffset", "sideOffset", "align", "align", "alignOffset", "alignOffset", "arrowPadding", "arrowPadding", "avoidCollisions", "avoidCollisions", "collisionBoundary", "collisionBoundary", "collisionPadding", "collisionPadding", "sticky", "sticky", "hideWhenDetached", "hideWhenDetached", "positionStrategy", "positionStrategy", "updatePositionStrategy", "updatePositionStrategy"] }], ngImport: i0 }); }
|
|
656
|
+
}
|
|
657
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastPositioner, decorators: [{
|
|
658
|
+
type: Directive,
|
|
659
|
+
args: [{
|
|
660
|
+
selector: '[rdxToastPositioner]',
|
|
661
|
+
exportAs: 'rdxToastPositioner',
|
|
662
|
+
providers: [
|
|
663
|
+
provideRdxPopperContentConfig({
|
|
664
|
+
side: 'top',
|
|
665
|
+
sideOffset: 8,
|
|
666
|
+
align: 'center',
|
|
667
|
+
arrowPadding: 6,
|
|
668
|
+
collisionPadding: 8,
|
|
669
|
+
updatePositionStrategy: 'always'
|
|
670
|
+
})
|
|
671
|
+
],
|
|
672
|
+
hostDirectives: [
|
|
673
|
+
RdxPopper,
|
|
674
|
+
{
|
|
675
|
+
directive: RdxPopperContentWrapper,
|
|
676
|
+
inputs: [
|
|
677
|
+
'anchor',
|
|
678
|
+
'side',
|
|
679
|
+
'sideOffset',
|
|
680
|
+
'align',
|
|
681
|
+
'alignOffset',
|
|
682
|
+
'arrowPadding',
|
|
683
|
+
'avoidCollisions',
|
|
684
|
+
'collisionBoundary',
|
|
685
|
+
'collisionPadding',
|
|
686
|
+
'sticky',
|
|
687
|
+
'hideWhenDetached',
|
|
688
|
+
'positionStrategy',
|
|
689
|
+
'updatePositionStrategy'
|
|
690
|
+
]
|
|
691
|
+
}
|
|
692
|
+
],
|
|
693
|
+
host: {
|
|
694
|
+
'[attr.data-side]': 'wrapper.placedSide()',
|
|
695
|
+
'[attr.data-align]': 'wrapper.placedAlign()',
|
|
696
|
+
'[attr.data-anchor-hidden]': 'wrapper.anchorHidden() ? "" : undefined',
|
|
697
|
+
'[style.--toast-anchor-width]': '"var(--radix-popper-anchor-width)"',
|
|
698
|
+
'[style.--toast-transform-origin]': '"var(--radix-popper-transform-origin)"'
|
|
699
|
+
}
|
|
700
|
+
}]
|
|
701
|
+
}] });
|
|
702
|
+
|
|
703
|
+
/** Accessible title for a toast; registers so the root only points `aria-labelledby` at a real node. */
|
|
704
|
+
class RdxToastTitle {
|
|
705
|
+
constructor() {
|
|
706
|
+
this.rootContext = injectRdxToastRootContext();
|
|
707
|
+
this.rootContext.setTitlePresent(true);
|
|
708
|
+
inject(DestroyRef).onDestroy(() => this.rootContext.setTitlePresent(false));
|
|
709
|
+
}
|
|
710
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastTitle, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
711
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxToastTitle, isStandalone: true, selector: "[rdxToastTitle]", host: { properties: { "id": "rootContext.titleId" } }, exportAs: ["rdxToastTitle"], ngImport: i0 }); }
|
|
712
|
+
}
|
|
713
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastTitle, decorators: [{
|
|
714
|
+
type: Directive,
|
|
715
|
+
args: [{
|
|
716
|
+
selector: '[rdxToastTitle]',
|
|
717
|
+
exportAs: 'rdxToastTitle',
|
|
718
|
+
host: {
|
|
719
|
+
'[id]': 'rootContext.titleId'
|
|
720
|
+
}
|
|
721
|
+
}]
|
|
722
|
+
}], ctorParameters: () => [] });
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* The positioned region that holds the visible toasts — the Angular counterpart of
|
|
726
|
+
* `<Toast.Viewport>`. Exposes the queue for templates to render, and while it is hovered or focused
|
|
727
|
+
* it expands the stack (`data-expanded` on each root) and pauses every auto-dismiss timer, resuming
|
|
728
|
+
* once neither hover nor focus remains — matching Base UI.
|
|
729
|
+
*
|
|
730
|
+
* Headless: it carries no positioning styles. Consumers position it (e.g. fixed bottom-right) and
|
|
731
|
+
* iterate `viewport.toasts()` with `@for`.
|
|
732
|
+
*/
|
|
733
|
+
class RdxToastViewport {
|
|
734
|
+
constructor() {
|
|
735
|
+
this.manager = inject(RdxToastManager);
|
|
736
|
+
/** The live toast queue to render. */
|
|
737
|
+
this.toasts = this.manager.toasts;
|
|
738
|
+
this.hovered = signal(false, ...(ngDevMode ? [{ debugName: "hovered" }] : /* istanbul ignore next */ []));
|
|
739
|
+
this.focused = signal(false, ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
|
|
740
|
+
/** Tracks the last expanded state so pause/resume stay balanced across transitions. */
|
|
741
|
+
this.wasExpanded = false;
|
|
742
|
+
effect(() => {
|
|
743
|
+
const expanded = this.hovered() || this.focused();
|
|
744
|
+
this.manager.setExpanded(expanded);
|
|
745
|
+
if (expanded && !this.wasExpanded) {
|
|
746
|
+
this.wasExpanded = true;
|
|
747
|
+
this.manager.pauseAll();
|
|
748
|
+
}
|
|
749
|
+
else if (!expanded && this.wasExpanded) {
|
|
750
|
+
this.wasExpanded = false;
|
|
751
|
+
this.manager.resumeAll();
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
// If the viewport is destroyed while still expanded, balance the outstanding pause so a
|
|
755
|
+
// longer-lived manager doesn't stay paused forever.
|
|
756
|
+
inject(DestroyRef).onDestroy(() => {
|
|
757
|
+
if (this.wasExpanded) {
|
|
758
|
+
this.wasExpanded = false;
|
|
759
|
+
this.manager.setExpanded(false);
|
|
760
|
+
this.manager.resumeAll();
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
onFocusOut(event) {
|
|
765
|
+
// Only clear focus when it actually leaves the viewport subtree.
|
|
766
|
+
const next = event.relatedTarget;
|
|
767
|
+
const host = event.currentTarget;
|
|
768
|
+
if (!host || !next || !host.contains(next)) {
|
|
769
|
+
this.focused.set(false);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastViewport, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
773
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxToastViewport, isStandalone: true, selector: "[rdxToastViewport]", host: { attributes: { "role": "region", "tabindex": "-1" }, listeners: { "mouseenter": "hovered.set(true)", "mouseleave": "hovered.set(false)", "focusin": "focused.set(true)", "focusout": "onFocusOut($event)" } }, exportAs: ["rdxToastViewport"], ngImport: i0 }); }
|
|
774
|
+
}
|
|
775
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastViewport, decorators: [{
|
|
776
|
+
type: Directive,
|
|
777
|
+
args: [{
|
|
778
|
+
selector: '[rdxToastViewport]',
|
|
779
|
+
exportAs: 'rdxToastViewport',
|
|
780
|
+
host: {
|
|
781
|
+
role: 'region',
|
|
782
|
+
tabindex: '-1',
|
|
783
|
+
'(mouseenter)': 'hovered.set(true)',
|
|
784
|
+
'(mouseleave)': 'hovered.set(false)',
|
|
785
|
+
'(focusin)': 'focused.set(true)',
|
|
786
|
+
'(focusout)': 'onFocusOut($event)'
|
|
787
|
+
}
|
|
788
|
+
}]
|
|
789
|
+
}], ctorParameters: () => [] });
|
|
790
|
+
|
|
791
|
+
const toastImports = [
|
|
792
|
+
RdxToastProvider,
|
|
793
|
+
RdxToastPortal,
|
|
794
|
+
RdxToastViewport,
|
|
795
|
+
RdxToastPositioner,
|
|
796
|
+
RdxToastRoot,
|
|
797
|
+
RdxToastContent,
|
|
798
|
+
RdxToastTitle,
|
|
799
|
+
RdxToastDescription,
|
|
800
|
+
RdxToastClose,
|
|
801
|
+
RdxToastAction
|
|
802
|
+
];
|
|
803
|
+
class RdxToastModule {
|
|
804
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
805
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: RdxToastModule, imports: [RdxToastProvider,
|
|
806
|
+
RdxToastPortal,
|
|
807
|
+
RdxToastViewport,
|
|
808
|
+
RdxToastPositioner,
|
|
809
|
+
RdxToastRoot,
|
|
810
|
+
RdxToastContent,
|
|
811
|
+
RdxToastTitle,
|
|
812
|
+
RdxToastDescription,
|
|
813
|
+
RdxToastClose,
|
|
814
|
+
RdxToastAction], exports: [RdxToastProvider,
|
|
815
|
+
RdxToastPortal,
|
|
816
|
+
RdxToastViewport,
|
|
817
|
+
RdxToastPositioner,
|
|
818
|
+
RdxToastRoot,
|
|
819
|
+
RdxToastContent,
|
|
820
|
+
RdxToastTitle,
|
|
821
|
+
RdxToastDescription,
|
|
822
|
+
RdxToastClose,
|
|
823
|
+
RdxToastAction] }); }
|
|
824
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastModule }); }
|
|
825
|
+
}
|
|
826
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxToastModule, decorators: [{
|
|
827
|
+
type: NgModule,
|
|
828
|
+
args: [{
|
|
829
|
+
imports: [...toastImports],
|
|
830
|
+
exports: [...toastImports]
|
|
831
|
+
}]
|
|
832
|
+
}] });
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Generated bundle index. Do not edit.
|
|
836
|
+
*/
|
|
837
|
+
|
|
838
|
+
export { RdxToastAction, RdxToastClose, RdxToastContent, RdxToastDescription, RdxToastManager, RdxToastModule, RdxToastPortal, RdxToastPositioner, RdxToastProvider, RdxToastRoot, RdxToastTitle, RdxToastViewport, injectRdxToastRootContext, provideRdxToastManager, provideRdxToastRootContext, toastImports, useToastSwipe };
|
|
839
|
+
//# sourceMappingURL=radix-ng-primitives-toast.mjs.map
|