@rozie-ui/toast-solid 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 One Learning Community LTD
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 ADDED
@@ -0,0 +1,85 @@
1
+ # @rozie-ui/toast-solid
2
+
3
+ Idiomatic **solid** `Toaster` — a headless, accessible toast / notification host (a live-region queue with per-toast auto-dismiss timers, hover-to-pause, six corner positions, and a per-toast close button) compiled from one [Rozie](https://github.com/One-Learning-Community/rozie.js) source. It is **not** a global singleton + context system: the host owns the queue + timers as internal state and exposes an imperative `show` / `dismiss` / `clear` handle you drive via `ref` — "call from anywhere" is your app's wiring concern (stash the ref). Every visual value is a CSS custom property, so it re-skins to any design system. This package is generated; do not edit `src/` by hand.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm i @rozie-ui/toast-solid
9
+ ```
10
+
11
+ Peer dependencies: `solid-js`. Install them alongside this package.
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import { Toaster, type ToasterHandle } from '@rozie-ui/toast-solid';
17
+
18
+ export function Demo() {
19
+ let toaster: ToasterHandle | undefined;
20
+ // The ref callback receives the HANDLE object (not the DOM node).
21
+ return (
22
+ <>
23
+ <button onClick={() => toaster?.show({ message: 'Saved!', type: 'success' })}>Save</button>
24
+ <button onClick={() => toaster?.show({ message: 'Something failed', type: 'error' })}>Fail</button>
25
+
26
+ {/* Mount the host once (typically near the app root). */}
27
+ <Toaster ref={(h) => (toaster = h)} position="bottom-right" duration={4000} />
28
+ </>
29
+ );
30
+ }
31
+ ```
32
+
33
+ ## Theming
34
+
35
+ Every visual value is a `--rozie-toast-*` CSS custom property — override any of them at any ancestor scope. Ready-made design-system bridges ship in the package:
36
+
37
+ ```tsx
38
+ import '@rozie-ui/toast-solid/themes/shadcn.css'; // or material.css, bootstrap.css, base.css
39
+ ```
40
+
41
+ ## Props
42
+
43
+ | Name | Type | Default | Two-way (model) | Required |
44
+ | --- | --- | --- | :---: | :---: |
45
+ | `position` | `String` | `"bottom-right"` | | |
46
+ | `duration` | `Number` | `4000` | | |
47
+ | `max` | `Number` | `0` | | |
48
+ | `disablePauseOnHover` | `Boolean` | `false` | | |
49
+ | `ariaLabel` | `String` | `null` | | |
50
+ | `disableSwipe` | `Boolean` | `false` | | |
51
+ | `stacked` | `Boolean` | `false` | | |
52
+
53
+ ## Events
54
+
55
+ | Event | Description |
56
+ | --- | --- |
57
+ | `dismissed` | Fired exactly once per toast, at dismissal initiation (before the exit animation runs). Payload is ONE object `{ toast, reason }` — `toast` is the full queue entry, `reason` is `'timeout'` (auto-dismiss), `'swipe'` (pointer swipe past threshold), `'close'` (the built-in close button), or `'api'` (the `dismiss(id)` verb). `clear()` removes every toast immediately and does NOT fire `dismissed` (documented bulk behavior). |
58
+
59
+ ## Imperative handle
60
+
61
+ The component has no events — its primary API is an imperative handle (declared once in the Rozie source via `$expose`). Grab a handle with the native ref mechanism and call the methods directly. None of the verbs overrides an inherited host-element member, so the Lit custom element emits no ROZ137 warning:
62
+
63
+ | Method | Description |
64
+ | --- | --- |
65
+ | `show` | Enqueue a toast. Accepts `{ message, type, duration, id }` (all optional — `message` defaults to `''`, `type` to `'info'`, `duration` to the `duration` prop). Returns the toast `id`. A non-sticky toast (duration > 0) auto-dismisses; `duration: 0` makes it sticky. |
66
+ | `dismiss` | Remove a single toast by the `id` returned from `show` (routes through the exit lifecycle with reason `'api'` — fires `dismissed`, plays the exit animation, then removes it). |
67
+ | `clear` | Remove every visible toast at once immediately (no exit animation) and clear all pending auto-dismiss timers. Does NOT fire `dismissed`. |
68
+ | `patch` | Update an existing toast in place. Accepts `(id, { message, type, duration })` — only the keys you pass are merged into the matching entry. Returns `true` if the id existed, `false` otherwise (no throw). Including a `duration` key clears and restarts that toast's auto-dismiss timer (`0` makes it sticky; a positive value arms/re-arms it); omitting `duration` leaves a running timer untouched. |
69
+ | `promise` | Sugar over `show`/`patch` for an async operation: `promise(p, { loading, success, error })` immediately shows a `{ type: 'loading', duration: 0 }` toast and returns its `id` SYNCHRONOUSLY. On resolve it patches the SAME toast to `{ type: 'success', message: resolve(success, value) }` (the auto-dismiss timer starts AT SETTLE); on reject, likewise with `error`. `success`/`error` accept a string or a `(value) => string` function. Never resurrects a toast dismissed while `p` was still pending, and never returns a derived promise — your own `.then`/`.catch` on `p` still fire. |
70
+
71
+ ```tsx
72
+ import { Toaster, type ToasterHandle } from '@rozie-ui/toast-solid';
73
+
74
+ let toaster: ToasterHandle | undefined;
75
+ // The ref callback receives the HANDLE object (not the DOM node).
76
+ <Toaster ref={(h) => (toaster = h)} />;
77
+ toaster?.show({ message: 'Saved', type: 'success' });
78
+ toaster?.clear();
79
+ ```
80
+
81
+ ## Slots
82
+
83
+ | Slot | Params |
84
+ | --- | --- |
85
+ | toast | toast, dismiss |
package/dist/index.cjs ADDED
@@ -0,0 +1,482 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ let Solid_js = require("solid-js");
6
+ let _solid_primitives_keyed = require("@solid-primitives/keyed");
7
+ let _rozie_runtime_solid = require("@rozie/runtime-solid");
8
+ //#region src/Toaster.tsx
9
+ (0, _rozie_runtime_solid.__rozieInjectStyle)("Toaster-12d4265c", `@media (prefers-reduced-motion: reduce) {
10
+ .rozie-toast[data-rozie-s-12d4265c] {
11
+ animation-name: rozie-toast-fade-in;
12
+ animation-duration: 1ms;
13
+ }
14
+ .rozie-toast--exiting[data-rozie-s-12d4265c] {
15
+ animation-name: rozie-toast-fade-out;
16
+ animation-duration: 1ms;
17
+ }
18
+ }
19
+ .rozie-toaster[data-rozie-s-12d4265c] {
20
+ position: fixed;
21
+ z-index: var(--rozie-toast-z, 9999);
22
+ display: flex;
23
+ flex-direction: column;
24
+ gap: var(--rozie-toast-gap, 0.5rem);
25
+ padding: var(--rozie-toast-region-padding, 1rem);
26
+ max-width: var(--rozie-toast-max-width, calc(100vw - 2rem));
27
+ pointer-events: none;
28
+ font: var(--rozie-toast-font, inherit);
29
+ }
30
+ .rozie-toaster[data-rozie-s-12d4265c] > *[data-rozie-s-12d4265c] {
31
+ pointer-events: auto;
32
+ }
33
+ .rozie-toaster--top-left[data-rozie-s-12d4265c] { top: 0; left: 0; align-items: flex-start; }
34
+ .rozie-toaster--top-right[data-rozie-s-12d4265c] { top: 0; right: 0; align-items: flex-end; }
35
+ .rozie-toaster--top-center[data-rozie-s-12d4265c] { top: 0; left: 50%; transform: translateX(-50%); align-items: center; }
36
+ .rozie-toaster--bottom-left[data-rozie-s-12d4265c] { bottom: 0; left: 0; align-items: flex-start; flex-direction: column-reverse; }
37
+ .rozie-toaster--bottom-right[data-rozie-s-12d4265c] { bottom: 0; right: 0; align-items: flex-end; flex-direction: column-reverse; }
38
+ .rozie-toaster--bottom-center[data-rozie-s-12d4265c] { bottom: 0; left: 50%; transform: translateX(-50%); align-items: center; flex-direction: column-reverse; }
39
+ .rozie-toaster--stacked[data-rozie-s-12d4265c] .rozie-toast[data-rozie-s-12d4265c] {
40
+ grid-area: 1 / 1;
41
+ z-index: calc(100 - var(--rozie-toast-depth, 0));
42
+ }
43
+ .rozie-toaster--stacked[data-rozie-s-12d4265c]:not([data-rozie-s-12d4265c]:hover):not([data-rozie-s-12d4265c]:focus-within) {
44
+ display: grid;
45
+ }
46
+ .rozie-toaster--stacked[data-rozie-s-12d4265c]:not([data-rozie-s-12d4265c]:hover):not([data-rozie-s-12d4265c]:focus-within) .rozie-toast[data-rozie-s-12d4265c] {
47
+ transform:
48
+ translateY(calc(var(--rozie-toast-depth, 0) * var(--rozie-toast-stack-offset, 8px)))
49
+ scale(calc(1 - var(--rozie-toast-depth, 0) * var(--rozie-toast-stack-scale-step, 0.05)));
50
+ opacity: calc(1 - min(1, max(0, var(--rozie-toast-depth, 0) - 2)));
51
+ }
52
+ .rozie-toaster--stacked.rozie-toaster--bottom-left[data-rozie-s-12d4265c]:not([data-rozie-s-12d4265c]:hover):not([data-rozie-s-12d4265c]:focus-within) .rozie-toast[data-rozie-s-12d4265c],
53
+ .rozie-toaster--stacked.rozie-toaster--bottom-right[data-rozie-s-12d4265c]:not([data-rozie-s-12d4265c]:hover):not([data-rozie-s-12d4265c]:focus-within) .rozie-toast[data-rozie-s-12d4265c],
54
+ .rozie-toaster--stacked.rozie-toaster--bottom-center[data-rozie-s-12d4265c]:not([data-rozie-s-12d4265c]:hover):not([data-rozie-s-12d4265c]:focus-within) .rozie-toast[data-rozie-s-12d4265c] {
55
+ transform:
56
+ translateY(calc(var(--rozie-toast-depth, 0) * var(--rozie-toast-stack-offset, 8px) * -1))
57
+ scale(calc(1 - var(--rozie-toast-depth, 0) * var(--rozie-toast-stack-scale-step, 0.05)));
58
+ }
59
+ .rozie-toast[data-rozie-s-12d4265c] {
60
+ display: flex;
61
+ align-items: center;
62
+ gap: var(--rozie-toast-content-gap, 0.75rem);
63
+ min-width: var(--rozie-toast-min-width, 16rem);
64
+ max-width: var(--rozie-toast-toast-max-width, 24rem);
65
+ padding: var(--rozie-toast-padding, 0.75rem 1rem);
66
+ color: var(--rozie-toast-color, #fff);
67
+ background: var(--rozie-toast-bg, #333);
68
+ border-radius: var(--rozie-toast-radius, 0.5rem);
69
+ box-shadow: var(--rozie-toast-shadow, 0 6px 20px rgba(0, 0, 0, 0.25));
70
+ /* Swipe: page scroll stays alive on touch along the axis the toast does
71
+ NOT move on. The transition here drives the spring-back (the active-drag
72
+ :style sets an inline \`transition: none\` to track the finger 1:1;
73
+ releasing it without a further gesture falls back to this transition). */
74
+ touch-action: pan-y;
75
+ transition: transform 200ms ease, opacity 200ms ease;
76
+ }
77
+ .rozie-toaster--top-center[data-rozie-s-12d4265c] .rozie-toast[data-rozie-s-12d4265c],
78
+ .rozie-toaster--bottom-center[data-rozie-s-12d4265c] .rozie-toast[data-rozie-s-12d4265c] {
79
+ touch-action: pan-x;
80
+ }
81
+ .rozie-toast--success[data-rozie-s-12d4265c] { background: var(--rozie-toast-success-bg, #16a34a); }
82
+ .rozie-toast--error[data-rozie-s-12d4265c] { background: var(--rozie-toast-error-bg, #dc2626); }
83
+ .rozie-toast--warning[data-rozie-s-12d4265c] { background: var(--rozie-toast-warning-bg, #ca8a04); }
84
+ .rozie-toast--info[data-rozie-s-12d4265c] { background: var(--rozie-toast-info-bg, var(--rozie-toast-bg, #333)); }
85
+ from[data-rozie-s-12d4265c] { opacity: 0; transform: translateY(-0.5rem); }
86
+ to[data-rozie-s-12d4265c] { opacity: 1; transform: translateY(0); }
87
+ from[data-rozie-s-12d4265c] { opacity: 0; transform: translateY(0.5rem); }
88
+ to[data-rozie-s-12d4265c] { opacity: 1; transform: translateY(0); }
89
+ from[data-rozie-s-12d4265c] { opacity: 1; transform: translateY(0); }
90
+ to[data-rozie-s-12d4265c] { opacity: 0; transform: translateY(-0.5rem); }
91
+ from[data-rozie-s-12d4265c] { opacity: 1; transform: translateY(0); }
92
+ to[data-rozie-s-12d4265c] { opacity: 0; transform: translateY(0.5rem); }
93
+ .rozie-toast[data-rozie-s-12d4265c] {
94
+ animation: rozie-toast-enter var(--rozie-toast-enter-duration, 200ms) ease-out;
95
+ }
96
+ .rozie-toaster--bottom-left[data-rozie-s-12d4265c] .rozie-toast[data-rozie-s-12d4265c],
97
+ .rozie-toaster--bottom-right[data-rozie-s-12d4265c] .rozie-toast[data-rozie-s-12d4265c],
98
+ .rozie-toaster--bottom-center[data-rozie-s-12d4265c] .rozie-toast[data-rozie-s-12d4265c] {
99
+ animation-name: rozie-toast-enter-from-bottom;
100
+ }
101
+ .rozie-toast--exiting[data-rozie-s-12d4265c] {
102
+ animation: rozie-toast-exit var(--rozie-toast-exit-duration, 200ms) ease-in forwards;
103
+ }
104
+ .rozie-toaster--bottom-left[data-rozie-s-12d4265c] .rozie-toast--exiting[data-rozie-s-12d4265c],
105
+ .rozie-toaster--bottom-right[data-rozie-s-12d4265c] .rozie-toast--exiting[data-rozie-s-12d4265c],
106
+ .rozie-toaster--bottom-center[data-rozie-s-12d4265c] .rozie-toast--exiting[data-rozie-s-12d4265c] {
107
+ animation-name: rozie-toast-exit-to-bottom;
108
+ }
109
+ from[data-rozie-s-12d4265c] { opacity: 0; }
110
+ to[data-rozie-s-12d4265c] { opacity: 1; }
111
+ from[data-rozie-s-12d4265c] { opacity: 1; }
112
+ to[data-rozie-s-12d4265c] { opacity: 0; }
113
+ from[data-rozie-s-12d4265c] { opacity: 1; transform: translateX(0); }
114
+ to[data-rozie-s-12d4265c] { opacity: 0; transform: translateX(calc(var(--rozie-toast-swipe-exit, 1) * 100%)); }
115
+ from[data-rozie-s-12d4265c] { opacity: 1; transform: translateY(0); }
116
+ to[data-rozie-s-12d4265c] { opacity: 0; transform: translateY(calc(var(--rozie-toast-swipe-exit, 1) * 100%)); }
117
+ .rozie-toast--exiting.rozie-toast--swipe-exit[data-rozie-s-12d4265c] {
118
+ animation-name: rozie-toast-swipe-exit-x;
119
+ }
120
+ .rozie-toaster--top-center[data-rozie-s-12d4265c] .rozie-toast--exiting.rozie-toast--swipe-exit[data-rozie-s-12d4265c],
121
+ .rozie-toaster--bottom-center[data-rozie-s-12d4265c] .rozie-toast--exiting.rozie-toast--swipe-exit[data-rozie-s-12d4265c] {
122
+ animation-name: rozie-toast-swipe-exit-y;
123
+ }
124
+ .rozie-toast-spinner[data-rozie-s-12d4265c] {
125
+ flex: 0 0 auto;
126
+ width: var(--rozie-toast-spinner-size, 1em);
127
+ height: var(--rozie-toast-spinner-size, 1em);
128
+ border: 2px solid color-mix(in srgb, var(--rozie-toast-spinner-color, currentColor) 25%, transparent);
129
+ border-top-color: var(--rozie-toast-spinner-color, currentColor);
130
+ border-radius: 50%;
131
+ animation: rozie-toast-spin 0.75s linear infinite;
132
+ }
133
+ to[data-rozie-s-12d4265c] { transform: rotate(360deg); }
134
+ .rozie-toast-message[data-rozie-s-12d4265c] {
135
+ flex: 1 1 auto;
136
+ font-size: var(--rozie-toast-font-size, 0.9rem);
137
+ }
138
+ .rozie-toast-close[data-rozie-s-12d4265c] {
139
+ flex: 0 0 auto;
140
+ display: inline-flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ width: var(--rozie-toast-close-size, 1.25rem);
144
+ height: var(--rozie-toast-close-size, 1.25rem);
145
+ padding: 0;
146
+ font-size: 1.1rem;
147
+ line-height: 1;
148
+ color: inherit;
149
+ background: transparent;
150
+ border: none;
151
+ border-radius: 0.25rem;
152
+ opacity: var(--rozie-toast-close-opacity, 0.75);
153
+ cursor: pointer;
154
+ }
155
+ .rozie-toast-close[data-rozie-s-12d4265c]:hover {
156
+ opacity: 1;
157
+ }`);
158
+ function Toaster(_props) {
159
+ const [local, attrs] = (0, Solid_js.splitProps)((0, Solid_js.mergeProps)({
160
+ position: "bottom-right",
161
+ duration: 4e3,
162
+ max: 0,
163
+ disablePauseOnHover: false,
164
+ ariaLabel: null,
165
+ disableSwipe: false,
166
+ stacked: false
167
+ }, _props), [
168
+ "position",
169
+ "duration",
170
+ "max",
171
+ "disablePauseOnHover",
172
+ "ariaLabel",
173
+ "disableSwipe",
174
+ "stacked",
175
+ "ref"
176
+ ]);
177
+ (0, Solid_js.onMount)(() => {
178
+ local.ref?.({
179
+ show,
180
+ dismiss,
181
+ clear,
182
+ patch,
183
+ promise
184
+ });
185
+ });
186
+ const [toasts, setToasts] = (0, Solid_js.createSignal)([]);
187
+ const [seq, setSeq] = (0, Solid_js.createSignal)(0);
188
+ const [swipe, setSwipe] = (0, Solid_js.createSignal)(null);
189
+ const [swipeGesture, setSwipeGesture] = (0, Solid_js.createSignal)(null);
190
+ (0, Solid_js.onCleanup)(() => {
191
+ unmounted = true;
192
+ teardownTimers();
193
+ });
194
+ let timers = {};
195
+ let exitFailsafes = {};
196
+ let unmounted = false;
197
+ let seqLocal = 0;
198
+ let paused = false;
199
+ function startTimer(toast) {
200
+ if (!toast || !toast.duration || toast.duration <= 0) return;
201
+ if (typeof window === "undefined") return;
202
+ const existing = timers[toast.id];
203
+ if (existing && existing.handle != null) window.clearTimeout(existing.handle);
204
+ const remaining = toast.duration;
205
+ const handle = window.setTimeout(() => dismissBegin(toast.id, "timeout"), remaining);
206
+ timers[toast.id] = {
207
+ handle,
208
+ startedAt: Date.now(),
209
+ remaining
210
+ };
211
+ }
212
+ function clearTimer(id) {
213
+ const entry = timers[id];
214
+ if (entry && entry.handle != null && typeof window !== "undefined") window.clearTimeout(entry.handle);
215
+ delete timers[id];
216
+ }
217
+ function pauseTimers() {
218
+ paused = true;
219
+ if (typeof window === "undefined") return;
220
+ for (const id in timers) {
221
+ const entry = timers[id];
222
+ if (entry.handle == null) continue;
223
+ window.clearTimeout(entry.handle);
224
+ const elapsed = Date.now() - entry.startedAt;
225
+ const remaining = Math.max(0, entry.remaining - elapsed);
226
+ timers[id] = {
227
+ handle: null,
228
+ startedAt: entry.startedAt,
229
+ remaining
230
+ };
231
+ }
232
+ }
233
+ function resumeTimers() {
234
+ paused = false;
235
+ if (typeof window === "undefined") return;
236
+ for (const id in timers) {
237
+ const entry = timers[id];
238
+ if (entry.handle != null) continue;
239
+ if (entry.remaining == null || entry.remaining <= 0) {
240
+ dismissBegin(id, "timeout");
241
+ continue;
242
+ }
243
+ const remaining = entry.remaining;
244
+ const handle = window.setTimeout(() => dismissBegin(id, "timeout"), remaining);
245
+ timers[id] = {
246
+ handle,
247
+ startedAt: Date.now(),
248
+ remaining
249
+ };
250
+ }
251
+ }
252
+ function teardownTimers() {
253
+ if (typeof window !== "undefined") {
254
+ for (const id in timers) {
255
+ const entry = timers[id];
256
+ if (entry.handle != null) window.clearTimeout(entry.handle);
257
+ }
258
+ for (const id in exitFailsafes) if (exitFailsafes[id] != null) window.clearTimeout(exitFailsafes[id]);
259
+ }
260
+ timers = {};
261
+ exitFailsafes = {};
262
+ }
263
+ function show(input) {
264
+ const t = input || {};
265
+ let id;
266
+ if (t.id != null) id = String(t.id);
267
+ else {
268
+ const s = Math.max(seq(), seqLocal);
269
+ id = "t" + s;
270
+ seqLocal = s + 1;
271
+ setSeq(s + 1);
272
+ }
273
+ const toast = {
274
+ id,
275
+ message: t.message != null ? t.message : "",
276
+ type: t.type || "info",
277
+ duration: t.duration != null ? t.duration : local.duration
278
+ };
279
+ setToasts(toasts().concat([toast]).slice(local.max > 0 ? Math.max(0, toasts().length + 1 - local.max) : 0));
280
+ startTimer(toast);
281
+ return id;
282
+ }
283
+ const EXIT_FAILSAFE_MS = 350;
284
+ function removeToast(id) {
285
+ if (typeof window !== "undefined" && exitFailsafes[id] != null) window.clearTimeout(exitFailsafes[id]);
286
+ delete exitFailsafes[id];
287
+ setToasts(toasts().filter((t) => t.id !== id));
288
+ }
289
+ function dismissBegin(id, reason, extra) {
290
+ const entry = toasts().find((t) => t.id === id);
291
+ if (!entry || entry.exiting) return;
292
+ clearTimer(id);
293
+ _props.onDismissed?.({
294
+ toast: entry,
295
+ reason
296
+ });
297
+ setToasts(toasts().map((t) => t.id === id ? {
298
+ ...t,
299
+ exiting: true,
300
+ ...extra || {}
301
+ } : t));
302
+ if (typeof window === "undefined") removeToast(id);
303
+ else exitFailsafes[id] = window.setTimeout(() => removeToast(id), EXIT_FAILSAFE_MS);
304
+ }
305
+ function dismiss(id) {
306
+ dismissBegin(id, "api");
307
+ }
308
+ function clear() {
309
+ teardownTimers();
310
+ setToasts([]);
311
+ }
312
+ function patch(id, changes) {
313
+ const c = changes || {};
314
+ let existed = false;
315
+ const next = toasts().map((t) => {
316
+ if (t.id !== id) return t;
317
+ if (t.exiting) return t;
318
+ existed = true;
319
+ const merged = { ...t };
320
+ if (c.message !== void 0) merged.message = c.message;
321
+ if (c.type !== void 0) merged.type = c.type;
322
+ if (c.duration !== void 0) merged.duration = c.duration;
323
+ return merged;
324
+ });
325
+ if (!existed) return false;
326
+ setToasts(next);
327
+ if (c.duration !== void 0) {
328
+ clearTimer(id);
329
+ const patched = next.find((t) => t.id === id);
330
+ if (paused) {
331
+ if (patched && patched.duration > 0 && typeof window !== "undefined") timers[id] = {
332
+ handle: null,
333
+ startedAt: Date.now(),
334
+ remaining: patched.duration
335
+ };
336
+ } else startTimer(patched);
337
+ }
338
+ return true;
339
+ }
340
+ function settlePromise(id, type, messageOrFn, value) {
341
+ if (unmounted) return;
342
+ const entry = toasts().find((t) => t.id === id);
343
+ if (!entry || entry.exiting) return;
344
+ patch(id, {
345
+ type,
346
+ message: typeof messageOrFn === "function" ? messageOrFn(value) : messageOrFn,
347
+ duration: local.duration
348
+ });
349
+ }
350
+ function promise(p, opts) {
351
+ const o = opts || {};
352
+ const id = show({
353
+ type: "loading",
354
+ duration: 0,
355
+ message: o.loading
356
+ });
357
+ if (p && typeof p.then === "function") p.then((value) => settlePromise(id, "success", o.success, value)).catch((err) => settlePromise(id, "error", o.error, err));
358
+ return id;
359
+ }
360
+ function swipeAxisFor(position) {
361
+ return position === "top-center" || position === "bottom-center" ? "y" : "x";
362
+ }
363
+ function swipeSignFor(position) {
364
+ if (position === "top-right" || position === "bottom-right") return 1;
365
+ if (position === "top-left" || position === "bottom-left") return -1;
366
+ if (position === "bottom-center") return 1;
367
+ return -1;
368
+ }
369
+ function onToastPointerDown(t, event) {
370
+ if (local.disableSwipe) return;
371
+ if (event.button != null && event.button !== 0) return;
372
+ if (event.target && event.target.closest ? event.target.closest("button, a") : null) return;
373
+ const axis = swipeAxisFor(local.position);
374
+ const sign = swipeSignFor(local.position);
375
+ const el = event.currentTarget;
376
+ const size = axis === "x" ? el.offsetWidth : el.offsetHeight;
377
+ setSwipeGesture({
378
+ id: t.id,
379
+ axis,
380
+ sign,
381
+ size,
382
+ startX: event.clientX,
383
+ startY: event.clientY,
384
+ startTime: Date.now()
385
+ });
386
+ if (el && el.setPointerCapture) try {
387
+ el.setPointerCapture(event.pointerId);
388
+ } catch (e) {}
389
+ }
390
+ function onToastPointerMove(t, event) {
391
+ if (local.disableSwipe) return;
392
+ const gesture = swipeGesture();
393
+ if (!gesture || gesture.id !== t.id) return;
394
+ const raw = gesture.axis === "x" ? event.clientX - gesture.startX : event.clientY - gesture.startY;
395
+ const d = raw * gesture.sign > 0 ? raw : raw * .15;
396
+ setSwipe({
397
+ id: t.id,
398
+ d,
399
+ axis: gesture.axis,
400
+ sign: gesture.sign,
401
+ size: gesture.size
402
+ });
403
+ }
404
+ function onToastPointerUp(t, event) {
405
+ if (local.disableSwipe) return;
406
+ const gesture = swipeGesture();
407
+ setSwipeGesture(null);
408
+ const dragState = swipe();
409
+ setSwipe(null);
410
+ if (!gesture || gesture.id !== t.id || !dragState) return;
411
+ const elapsed = Math.max(1, Date.now() - gesture.startTime);
412
+ const magnitude = dragState.d * gesture.sign;
413
+ const velocity = magnitude / elapsed;
414
+ if (magnitude > 0 && (magnitude > gesture.size * .45 || velocity > .11)) dismissBegin(t.id, "swipe", { swipeExitSign: gesture.sign });
415
+ }
416
+ function onToastPointerCancel(t) {
417
+ if (local.disableSwipe) return;
418
+ if (swipeGesture() && swipeGesture().id === t.id) setSwipeGesture(null);
419
+ if (swipe() && swipe().id === t.id) setSwipe(null);
420
+ }
421
+ function depth(t) {
422
+ const idx = toasts().findIndex((x) => x.id === t.id);
423
+ return idx === -1 ? 0 : toasts().length - 1 - idx;
424
+ }
425
+ function toastStyle(t) {
426
+ const depthDecl = "--rozie-toast-depth: " + depth(t) + ";";
427
+ if (t.exiting) return t.swipeExitSign != null ? depthDecl + " --rozie-toast-swipe-exit: " + t.swipeExitSign + ";" : depthDecl;
428
+ const dragState = swipe();
429
+ if (!dragState || dragState.id !== t.id) return depthDecl;
430
+ const translate = dragState.axis === "x" ? "translateX(" + dragState.d + "px)" : "translateY(" + dragState.d + "px)";
431
+ const magnitude = dragState.d * dragState.sign;
432
+ const opacity = magnitude > 0 && dragState.size > 0 ? Math.max(.3, 1 - magnitude / dragState.size) : 1;
433
+ return depthDecl + " transform: " + translate + "; opacity: " + opacity + "; transition: none;";
434
+ }
435
+ function onMouseEnter() {
436
+ if (local.disablePauseOnHover) return;
437
+ pauseTimers();
438
+ }
439
+ function onMouseLeave() {
440
+ if (local.disablePauseOnHover) return;
441
+ resumeTimers();
442
+ }
443
+ function regionLabel() {
444
+ return local.ariaLabel != null ? local.ariaLabel : "Notifications";
445
+ }
446
+ function liveFor(type) {
447
+ return type === "error" || type === "warning" ? "assertive" : "polite";
448
+ }
449
+ return <>
450
+ <div role="region" aria-label={(0, _rozie_runtime_solid.rozieAttr)(regionLabel())} {...attrs} class={"rozie-toaster " + (0, _rozie_runtime_solid.rozieClass)("rozie-toaster--" + local.position + (local.stacked ? " rozie-toaster--stacked" : "")) + (attrs.class ? " " + attrs.class : "")} {...(0, _rozie_runtime_solid.mergeListeners)({
451
+ onMouseEnter: ($event) => {
452
+ onMouseEnter();
453
+ },
454
+ onMouseLeave: ($event) => {
455
+ onMouseLeave();
456
+ }
457
+ }, attrs)} data-rozie-s-12d4265c="">
458
+
459
+ <_solid_primitives_keyed.Key each={toasts()} by={(t) => t.id}>{(t) => <div role="status" aria-live={(0, _rozie_runtime_solid.rozieAttr)(liveFor(t().type))} class={"rozie-toast " + (0, _rozie_runtime_solid.rozieClass)("rozie-toast--" + t().type + (t().exiting ? " rozie-toast--exiting" : "") + (t().swipeExitSign != null ? " rozie-toast--swipe-exit" : ""))} style={(0, _rozie_runtime_solid.parseInlineStyle)(toastStyle(t()))} onAnimationEnd={($event) => {
460
+ t().exiting && removeToast(t().id);
461
+ }} onPointerDown={($event) => {
462
+ onToastPointerDown(t(), $event);
463
+ }} onPointerMove={($event) => {
464
+ onToastPointerMove(t(), $event);
465
+ }} onPointerUp={($event) => {
466
+ onToastPointerUp(t(), $event);
467
+ }} onPointerCancel={($event) => {
468
+ onToastPointerCancel(t());
469
+ }} data-rozie-s-12d4265c="">
470
+ {(_props.toastSlot ?? _props.slots?.["toast"])?.({
471
+ toast: t(),
472
+ dismiss
473
+ }) ?? <>{<Solid_js.Show when={t().type === "loading"}><span class={"rozie-toast-spinner"} aria-hidden="true" data-rozie-s-12d4265c="" /></Solid_js.Show>}<span class={"rozie-toast-message"} data-rozie-s-12d4265c="">{(0, _rozie_runtime_solid.rozieDisplay)(t().message)}</span><button type="button" aria-label="Dismiss" class={"rozie-toast-close"} onClick={($event) => {
474
+ dismissBegin(t().id, "close");
475
+ }} data-rozie-s-12d4265c="">×</button></>}
476
+ </div>}</_solid_primitives_keyed.Key>
477
+ </div>
478
+ </>;
479
+ }
480
+ //#endregion
481
+ exports.Toaster = Toaster;
482
+ exports.default = Toaster;
@@ -0,0 +1,51 @@
1
+ import { JSX } from "solid-js";
2
+
3
+ //#region src/Toaster.d.ts
4
+ interface ToastSlotCtx {
5
+ toast: any;
6
+ dismiss: any;
7
+ }
8
+ interface ToasterProps {
9
+ /**
10
+ * Which corner the toast stack renders in: `'top-left'`, `'top-right'`, `'top-center'`, `'bottom-left'`, `'bottom-right'`, or `'bottom-center'`. Drives the fixed-position layout and the stack direction.
11
+ */
12
+ position?: string;
13
+ /**
14
+ * Default auto-dismiss time in milliseconds, applied to any toast that does not pass its own `duration`. `0` (or a per-toast `duration` of `0`) makes the toast sticky — it stays until explicitly dismissed.
15
+ */
16
+ duration?: number;
17
+ /**
18
+ * Maximum number of visible toasts (`0` = unlimited). When the queue exceeds this, the oldest toasts drop off the stack.
19
+ */
20
+ max?: number;
21
+ /**
22
+ * Opt **out** of pausing the auto-dismiss timers while the pointer is over the stack. By default hovering pauses every timer and leaving restarts them; set this to keep toasts dismissing on schedule regardless of hover.
23
+ */
24
+ disablePauseOnHover?: boolean;
25
+ /**
26
+ * Accessible name for the live region (`role="region"`), applied as its `aria-label`. Defaults to `'Notifications'` when not set, so assistive tech can navigate to the toast stack as a landmark.
27
+ */
28
+ ariaLabel?: (string) | null;
29
+ /**
30
+ * Opt **out** of pointer swipe-to-dismiss. By default, dragging a toast past 45% of its own width/height (direction auto-derived from `position`) or a fast flick dismisses it with reason `'swipe'`; a short drag springs back. A drag starting on the close button (or any button/link) never swipes.
31
+ */
32
+ disableSwipe?: boolean;
33
+ /**
34
+ * Opt **in** to a sonner-style collapsed stack: a single-cell grid overlay with depth-driven transforms (toasts at depth 3+ fade to invisible), newest on top. Hovering the region or moving keyboard focus into it expands to the normal flex-column stack; leaving re-collapses. `false` (default) renders the plain flex column at all times.
35
+ */
36
+ stacked?: boolean;
37
+ onDismissed?: (...args: unknown[]) => void;
38
+ toastSlot?: (ctx: ToastSlotCtx) => JSX.Element;
39
+ slots?: Record<string, (ctx: any) => JSX.Element>;
40
+ ref?: (h: ToasterHandle) => void;
41
+ }
42
+ interface ToasterHandle {
43
+ show: (...args: any[]) => any;
44
+ dismiss: (...args: any[]) => any;
45
+ clear: (...args: any[]) => any;
46
+ patch: (...args: any[]) => any;
47
+ promise: (...args: any[]) => any;
48
+ }
49
+ declare function Toaster(_props: ToasterProps): JSX.Element;
50
+ //#endregion
51
+ export { Toaster, Toaster as default, type ToasterHandle };