@rozie-ui/toast-lit 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/dist/index.mjs ADDED
@@ -0,0 +1,593 @@
1
+ import { LitElement, css, html, nothing } from "lit";
2
+ import { customElement, property, queryAssignedElements, state } from "lit/decorators.js";
3
+ import { SignalWatcher, signal } from "@lit-labs/preact-signals";
4
+ import { rozieAttr, rozieClass, rozieDisplay, rozieListeners, rozieSpread, rozieStyle } from "@rozie/runtime-lit";
5
+ import { repeat } from "lit/directives/repeat.js";
6
+ //#region \0@oxc-project+runtime@0.127.0/helpers/decorate.js
7
+ function __decorate(decorators, target, key, desc) {
8
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
9
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
10
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
11
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
12
+ }
13
+ //#endregion
14
+ //#region src/Toaster.ts
15
+ let Toaster = class Toaster extends SignalWatcher(LitElement) {
16
+ constructor(..._args) {
17
+ super(..._args);
18
+ this.position = "bottom-right";
19
+ this.duration = 4e3;
20
+ this.max = 0;
21
+ this.disablePauseOnHover = false;
22
+ this.ariaLabel = null;
23
+ this.disableSwipe = false;
24
+ this.stacked = false;
25
+ this._toasts = signal([]);
26
+ this._seq = signal(0);
27
+ this._swipe = signal(null);
28
+ this._swipeGesture = signal(null);
29
+ this._hasSlotToast = false;
30
+ this._disconnectCleanups = [];
31
+ this._rozieTornDown = false;
32
+ this.timers = {};
33
+ this.exitFailsafes = {};
34
+ this.unmounted = false;
35
+ this.seqLocal = 0;
36
+ this.paused = false;
37
+ this.startTimer = (toast) => {
38
+ if (!toast || !toast.duration || toast.duration <= 0) return;
39
+ if (typeof window === "undefined") return;
40
+ const existing = this.timers[toast.id];
41
+ if (existing && existing.handle != null) window.clearTimeout(existing.handle);
42
+ const remaining = toast.duration;
43
+ const handle = window.setTimeout(() => this.dismissBegin(toast.id, "timeout"), remaining);
44
+ this.timers[toast.id] = {
45
+ handle,
46
+ startedAt: Date.now(),
47
+ remaining
48
+ };
49
+ };
50
+ this.clearTimer = (id) => {
51
+ const entry = this.timers[id];
52
+ if (entry && entry.handle != null && typeof window !== "undefined") window.clearTimeout(entry.handle);
53
+ delete this.timers[id];
54
+ };
55
+ this.pauseTimers = () => {
56
+ this.paused = true;
57
+ if (typeof window === "undefined") return;
58
+ for (const id in this.timers) {
59
+ const entry = this.timers[id];
60
+ if (entry.handle == null) continue;
61
+ window.clearTimeout(entry.handle);
62
+ const elapsed = Date.now() - entry.startedAt;
63
+ const remaining = Math.max(0, entry.remaining - elapsed);
64
+ this.timers[id] = {
65
+ handle: null,
66
+ startedAt: entry.startedAt,
67
+ remaining
68
+ };
69
+ }
70
+ };
71
+ this.resumeTimers = () => {
72
+ this.paused = false;
73
+ if (typeof window === "undefined") return;
74
+ for (const id in this.timers) {
75
+ const entry = this.timers[id];
76
+ if (entry.handle != null) continue;
77
+ if (entry.remaining == null || entry.remaining <= 0) {
78
+ this.dismissBegin(id, "timeout");
79
+ continue;
80
+ }
81
+ const remaining = entry.remaining;
82
+ const handle = window.setTimeout(() => this.dismissBegin(id, "timeout"), remaining);
83
+ this.timers[id] = {
84
+ handle,
85
+ startedAt: Date.now(),
86
+ remaining
87
+ };
88
+ }
89
+ };
90
+ this.teardownTimers = () => {
91
+ if (typeof window !== "undefined") {
92
+ for (const id in this.timers) {
93
+ const entry = this.timers[id];
94
+ if (entry.handle != null) window.clearTimeout(entry.handle);
95
+ }
96
+ for (const id in this.exitFailsafes) if (this.exitFailsafes[id] != null) window.clearTimeout(this.exitFailsafes[id]);
97
+ }
98
+ this.timers = {};
99
+ this.exitFailsafes = {};
100
+ };
101
+ this.show = (input) => {
102
+ const t = input || {};
103
+ let id;
104
+ if (t.id != null) id = String(t.id);
105
+ else {
106
+ const s = Math.max(this._seq.value, this.seqLocal);
107
+ id = "t" + s;
108
+ this.seqLocal = s + 1;
109
+ this._seq.value = s + 1;
110
+ }
111
+ const toast = {
112
+ id,
113
+ message: t.message != null ? t.message : "",
114
+ type: t.type || "info",
115
+ duration: t.duration != null ? t.duration : this.duration
116
+ };
117
+ this._toasts.value = this._toasts.value.concat([toast]).slice(this.max > 0 ? Math.max(0, this._toasts.value.length + 1 - this.max) : 0);
118
+ this.startTimer(toast);
119
+ return id;
120
+ };
121
+ this.EXIT_FAILSAFE_MS = 350;
122
+ this.removeToast = (id) => {
123
+ if (typeof window !== "undefined" && this.exitFailsafes[id] != null) window.clearTimeout(this.exitFailsafes[id]);
124
+ delete this.exitFailsafes[id];
125
+ this._toasts.value = this._toasts.value.filter((t) => t.id !== id);
126
+ };
127
+ this.dismissBegin = (id, reason, extra) => {
128
+ const entry = this._toasts.value.find((t) => t.id === id);
129
+ if (!entry || entry.exiting) return;
130
+ this.clearTimer(id);
131
+ this.dispatchEvent(new CustomEvent("dismissed", {
132
+ detail: {
133
+ toast: entry,
134
+ reason
135
+ },
136
+ bubbles: true,
137
+ composed: true
138
+ }));
139
+ this._toasts.value = this._toasts.value.map((t) => t.id === id ? {
140
+ ...t,
141
+ exiting: true,
142
+ ...extra || {}
143
+ } : t);
144
+ if (typeof window === "undefined") this.removeToast(id);
145
+ else this.exitFailsafes[id] = window.setTimeout(() => this.removeToast(id), this.EXIT_FAILSAFE_MS);
146
+ };
147
+ this.dismiss = (id) => {
148
+ this.dismissBegin(id, "api");
149
+ };
150
+ this.clear = () => {
151
+ this.teardownTimers();
152
+ this._toasts.value = [];
153
+ };
154
+ this.patch = (id, changes) => {
155
+ const c = changes || {};
156
+ let existed = false;
157
+ const next = this._toasts.value.map((t) => {
158
+ if (t.id !== id) return t;
159
+ if (t.exiting) return t;
160
+ existed = true;
161
+ const merged = { ...t };
162
+ if (c.message !== void 0) merged.message = c.message;
163
+ if (c.type !== void 0) merged.type = c.type;
164
+ if (c.duration !== void 0) merged.duration = c.duration;
165
+ return merged;
166
+ });
167
+ if (!existed) return false;
168
+ this._toasts.value = next;
169
+ if (c.duration !== void 0) {
170
+ this.clearTimer(id);
171
+ const patched = next.find((t) => t.id === id);
172
+ if (this.paused) {
173
+ if (patched && patched.duration > 0 && typeof window !== "undefined") this.timers[id] = {
174
+ handle: null,
175
+ startedAt: Date.now(),
176
+ remaining: patched.duration
177
+ };
178
+ } else this.startTimer(patched);
179
+ }
180
+ return true;
181
+ };
182
+ this.settlePromise = (id, type, messageOrFn, value) => {
183
+ if (this.unmounted) return;
184
+ const entry = this._toasts.value.find((t) => t.id === id);
185
+ if (!entry || entry.exiting) return;
186
+ const message = typeof messageOrFn === "function" ? messageOrFn(value) : messageOrFn;
187
+ this.patch(id, {
188
+ type,
189
+ message,
190
+ duration: this.duration
191
+ });
192
+ };
193
+ this.promise = (p, opts) => {
194
+ const o = opts || {};
195
+ const id = this.show({
196
+ type: "loading",
197
+ duration: 0,
198
+ message: o.loading
199
+ });
200
+ if (p && typeof p.then === "function") p.then((value) => this.settlePromise(id, "success", o.success, value)).catch((err) => this.settlePromise(id, "error", o.error, err));
201
+ return id;
202
+ };
203
+ this.swipeAxisFor = (position) => position === "top-center" || position === "bottom-center" ? "y" : "x";
204
+ this.swipeSignFor = (position) => {
205
+ if (position === "top-right" || position === "bottom-right") return 1;
206
+ if (position === "top-left" || position === "bottom-left") return -1;
207
+ if (position === "bottom-center") return 1;
208
+ return -1;
209
+ };
210
+ this.onToastPointerDown = (t, event) => {
211
+ if (this.disableSwipe) return;
212
+ if (event.button != null && event.button !== 0) return;
213
+ if (event.target && event.target.closest ? event.target.closest("button, a") : null) return;
214
+ const axis = this.swipeAxisFor(this.position);
215
+ const sign = this.swipeSignFor(this.position);
216
+ const el = event.currentTarget;
217
+ const size = axis === "x" ? el.offsetWidth : el.offsetHeight;
218
+ this._swipeGesture.value = {
219
+ id: t.id,
220
+ axis,
221
+ sign,
222
+ size,
223
+ startX: event.clientX,
224
+ startY: event.clientY,
225
+ startTime: Date.now()
226
+ };
227
+ if (el && el.setPointerCapture) try {
228
+ el.setPointerCapture(event.pointerId);
229
+ } catch (e) {}
230
+ };
231
+ this.onToastPointerMove = (t, event) => {
232
+ if (this.disableSwipe) return;
233
+ const gesture = this._swipeGesture.value;
234
+ if (!gesture || gesture.id !== t.id) return;
235
+ const raw = gesture.axis === "x" ? event.clientX - gesture.startX : event.clientY - gesture.startY;
236
+ const d = raw * gesture.sign > 0 ? raw : raw * .15;
237
+ this._swipe.value = {
238
+ id: t.id,
239
+ d,
240
+ axis: gesture.axis,
241
+ sign: gesture.sign,
242
+ size: gesture.size
243
+ };
244
+ };
245
+ this.onToastPointerUp = (t, event) => {
246
+ if (this.disableSwipe) return;
247
+ const gesture = this._swipeGesture.value;
248
+ this._swipeGesture.value = null;
249
+ const dragState = this._swipe.value;
250
+ this._swipe.value = null;
251
+ if (!gesture || gesture.id !== t.id || !dragState) return;
252
+ const elapsed = Math.max(1, Date.now() - gesture.startTime);
253
+ const magnitude = dragState.d * gesture.sign;
254
+ const velocity = magnitude / elapsed;
255
+ if (magnitude > 0 && (magnitude > gesture.size * .45 || velocity > .11)) this.dismissBegin(t.id, "swipe", { swipeExitSign: gesture.sign });
256
+ };
257
+ this.onToastPointerCancel = (t) => {
258
+ if (this.disableSwipe) return;
259
+ if (this._swipeGesture.value && this._swipeGesture.value.id === t.id) this._swipeGesture.value = null;
260
+ if (this._swipe.value && this._swipe.value.id === t.id) this._swipe.value = null;
261
+ };
262
+ this.depth = (t) => {
263
+ const idx = this._toasts.value.findIndex((x) => x.id === t.id);
264
+ return idx === -1 ? 0 : this._toasts.value.length - 1 - idx;
265
+ };
266
+ this.toastStyle = (t) => {
267
+ const depthDecl = "--rozie-toast-depth: " + this.depth(t) + ";";
268
+ if (t.exiting) return t.swipeExitSign != null ? depthDecl + " --rozie-toast-swipe-exit: " + t.swipeExitSign + ";" : depthDecl;
269
+ const dragState = this._swipe.value;
270
+ if (!dragState || dragState.id !== t.id) return depthDecl;
271
+ const translate = dragState.axis === "x" ? "translateX(" + dragState.d + "px)" : "translateY(" + dragState.d + "px)";
272
+ const magnitude = dragState.d * dragState.sign;
273
+ const opacity = magnitude > 0 && dragState.size > 0 ? Math.max(.3, 1 - magnitude / dragState.size) : 1;
274
+ return depthDecl + " transform: " + translate + "; opacity: " + opacity + "; transition: none;";
275
+ };
276
+ this.onMouseEnter = () => {
277
+ if (this.disablePauseOnHover) return;
278
+ this.pauseTimers();
279
+ };
280
+ this.onMouseLeave = () => {
281
+ if (this.disablePauseOnHover) return;
282
+ this.resumeTimers();
283
+ };
284
+ this.regionLabel = () => this.ariaLabel != null ? this.ariaLabel : "Notifications";
285
+ this.liveFor = (type) => type === "error" || type === "warning" ? "assertive" : "polite";
286
+ }
287
+ static {
288
+ this.styles = css`
289
+ :host{display:contents}
290
+ @media (prefers-reduced-motion: reduce) {
291
+ .rozie-toast[data-rozie-s-12d4265c] {
292
+ animation-name: rozie-toast-fade-in;
293
+ animation-duration: 1ms;
294
+ }
295
+ .rozie-toast--exiting[data-rozie-s-12d4265c] {
296
+ animation-name: rozie-toast-fade-out;
297
+ animation-duration: 1ms;
298
+ }
299
+ }
300
+ .rozie-toaster[data-rozie-s-12d4265c] {
301
+ position: fixed;
302
+ z-index: var(--rozie-toast-z, 9999);
303
+ display: flex;
304
+ flex-direction: column;
305
+ gap: var(--rozie-toast-gap, 0.5rem);
306
+ padding: var(--rozie-toast-region-padding, 1rem);
307
+ max-width: var(--rozie-toast-max-width, calc(100vw - 2rem));
308
+ pointer-events: none;
309
+ font: var(--rozie-toast-font, inherit);
310
+ }
311
+ .rozie-toaster[data-rozie-s-12d4265c] > *[data-rozie-s-12d4265c] {
312
+ pointer-events: auto;
313
+ }
314
+ .rozie-toaster--top-left[data-rozie-s-12d4265c] { top: 0; left: 0; align-items: flex-start; }
315
+ .rozie-toaster--top-right[data-rozie-s-12d4265c] { top: 0; right: 0; align-items: flex-end; }
316
+ .rozie-toaster--top-center[data-rozie-s-12d4265c] { top: 0; left: 50%; transform: translateX(-50%); align-items: center; }
317
+ .rozie-toaster--bottom-left[data-rozie-s-12d4265c] { bottom: 0; left: 0; align-items: flex-start; flex-direction: column-reverse; }
318
+ .rozie-toaster--bottom-right[data-rozie-s-12d4265c] { bottom: 0; right: 0; align-items: flex-end; flex-direction: column-reverse; }
319
+ .rozie-toaster--bottom-center[data-rozie-s-12d4265c] { bottom: 0; left: 50%; transform: translateX(-50%); align-items: center; flex-direction: column-reverse; }
320
+ .rozie-toaster--stacked[data-rozie-s-12d4265c] .rozie-toast[data-rozie-s-12d4265c] {
321
+ grid-area: 1 / 1;
322
+ z-index: calc(100 - var(--rozie-toast-depth, 0));
323
+ }
324
+ .rozie-toaster--stacked[data-rozie-s-12d4265c]:not([data-rozie-s-12d4265c]:hover):not([data-rozie-s-12d4265c]:focus-within) {
325
+ display: grid;
326
+ }
327
+ .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] {
328
+ transform:
329
+ translateY(calc(var(--rozie-toast-depth, 0) * var(--rozie-toast-stack-offset, 8px)))
330
+ scale(calc(1 - var(--rozie-toast-depth, 0) * var(--rozie-toast-stack-scale-step, 0.05)));
331
+ opacity: calc(1 - min(1, max(0, var(--rozie-toast-depth, 0) - 2)));
332
+ }
333
+ .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],
334
+ .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],
335
+ .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] {
336
+ transform:
337
+ translateY(calc(var(--rozie-toast-depth, 0) * var(--rozie-toast-stack-offset, 8px) * -1))
338
+ scale(calc(1 - var(--rozie-toast-depth, 0) * var(--rozie-toast-stack-scale-step, 0.05)));
339
+ }
340
+ .rozie-toast[data-rozie-s-12d4265c] {
341
+ display: flex;
342
+ align-items: center;
343
+ gap: var(--rozie-toast-content-gap, 0.75rem);
344
+ min-width: var(--rozie-toast-min-width, 16rem);
345
+ max-width: var(--rozie-toast-toast-max-width, 24rem);
346
+ padding: var(--rozie-toast-padding, 0.75rem 1rem);
347
+ color: var(--rozie-toast-color, #fff);
348
+ background: var(--rozie-toast-bg, #333);
349
+ border-radius: var(--rozie-toast-radius, 0.5rem);
350
+ box-shadow: var(--rozie-toast-shadow, 0 6px 20px rgba(0, 0, 0, 0.25));
351
+ /* Swipe: page scroll stays alive on touch along the axis the toast does
352
+ NOT move on. The transition here drives the spring-back (the active-drag
353
+ :style sets an inline \`transition: none\` to track the finger 1:1;
354
+ releasing it without a further gesture falls back to this transition). */
355
+ touch-action: pan-y;
356
+ transition: transform 200ms ease, opacity 200ms ease;
357
+ }
358
+ .rozie-toaster--top-center[data-rozie-s-12d4265c] .rozie-toast[data-rozie-s-12d4265c],
359
+ .rozie-toaster--bottom-center[data-rozie-s-12d4265c] .rozie-toast[data-rozie-s-12d4265c] {
360
+ touch-action: pan-x;
361
+ }
362
+ .rozie-toast--success[data-rozie-s-12d4265c] { background: var(--rozie-toast-success-bg, #16a34a); }
363
+ .rozie-toast--error[data-rozie-s-12d4265c] { background: var(--rozie-toast-error-bg, #dc2626); }
364
+ .rozie-toast--warning[data-rozie-s-12d4265c] { background: var(--rozie-toast-warning-bg, #ca8a04); }
365
+ .rozie-toast--info[data-rozie-s-12d4265c] { background: var(--rozie-toast-info-bg, var(--rozie-toast-bg, #333)); }
366
+ from[data-rozie-s-12d4265c] { opacity: 0; transform: translateY(-0.5rem); }
367
+ to[data-rozie-s-12d4265c] { opacity: 1; transform: translateY(0); }
368
+ from[data-rozie-s-12d4265c] { opacity: 0; transform: translateY(0.5rem); }
369
+ to[data-rozie-s-12d4265c] { opacity: 1; transform: translateY(0); }
370
+ from[data-rozie-s-12d4265c] { opacity: 1; transform: translateY(0); }
371
+ to[data-rozie-s-12d4265c] { opacity: 0; transform: translateY(-0.5rem); }
372
+ from[data-rozie-s-12d4265c] { opacity: 1; transform: translateY(0); }
373
+ to[data-rozie-s-12d4265c] { opacity: 0; transform: translateY(0.5rem); }
374
+ .rozie-toast[data-rozie-s-12d4265c] {
375
+ animation: rozie-toast-enter var(--rozie-toast-enter-duration, 200ms) ease-out;
376
+ }
377
+ .rozie-toaster--bottom-left[data-rozie-s-12d4265c] .rozie-toast[data-rozie-s-12d4265c],
378
+ .rozie-toaster--bottom-right[data-rozie-s-12d4265c] .rozie-toast[data-rozie-s-12d4265c],
379
+ .rozie-toaster--bottom-center[data-rozie-s-12d4265c] .rozie-toast[data-rozie-s-12d4265c] {
380
+ animation-name: rozie-toast-enter-from-bottom;
381
+ }
382
+ .rozie-toast--exiting[data-rozie-s-12d4265c] {
383
+ animation: rozie-toast-exit var(--rozie-toast-exit-duration, 200ms) ease-in forwards;
384
+ }
385
+ .rozie-toaster--bottom-left[data-rozie-s-12d4265c] .rozie-toast--exiting[data-rozie-s-12d4265c],
386
+ .rozie-toaster--bottom-right[data-rozie-s-12d4265c] .rozie-toast--exiting[data-rozie-s-12d4265c],
387
+ .rozie-toaster--bottom-center[data-rozie-s-12d4265c] .rozie-toast--exiting[data-rozie-s-12d4265c] {
388
+ animation-name: rozie-toast-exit-to-bottom;
389
+ }
390
+ from[data-rozie-s-12d4265c] { opacity: 0; }
391
+ to[data-rozie-s-12d4265c] { opacity: 1; }
392
+ from[data-rozie-s-12d4265c] { opacity: 1; }
393
+ to[data-rozie-s-12d4265c] { opacity: 0; }
394
+ from[data-rozie-s-12d4265c] { opacity: 1; transform: translateX(0); }
395
+ to[data-rozie-s-12d4265c] { opacity: 0; transform: translateX(calc(var(--rozie-toast-swipe-exit, 1) * 100%)); }
396
+ from[data-rozie-s-12d4265c] { opacity: 1; transform: translateY(0); }
397
+ to[data-rozie-s-12d4265c] { opacity: 0; transform: translateY(calc(var(--rozie-toast-swipe-exit, 1) * 100%)); }
398
+ .rozie-toast--exiting.rozie-toast--swipe-exit[data-rozie-s-12d4265c] {
399
+ animation-name: rozie-toast-swipe-exit-x;
400
+ }
401
+ .rozie-toaster--top-center[data-rozie-s-12d4265c] .rozie-toast--exiting.rozie-toast--swipe-exit[data-rozie-s-12d4265c],
402
+ .rozie-toaster--bottom-center[data-rozie-s-12d4265c] .rozie-toast--exiting.rozie-toast--swipe-exit[data-rozie-s-12d4265c] {
403
+ animation-name: rozie-toast-swipe-exit-y;
404
+ }
405
+ .rozie-toast-spinner[data-rozie-s-12d4265c] {
406
+ flex: 0 0 auto;
407
+ width: var(--rozie-toast-spinner-size, 1em);
408
+ height: var(--rozie-toast-spinner-size, 1em);
409
+ border: 2px solid color-mix(in srgb, var(--rozie-toast-spinner-color, currentColor) 25%, transparent);
410
+ border-top-color: var(--rozie-toast-spinner-color, currentColor);
411
+ border-radius: 50%;
412
+ animation: rozie-toast-spin 0.75s linear infinite;
413
+ }
414
+ to[data-rozie-s-12d4265c] { transform: rotate(360deg); }
415
+ .rozie-toast-message[data-rozie-s-12d4265c] {
416
+ flex: 1 1 auto;
417
+ font-size: var(--rozie-toast-font-size, 0.9rem);
418
+ }
419
+ .rozie-toast-close[data-rozie-s-12d4265c] {
420
+ flex: 0 0 auto;
421
+ display: inline-flex;
422
+ align-items: center;
423
+ justify-content: center;
424
+ width: var(--rozie-toast-close-size, 1.25rem);
425
+ height: var(--rozie-toast-close-size, 1.25rem);
426
+ padding: 0;
427
+ font-size: 1.1rem;
428
+ line-height: 1;
429
+ color: inherit;
430
+ background: transparent;
431
+ border: none;
432
+ border-radius: 0.25rem;
433
+ opacity: var(--rozie-toast-close-opacity, 0.75);
434
+ cursor: pointer;
435
+ }
436
+ .rozie-toast-close[data-rozie-s-12d4265c]:hover {
437
+ opacity: 1;
438
+ }
439
+ `;
440
+ }
441
+ _armListeners() {
442
+ {
443
+ const slotEl = this.shadowRoot?.querySelector("slot[name=\"toast\"]");
444
+ if (slotEl !== null && slotEl !== void 0) {
445
+ const update = () => {
446
+ this._hasSlotToast = this._slotToastElements.length > 0;
447
+ };
448
+ slotEl.addEventListener("slotchange", update);
449
+ this._disconnectCleanups.push(() => slotEl.removeEventListener("slotchange", update));
450
+ update();
451
+ }
452
+ }
453
+ }
454
+ connectedCallback() {
455
+ this._hasSlotToast = Array.from(this.children).some((el) => el.getAttribute("slot") === "toast");
456
+ super.connectedCallback();
457
+ if (this.hasUpdated && this._rozieTornDown) {
458
+ this._rozieTornDown = false;
459
+ this._armListeners();
460
+ }
461
+ }
462
+ firstUpdated() {
463
+ this._armListeners();
464
+ }
465
+ disconnectedCallback() {
466
+ super.disconnectedCallback();
467
+ queueMicrotask(() => {
468
+ if (this.isConnected || this._rozieTornDown) return;
469
+ this._rozieTornDown = true;
470
+ for (const fn of this._disconnectCleanups) fn();
471
+ this._disconnectCleanups = [];
472
+ });
473
+ }
474
+ render() {
475
+ return html`
476
+ <div class="rozie-toaster ${rozieClass("rozie-toaster--" + this.position + (this.stacked ? " rozie-toaster--stacked" : ""))}" role="region" aria-label=${rozieAttr(this.regionLabel())} ${rozieSpread(this.$attrs)} @mouseenter=${($event) => {
477
+ this.onMouseEnter();
478
+ }} @mouseleave=${($event) => {
479
+ this.onMouseLeave();
480
+ }} ${rozieListeners(this.$listeners)} data-rozie-s-12d4265c>
481
+
482
+ ${repeat(this._toasts.value, (t, _idx) => t.id, (t, _idx) => html`<div class="rozie-toast ${rozieClass("rozie-toast--" + t.type + (t.exiting ? " rozie-toast--exiting" : "") + (t.swipeExitSign != null ? " rozie-toast--swipe-exit" : ""))}" key=${rozieAttr(t.id)} style=${rozieStyle(this.toastStyle(t))} role="status" aria-live=${rozieAttr(this.liveFor(t.type))} @animationend=${($event) => {
483
+ t.exiting && this.removeToast(t.id);
484
+ }} @pointerdown=${($event) => {
485
+ this.onToastPointerDown(t, $event);
486
+ }} @pointermove=${($event) => {
487
+ this.onToastPointerMove(t, $event);
488
+ }} @pointerup=${($event) => {
489
+ this.onToastPointerUp(t, $event);
490
+ }} @pointercancel=${($event) => {
491
+ this.onToastPointerCancel(t);
492
+ }} data-rozie-s-12d4265c>
493
+ ${this.toast !== void 0 ? this.toast({
494
+ toast: t,
495
+ dismiss: this.dismiss
496
+ }) : html`<slot name="toast" data-rozie-params=${(() => {
497
+ try {
498
+ return JSON.stringify({ toast: t });
499
+ } catch {
500
+ return "{}";
501
+ }
502
+ })()} @rozie-toast-dismiss=${($event) => this.dismiss($event.detail)}>
503
+ ${t.type === "loading" ? html`<span class="rozie-toast-spinner" aria-hidden="true" data-rozie-s-12d4265c></span>` : nothing}<span class="rozie-toast-message" data-rozie-s-12d4265c>${rozieDisplay(t.message)}</span>
504
+ <button class="rozie-toast-close" type="button" aria-label="Dismiss" @click=${($event) => {
505
+ this.dismissBegin(t.id, "close");
506
+ }} data-rozie-s-12d4265c>×</button>
507
+ </slot>`}
508
+ </div>`)}
509
+ </div>
510
+ `;
511
+ }
512
+ /**
513
+ * Plan 14-05 — cross-framework attribute fallthrough source. Reads the
514
+ * host custom element's attributes on each call so a consumer-side bound
515
+ * attribute flows through on every render. The `rozieSpread` directive
516
+ * (D-02) does the cross-render diff downstream.
517
+ *
518
+ * Phase 15 follow-up Bug A — declared-prop attribute names are filtered
519
+ * out so `$attrs` returns "rest after declared props" (semantic parity
520
+ * with React/Vue/Svelte/Solid/Angular). Both Lit attribute-naming
521
+ * forms are folded into the skip set: kebab-case for model props
522
+ * (explicit `attribute:`) AND lowercased property name (Lit's default).
523
+ */
524
+ get $attrs() {
525
+ const __skip = new Set([
526
+ "position",
527
+ "duration",
528
+ "max",
529
+ "disable-pause-on-hover",
530
+ "disablepauseonhover",
531
+ "aria-label",
532
+ "arialabel",
533
+ "disable-swipe",
534
+ "disableswipe",
535
+ "stacked"
536
+ ]);
537
+ const out = {};
538
+ for (const a of Array.from(this.attributes)) {
539
+ if (__skip.has(a.name)) continue;
540
+ out[a.name] = a.value;
541
+ }
542
+ return out;
543
+ }
544
+ /**
545
+ * Phase 15 D-19 — consumer-passed listener cluster placeholder.
546
+ * Lit attaches event listeners directly on the host element via
547
+ * `addEventListener` (no per-instance prop rest binding), so the
548
+ * runtime value is undefined; the `rozieListeners` directive's
549
+ * nullish coercion (`obj ?? {}`) handles the no-op cleanly.
550
+ * The declaration exists to satisfy `tsc --noEmit` on consumer
551
+ * projects with strict mode — bare `$listeners` in `render()`
552
+ * would otherwise raise TS2304 (Cannot find name).
553
+ */
554
+ get $listeners() {}
555
+ };
556
+ __decorate([property({
557
+ type: String,
558
+ reflect: true
559
+ })], Toaster.prototype, "position", void 0);
560
+ __decorate([property({
561
+ type: Number,
562
+ reflect: true
563
+ })], Toaster.prototype, "duration", void 0);
564
+ __decorate([property({
565
+ type: Number,
566
+ reflect: true
567
+ })], Toaster.prototype, "max", void 0);
568
+ __decorate([property({
569
+ type: Boolean,
570
+ reflect: true
571
+ })], Toaster.prototype, "disablePauseOnHover", void 0);
572
+ __decorate([property({
573
+ type: String,
574
+ reflect: true
575
+ })], Toaster.prototype, "ariaLabel", void 0);
576
+ __decorate([property({
577
+ type: Boolean,
578
+ reflect: true
579
+ })], Toaster.prototype, "disableSwipe", void 0);
580
+ __decorate([property({
581
+ type: Boolean,
582
+ reflect: true
583
+ })], Toaster.prototype, "stacked", void 0);
584
+ __decorate([state()], Toaster.prototype, "_hasSlotToast", void 0);
585
+ __decorate([queryAssignedElements({
586
+ slot: "toast",
587
+ flatten: true
588
+ })], Toaster.prototype, "_slotToastElements", void 0);
589
+ __decorate([property({ attribute: false })], Toaster.prototype, "toast", void 0);
590
+ Toaster = __decorate([customElement("rozie-toaster")], Toaster);
591
+ var Toaster_default = Toaster;
592
+ //#endregion
593
+ export { Toaster_default as Toaster, Toaster_default as default };
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@rozie-ui/toast-lit",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "private": false,
6
+ "license": "MIT",
7
+ "description": "Idiomatic Lit headless, accessible toast / notification host (queue, auto-dismiss, hover-pause, positioning, imperative show/dismiss/clear handle) — one accessible Rozie source compiled to a Lit custom element.",
8
+ "keywords": [
9
+ "rozie",
10
+ "rozie-ui",
11
+ "toast",
12
+ "toaster",
13
+ "notification",
14
+ "snackbar",
15
+ "headless",
16
+ "accessibility",
17
+ "aria",
18
+ "component",
19
+ "lit"
20
+ ],
21
+ "author": "One Learning Community (https://github.com/One-Learning-Community)",
22
+ "homepage": "https://github.com/One-Learning-Community/rozie.js#readme",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/One-Learning-Community/rozie.js.git",
26
+ "directory": "packages/ui/toast/packages/lit"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/One-Learning-Community/rozie.js/issues"
30
+ },
31
+ "main": "./dist/index.cjs",
32
+ "module": "./dist/index.mjs",
33
+ "types": "./dist/index.d.mts",
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.mts",
37
+ "import": "./dist/index.mjs",
38
+ "require": "./dist/index.cjs"
39
+ },
40
+ "./source": "./src/Toaster.ts",
41
+ "./themes/*": "./src/themes/*"
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "src"
46
+ ],
47
+ "dependencies": {
48
+ "@rozie/runtime-lit": "0.2.0"
49
+ },
50
+ "peerDependencies": {
51
+ "lit": "^3.2",
52
+ "@lit-labs/preact-signals": "^1.0.0",
53
+ "@preact/signals-core": "^1.3.0"
54
+ },
55
+ "peerDependenciesMeta": {
56
+ "lit": {
57
+ "optional": false
58
+ },
59
+ "@lit-labs/preact-signals": {
60
+ "optional": false
61
+ },
62
+ "@preact/signals-core": {
63
+ "optional": false
64
+ }
65
+ },
66
+ "publishConfig": {
67
+ "access": "public"
68
+ },
69
+ "scripts": {
70
+ "build": "tsdown",
71
+ "typecheck": "tsc --noEmit"
72
+ }
73
+ }