@spaethtech/svelte-ui 0.21.5-dev.147.eb2f000 → 0.21.5-dev.149.8ed1480
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/components/Toast/ToastRow.svelte +104 -0
- package/dist/components/Toast/ToastRow.svelte.d.ts +13 -0
- package/dist/components/Toast/Toaster.svelte +5 -19
- package/dist/components/Toast/toast.svelte.d.ts +15 -0
- package/dist/components/Toast/toast.svelte.js +4 -3
- package/dist/index.d.ts +1 -1
- package/dist/utils.d.ts +1 -1
- package/docs/usage.md +43 -0
- package/package.json +6 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* One toast row: a filled Alert plus (optionally) an action button and a close button. Owns its own
|
|
4
|
+
* auto-dismiss countdown so it can PAUSE while the pointer is over it or keyboard focus is inside it
|
|
5
|
+
* (a `holds` counter handles pointer + focus overlapping) — that keeps the `action` reachable by
|
|
6
|
+
* mouse AND keyboard. `timeout: 0` is sticky (no countdown). Controls are our <Button>, never raw
|
|
7
|
+
* <button>, per the composition standard.
|
|
8
|
+
*/
|
|
9
|
+
import { onMount } from "svelte";
|
|
10
|
+
import Alert from "../Alert.svelte";
|
|
11
|
+
import Button from "../Button.svelte";
|
|
12
|
+
import IconClose from "~icons/mdi/close";
|
|
13
|
+
import { uiI18n } from "../../i18n.js";
|
|
14
|
+
import { dismissToast, type ToastItem } from "./toast.svelte.js";
|
|
15
|
+
import type { Variant } from "../../types/variants.js";
|
|
16
|
+
import type { Responsive } from "../../types/responsive.js";
|
|
17
|
+
import type { Size } from "../../types/sizes.js";
|
|
18
|
+
|
|
19
|
+
const i18n = uiI18n();
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
item,
|
|
23
|
+
fallbackVariant,
|
|
24
|
+
fallbackSize,
|
|
25
|
+
}: {
|
|
26
|
+
item: ToastItem;
|
|
27
|
+
/** Toaster-level defaults for toasts that didn't set their own. */
|
|
28
|
+
fallbackVariant?: Variant;
|
|
29
|
+
fallbackSize?: Responsive<Size>;
|
|
30
|
+
} = $props();
|
|
31
|
+
|
|
32
|
+
// Auto-dismiss countdown with pause/resume. `remaining` ms is decremented by whatever has elapsed
|
|
33
|
+
// each time we pause; while `holds > 0` (hovered and/or focused) no timer runs.
|
|
34
|
+
let remaining = item.timeout;
|
|
35
|
+
let startedAt = 0;
|
|
36
|
+
let handle: ReturnType<typeof setTimeout> | undefined;
|
|
37
|
+
let holds = 0;
|
|
38
|
+
|
|
39
|
+
function clear() {
|
|
40
|
+
if (handle !== undefined) {
|
|
41
|
+
clearTimeout(handle);
|
|
42
|
+
handle = undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function run() {
|
|
46
|
+
clear();
|
|
47
|
+
if (item.timeout <= 0 || typeof window === "undefined") return;
|
|
48
|
+
startedAt = Date.now();
|
|
49
|
+
handle = window.setTimeout(() => dismissToast(item.id), remaining);
|
|
50
|
+
}
|
|
51
|
+
function hold() {
|
|
52
|
+
if (item.timeout <= 0) return;
|
|
53
|
+
if (handle !== undefined) remaining -= Date.now() - startedAt; // bank elapsed time once
|
|
54
|
+
clear();
|
|
55
|
+
holds++;
|
|
56
|
+
}
|
|
57
|
+
function release() {
|
|
58
|
+
if (item.timeout <= 0) return;
|
|
59
|
+
holds = Math.max(0, holds - 1);
|
|
60
|
+
if (holds > 0) return;
|
|
61
|
+
if (remaining > 0) run();
|
|
62
|
+
else dismissToast(item.id);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
onMount(() => {
|
|
66
|
+
run();
|
|
67
|
+
return clear;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
function runAction() {
|
|
71
|
+
// Run the handler, then dismiss — unless the action opted out with `dismiss: false` (keep open).
|
|
72
|
+
item.action?.onclick();
|
|
73
|
+
if (item.action?.dismiss !== false) dismissToast(item.id);
|
|
74
|
+
}
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
78
|
+
<div
|
|
79
|
+
class="pointer-events-auto flex min-w-56 items-start gap-1 rounded shadow-lg [background-color:var(--ui-color-background)]"
|
|
80
|
+
role="status"
|
|
81
|
+
onpointerenter={hold}
|
|
82
|
+
onpointerleave={release}
|
|
83
|
+
onfocusin={hold}
|
|
84
|
+
onfocusout={release}
|
|
85
|
+
>
|
|
86
|
+
<Alert variant={item.variant ?? fallbackVariant} size={item.size ?? fallbackSize} filled class="grow"
|
|
87
|
+
>{item.message}</Alert
|
|
88
|
+
>
|
|
89
|
+
{#if item.action}
|
|
90
|
+
<Button variant="ghost" size="sm" class="shrink-0 self-center" onclick={runAction}>
|
|
91
|
+
{item.action.label}
|
|
92
|
+
</Button>
|
|
93
|
+
{/if}
|
|
94
|
+
<Button
|
|
95
|
+
variant="ghost"
|
|
96
|
+
size="sm"
|
|
97
|
+
class="shrink-0 self-center"
|
|
98
|
+
aria-label={i18n.messages.dismiss}
|
|
99
|
+
title={i18n.messages.dismiss}
|
|
100
|
+
onclick={() => dismissToast(item.id)}
|
|
101
|
+
>
|
|
102
|
+
{#snippet icon()}<IconClose />{/snippet}
|
|
103
|
+
</Button>
|
|
104
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ToastItem } from "./toast.svelte.js";
|
|
2
|
+
import type { Variant } from "../../types/variants.js";
|
|
3
|
+
import type { Responsive } from "../../types/responsive.js";
|
|
4
|
+
import type { Size } from "../../types/sizes.js";
|
|
5
|
+
type $$ComponentProps = {
|
|
6
|
+
item: ToastItem;
|
|
7
|
+
/** Toaster-level defaults for toasts that didn't set their own. */
|
|
8
|
+
fallbackVariant?: Variant;
|
|
9
|
+
fallbackSize?: Responsive<Size>;
|
|
10
|
+
};
|
|
11
|
+
declare const ToastRow: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
12
|
+
type ToastRow = ReturnType<typeof ToastRow>;
|
|
13
|
+
export default ToastRow;
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { uiI18n } from "../../i18n.js";
|
|
3
|
-
const i18n = uiI18n();
|
|
4
2
|
/**
|
|
5
3
|
* Renders the toast store's items in a fixed, top-layer stack. Mount ONCE at the app root; push with
|
|
6
|
-
* `toast.*` from anywhere. Each toast is
|
|
4
|
+
* `toast.*` from anywhere. Each toast is a <ToastRow> (filled Alert + optional action + close),
|
|
5
|
+
* which owns its own pausable auto-dismiss countdown.
|
|
7
6
|
*/
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import { getToasts, dismissToast } from "./toast.svelte.js";
|
|
7
|
+
import ToastRow from "./ToastRow.svelte";
|
|
8
|
+
import { getToasts } from "./toast.svelte.js";
|
|
11
9
|
import type { Size } from "../../types/sizes.js";
|
|
12
10
|
import type { Responsive } from "../../types/responsive.js";
|
|
13
11
|
import type { Variant } from "../../types/variants.js";
|
|
@@ -35,19 +33,7 @@
|
|
|
35
33
|
{#if items.length}
|
|
36
34
|
<div class="pointer-events-none fixed z-[100] flex max-w-[90vw] flex-col gap-2 {posClass}">
|
|
37
35
|
{#each items as t (t.id)}
|
|
38
|
-
<
|
|
39
|
-
class="pointer-events-auto flex min-w-56 items-start gap-2 rounded shadow-lg [background-color:var(--ui-color-background)]"
|
|
40
|
-
>
|
|
41
|
-
<Alert variant={t.variant ?? variant} size={t.size ?? size} filled class="grow"
|
|
42
|
-
>{t.message}</Alert
|
|
43
|
-
>
|
|
44
|
-
<button
|
|
45
|
-
type="button"
|
|
46
|
-
aria-label={i18n.messages.dismiss}
|
|
47
|
-
class="shrink-0 self-center pr-2 opacity-60 hover:opacity-100"
|
|
48
|
-
onclick={() => dismissToast(t.id)}><IconClose class="size-4" /></button
|
|
49
|
-
>
|
|
50
|
-
</div>
|
|
36
|
+
<ToastRow item={t} fallbackVariant={variant} fallbackSize={size} />
|
|
51
37
|
{/each}
|
|
52
38
|
</div>
|
|
53
39
|
{/if}
|
|
@@ -5,16 +5,31 @@ import type { Size } from "../../types/sizes.js";
|
|
|
5
5
|
* shared Variant (status subset) + Size axes. Auto-dismisses after `timeout` ms (0 = sticky).
|
|
6
6
|
*/
|
|
7
7
|
export type ToastVariant = "danger" | "success" | "warning" | "info" | "primary";
|
|
8
|
+
/**
|
|
9
|
+
* An optional action rendered as a small button inside the toast (e.g. "Undo"). Clicking it runs
|
|
10
|
+
* `onclick`, then the toast is dismissed. Set `dismiss: false` to KEEP the toast open instead (e.g.
|
|
11
|
+
* to show progress) and close it later via the id returned from `toast.*` + `dismissToast`.
|
|
12
|
+
*/
|
|
13
|
+
export type ToastAction = {
|
|
14
|
+
/** Button label, e.g. "Undo". */
|
|
15
|
+
label: string;
|
|
16
|
+
/** Click handler (its return value is ignored). */
|
|
17
|
+
onclick: () => void;
|
|
18
|
+
/** Dismiss the toast after `onclick` runs. Default `true`; `false` keeps it open. */
|
|
19
|
+
dismiss?: boolean;
|
|
20
|
+
};
|
|
8
21
|
export type ToastItem = {
|
|
9
22
|
id: number;
|
|
10
23
|
variant: ToastVariant;
|
|
11
24
|
message: string;
|
|
12
25
|
size: Size;
|
|
13
26
|
timeout: number;
|
|
27
|
+
action?: ToastAction;
|
|
14
28
|
};
|
|
15
29
|
export type ToastOptions = {
|
|
16
30
|
timeout?: number;
|
|
17
31
|
size?: Size;
|
|
32
|
+
action?: ToastAction;
|
|
18
33
|
};
|
|
19
34
|
/** The live toast list (read in <Toaster>; mutations are reactive). */
|
|
20
35
|
export declare function getToasts(): ToastItem[];
|
|
@@ -9,12 +9,13 @@ export function dismissToast(id) {
|
|
|
9
9
|
if (i >= 0)
|
|
10
10
|
items.splice(i, 1);
|
|
11
11
|
}
|
|
12
|
+
// NOTE: auto-dismiss timing lives in <Toaster> (per toast row), NOT here — the row can then PAUSE the
|
|
13
|
+
// countdown while the pointer is over it or keyboard focus is inside it, so an `action` button stays
|
|
14
|
+
// reachable. `push` only records `timeout` (0 = sticky); the row reads it and owns the timer.
|
|
12
15
|
function push(variant, message, opts = {}) {
|
|
13
16
|
const id = ++seq;
|
|
14
17
|
const timeout = opts.timeout ?? 4000;
|
|
15
|
-
items.push({ id, variant, message, size: opts.size ?? "md", timeout });
|
|
16
|
-
if (timeout > 0 && typeof window !== "undefined")
|
|
17
|
-
window.setTimeout(() => dismissToast(id), timeout);
|
|
18
|
+
items.push({ id, variant, message, size: opts.size ?? "md", timeout, action: opts.action });
|
|
18
19
|
return id;
|
|
19
20
|
}
|
|
20
21
|
export const toast = {
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export { Stat } from "./components/Stat/index.js";
|
|
|
15
15
|
export { Timeline } from "./components/Timeline/index.js";
|
|
16
16
|
export type { TimelineItem } from "./components/Timeline/index.js";
|
|
17
17
|
export { default as Toaster } from "./components/Toast/Toaster.svelte";
|
|
18
|
-
export { toast, getToasts, dismissToast, type ToastVariant, type ToastItem, } from "./components/Toast/toast.svelte.js";
|
|
18
|
+
export { toast, getToasts, dismissToast, type ToastVariant, type ToastItem, type ToastOptions, type ToastAction, } from "./components/Toast/toast.svelte.js";
|
|
19
19
|
export { default as Dialog } from "./components/Dialog.svelte";
|
|
20
20
|
export { Drawer } from "./components/Drawer/index.js";
|
|
21
21
|
export { default as ConfirmDialog } from "./components/ConfirmDialog.svelte";
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { toast, getToasts, dismissToast, type ToastVariant, type ToastItem, } from "./components/Toast/toast.svelte.js";
|
|
1
|
+
export { toast, getToasts, dismissToast, type ToastVariant, type ToastItem, type ToastOptions, type ToastAction, } from "./components/Toast/toast.svelte.js";
|
|
2
2
|
export { anchored, computePlacement, resolveBoundary } from "./positioning/anchored.js";
|
|
3
3
|
export type { Side, Align, Boundary, PlaceOptions, Placement, AnchoredParams, } from "./positioning/anchored.js";
|
|
4
4
|
export { tooltip } from "./positioning/tooltip.js";
|
package/docs/usage.md
CHANGED
|
@@ -1169,6 +1169,49 @@ reads `GridState.group` (`{ key, dir }`) and does `ORDER BY <group>, <sort>`.
|
|
|
1169
1169
|
client-side counterpart to a remote `query()`; `matchesRow(row, ast)` is the underlying AST evaluator if
|
|
1170
1170
|
you need to filter an array yourself. See the live demo for the full DataTable + Query walkthrough.
|
|
1171
1171
|
|
|
1172
|
+
### Toasts
|
|
1173
|
+
|
|
1174
|
+
Mount a single `<Toaster />` once at the app root, then push from anywhere with `toast.*`. The push
|
|
1175
|
+
functions (`toast`, `dismissToast`, `getToasts`) are component-free values, so import them from the
|
|
1176
|
+
dedicated **`@spaethtech/svelte-ui/toast`** subpath (or `/utils`, or the root barrel) to stay barrel-free.
|
|
1177
|
+
|
|
1178
|
+
```svelte
|
|
1179
|
+
<script>
|
|
1180
|
+
import { Toaster } from "@spaethtech/svelte-ui/toaster"; // mount once (e.g. +layout.svelte)
|
|
1181
|
+
import { toast } from "@spaethtech/svelte-ui/toast"; // push from anywhere
|
|
1182
|
+
</script>
|
|
1183
|
+
|
|
1184
|
+
<Toaster position="bottom-right" />
|
|
1185
|
+
|
|
1186
|
+
<!-- status variants; each returns an id you can toast.dismiss(id) -->
|
|
1187
|
+
toast.success("Saved");
|
|
1188
|
+
toast.info("Heads up", { timeout: 0 }); // 0 = sticky (dismiss with the × button)
|
|
1189
|
+
toast.warning("Almost full", { size: "lg" });
|
|
1190
|
+
```
|
|
1191
|
+
|
|
1192
|
+
**Action button (e.g. Undo).** Pass an `action`; it renders a small button that runs `onclick` and then
|
|
1193
|
+
dismisses the toast. The auto-dismiss countdown pauses while the pointer is over the toast or keyboard
|
|
1194
|
+
focus is inside it, so the action stays reachable. Set `dismiss: false` to keep the toast open instead
|
|
1195
|
+
(close it later by id).
|
|
1196
|
+
|
|
1197
|
+
```svelte
|
|
1198
|
+
let archived = $state(false);
|
|
1199
|
+
|
|
1200
|
+
function archive() {
|
|
1201
|
+
archived = true;
|
|
1202
|
+
toast.info("Archived “Breaking Bad”", {
|
|
1203
|
+
action: { label: "Undo", onclick: () => (archived = false) },
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Keep-open variant (show progress, then dismiss yourself):
|
|
1208
|
+
const id = toast.info("Uploading…", {
|
|
1209
|
+
timeout: 0,
|
|
1210
|
+
action: { label: "Cancel", onclick: cancelUpload, dismiss: false },
|
|
1211
|
+
});
|
|
1212
|
+
// … later: toast.dismiss(id);
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1172
1215
|
## Theme Integration
|
|
1173
1216
|
|
|
1174
1217
|
### Using CSS Variables
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spaethtech/svelte-ui",
|
|
3
|
-
"version": "0.21.5-dev.
|
|
3
|
+
"version": "0.21.5-dev.149.8ed1480",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/spaethtech/svelte-ui.git"
|
|
@@ -109,6 +109,11 @@
|
|
|
109
109
|
"types": "./dist/components/Toast/Toaster.svelte.d.ts",
|
|
110
110
|
"svelte": "./dist/components/Toast/Toaster.svelte"
|
|
111
111
|
},
|
|
112
|
+
"./toast": {
|
|
113
|
+
"types": "./dist/components/Toast/toast.svelte.d.ts",
|
|
114
|
+
"svelte": "./dist/components/Toast/toast.svelte.js",
|
|
115
|
+
"default": "./dist/components/Toast/toast.svelte.js"
|
|
116
|
+
},
|
|
112
117
|
"./dialog": {
|
|
113
118
|
"types": "./dist/components/Dialog.svelte.d.ts",
|
|
114
119
|
"svelte": "./dist/components/Dialog.svelte"
|